2018-11-15 23:35:52 +00:00
|
|
|
/*****************************************************************************\
|
|
|
|
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.
|
|
|
|
\*****************************************************************************/
|
2010-09-25 15:46:12 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
#include <cmath>
|
|
|
|
#include <vector>
|
2018-08-16 19:04:43 +00:00
|
|
|
#include "../snes9x.h"
|
2010-09-25 15:46:12 +00:00
|
|
|
#include "apu.h"
|
2018-08-16 19:04:43 +00:00
|
|
|
#include "../msu1.h"
|
|
|
|
#include "../snapshot.h"
|
|
|
|
#include "../display.h"
|
2010-09-25 16:46:06 +00:00
|
|
|
#include "hermite_resampler.h"
|
2011-06-18 10:31:44 +00:00
|
|
|
|
2018-08-16 19:04:43 +00:00
|
|
|
#include "bapu/snes/snes.hpp"
|
2010-09-25 15:46:12 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
static const int APU_DEFAULT_INPUT_RATE = 31950; // ~59.94Hz
|
|
|
|
static const int APU_SAMPLE_BLOCK = 48;
|
|
|
|
static const int APU_NUMERATOR_NTSC = 15664;
|
|
|
|
static const int APU_DENOMINATOR_NTSC = 328125;
|
|
|
|
static const int APU_NUMERATOR_PAL = 34176;
|
|
|
|
static const int APU_DENOMINATOR_PAL = 709379;
|
|
|
|
|
2019-02-06 21:00:45 +00:00
|
|
|
// Max number of sample frames 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.
|
|
|
|
static const int MAX_SAMPLE_FRAMES = 600;
|
|
|
|
static const int SAMPLE_FRAMES_LIMIT = 550;
|
2010-09-25 15:46:12 +00:00
|
|
|
|
2011-06-12 06:25:22 +00:00
|
|
|
namespace SNES
|
|
|
|
{
|
2012-02-03 22:43:32 +00:00
|
|
|
#include "bapu/dsp/blargg_endian.h"
|
2019-02-05 23:21:23 +00:00
|
|
|
CPU cpu;
|
2011-06-12 06:25:22 +00:00
|
|
|
}
|
|
|
|
|
2010-09-25 15:46:12 +00:00
|
|
|
namespace spc
|
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
static apu_callback callback = NULL;
|
|
|
|
static void *callback_data = NULL;
|
2010-09-25 15:46:12 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
static bool8 sound_in_sync = TRUE;
|
|
|
|
static bool8 sound_enabled = FALSE;
|
|
|
|
static uint16 dsp_buffer[MAX_SAMPLE_FRAMES * 2];
|
2010-09-25 15:46:12 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
static Resampler *resampler = NULL;
|
2010-09-25 15:46:12 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
static int32 reference_time;
|
|
|
|
static uint32 remainder;
|
2010-09-25 15:46:12 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
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;
|
2017-11-20 18:07:54 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
static double dynamic_rate_multiplier = 1.0;
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
|
|
|
|
2016-11-03 18:40:47 +00:00
|
|
|
namespace msu
|
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
// Always 16-bit, Stereo; 1.5x dsp buffer to never overflow
|
|
|
|
static const int buffer_size = MAX_SAMPLE_FRAMES * 6;
|
|
|
|
static uint8 mixing_buffer[buffer_size];
|
|
|
|
static Resampler *resampler = NULL;
|
|
|
|
static std::vector<uint16> resample_buffer;
|
2016-11-03 18:40:47 +00:00
|
|
|
}
|
2019-02-05 23:20:23 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
static void UpdatePlaybackRate(void);
|
|
|
|
static void SPCSnapshotCallback(void);
|
|
|
|
static inline int S9xAPUGetClock(int32);
|
|
|
|
static inline int S9xAPUGetClockRemainder(int32);
|
2019-02-05 23:20:23 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
static void reset_dsp_output()
|
|
|
|
{
|
|
|
|
SNES::dsp.spc_dsp.set_output((SNES::SPC_DSP::sample_t *)spc::dsp_buffer,
|
|
|
|
MAX_SAMPLE_FRAMES * 2);
|
|
|
|
}
|
2019-02-05 23:20:23 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
bool8 S9xMixSamples(uint8 *dest, int sample_count)
|
|
|
|
{
|
|
|
|
int16 *out = (int16 *)dest;
|
2018-04-13 15:25:26 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
if (Settings.Mute)
|
|
|
|
{
|
|
|
|
memset(out, 0, sample_count << 1);
|
|
|
|
spc::resampler->clear();
|
2019-02-05 23:20:23 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
if (Settings.MSU1)
|
|
|
|
msu::resampler->clear();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (spc::resampler->avail() >= sample_count)
|
|
|
|
{
|
|
|
|
spc::resampler->read((short *)out, sample_count);
|
|
|
|
|
|
|
|
if (Settings.MSU1)
|
|
|
|
{
|
|
|
|
if (msu::resampler->avail() >= sample_count)
|
|
|
|
{
|
|
|
|
if ((int)msu::resample_buffer.size() < sample_count)
|
|
|
|
msu::resample_buffer.resize(sample_count);
|
|
|
|
msu::resampler->read((short *)msu::resample_buffer.data(),
|
|
|
|
sample_count);
|
|
|
|
for (int i = 0; i < sample_count; ++i)
|
|
|
|
out[i] += msu::resample_buffer[i];
|
|
|
|
}
|
|
|
|
else // should never occur
|
|
|
|
assert(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
memset(out, 0, sample_count << 1);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2019-02-05 23:20:23 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
return true;
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
int S9xGetSampleCount(void)
|
2010-09-25 15:46:12 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
return spc::resampler->avail();
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
void S9xFinalizeSamples(void)
|
2010-09-25 15:46:12 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
if (!Settings.Mute)
|
|
|
|
{
|
|
|
|
if (!spc::resampler->push((short *)spc::dsp_buffer,
|
|
|
|
SNES::dsp.spc_dsp.sample_count()))
|
|
|
|
{
|
2019-02-06 21:00:45 +00:00
|
|
|
// Don't drop samples if SoundSync is enabled. The port will wait
|
|
|
|
// and call S9xSyncSound again to catch up.
|
|
|
|
// If the sample count is over a frame, it indicates the port
|
|
|
|
// didn't implement SoundSync correctly.
|
|
|
|
if (Settings.SoundSync && !Settings.TurboMode && !Settings.Mute &&
|
|
|
|
SNES::dsp.spc_dsp.sample_count() < SAMPLE_FRAMES_LIMIT)
|
|
|
|
{
|
|
|
|
spc::sound_in_sync = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
spc::resampler->clear();
|
2019-02-06 21:00:45 +00:00
|
|
|
spc::resampler->push((short *)spc::dsp_buffer,
|
|
|
|
SNES::dsp.spc_dsp.sample_count());
|
|
|
|
if (Settings.MSU1)
|
|
|
|
msu::resampler->clear();
|
2019-02-05 23:21:23 +00:00
|
|
|
}
|
|
|
|
}
|
2010-09-25 15:46:12 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
// 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::mixing_buffer, msu::buffer_size);
|
|
|
|
S9xMSU1Generate(SNES::dsp.spc_dsp.sample_count());
|
2019-02-06 21:00:45 +00:00
|
|
|
|
|
|
|
if (!msu::resampler->push((short *)msu::mixing_buffer, S9xMSU1Samples()))
|
2019-02-05 23:21:23 +00:00
|
|
|
{
|
|
|
|
// should not occur, msu buffer is larger and we drop msu samples if spc buffer overruns
|
|
|
|
assert(0);
|
|
|
|
}
|
|
|
|
}
|
2019-02-05 23:20:23 +00:00
|
|
|
|
2019-02-06 21:00:45 +00:00
|
|
|
spc::sound_in_sync = true;
|
2019-02-05 23:20:23 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
reset_dsp_output();
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
void S9xLandSamples(void)
|
2010-09-25 15:46:12 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
if (spc::callback != NULL)
|
|
|
|
spc::callback(spc::callback_data);
|
|
|
|
else
|
|
|
|
S9xFinalizeSamples();
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
void S9xClearSamples(void)
|
2010-09-25 15:46:12 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
spc::resampler->clear();
|
|
|
|
if (Settings.MSU1)
|
|
|
|
msu::resampler->clear();
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
bool8 S9xSyncSound(void)
|
2010-09-25 15:46:12 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
if (!Settings.SoundSync || spc::sound_in_sync)
|
|
|
|
return (TRUE);
|
2010-09-25 15:46:12 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
S9xLandSamples();
|
2010-09-25 15:46:12 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
return (spc::sound_in_sync);
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
void S9xSetSamplesAvailableCallback(apu_callback callback, void *data)
|
2010-09-25 15:46:12 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
spc::callback = callback;
|
|
|
|
spc::callback_data = data;
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
void S9xUpdateDynamicRate(int avail, int buffer_size)
|
2017-11-20 18:07:54 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
spc::dynamic_rate_multiplier = 1.0 + (Settings.DynamicRateLimit * (buffer_size - 2 * avail)) /
|
|
|
|
(double)(1000 * buffer_size);
|
2017-11-23 00:18:33 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
UpdatePlaybackRate();
|
2017-11-20 18:07:54 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
static void UpdatePlaybackRate(void)
|
2010-09-25 15:46:12 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
if (Settings.SoundInputRate == 0)
|
|
|
|
Settings.SoundInputRate = APU_DEFAULT_INPUT_RATE;
|
2010-09-25 15:46:12 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
double time_ratio = (double)Settings.SoundInputRate * spc::timing_hack_numerator / (Settings.SoundPlaybackRate * spc::timing_hack_denominator);
|
2017-11-20 18:07:54 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
if (Settings.DynamicRateControl)
|
|
|
|
{
|
|
|
|
time_ratio *= spc::dynamic_rate_multiplier;
|
|
|
|
}
|
2017-11-20 18:07:54 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
spc::resampler->time_ratio(time_ratio);
|
2016-11-03 18:40:47 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
if (Settings.MSU1)
|
|
|
|
{
|
|
|
|
time_ratio = (44100.0 / Settings.SoundPlaybackRate) * (Settings.SoundInputRate / 32040.0);
|
|
|
|
msu::resampler->time_ratio(time_ratio);
|
|
|
|
}
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
|
|
|
|
2019-02-06 01:56:59 +00:00
|
|
|
bool8 S9xInitSound(int buffer_ms, int unused2)
|
2010-09-25 15:46:12 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
// The resampler and spc unit use samples (16-bit short) as arguments.
|
2019-02-06 01:56:59 +00:00
|
|
|
int buffer_size_samples = MAX_SAMPLE_FRAMES * 2;
|
|
|
|
if (buffer_ms > 0)
|
|
|
|
buffer_size_samples = Settings.SoundPlaybackRate * buffer_ms * 2 / 1000;
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
if (!spc::resampler)
|
|
|
|
{
|
2019-02-06 01:56:59 +00:00
|
|
|
spc::resampler = new HermiteResampler(buffer_size_samples);
|
2019-02-05 23:21:23 +00:00
|
|
|
if (!spc::resampler)
|
|
|
|
return (FALSE);
|
|
|
|
}
|
2018-04-13 15:25:26 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
if (!msu::resampler)
|
|
|
|
{
|
2019-02-06 01:56:59 +00:00
|
|
|
msu::resampler = new HermiteResampler(buffer_size_samples * 3 / 2);
|
2019-02-05 23:21:23 +00:00
|
|
|
if (!msu::resampler)
|
|
|
|
return (FALSE);
|
|
|
|
}
|
|
|
|
else
|
2019-02-06 01:56:59 +00:00
|
|
|
msu::resampler->resize(buffer_size_samples * 3 / 2);
|
2018-04-13 15:25:26 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
reset_dsp_output();
|
2019-02-05 23:20:23 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
UpdatePlaybackRate();
|
2019-02-05 23:20:23 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
spc::sound_enabled = S9xOpenSoundDevice();
|
2019-02-05 23:20:23 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
return (spc::sound_enabled);
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
void S9xSetSoundControl(uint8 voice_switch)
|
2010-09-25 15:46:12 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
SNES::dsp.spc_dsp.set_stereo_switch(voice_switch << 8 | voice_switch);
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
void S9xSetSoundMute(bool8 mute)
|
2010-09-25 15:46:12 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
Settings.Mute = mute;
|
|
|
|
if (!spc::sound_enabled)
|
|
|
|
Settings.Mute = TRUE;
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
void S9xDumpSPCSnapshot(void)
|
2010-09-25 15:46:12 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
SNES::dsp.spc_dsp.dump_spc_snapshot();
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
static void SPCSnapshotCallback(void)
|
2010-09-25 15:46:12 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
S9xSPCDump(S9xGetFilenameInc((".spc"), SPC_DIR));
|
|
|
|
printf("Dumped key-on triggered spc snapshot.\n");
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
bool8 S9xInitAPU(void)
|
2010-09-25 15:46:12 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
spc::resampler = NULL;
|
|
|
|
msu::resampler = NULL;
|
2010-09-25 15:46:12 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
return (TRUE);
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
void S9xDeinitAPU(void)
|
2010-09-25 15:46:12 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
if (spc::resampler)
|
|
|
|
{
|
|
|
|
delete spc::resampler;
|
|
|
|
spc::resampler = NULL;
|
|
|
|
}
|
2019-02-05 23:20:23 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
if (msu::resampler)
|
|
|
|
{
|
|
|
|
delete msu::resampler;
|
|
|
|
msu::resampler = NULL;
|
|
|
|
}
|
2019-02-05 23:20:23 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
S9xMSU1DeInit();
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
static inline int S9xAPUGetClock(int32 cpucycles)
|
2010-09-25 15:46:12 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
return (spc::ratio_numerator * (cpucycles - spc::reference_time) + spc::remainder) /
|
|
|
|
spc::ratio_denominator;
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
static inline int S9xAPUGetClockRemainder(int32 cpucycles)
|
2010-09-25 15:46:12 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
return (spc::ratio_numerator * (cpucycles - spc::reference_time) + spc::remainder) %
|
|
|
|
spc::ratio_denominator;
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
uint8 S9xAPUReadPort(int port)
|
2010-09-25 15:46:12 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
S9xAPUExecute();
|
|
|
|
return ((uint8)SNES::smp.port_read(port & 3));
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
void S9xAPUWritePort(int port, uint8 byte)
|
2010-09-25 15:46:12 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
S9xAPUExecute();
|
|
|
|
SNES::cpu.port_write(port & 3, byte);
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
void S9xAPUSetReferenceTime(int32 cpucycles)
|
2010-09-25 15:46:12 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
spc::reference_time = cpucycles;
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
void S9xAPUExecute(void)
|
2010-09-25 15:46:12 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
SNES::smp.clock -= S9xAPUGetClock(CPU.Cycles);
|
|
|
|
SNES::smp.enter();
|
2018-04-13 15:25:26 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
spc::remainder = S9xAPUGetClockRemainder(CPU.Cycles);
|
2018-04-13 15:25:26 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
S9xAPUSetReferenceTime(CPU.Cycles);
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
void S9xAPUEndScanline(void)
|
2010-09-25 15:46:12 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
S9xAPUExecute();
|
|
|
|
SNES::dsp.synchronize();
|
2010-09-25 15:46:12 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
if (SNES::dsp.spc_dsp.sample_count() >= APU_SAMPLE_BLOCK || !spc::sound_in_sync)
|
|
|
|
S9xLandSamples();
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
void S9xAPUTimingSetSpeedup(int ticks)
|
2010-09-25 15:46:12 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
if (ticks != 0)
|
|
|
|
printf("APU speedup hack: %d\n", ticks);
|
2010-09-25 15:46:12 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
spc::timing_hack_denominator = 256 - ticks;
|
2010-09-25 16:46:06 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
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;
|
2010-09-25 15:46:12 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
UpdatePlaybackRate();
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
void S9xResetAPU(void)
|
2010-09-25 15:46:12 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
spc::reference_time = 0;
|
|
|
|
spc::remainder = 0;
|
2011-06-18 10:31:44 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
SNES::cpu.reset();
|
|
|
|
SNES::cpu.frequency = Settings.PAL ? PAL_MASTER_CLOCK : NTSC_MASTER_CLOCK;
|
|
|
|
SNES::smp.power();
|
|
|
|
SNES::dsp.power();
|
|
|
|
reset_dsp_output();
|
|
|
|
SNES::dsp.spc_dsp.set_spc_snapshot_callback(SPCSnapshotCallback);
|
2010-09-25 15:46:12 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
spc::resampler->clear();
|
2016-11-03 18:40:47 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
if (Settings.MSU1)
|
|
|
|
msu::resampler->clear();
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
void S9xSoftResetAPU(void)
|
2010-09-25 15:46:12 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
spc::reference_time = 0;
|
|
|
|
spc::remainder = 0;
|
|
|
|
SNES::cpu.reset();
|
|
|
|
SNES::smp.reset();
|
|
|
|
SNES::dsp.reset();
|
|
|
|
reset_dsp_output();
|
2010-09-25 15:46:12 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
spc::resampler->clear();
|
2016-11-03 18:40:47 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
if (Settings.MSU1)
|
|
|
|
msu::resampler->clear();
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
void S9xAPUSaveState(uint8 *block)
|
2010-09-25 15:46:12 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
uint8 *ptr = block;
|
2010-09-25 15:46:12 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
SNES::smp.save_state(&ptr);
|
|
|
|
SNES::dsp.save_state(&ptr);
|
2010-09-25 15:46:12 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
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);
|
2014-02-18 21:34:23 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
memset(ptr, 0, SPC_SAVE_STATE_BLOCK_SIZE - (ptr - block));
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
void S9xAPULoadState(uint8 *block)
|
2010-09-25 15:46:12 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
uint8 *ptr = block;
|
2010-09-25 15:46:12 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
SNES::smp.load_state(&ptr);
|
|
|
|
SNES::dsp.load_state(&ptr);
|
2010-09-25 15:46:12 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
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);
|
2010-09-25 15:46:12 +00:00
|
|
|
}
|
2011-06-26 10:33:14 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
static void to_var_from_buf(uint8 **buf, void *var, size_t size)
|
2012-01-20 03:24:36 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
memcpy(var, *buf, size);
|
|
|
|
*buf += size;
|
2012-01-20 03:24:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#undef IF_0_THEN_256
|
2019-02-05 23:21:23 +00:00
|
|
|
#define IF_0_THEN_256(n) ((uint8)((n)-1) + 1)
|
2012-01-20 03:24:36 +00:00
|
|
|
void S9xAPULoadBlarggState(uint8 *oldblock)
|
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
uint8 *ptr = oldblock;
|
2012-01-20 03:24:36 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
SNES::SPC_State_Copier copier(&ptr, to_var_from_buf);
|
2012-01-20 03:24:36 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
copier.copy(SNES::smp.apuram, 0x10000); // RAM
|
2012-01-20 03:24:36 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
uint8 regs_in[0x10];
|
|
|
|
uint8 regs[0x10];
|
2018-07-28 20:59:15 +00:00
|
|
|
uint16 pc, spc_time, dsp_time;
|
2019-02-05 23:21:23 +00:00
|
|
|
uint8 a, x, y, psw, sp;
|
2012-01-20 03:24:36 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
copier.copy(regs, 0x10); // REGS
|
|
|
|
copier.copy(regs_in, 0x10); // REGS_IN
|
2012-01-20 03:24:36 +00:00
|
|
|
|
|
|
|
// CPU Regs
|
2019-02-05 23:21:23 +00:00
|
|
|
pc = copier.copy_int(0, sizeof(uint16));
|
|
|
|
a = copier.copy_int(0, sizeof(uint8));
|
|
|
|
x = copier.copy_int(0, sizeof(uint8));
|
|
|
|
y = copier.copy_int(0, sizeof(uint8));
|
|
|
|
psw = copier.copy_int(0, sizeof(uint8));
|
|
|
|
sp = copier.copy_int(0, sizeof(uint8));
|
2012-01-20 03:24:36 +00:00
|
|
|
copier.extra();
|
|
|
|
|
|
|
|
// times
|
2019-02-05 23:21:23 +00:00
|
|
|
spc_time = copier.copy_int(0, sizeof(uint16));
|
|
|
|
dsp_time = copier.copy_int(0, sizeof(uint16));
|
2012-01-20 03:24:36 +00:00
|
|
|
|
|
|
|
int cur_time = S9xAPUGetClock(CPU.Cycles);
|
|
|
|
|
|
|
|
// spc_time is absolute, dsp_time is relative
|
|
|
|
// smp.clock is relative, dsp.clock relative but counting upwards
|
|
|
|
SNES::smp.clock = spc_time - cur_time;
|
|
|
|
SNES::dsp.clock = -1 * dsp_time;
|
|
|
|
|
|
|
|
// DSP
|
|
|
|
SNES::dsp.load_state(&ptr);
|
|
|
|
|
|
|
|
// Timers
|
2018-07-28 20:59:15 +00:00
|
|
|
uint16 next_time[3];
|
|
|
|
uint8 divider[3], counter[3];
|
2019-02-05 23:21:23 +00:00
|
|
|
for (int i = 0; i < 3; i++)
|
2012-01-20 03:24:36 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
next_time[i] = copier.copy_int(0, sizeof(uint16));
|
|
|
|
divider[i] = copier.copy_int(0, sizeof(uint8));
|
|
|
|
counter[i] = copier.copy_int(0, sizeof(uint8));
|
2018-07-28 20:59:15 +00:00
|
|
|
copier.extra();
|
2012-01-20 03:24:36 +00:00
|
|
|
}
|
|
|
|
// construct timers out of available parts from blargg smp
|
2019-02-05 23:21:23 +00:00
|
|
|
SNES::smp.timer0.enable = regs[1] >> 0 & 1; // regs[1] = CONTROL
|
|
|
|
SNES::smp.timer0.target = IF_0_THEN_256(regs[10]); // regs[10+i] = TiTARGET
|
2012-01-20 03:24:36 +00:00
|
|
|
// blargg counts time, get ticks through timer frequency
|
|
|
|
// (assume tempo = 256)
|
|
|
|
SNES::smp.timer0.stage1_ticks = 128 - (next_time[0] - cur_time) / 128;
|
|
|
|
SNES::smp.timer0.stage2_ticks = divider[0];
|
|
|
|
SNES::smp.timer0.stage3_ticks = counter[0];
|
|
|
|
|
|
|
|
SNES::smp.timer1.enable = regs[1] >> 1 & 1;
|
|
|
|
SNES::smp.timer1.target = IF_0_THEN_256(regs[11]);
|
|
|
|
SNES::smp.timer1.stage1_ticks = 128 - (next_time[1] - cur_time) / 128;
|
|
|
|
SNES::smp.timer1.stage2_ticks = divider[0];
|
|
|
|
SNES::smp.timer1.stage3_ticks = counter[0];
|
|
|
|
|
|
|
|
SNES::smp.timer2.enable = regs[1] >> 2 & 1;
|
|
|
|
SNES::smp.timer2.target = IF_0_THEN_256(regs[12]);
|
|
|
|
SNES::smp.timer2.stage1_ticks = 16 - (next_time[2] - cur_time) / 16;
|
|
|
|
SNES::smp.timer2.stage2_ticks = divider[0];
|
|
|
|
SNES::smp.timer2.stage3_ticks = counter[0];
|
|
|
|
|
|
|
|
copier.extra();
|
|
|
|
|
|
|
|
SNES::smp.opcode_number = 0;
|
|
|
|
SNES::smp.opcode_cycle = 0;
|
|
|
|
|
|
|
|
SNES::smp.regs.pc = pc;
|
|
|
|
SNES::smp.regs.sp = sp;
|
2013-05-18 20:10:50 +00:00
|
|
|
SNES::smp.regs.B.a = a;
|
2012-01-20 03:24:36 +00:00
|
|
|
SNES::smp.regs.x = x;
|
2013-05-18 20:10:50 +00:00
|
|
|
SNES::smp.regs.B.y = y;
|
2012-01-20 03:24:36 +00:00
|
|
|
|
|
|
|
// blargg's psw has same layout as byuu's flags
|
|
|
|
SNES::smp.regs.p = psw;
|
|
|
|
|
|
|
|
// blargg doesn't explicitly store iplrom_enable
|
|
|
|
SNES::smp.status.iplrom_enable = regs[1] & 0x80;
|
|
|
|
|
|
|
|
SNES::smp.status.dsp_addr = regs[2];
|
|
|
|
|
|
|
|
SNES::smp.status.ram00f8 = regs_in[8];
|
|
|
|
SNES::smp.status.ram00f9 = regs_in[9];
|
|
|
|
|
|
|
|
// default to 0 - we are on an opcode boundary, shouldn't matter
|
2019-02-05 23:21:23 +00:00
|
|
|
SNES::smp.rd = SNES::smp.wr = SNES::smp.dp = SNES::smp.sp = SNES::smp.ya = SNES::smp.bit = 0;
|
2012-01-20 03:24:36 +00:00
|
|
|
|
|
|
|
spc::reference_time = SNES::get_le32(ptr);
|
|
|
|
ptr += sizeof(int32);
|
|
|
|
spc::remainder = SNES::get_le32(ptr);
|
|
|
|
|
|
|
|
// blargg stores CPUIx in regs_in
|
2019-02-05 23:21:23 +00:00
|
|
|
memcpy(SNES::cpu.registers, regs_in + 4, 4);
|
2012-01-20 03:24:36 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
bool8 S9xSPCDump(const char *filename)
|
2011-06-26 10:33:14 +00:00
|
|
|
{
|
2019-02-05 23:21:23 +00:00
|
|
|
FILE *fs;
|
|
|
|
uint8 buf[SPC_FILE_SIZE];
|
|
|
|
size_t ignore;
|
2011-06-26 10:33:14 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
fs = fopen(filename, "wb");
|
|
|
|
if (!fs)
|
|
|
|
return (FALSE);
|
2011-06-26 10:33:14 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
S9xSetSoundMute(TRUE);
|
2011-06-26 10:33:14 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
SNES::smp.save_spc(buf);
|
2011-06-26 10:33:14 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
ignore = fwrite(buf, SPC_FILE_SIZE, 1, fs);
|
2016-09-27 20:25:37 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
if (ignore == 0)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "Couldn't write file %s.\n", filename);
|
|
|
|
}
|
2011-06-26 10:33:14 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
fclose(fs);
|
2011-06-26 10:33:14 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
S9xSetSoundMute(FALSE);
|
2011-06-26 10:33:14 +00:00
|
|
|
|
2019-02-05 23:21:23 +00:00
|
|
|
return (TRUE);
|
2011-06-26 10:33:14 +00:00
|
|
|
}
|