SPC: Rewrite SPC core
This commit is contained in:
parent
ebfd42f5b6
commit
66c457771c
16 changed files with 2143 additions and 2760 deletions
|
@ -86,7 +86,6 @@
|
|||
<ClInclude Include="DrawRectangleCommand.h" />
|
||||
<ClInclude Include="DrawScreenBufferCommand.h" />
|
||||
<ClInclude Include="DrawStringCommand.h" />
|
||||
<ClInclude Include="dsp.h" />
|
||||
<ClInclude Include="FrameLimiter.h" />
|
||||
<ClInclude Include="IAudioDevice.h" />
|
||||
<ClInclude Include="IInputProvider.h" />
|
||||
|
@ -115,11 +114,11 @@
|
|||
<ClInclude Include="ShortcutKeyHandler.h" />
|
||||
<ClInclude Include="SnesController.h" />
|
||||
<ClInclude Include="SnesMouse.h" />
|
||||
<ClInclude Include="SNES_SPC.h" />
|
||||
<ClInclude Include="SoundMixer.h" />
|
||||
<ClInclude Include="SoundResampler.h" />
|
||||
<ClInclude Include="Spc.h" />
|
||||
<ClInclude Include="SPC_CPU.h" />
|
||||
<ClInclude Include="SpcTimer.h" />
|
||||
<ClInclude Include="SpcTypes.h" />
|
||||
<ClInclude Include="SPC_DSP.h" />
|
||||
<ClInclude Include="SPC_Filter.h" />
|
||||
<ClInclude Include="stdafx.h" />
|
||||
|
@ -169,12 +168,10 @@
|
|||
<ClCompile Include="SaveStateManager.cpp" />
|
||||
<ClCompile Include="ScaleFilter.cpp" />
|
||||
<ClCompile Include="ShortcutKeyHandler.cpp" />
|
||||
<ClCompile Include="SNES_SPC.cpp" />
|
||||
<ClCompile Include="SNES_SPC_misc.cpp" />
|
||||
<ClCompile Include="SNES_SPC_state.cpp" />
|
||||
<ClCompile Include="SoundMixer.cpp" />
|
||||
<ClCompile Include="SoundResampler.cpp" />
|
||||
<ClCompile Include="Spc.cpp" />
|
||||
<ClCompile Include="Spc.Instructions.cpp" />
|
||||
<ClCompile Include="SPC_DSP.cpp" />
|
||||
<ClCompile Include="SPC_Filter.cpp" />
|
||||
<ClCompile Include="stdafx.cpp">
|
||||
|
|
|
@ -114,31 +114,22 @@
|
|||
<Filter>Misc</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="blargg_common.h">
|
||||
<Filter>SNES\SPC</Filter>
|
||||
<Filter>SNES\DSP</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="blargg_config.h">
|
||||
<Filter>SNES\SPC</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="dsp.h">
|
||||
<Filter>SNES\SPC</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SNES_SPC.h">
|
||||
<Filter>SNES\SPC</Filter>
|
||||
<Filter>SNES\DSP</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SPC_Filter.h">
|
||||
<Filter>SNES\SPC</Filter>
|
||||
<Filter>SNES\DSP</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SPC_DSP.h">
|
||||
<Filter>SNES\SPC</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SPC_CPU.h">
|
||||
<Filter>SNES\SPC</Filter>
|
||||
<Filter>SNES\DSP</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="blargg_source.h">
|
||||
<Filter>SNES\SPC</Filter>
|
||||
<Filter>SNES\DSP</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="blargg_endian.h">
|
||||
<Filter>SNES\SPC</Filter>
|
||||
<Filter>SNES\DSP</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Spc.h">
|
||||
<Filter>SNES</Filter>
|
||||
|
@ -254,6 +245,12 @@
|
|||
<ClInclude Include="SystemActionManager.h">
|
||||
<Filter>SNES\Input</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SpcTypes.h">
|
||||
<Filter>SNES</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SpcTimer.h">
|
||||
<Filter>SNES</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="stdafx.cpp" />
|
||||
|
@ -311,20 +308,11 @@
|
|||
<ClCompile Include="NotificationManager.cpp">
|
||||
<Filter>Misc</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SNES_SPC.cpp">
|
||||
<Filter>SNES\SPC</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SPC_DSP.cpp">
|
||||
<Filter>SNES\SPC</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SNES_SPC_misc.cpp">
|
||||
<Filter>SNES\SPC</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SNES_SPC_state.cpp">
|
||||
<Filter>SNES\SPC</Filter>
|
||||
<Filter>SNES\DSP</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SPC_Filter.cpp">
|
||||
<Filter>SNES\SPC</Filter>
|
||||
<Filter>SNES\DSP</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Spc.cpp">
|
||||
<Filter>SNES</Filter>
|
||||
|
@ -408,6 +396,9 @@
|
|||
<ClCompile Include="WaveRecorder.cpp">
|
||||
<Filter>Misc</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Spc.Instructions.cpp">
|
||||
<Filter>SNES</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="SNES">
|
||||
|
@ -428,14 +419,14 @@
|
|||
<Filter Include="Video\DebugHud">
|
||||
<UniqueIdentifier>{0d0cf24e-4126-4bfb-b391-27a015e7722a}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="SNES\SPC">
|
||||
<UniqueIdentifier>{e0e6a67b-620e-4fe4-9846-26a6c80ca6b6}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="SNES\Input">
|
||||
<UniqueIdentifier>{3a9ea5fe-e818-4e65-bd50-cb9591de3e0d}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Audio">
|
||||
<UniqueIdentifier>{cfc63477-84ed-48e4-81dc-8f960303b40a}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="SNES\DSP">
|
||||
<UniqueIdentifier>{e0e6a67b-620e-4fe4-9846-26a6c80ca6b6}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -19,7 +19,7 @@ uint8_t RegisterHandlerB::Read(uint32_t addr)
|
|||
{
|
||||
addr &= 0xFFFF;
|
||||
if(addr >= 0x2140 && addr <= 0x217F) {
|
||||
return _spc->Read(addr & 0x03);
|
||||
return _spc->CpuReadRegister(addr & 0x03);
|
||||
} else if(addr == 0x2180) {
|
||||
uint8_t value = _workRam[_wramPosition];
|
||||
_console->ProcessWorkRamRead(_wramPosition, value);
|
||||
|
@ -40,7 +40,7 @@ void RegisterHandlerB::Write(uint32_t addr, uint8_t value)
|
|||
{
|
||||
addr &= 0xFFFF;
|
||||
if(addr >= 0x2140 && addr <= 0x217F) {
|
||||
return _spc->Write(addr & 0x03, value);
|
||||
return _spc->CpuWriteRegister(addr & 0x03, value);
|
||||
} if(addr >= 0x2180 && addr <= 0x2183) {
|
||||
switch(addr & 0xFFFF) {
|
||||
case 0x2180:
|
||||
|
|
|
@ -1,565 +0,0 @@
|
|||
#include "stdafx.h"
|
||||
// Core SPC emulation: CPU, timers, SMP registers, memory
|
||||
|
||||
// snes_spc 0.9.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "SNES_SPC.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* Copyright (C) 2004-2007 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
#define RAM (m.ram.ram)
|
||||
#define REGS (m.smp_regs [0])
|
||||
#define REGS_IN (m.smp_regs [1])
|
||||
|
||||
// (n ? n : 256)
|
||||
#define IF_0_THEN_256( n ) ((uint8_t) ((n) - 1) + 1)
|
||||
|
||||
// Note: SPC_MORE_ACCURACY exists mainly so I can run my validation tests, which
|
||||
// do crazy echo buffer accesses.
|
||||
#ifndef SPC_MORE_ACCURACY
|
||||
#define SPC_MORE_ACCURACY 0
|
||||
#endif
|
||||
|
||||
#ifdef BLARGG_ENABLE_OPTIMIZER
|
||||
#include BLARGG_ENABLE_OPTIMIZER
|
||||
#endif
|
||||
|
||||
|
||||
//// Timers
|
||||
|
||||
#if SPC_DISABLE_TEMPO
|
||||
#define TIMER_DIV( t, n ) ((n) >> t->prescaler)
|
||||
#define TIMER_MUL( t, n ) ((n) << t->prescaler)
|
||||
#else
|
||||
#define TIMER_DIV( t, n ) ((n) / t->prescaler)
|
||||
#define TIMER_MUL( t, n ) ((n) * t->prescaler)
|
||||
#endif
|
||||
|
||||
SNES_SPC::Timer* SNES_SPC::run_timer_( Timer* t, rel_time_t time )
|
||||
{
|
||||
int elapsed = TIMER_DIV( t, time - t->next_time ) + 1;
|
||||
t->next_time += TIMER_MUL( t, elapsed );
|
||||
|
||||
if ( t->enabled )
|
||||
{
|
||||
int remain = IF_0_THEN_256( t->period - t->divider );
|
||||
int divider = t->divider + elapsed;
|
||||
int over = elapsed - remain;
|
||||
if ( over >= 0 )
|
||||
{
|
||||
int n = over / t->period;
|
||||
t->counter = (t->counter + 1 + n) & 0x0F;
|
||||
divider = over - n * t->period;
|
||||
}
|
||||
t->divider = (uint8_t) divider;
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
inline SNES_SPC::Timer* SNES_SPC::run_timer( Timer* t, rel_time_t time )
|
||||
{
|
||||
if ( time >= t->next_time )
|
||||
t = run_timer_( t, time );
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
//// ROM
|
||||
|
||||
void SNES_SPC::enable_rom( int enable )
|
||||
{
|
||||
if ( m.rom_enabled != enable )
|
||||
{
|
||||
m.rom_enabled = enable;
|
||||
if ( enable )
|
||||
memcpy( m.hi_ram, &RAM [rom_addr], sizeof m.hi_ram );
|
||||
memcpy( &RAM [rom_addr], (enable ? m.rom : m.hi_ram), rom_size );
|
||||
// TODO: ROM can still get overwritten when DSP writes to echo buffer
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// DSP
|
||||
|
||||
#if SPC_LESS_ACCURATE
|
||||
int const max_reg_time = 29;
|
||||
|
||||
signed char const SNES_SPC::reg_times_ [256] =
|
||||
{
|
||||
-1, 0,-11,-10,-15,-11, -2, -2, 4, 3, 14, 14, 26, 26, 14, 22,
|
||||
2, 3, 0, 1,-12, 0, 1, 1, 7, 6, 14, 14, 27, 14, 14, 23,
|
||||
5, 6, 3, 4, -1, 3, 4, 4, 10, 9, 14, 14, 26, -5, 14, 23,
|
||||
8, 9, 6, 7, 2, 6, 7, 7, 13, 12, 14, 14, 27, -4, 14, 24,
|
||||
11, 12, 9, 10, 5, 9, 10, 10, 16, 15, 14, 14, -2, -4, 14, 24,
|
||||
14, 15, 12, 13, 8, 12, 13, 13, 19, 18, 14, 14, -2,-36, 14, 24,
|
||||
17, 18, 15, 16, 11, 15, 16, 16, 22, 21, 14, 14, 28, -3, 14, 25,
|
||||
20, 21, 18, 19, 14, 18, 19, 19, 25, 24, 14, 14, 14, 29, 14, 25,
|
||||
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||
};
|
||||
|
||||
#define RUN_DSP( time, offset ) \
|
||||
int count = (time) - (offset) - m.dsp_time;\
|
||||
if ( count >= 0 )\
|
||||
{\
|
||||
int clock_count = (count & ~(clocks_per_sample - 1)) + clocks_per_sample;\
|
||||
m.dsp_time += clock_count;\
|
||||
dsp.run( clock_count );\
|
||||
}
|
||||
#else
|
||||
#define RUN_DSP( time, offset ) \
|
||||
{\
|
||||
int count = (time) - m.dsp_time;\
|
||||
if ( !SPC_MORE_ACCURACY || count )\
|
||||
{\
|
||||
assert( count > 0 );\
|
||||
m.dsp_time = (time);\
|
||||
dsp.run( count );\
|
||||
}\
|
||||
}
|
||||
#endif
|
||||
|
||||
int SNES_SPC::dsp_read( rel_time_t time )
|
||||
{
|
||||
RUN_DSP( time, reg_times [REGS [r_dspaddr] & 0x7F] );
|
||||
|
||||
int result = dsp.read( REGS [r_dspaddr] & 0x7F );
|
||||
|
||||
#ifdef SPC_DSP_READ_HOOK
|
||||
SPC_DSP_READ_HOOK( spc_time + time, (REGS [r_dspaddr] & 0x7F), result );
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline void SNES_SPC::dsp_write( int data, rel_time_t time )
|
||||
{
|
||||
RUN_DSP( time, reg_times [REGS [r_dspaddr]] )
|
||||
#if SPC_LESS_ACCURATE
|
||||
else if ( m.dsp_time == skipping_time )
|
||||
{
|
||||
int r = REGS [r_dspaddr];
|
||||
if ( r == SPC_DSP::r_kon )
|
||||
m.skipped_kon |= data & ~dsp.read( SPC_DSP::r_koff );
|
||||
|
||||
if ( r == SPC_DSP::r_koff )
|
||||
{
|
||||
m.skipped_koff |= data;
|
||||
m.skipped_kon &= ~data;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SPC_DSP_WRITE_HOOK
|
||||
SPC_DSP_WRITE_HOOK( m.spc_time + time, REGS [r_dspaddr], (uint8_t) data );
|
||||
#endif
|
||||
|
||||
if ( REGS [r_dspaddr] <= 0x7F )
|
||||
dsp.write( REGS [r_dspaddr], data );
|
||||
else if ( !SPC_MORE_ACCURACY )
|
||||
dprintf( "SPC wrote to DSP register > $7F\n" );
|
||||
}
|
||||
|
||||
|
||||
//// Memory access extras
|
||||
|
||||
#if SPC_MORE_ACCURACY
|
||||
#define MEM_ACCESS( time, addr ) \
|
||||
{\
|
||||
if ( time >= m.dsp_time )\
|
||||
{\
|
||||
RUN_DSP( time, max_reg_time );\
|
||||
}\
|
||||
}
|
||||
#elif !defined (NDEBUG)
|
||||
// Debug-only check for read/write within echo buffer, since this might result in
|
||||
// inaccurate emulation due to the DSP not being caught up to the present.
|
||||
|
||||
bool SNES_SPC::check_echo_access( int addr )
|
||||
{
|
||||
if ( !(dsp.read( SPC_DSP::r_flg ) & 0x20) )
|
||||
{
|
||||
int start = 0x100 * dsp.read( SPC_DSP::r_esa );
|
||||
int size = 0x800 * (dsp.read( SPC_DSP::r_edl ) & 0x0F);
|
||||
int end = start + (size ? size : 4);
|
||||
if ( start <= addr && addr < end )
|
||||
{
|
||||
if ( !m.echo_accessed )
|
||||
{
|
||||
m.echo_accessed = 1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#define MEM_ACCESS( time, addr ) check( !check_echo_access( (uint16_t) addr ) );
|
||||
#else
|
||||
#define MEM_ACCESS( time, addr )
|
||||
#endif
|
||||
|
||||
|
||||
//// CPU write
|
||||
|
||||
#if SPC_MORE_ACCURACY
|
||||
static unsigned char const glitch_probs [3] [256] =
|
||||
{
|
||||
0xC3,0x92,0x5B,0x1C,0xD1,0x92,0x5B,0x1C,0xDB,0x9C,0x72,0x18,0xCD,0x5C,0x38,0x0B,
|
||||
0xE1,0x9C,0x74,0x17,0xCF,0x75,0x45,0x0C,0xCF,0x6E,0x4A,0x0D,0xA3,0x3A,0x1D,0x08,
|
||||
0xDB,0xA0,0x82,0x19,0xD9,0x73,0x3C,0x0E,0xCB,0x76,0x52,0x0B,0xA5,0x46,0x1D,0x09,
|
||||
0xDA,0x74,0x55,0x0F,0xA2,0x3F,0x21,0x05,0x9A,0x40,0x20,0x07,0x63,0x1E,0x10,0x01,
|
||||
0xDF,0xA9,0x85,0x1D,0xD3,0x84,0x4B,0x0E,0xCF,0x6F,0x49,0x0F,0xB3,0x48,0x1E,0x05,
|
||||
0xD8,0x77,0x52,0x12,0xB7,0x49,0x23,0x06,0xAA,0x45,0x28,0x07,0x7D,0x28,0x0F,0x07,
|
||||
0xCC,0x7B,0x4A,0x0E,0xB2,0x4F,0x24,0x07,0xAD,0x43,0x2C,0x06,0x86,0x29,0x11,0x07,
|
||||
0xAE,0x48,0x1F,0x0A,0x76,0x21,0x19,0x05,0x76,0x21,0x14,0x05,0x44,0x11,0x0B,0x01,
|
||||
0xE7,0xAD,0x96,0x23,0xDC,0x86,0x59,0x0E,0xDC,0x7C,0x5F,0x15,0xBB,0x53,0x2E,0x09,
|
||||
0xD6,0x7C,0x4A,0x16,0xBB,0x4A,0x25,0x08,0xB3,0x4F,0x28,0x0B,0x8E,0x23,0x15,0x08,
|
||||
0xCF,0x7F,0x57,0x11,0xB5,0x4A,0x23,0x0A,0xAA,0x42,0x28,0x05,0x7D,0x22,0x12,0x03,
|
||||
0xA6,0x49,0x28,0x09,0x82,0x2B,0x0D,0x04,0x7A,0x20,0x0F,0x04,0x3D,0x0F,0x09,0x03,
|
||||
0xD1,0x7C,0x4C,0x0F,0xAF,0x4E,0x21,0x09,0xA8,0x46,0x2A,0x07,0x85,0x1F,0x0E,0x07,
|
||||
0xA6,0x3F,0x26,0x07,0x7C,0x24,0x14,0x07,0x78,0x22,0x16,0x04,0x46,0x12,0x0A,0x02,
|
||||
0xA6,0x41,0x2C,0x0A,0x7E,0x28,0x11,0x05,0x73,0x1B,0x14,0x05,0x3D,0x11,0x0A,0x02,
|
||||
0x70,0x22,0x17,0x05,0x48,0x13,0x08,0x03,0x3C,0x07,0x0D,0x07,0x26,0x07,0x06,0x01,
|
||||
|
||||
0xE0,0x9F,0xDA,0x7C,0x4F,0x18,0x28,0x0D,0xE9,0x9F,0xDA,0x7C,0x4F,0x18,0x1F,0x07,
|
||||
0xE6,0x97,0xD8,0x72,0x64,0x13,0x26,0x09,0xDC,0x67,0xA9,0x38,0x21,0x07,0x15,0x06,
|
||||
0xE9,0x91,0xD2,0x6B,0x63,0x14,0x2B,0x0E,0xD6,0x61,0xB7,0x41,0x2B,0x0E,0x10,0x09,
|
||||
0xCF,0x59,0xB0,0x2F,0x35,0x08,0x0F,0x07,0xB6,0x30,0x7A,0x21,0x17,0x07,0x09,0x03,
|
||||
0xE7,0xA3,0xE5,0x6B,0x65,0x1F,0x34,0x09,0xD8,0x6B,0xBE,0x45,0x27,0x07,0x10,0x07,
|
||||
0xDA,0x54,0xB1,0x39,0x2E,0x0E,0x17,0x08,0xA9,0x3C,0x86,0x22,0x16,0x06,0x07,0x03,
|
||||
0xD4,0x51,0xBC,0x3D,0x38,0x0A,0x13,0x06,0xB2,0x37,0x79,0x1C,0x17,0x05,0x0E,0x06,
|
||||
0xA7,0x31,0x74,0x1C,0x11,0x06,0x0C,0x02,0x6D,0x1A,0x38,0x10,0x0B,0x05,0x06,0x03,
|
||||
0xEB,0x9A,0xE1,0x7A,0x6F,0x13,0x34,0x0E,0xE6,0x75,0xC5,0x45,0x3E,0x0B,0x1A,0x05,
|
||||
0xD8,0x63,0xC1,0x40,0x3C,0x1B,0x19,0x06,0xB3,0x42,0x83,0x29,0x18,0x0A,0x08,0x04,
|
||||
0xD4,0x58,0xBA,0x43,0x3F,0x0A,0x1F,0x09,0xB1,0x33,0x8A,0x1F,0x1F,0x06,0x0D,0x05,
|
||||
0xAF,0x3C,0x7A,0x1F,0x16,0x08,0x0A,0x01,0x72,0x1B,0x52,0x0D,0x0B,0x09,0x06,0x01,
|
||||
0xCF,0x63,0xB7,0x47,0x40,0x10,0x14,0x06,0xC0,0x41,0x96,0x20,0x1C,0x09,0x10,0x05,
|
||||
0xA6,0x35,0x82,0x1A,0x20,0x0C,0x0E,0x04,0x80,0x1F,0x53,0x0F,0x0B,0x02,0x06,0x01,
|
||||
0xA6,0x31,0x81,0x1B,0x1D,0x01,0x08,0x08,0x7B,0x20,0x4D,0x19,0x0E,0x05,0x07,0x03,
|
||||
0x6B,0x17,0x49,0x07,0x0E,0x03,0x0A,0x05,0x37,0x0B,0x1F,0x06,0x04,0x02,0x07,0x01,
|
||||
|
||||
0xF0,0xD6,0xED,0xAD,0xEC,0xB1,0xEB,0x79,0xAC,0x22,0x47,0x1E,0x6E,0x1B,0x32,0x0A,
|
||||
0xF0,0xD6,0xEA,0xA4,0xED,0xC4,0xDE,0x82,0x98,0x1F,0x50,0x13,0x52,0x15,0x2A,0x0A,
|
||||
0xF1,0xD1,0xEB,0xA2,0xEB,0xB7,0xD8,0x69,0xA2,0x1F,0x5B,0x18,0x55,0x18,0x2C,0x0A,
|
||||
0xED,0xB5,0xDE,0x7E,0xE6,0x85,0xD3,0x59,0x59,0x0F,0x2C,0x09,0x24,0x07,0x15,0x09,
|
||||
0xF1,0xD6,0xEA,0xA0,0xEC,0xBB,0xDA,0x77,0xA9,0x23,0x58,0x14,0x5D,0x12,0x2F,0x09,
|
||||
0xF1,0xC1,0xE3,0x86,0xE4,0x87,0xD2,0x4E,0x68,0x15,0x26,0x0B,0x27,0x09,0x15,0x02,
|
||||
0xEE,0xA6,0xE0,0x5C,0xE0,0x77,0xC3,0x41,0x67,0x1B,0x3C,0x07,0x2A,0x06,0x19,0x07,
|
||||
0xE4,0x75,0xC6,0x43,0xCC,0x50,0x95,0x23,0x35,0x09,0x14,0x04,0x15,0x05,0x0B,0x04,
|
||||
0xEE,0xD6,0xED,0xAD,0xEC,0xB1,0xEB,0x79,0xAC,0x22,0x56,0x14,0x5A,0x12,0x26,0x0A,
|
||||
0xEE,0xBB,0xE7,0x7E,0xE9,0x8D,0xCB,0x49,0x67,0x11,0x34,0x07,0x2B,0x0B,0x14,0x07,
|
||||
0xED,0xA7,0xE5,0x76,0xE3,0x7E,0xC4,0x4B,0x77,0x14,0x34,0x08,0x27,0x07,0x14,0x04,
|
||||
0xE7,0x8B,0xD2,0x4C,0xCA,0x56,0x9E,0x31,0x36,0x0C,0x11,0x07,0x14,0x04,0x0A,0x02,
|
||||
0xF0,0x9B,0xEA,0x6F,0xE5,0x81,0xC4,0x43,0x74,0x10,0x30,0x0B,0x2D,0x08,0x1B,0x06,
|
||||
0xE6,0x83,0xCA,0x48,0xD9,0x56,0xA7,0x23,0x3B,0x09,0x12,0x09,0x15,0x07,0x0A,0x03,
|
||||
0xE5,0x5F,0xCB,0x3C,0xCF,0x48,0x91,0x22,0x31,0x0A,0x17,0x08,0x15,0x04,0x0D,0x02,
|
||||
0xD1,0x43,0x91,0x20,0xA9,0x2D,0x54,0x12,0x17,0x07,0x09,0x02,0x0C,0x04,0x05,0x03,
|
||||
};
|
||||
#endif
|
||||
|
||||
// divided into multiple functions to keep rarely-used functionality separate
|
||||
// so often-used functionality can be optimized better by compiler
|
||||
|
||||
// If write isn't preceded by read, data has this added to it
|
||||
int const no_read_before_write = 0x2000;
|
||||
|
||||
void SNES_SPC::cpu_write_smp_reg_( int data, rel_time_t time, int addr )
|
||||
{
|
||||
switch ( addr )
|
||||
{
|
||||
case r_t0target:
|
||||
case r_t1target:
|
||||
case r_t2target: {
|
||||
Timer* t = &m.timers [addr - r_t0target];
|
||||
int period = IF_0_THEN_256( data );
|
||||
if ( t->period != period )
|
||||
{
|
||||
t = run_timer( t, time );
|
||||
#if SPC_MORE_ACCURACY
|
||||
// Insane behavior when target is written just after counter is
|
||||
// clocked and counter matches new period and new period isn't 1, 2, 4, or 8
|
||||
if ( t->divider == (period & 0xFF) &&
|
||||
t->next_time == time + TIMER_MUL( t, 1 ) &&
|
||||
((period - 1) | ~0x0F) & period )
|
||||
{
|
||||
//dprintf( "SPC pathological timer target write\n" );
|
||||
|
||||
// If the period is 3, 5, or 9, there's a probability this behavior won't occur,
|
||||
// based on the previous period
|
||||
int prob = 0xFF;
|
||||
int old_period = t->period & 0xFF;
|
||||
if ( period == 3 ) prob = glitch_probs [0] [old_period];
|
||||
if ( period == 5 ) prob = glitch_probs [1] [old_period];
|
||||
if ( period == 9 ) prob = glitch_probs [2] [old_period];
|
||||
|
||||
// The glitch suppresses incrementing of one of the counter bits, based on
|
||||
// the lowest set bit in the new period
|
||||
int b = 1;
|
||||
while ( !(period & b) )
|
||||
b <<= 1;
|
||||
|
||||
if ( (rand() >> 4 & 0xFF) <= prob )
|
||||
t->divider = (t->divider - b) & 0xFF;
|
||||
}
|
||||
#endif
|
||||
t->period = period;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case r_t0out:
|
||||
case r_t1out:
|
||||
case r_t2out:
|
||||
if ( !SPC_MORE_ACCURACY )
|
||||
dprintf( "SPC wrote to counter %d\n", (int) addr - r_t0out );
|
||||
|
||||
if ( data < no_read_before_write / 2 )
|
||||
run_timer( &m.timers [addr - r_t0out], time - 1 )->counter = 0;
|
||||
break;
|
||||
|
||||
// Registers that act like RAM
|
||||
case 0x8:
|
||||
case 0x9:
|
||||
REGS_IN [addr] = (uint8_t) data;
|
||||
break;
|
||||
|
||||
case r_test:
|
||||
if ( (uint8_t) data != 0x0A )
|
||||
dprintf( "SPC wrote to test register\n" );
|
||||
break;
|
||||
|
||||
case r_control:
|
||||
// port clears
|
||||
if ( data & 0x10 )
|
||||
{
|
||||
REGS_IN [r_cpuio0] = 0;
|
||||
REGS_IN [r_cpuio1] = 0;
|
||||
}
|
||||
if ( data & 0x20 )
|
||||
{
|
||||
REGS_IN [r_cpuio2] = 0;
|
||||
REGS_IN [r_cpuio3] = 0;
|
||||
}
|
||||
|
||||
// timers
|
||||
{
|
||||
for ( int i = 0; i < timer_count; i++ )
|
||||
{
|
||||
Timer* t = &m.timers [i];
|
||||
int enabled = data >> i & 1;
|
||||
if ( t->enabled != enabled )
|
||||
{
|
||||
t = run_timer( t, time );
|
||||
t->enabled = enabled;
|
||||
if ( enabled )
|
||||
{
|
||||
t->divider = 0;
|
||||
t->counter = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
enable_rom( data & 0x80 );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SNES_SPC::cpu_write_smp_reg( int data, rel_time_t time, int addr )
|
||||
{
|
||||
if ( addr == r_dspdata ) // 99%
|
||||
dsp_write( data, time );
|
||||
else
|
||||
cpu_write_smp_reg_( data, time, addr );
|
||||
}
|
||||
|
||||
void SNES_SPC::cpu_write_high( int data, int i, rel_time_t time )
|
||||
{
|
||||
if ( i < rom_size )
|
||||
{
|
||||
m.hi_ram [i] = (uint8_t) data;
|
||||
if ( m.rom_enabled )
|
||||
RAM [i + rom_addr] = m.rom [i]; // restore overwritten ROM
|
||||
}
|
||||
else
|
||||
{
|
||||
assert( RAM [i + rom_addr] == (uint8_t) data );
|
||||
RAM [i + rom_addr] = cpu_pad_fill; // restore overwritten padding
|
||||
cpu_write( data, i + rom_addr - 0x10000, time );
|
||||
}
|
||||
}
|
||||
|
||||
int const bits_in_int = CHAR_BIT * sizeof (int);
|
||||
|
||||
void SNES_SPC::cpu_write( int data, int addr, rel_time_t time )
|
||||
{
|
||||
MEM_ACCESS( time, addr )
|
||||
|
||||
// RAM
|
||||
RAM [addr] = (uint8_t) data;
|
||||
int reg = addr - 0xF0;
|
||||
if ( reg >= 0 ) // 64%
|
||||
{
|
||||
// $F0-$FF
|
||||
if ( reg < reg_count ) // 87%
|
||||
{
|
||||
REGS [reg] = (uint8_t) data;
|
||||
|
||||
// Ports
|
||||
#ifdef SPC_PORT_WRITE_HOOK
|
||||
if ( (unsigned) (reg - r_cpuio0) < port_count )
|
||||
SPC_PORT_WRITE_HOOK( m.spc_time + time, (reg - r_cpuio0),
|
||||
(uint8_t) data, ®S [r_cpuio0] );
|
||||
#endif
|
||||
|
||||
// Registers other than $F2 and $F4-$F7
|
||||
//if ( reg != 2 && reg != 4 && reg != 5 && reg != 6 && reg != 7 )
|
||||
// TODO: this is a bit on the fragile side
|
||||
if ( ((~0x2F00 << (bits_in_int - 16)) << reg) < 0 ) // 36%
|
||||
cpu_write_smp_reg( data, time, reg );
|
||||
}
|
||||
// High mem/address wrap-around
|
||||
else
|
||||
{
|
||||
reg -= rom_addr - 0xF0;
|
||||
if ( reg >= 0 ) // 1% in IPL ROM area or address wrapped around
|
||||
cpu_write_high( data, reg, time );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// CPU read
|
||||
|
||||
inline int SNES_SPC::cpu_read_smp_reg( int reg, rel_time_t time )
|
||||
{
|
||||
int result = REGS_IN [reg];
|
||||
reg -= r_dspaddr;
|
||||
// DSP addr and data
|
||||
if ( (unsigned) reg <= 1 ) // 4% 0xF2 and 0xF3
|
||||
{
|
||||
result = REGS [r_dspaddr];
|
||||
if ( (unsigned) reg == 1 )
|
||||
result = dsp_read( time ); // 0xF3
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int SNES_SPC::cpu_read( int addr, rel_time_t time )
|
||||
{
|
||||
MEM_ACCESS( time, addr )
|
||||
|
||||
// RAM
|
||||
int result = RAM [addr];
|
||||
int reg = addr - 0xF0;
|
||||
if ( reg >= 0 ) // 40%
|
||||
{
|
||||
reg -= 0x10;
|
||||
if ( (unsigned) reg >= 0xFF00 ) // 21%
|
||||
{
|
||||
reg += 0x10 - r_t0out;
|
||||
|
||||
// Timers
|
||||
if ( (unsigned) reg < timer_count ) // 90%
|
||||
{
|
||||
Timer* t = &m.timers [reg];
|
||||
if ( time >= t->next_time )
|
||||
t = run_timer_( t, time );
|
||||
result = t->counter;
|
||||
t->counter = 0;
|
||||
}
|
||||
// Other registers
|
||||
else if ( reg < 0 ) // 10%
|
||||
{
|
||||
result = cpu_read_smp_reg( reg + r_t0out, time );
|
||||
}
|
||||
else // 1%
|
||||
{
|
||||
assert( reg + (r_t0out + 0xF0 - 0x10000) < 0x100 );
|
||||
result = cpu_read( reg + (r_t0out + 0xF0 - 0x10000), time );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
//// Run
|
||||
|
||||
// Prefix and suffix for CPU emulator function
|
||||
#define SPC_CPU_RUN_FUNC \
|
||||
BOOST::uint8_t* SNES_SPC::run_until_( time_t end_time )\
|
||||
{\
|
||||
rel_time_t rel_time = m.spc_time - end_time;\
|
||||
assert( rel_time <= 0 );\
|
||||
m.spc_time = end_time;\
|
||||
m.dsp_time += rel_time;\
|
||||
m.timers [0].next_time += rel_time;\
|
||||
m.timers [1].next_time += rel_time;\
|
||||
m.timers [2].next_time += rel_time;
|
||||
|
||||
#define SPC_CPU_RUN_FUNC_END \
|
||||
m.spc_time += rel_time;\
|
||||
m.dsp_time -= rel_time;\
|
||||
m.timers [0].next_time -= rel_time;\
|
||||
m.timers [1].next_time -= rel_time;\
|
||||
m.timers [2].next_time -= rel_time;\
|
||||
assert( m.spc_time <= end_time );\
|
||||
return ®S [r_cpuio0];\
|
||||
}
|
||||
|
||||
int const cpu_lag_max = 12 - 1; // DIV YA,X takes 12 clocks
|
||||
|
||||
void SNES_SPC::end_frame( time_t end_time )
|
||||
{
|
||||
// Catch CPU up to as close to end as possible. If final instruction
|
||||
// would exceed end, does NOT execute it and leaves m.spc_time < end.
|
||||
if ( end_time > m.spc_time )
|
||||
run_until_( end_time );
|
||||
|
||||
m.spc_time -= end_time;
|
||||
m.extra_clocks += end_time;
|
||||
|
||||
// Greatest number of clocks early that emulation can stop early due to
|
||||
// not being able to execute current instruction without going over
|
||||
// allowed time.
|
||||
assert( -cpu_lag_max <= m.spc_time && m.spc_time <= 0 );
|
||||
|
||||
// Catch timers up to CPU
|
||||
for ( int i = 0; i < timer_count; i++ )
|
||||
run_timer( &m.timers [i], 0 );
|
||||
|
||||
// Catch DSP up to CPU
|
||||
if ( m.dsp_time < 0 )
|
||||
{
|
||||
RUN_DSP( 0, max_reg_time );
|
||||
}
|
||||
|
||||
// Save any extra samples beyond what should be generated
|
||||
if ( m.buf_begin )
|
||||
save_extra();
|
||||
}
|
||||
|
||||
// Inclusion here allows static memory access functions and better optimization
|
||||
#include "SPC_CPU.h"
|
279
Core/SNES_SPC.h
279
Core/SNES_SPC.h
|
@ -1,279 +0,0 @@
|
|||
// SNES SPC-700 APU emulator
|
||||
|
||||
// snes_spc 0.9.0
|
||||
#ifndef SNES_SPC_H
|
||||
#define SNES_SPC_H
|
||||
|
||||
#include "SPC_DSP.h"
|
||||
#include "blargg_endian.h"
|
||||
|
||||
struct SNES_SPC {
|
||||
public:
|
||||
typedef BOOST::uint8_t uint8_t;
|
||||
|
||||
// Must be called once before using
|
||||
blargg_err_t init();
|
||||
|
||||
// Sample pairs generated per second
|
||||
enum { sample_rate = 32000 };
|
||||
|
||||
// Emulator use
|
||||
|
||||
// Sets IPL ROM data. Library does not include ROM data. Most SPC music files
|
||||
// don't need ROM, but a full emulator must provide this.
|
||||
enum { rom_size = 0x40 };
|
||||
void init_rom( uint8_t const rom [rom_size] );
|
||||
|
||||
// Sets destination for output samples
|
||||
typedef short sample_t;
|
||||
void set_output( sample_t* out, int out_size );
|
||||
|
||||
// Number of samples written to output since last set
|
||||
int sample_count() const;
|
||||
|
||||
// Resets SPC to power-on state. This resets your output buffer, so you must
|
||||
// call set_output() after this.
|
||||
void reset();
|
||||
|
||||
// Emulates pressing reset switch on SNES. This resets your output buffer, so
|
||||
// you must call set_output() after this.
|
||||
void soft_reset();
|
||||
|
||||
// 1024000 SPC clocks per second, sample pair every 32 clocks
|
||||
typedef int time_t;
|
||||
enum { clock_rate = 1024000 };
|
||||
enum { clocks_per_sample = 32 };
|
||||
|
||||
// Emulated port read/write at specified time
|
||||
enum { port_count = 4 };
|
||||
int read_port ( time_t, int port );
|
||||
void write_port( time_t, int port, int data );
|
||||
|
||||
// Runs SPC to end_time and starts a new time frame at 0
|
||||
void end_frame( time_t end_time );
|
||||
|
||||
// Sound control
|
||||
|
||||
// Mutes voices corresponding to non-zero bits in mask (issues repeated KOFF events).
|
||||
// Reduces emulation accuracy.
|
||||
enum { voice_count = 8 };
|
||||
void mute_voices( int mask );
|
||||
|
||||
// If true, prevents channels and global volumes from being phase-negated.
|
||||
// Only supported by fast DSP.
|
||||
void disable_surround( bool disable = true );
|
||||
|
||||
// Sets tempo, where tempo_unit = normal, tempo_unit / 2 = half speed, etc.
|
||||
enum { tempo_unit = 0x100 };
|
||||
void set_tempo( int );
|
||||
|
||||
// SPC music files
|
||||
|
||||
// Loads SPC data into emulator
|
||||
enum { spc_min_file_size = 0x10180 };
|
||||
enum { spc_file_size = 0x10200 };
|
||||
blargg_err_t load_spc( void const* in, long size );
|
||||
|
||||
// Clears echo region. Useful after loading an SPC as many have garbage in echo.
|
||||
void clear_echo();
|
||||
|
||||
// Plays for count samples and write samples to out. Discards samples if out
|
||||
// is NULL. Count must be a multiple of 2 since output is stereo.
|
||||
blargg_err_t play( int count, sample_t* out );
|
||||
|
||||
// Skips count samples. Several times faster than play() when using fast DSP.
|
||||
blargg_err_t skip( int count );
|
||||
|
||||
// State save/load (only available with accurate DSP)
|
||||
|
||||
#if !SPC_NO_COPY_STATE_FUNCS
|
||||
// Saves/loads state
|
||||
enum { state_size = 67 * 1024L }; // maximum space needed when saving
|
||||
typedef SPC_DSP::copy_func_t copy_func_t;
|
||||
void copy_state( unsigned char** io, copy_func_t );
|
||||
|
||||
// Writes minimal header to spc_out
|
||||
static void init_header( void* spc_out );
|
||||
|
||||
// Saves emulator state as SPC file data. Writes spc_file_size bytes to spc_out.
|
||||
// Does not set up SPC header; use init_header() for that.
|
||||
void save_spc( void* spc_out );
|
||||
|
||||
// Returns true if new key-on events occurred since last check. Useful for
|
||||
// trimming silence while saving an SPC.
|
||||
bool check_kon();
|
||||
#endif
|
||||
|
||||
public:
|
||||
BLARGG_DISABLE_NOTHROW
|
||||
|
||||
typedef BOOST::uint16_t uint16_t;
|
||||
|
||||
// Time relative to m_spc_time. Speeds up code a bit by eliminating need to
|
||||
// constantly add m_spc_time to time from CPU. CPU uses time that ends at
|
||||
// 0 to eliminate reloading end time every instruction. It pays off.
|
||||
typedef int rel_time_t;
|
||||
|
||||
struct Timer
|
||||
{
|
||||
rel_time_t next_time; // time of next event
|
||||
int prescaler;
|
||||
int period;
|
||||
int divider;
|
||||
int enabled;
|
||||
int counter;
|
||||
};
|
||||
enum { reg_count = 0x10 };
|
||||
enum { timer_count = 3 };
|
||||
enum { extra_size = SPC_DSP::extra_size };
|
||||
|
||||
enum { signature_size = 35 };
|
||||
|
||||
private:
|
||||
SPC_DSP dsp;
|
||||
|
||||
#if SPC_LESS_ACCURATE
|
||||
static signed char const reg_times_ [256];
|
||||
signed char reg_times [256];
|
||||
#endif
|
||||
|
||||
struct state_t
|
||||
{
|
||||
Timer timers [timer_count];
|
||||
|
||||
uint8_t smp_regs [2] [reg_count];
|
||||
|
||||
struct
|
||||
{
|
||||
int pc;
|
||||
int a;
|
||||
int x;
|
||||
int y;
|
||||
int psw;
|
||||
int sp;
|
||||
} cpu_regs;
|
||||
|
||||
rel_time_t dsp_time;
|
||||
time_t spc_time;
|
||||
bool echo_accessed;
|
||||
|
||||
int tempo;
|
||||
int skipped_kon;
|
||||
int skipped_koff;
|
||||
const char* cpu_error;
|
||||
|
||||
int extra_clocks;
|
||||
sample_t* buf_begin;
|
||||
sample_t const* buf_end;
|
||||
sample_t* extra_pos;
|
||||
sample_t extra_buf [extra_size];
|
||||
|
||||
int rom_enabled;
|
||||
uint8_t rom [rom_size];
|
||||
uint8_t hi_ram [rom_size];
|
||||
|
||||
unsigned char cycle_table [256];
|
||||
|
||||
struct
|
||||
{
|
||||
// padding to neutralize address overflow
|
||||
union {
|
||||
uint8_t padding1 [0x100];
|
||||
uint16_t align; // makes compiler align data for 16-bit access
|
||||
} padding1 [1];
|
||||
uint8_t ram [0x10000];
|
||||
uint8_t padding2 [0x100];
|
||||
} ram;
|
||||
};
|
||||
state_t m;
|
||||
|
||||
enum { rom_addr = 0xFFC0 };
|
||||
|
||||
enum { skipping_time = 127 };
|
||||
|
||||
// Value that padding should be filled with
|
||||
enum { cpu_pad_fill = 0xFF };
|
||||
|
||||
enum {
|
||||
r_test = 0x0, r_control = 0x1,
|
||||
r_dspaddr = 0x2, r_dspdata = 0x3,
|
||||
r_cpuio0 = 0x4, r_cpuio1 = 0x5,
|
||||
r_cpuio2 = 0x6, r_cpuio3 = 0x7,
|
||||
r_f8 = 0x8, r_f9 = 0x9,
|
||||
r_t0target = 0xA, r_t1target = 0xB, r_t2target = 0xC,
|
||||
r_t0out = 0xD, r_t1out = 0xE, r_t2out = 0xF
|
||||
};
|
||||
|
||||
void timers_loaded();
|
||||
void enable_rom( int enable );
|
||||
void reset_buf();
|
||||
void save_extra();
|
||||
void load_regs( uint8_t const in [reg_count] );
|
||||
void ram_loaded();
|
||||
void regs_loaded();
|
||||
void reset_time_regs();
|
||||
void reset_common( int timer_counter_init );
|
||||
|
||||
Timer* run_timer_ ( Timer* t, rel_time_t );
|
||||
Timer* run_timer ( Timer* t, rel_time_t );
|
||||
int dsp_read ( rel_time_t );
|
||||
void dsp_write ( int data, rel_time_t );
|
||||
void cpu_write_smp_reg_( int data, rel_time_t, int addr );
|
||||
void cpu_write_smp_reg ( int data, rel_time_t, int addr );
|
||||
void cpu_write_high ( int data, int i, rel_time_t );
|
||||
void cpu_write ( int data, int addr, rel_time_t );
|
||||
int cpu_read_smp_reg ( int i, rel_time_t );
|
||||
int cpu_read ( int addr, rel_time_t );
|
||||
unsigned CPU_mem_bit ( uint8_t const* pc, rel_time_t );
|
||||
|
||||
bool check_echo_access ( int addr );
|
||||
uint8_t* run_until_( time_t end_time );
|
||||
|
||||
struct spc_file_t
|
||||
{
|
||||
char signature [signature_size];
|
||||
uint8_t has_id666;
|
||||
uint8_t version;
|
||||
uint8_t pcl, pch;
|
||||
uint8_t a;
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
uint8_t psw;
|
||||
uint8_t sp;
|
||||
char text [212];
|
||||
uint8_t ram [0x10000];
|
||||
uint8_t dsp [128];
|
||||
uint8_t unused [0x40];
|
||||
uint8_t ipl_rom [0x40];
|
||||
};
|
||||
|
||||
static char const signature [signature_size + 1];
|
||||
|
||||
void save_regs( uint8_t out [reg_count] );
|
||||
};
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
inline int SNES_SPC::sample_count() const { return (m.extra_clocks >> 5) * 2; }
|
||||
|
||||
inline int SNES_SPC::read_port( time_t t, int port )
|
||||
{
|
||||
assert( (unsigned) port < port_count );
|
||||
return run_until_( t ) [port];
|
||||
}
|
||||
|
||||
inline void SNES_SPC::write_port( time_t t, int port, int data )
|
||||
{
|
||||
assert( (unsigned) port < port_count );
|
||||
run_until_( t ) [0x10 + port] = data;
|
||||
}
|
||||
|
||||
inline void SNES_SPC::mute_voices( int mask ) { dsp.mute_voices( mask ); }
|
||||
|
||||
inline void SNES_SPC::disable_surround( bool disable ) { dsp.disable_surround( disable ); }
|
||||
|
||||
#if !SPC_NO_COPY_STATE_FUNCS
|
||||
inline bool SNES_SPC::check_kon() { return dsp.check_kon(); }
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -1,381 +0,0 @@
|
|||
#include "stdafx.h"
|
||||
// SPC emulation support: init, sample buffering, reset, SPC loading
|
||||
|
||||
// snes_spc 0.9.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "SNES_SPC.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* Copyright (C) 2004-2007 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
#define RAM (m.ram.ram)
|
||||
#define REGS (m.smp_regs [0])
|
||||
#define REGS_IN (m.smp_regs [1])
|
||||
|
||||
// (n ? n : 256)
|
||||
#define IF_0_THEN_256( n ) ((uint8_t) ((n) - 1) + 1)
|
||||
|
||||
|
||||
//// Init
|
||||
|
||||
blargg_err_t SNES_SPC::init()
|
||||
{
|
||||
memset( &m, 0, sizeof m );
|
||||
dsp.init( RAM );
|
||||
|
||||
m.tempo = tempo_unit;
|
||||
|
||||
// Most SPC music doesn't need ROM, and almost all the rest only rely
|
||||
// on these two bytes
|
||||
m.rom [0x3E] = 0xFF;
|
||||
m.rom [0x3F] = 0xC0;
|
||||
|
||||
static unsigned char const cycle_table [128] =
|
||||
{// 01 23 45 67 89 AB CD EF
|
||||
0x28,0x47,0x34,0x36,0x26,0x54,0x54,0x68, // 0
|
||||
0x48,0x47,0x45,0x56,0x55,0x65,0x22,0x46, // 1
|
||||
0x28,0x47,0x34,0x36,0x26,0x54,0x54,0x74, // 2
|
||||
0x48,0x47,0x45,0x56,0x55,0x65,0x22,0x38, // 3
|
||||
0x28,0x47,0x34,0x36,0x26,0x44,0x54,0x66, // 4
|
||||
0x48,0x47,0x45,0x56,0x55,0x45,0x22,0x43, // 5
|
||||
0x28,0x47,0x34,0x36,0x26,0x44,0x54,0x75, // 6
|
||||
0x48,0x47,0x45,0x56,0x55,0x55,0x22,0x36, // 7
|
||||
0x28,0x47,0x34,0x36,0x26,0x54,0x52,0x45, // 8
|
||||
0x48,0x47,0x45,0x56,0x55,0x55,0x22,0xC5, // 9
|
||||
0x38,0x47,0x34,0x36,0x26,0x44,0x52,0x44, // A
|
||||
0x48,0x47,0x45,0x56,0x55,0x55,0x22,0x34, // B
|
||||
0x38,0x47,0x45,0x47,0x25,0x64,0x52,0x49, // C
|
||||
0x48,0x47,0x56,0x67,0x45,0x55,0x22,0x83, // D
|
||||
0x28,0x47,0x34,0x36,0x24,0x53,0x43,0x40, // E
|
||||
0x48,0x47,0x45,0x56,0x34,0x54,0x22,0x60, // F
|
||||
};
|
||||
|
||||
// unpack cycle table
|
||||
for ( int i = 0; i < 128; i++ )
|
||||
{
|
||||
int n = cycle_table [i];
|
||||
m.cycle_table [i * 2 + 0] = n >> 4;
|
||||
m.cycle_table [i * 2 + 1] = n & 0x0F;
|
||||
}
|
||||
|
||||
#if SPC_LESS_ACCURATE
|
||||
memcpy( reg_times, reg_times_, sizeof reg_times );
|
||||
#endif
|
||||
|
||||
reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SNES_SPC::init_rom( uint8_t const in [rom_size] )
|
||||
{
|
||||
memcpy( m.rom, in, sizeof m.rom );
|
||||
}
|
||||
|
||||
void SNES_SPC::set_tempo( int t )
|
||||
{
|
||||
m.tempo = t;
|
||||
int const timer2_shift = 4; // 64 kHz
|
||||
int const other_shift = 3; // 8 kHz
|
||||
|
||||
#if SPC_DISABLE_TEMPO
|
||||
m.timers [2].prescaler = timer2_shift;
|
||||
m.timers [1].prescaler = timer2_shift + other_shift;
|
||||
m.timers [0].prescaler = timer2_shift + other_shift;
|
||||
#else
|
||||
if ( !t )
|
||||
t = 1;
|
||||
int const timer2_rate = 1 << timer2_shift;
|
||||
int rate = (timer2_rate * tempo_unit + (t >> 1)) / t;
|
||||
if ( rate < timer2_rate / 4 )
|
||||
rate = timer2_rate / 4; // max 4x tempo
|
||||
m.timers [2].prescaler = rate;
|
||||
m.timers [1].prescaler = rate << other_shift;
|
||||
m.timers [0].prescaler = rate << other_shift;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Timer registers have been loaded. Applies these to the timers. Does not
|
||||
// reset timer prescalers or dividers.
|
||||
void SNES_SPC::timers_loaded()
|
||||
{
|
||||
int i;
|
||||
for ( i = 0; i < timer_count; i++ )
|
||||
{
|
||||
Timer* t = &m.timers [i];
|
||||
t->period = IF_0_THEN_256( REGS [r_t0target + i] );
|
||||
t->enabled = REGS [r_control] >> i & 1;
|
||||
t->counter = REGS_IN [r_t0out + i] & 0x0F;
|
||||
}
|
||||
|
||||
set_tempo( m.tempo );
|
||||
}
|
||||
|
||||
// Loads registers from unified 16-byte format
|
||||
void SNES_SPC::load_regs( uint8_t const in [reg_count] )
|
||||
{
|
||||
memcpy( REGS, in, reg_count );
|
||||
memcpy( REGS_IN, REGS, reg_count );
|
||||
|
||||
// These always read back as 0
|
||||
REGS_IN [r_test ] = 0;
|
||||
REGS_IN [r_control ] = 0;
|
||||
REGS_IN [r_t0target] = 0;
|
||||
REGS_IN [r_t1target] = 0;
|
||||
REGS_IN [r_t2target] = 0;
|
||||
}
|
||||
|
||||
// RAM was just loaded from SPC, with $F0-$FF containing SMP registers
|
||||
// and timer counts. Copies these to proper registers.
|
||||
void SNES_SPC::ram_loaded()
|
||||
{
|
||||
m.rom_enabled = 0;
|
||||
load_regs( &RAM [0xF0] );
|
||||
|
||||
// Put STOP instruction around memory to catch PC underflow/overflow
|
||||
memset( m.ram.padding1, cpu_pad_fill, sizeof m.ram.padding1 );
|
||||
memset( m.ram.padding2, cpu_pad_fill, sizeof m.ram.padding2 );
|
||||
}
|
||||
|
||||
// Registers were just loaded. Applies these new values.
|
||||
void SNES_SPC::regs_loaded()
|
||||
{
|
||||
enable_rom( REGS [r_control] & 0x80 );
|
||||
timers_loaded();
|
||||
}
|
||||
|
||||
void SNES_SPC::reset_time_regs()
|
||||
{
|
||||
m.cpu_error = 0;
|
||||
m.echo_accessed = 0;
|
||||
m.spc_time = 0;
|
||||
m.dsp_time = 0;
|
||||
#if SPC_LESS_ACCURATE
|
||||
m.dsp_time = clocks_per_sample + 1;
|
||||
#endif
|
||||
|
||||
for ( int i = 0; i < timer_count; i++ )
|
||||
{
|
||||
Timer* t = &m.timers [i];
|
||||
t->next_time = 1;
|
||||
t->divider = 0;
|
||||
}
|
||||
|
||||
regs_loaded();
|
||||
|
||||
m.extra_clocks = 0;
|
||||
reset_buf();
|
||||
}
|
||||
|
||||
void SNES_SPC::reset_common( int timer_counter_init )
|
||||
{
|
||||
int i;
|
||||
for ( i = 0; i < timer_count; i++ )
|
||||
REGS_IN [r_t0out + i] = timer_counter_init;
|
||||
|
||||
// Run IPL ROM
|
||||
memset( &m.cpu_regs, 0, sizeof m.cpu_regs );
|
||||
m.cpu_regs.pc = rom_addr;
|
||||
|
||||
REGS [r_test ] = 0x0A;
|
||||
REGS [r_control] = 0xB0; // ROM enabled, clear ports
|
||||
for ( i = 0; i < port_count; i++ )
|
||||
REGS_IN [r_cpuio0 + i] = 0;
|
||||
|
||||
reset_time_regs();
|
||||
}
|
||||
|
||||
void SNES_SPC::soft_reset()
|
||||
{
|
||||
reset_common( 0 );
|
||||
dsp.soft_reset();
|
||||
}
|
||||
|
||||
void SNES_SPC::reset()
|
||||
{
|
||||
memset( RAM, 0xFF, 0x10000 );
|
||||
ram_loaded();
|
||||
reset_common( 0x0F );
|
||||
dsp.reset();
|
||||
}
|
||||
|
||||
char const SNES_SPC::signature [signature_size + 1] =
|
||||
"SNES-SPC700 Sound File Data v0.30\x1A\x1A";
|
||||
|
||||
blargg_err_t SNES_SPC::load_spc( void const* data, long size )
|
||||
{
|
||||
spc_file_t const* const spc = (spc_file_t const*) data;
|
||||
|
||||
// be sure compiler didn't insert any padding into fle_t
|
||||
assert( sizeof (spc_file_t) == spc_min_file_size + 0x80 );
|
||||
|
||||
// Check signature and file size
|
||||
if ( size < signature_size || memcmp( spc, signature, 27 ) )
|
||||
return "Not an SPC file";
|
||||
|
||||
if ( size < spc_min_file_size )
|
||||
return "Corrupt SPC file";
|
||||
|
||||
// CPU registers
|
||||
m.cpu_regs.pc = spc->pch * 0x100 + spc->pcl;
|
||||
m.cpu_regs.a = spc->a;
|
||||
m.cpu_regs.x = spc->x;
|
||||
m.cpu_regs.y = spc->y;
|
||||
m.cpu_regs.psw = spc->psw;
|
||||
m.cpu_regs.sp = spc->sp;
|
||||
|
||||
// RAM and registers
|
||||
memcpy( RAM, spc->ram, 0x10000 );
|
||||
ram_loaded();
|
||||
|
||||
// DSP registers
|
||||
dsp.load( spc->dsp );
|
||||
|
||||
reset_time_regs();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SNES_SPC::clear_echo()
|
||||
{
|
||||
if ( !(dsp.read( SPC_DSP::r_flg ) & 0x20) )
|
||||
{
|
||||
int addr = 0x100 * dsp.read( SPC_DSP::r_esa );
|
||||
int end = addr + 0x800 * (dsp.read( SPC_DSP::r_edl ) & 0x0F);
|
||||
if ( end > 0x10000 )
|
||||
end = 0x10000;
|
||||
memset( &RAM [addr], 0xFF, end - addr );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// Sample output
|
||||
|
||||
void SNES_SPC::reset_buf()
|
||||
{
|
||||
// Start with half extra buffer of silence
|
||||
sample_t* out = m.extra_buf;
|
||||
while ( out < &m.extra_buf [extra_size / 2] )
|
||||
*out++ = 0;
|
||||
|
||||
m.extra_pos = out;
|
||||
m.buf_begin = 0;
|
||||
|
||||
dsp.set_output( 0, 0 );
|
||||
}
|
||||
|
||||
void SNES_SPC::set_output( sample_t* out, int size )
|
||||
{
|
||||
require( (size & 1) == 0 ); // size must be even
|
||||
|
||||
m.extra_clocks &= clocks_per_sample - 1;
|
||||
if ( out )
|
||||
{
|
||||
sample_t const* out_end = out + size;
|
||||
m.buf_begin = out;
|
||||
m.buf_end = out_end;
|
||||
|
||||
// Copy extra to output
|
||||
sample_t const* in = m.extra_buf;
|
||||
while ( in < m.extra_pos && out < out_end )
|
||||
*out++ = *in++;
|
||||
|
||||
// Handle output being full already
|
||||
if ( out >= out_end )
|
||||
{
|
||||
// Have DSP write to remaining extra space
|
||||
out = dsp.extra();
|
||||
out_end = &dsp.extra() [extra_size];
|
||||
|
||||
// Copy any remaining extra samples as if DSP wrote them
|
||||
while ( in < m.extra_pos )
|
||||
*out++ = *in++;
|
||||
assert( out <= out_end );
|
||||
}
|
||||
|
||||
dsp.set_output( out, (int)(out_end - out) );
|
||||
}
|
||||
else
|
||||
{
|
||||
reset_buf();
|
||||
}
|
||||
}
|
||||
|
||||
void SNES_SPC::save_extra()
|
||||
{
|
||||
// Get end pointers
|
||||
sample_t const* main_end = m.buf_end; // end of data written to buf
|
||||
sample_t const* dsp_end = dsp.out_pos(); // end of data written to dsp.extra()
|
||||
if ( m.buf_begin <= dsp_end && dsp_end <= main_end )
|
||||
{
|
||||
main_end = dsp_end;
|
||||
dsp_end = dsp.extra(); // nothing in DSP's extra
|
||||
}
|
||||
|
||||
// Copy any extra samples at these ends into extra_buf
|
||||
sample_t* out = m.extra_buf;
|
||||
sample_t const* in;
|
||||
for ( in = m.buf_begin + sample_count(); in < main_end; in++ )
|
||||
*out++ = *in;
|
||||
for ( in = dsp.extra(); in < dsp_end ; in++ )
|
||||
*out++ = *in;
|
||||
|
||||
m.extra_pos = out;
|
||||
assert( out <= &m.extra_buf [extra_size] );
|
||||
}
|
||||
|
||||
blargg_err_t SNES_SPC::play( int count, sample_t* out )
|
||||
{
|
||||
require( (count & 1) == 0 ); // must be even
|
||||
if ( count )
|
||||
{
|
||||
set_output( out, count );
|
||||
end_frame( count * (clocks_per_sample / 2) );
|
||||
}
|
||||
|
||||
const char* err = m.cpu_error;
|
||||
m.cpu_error = 0;
|
||||
return err;
|
||||
}
|
||||
|
||||
blargg_err_t SNES_SPC::skip( int count )
|
||||
{
|
||||
#if SPC_LESS_ACCURATE
|
||||
if ( count > 2 * sample_rate * 2 )
|
||||
{
|
||||
set_output( 0, 0 );
|
||||
|
||||
// Skip a multiple of 4 samples
|
||||
time_t end = count;
|
||||
count = (count & 3) + 1 * sample_rate * 2;
|
||||
end = (end - count) * (clocks_per_sample / 2);
|
||||
|
||||
m.skipped_kon = 0;
|
||||
m.skipped_koff = 0;
|
||||
|
||||
// Preserve DSP and timer synchronization
|
||||
// TODO: verify that this really preserves it
|
||||
int old_dsp_time = m.dsp_time + m.spc_time;
|
||||
m.dsp_time = end - m.spc_time + skipping_time;
|
||||
end_frame( end );
|
||||
m.dsp_time = m.dsp_time - skipping_time + old_dsp_time;
|
||||
|
||||
dsp.write( SPC_DSP::r_koff, m.skipped_koff & ~m.skipped_kon );
|
||||
dsp.write( SPC_DSP::r_kon , m.skipped_kon );
|
||||
clear_echo();
|
||||
}
|
||||
#endif
|
||||
|
||||
return play( count, 0 );
|
||||
}
|
|
@ -1,134 +0,0 @@
|
|||
#include "stdafx.h"
|
||||
// SPC emulation state save/load: copy_state(), save_spc()
|
||||
// Separate file to avoid linking in unless needed
|
||||
|
||||
// snes_spc 0.9.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "SNES_SPC.h"
|
||||
|
||||
#if !SPC_NO_COPY_STATE_FUNCS
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* Copyright (C) 2004-2007 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
#define RAM (m.ram.ram)
|
||||
#define REGS (m.smp_regs [0])
|
||||
#define REGS_IN (m.smp_regs [1])
|
||||
|
||||
void SNES_SPC::save_regs( uint8_t out [reg_count*2] )
|
||||
{
|
||||
// Use current timer counter values
|
||||
for ( int i = 0; i < timer_count; i++ )
|
||||
out [r_t0out + i] = m.timers [i].counter;
|
||||
|
||||
// Last written values
|
||||
memcpy( out, REGS, r_t0out );
|
||||
memcpy( out + reg_count, REGS_IN, reg_count);
|
||||
}
|
||||
|
||||
void SNES_SPC::init_header( void* spc_out )
|
||||
{
|
||||
spc_file_t* const spc = (spc_file_t*) spc_out;
|
||||
|
||||
spc->has_id666 = 26; // has none
|
||||
spc->version = 30;
|
||||
memcpy( spc, signature, sizeof spc->signature );
|
||||
memset( spc->text, 0, sizeof spc->text );
|
||||
}
|
||||
|
||||
void SNES_SPC::save_spc( void* spc_out )
|
||||
{
|
||||
spc_file_t* const spc = (spc_file_t*) spc_out;
|
||||
|
||||
// CPU
|
||||
spc->pcl = (uint8_t) (m.cpu_regs.pc >> 0);
|
||||
spc->pch = (uint8_t) (m.cpu_regs.pc >> 8);
|
||||
spc->a = m.cpu_regs.a;
|
||||
spc->x = m.cpu_regs.x;
|
||||
spc->y = m.cpu_regs.y;
|
||||
spc->psw = m.cpu_regs.psw;
|
||||
spc->sp = m.cpu_regs.sp;
|
||||
|
||||
// RAM, ROM
|
||||
memcpy( spc->ram, RAM, sizeof spc->ram );
|
||||
if ( m.rom_enabled )
|
||||
memcpy( spc->ram + rom_addr, m.hi_ram, sizeof m.hi_ram );
|
||||
memset( spc->unused, 0, sizeof spc->unused );
|
||||
memcpy( spc->ipl_rom, m.rom, sizeof spc->ipl_rom );
|
||||
|
||||
// SMP registers
|
||||
save_regs( &spc->ram [0xF0] );
|
||||
int i;
|
||||
for ( i = 0; i < port_count; i++ )
|
||||
spc->ram [0xF0 + r_cpuio0 + i] = REGS_IN [r_cpuio0 + i];
|
||||
|
||||
// DSP registers
|
||||
for ( i = 0; i < SPC_DSP::register_count; i++ )
|
||||
spc->dsp [i] = dsp.read( i );
|
||||
}
|
||||
|
||||
void SNES_SPC::copy_state( unsigned char** io, copy_func_t copy )
|
||||
{
|
||||
SPC_State_Copier copier( io, copy );
|
||||
|
||||
// Make state data more readable by putting 64K RAM, 16 SMP registers,
|
||||
// then DSP (with its 128 registers) first
|
||||
|
||||
// RAM
|
||||
enable_rom( 0 ); // will get re-enabled if necessary in regs_loaded() below
|
||||
copier.copy( RAM, 0x10000 );
|
||||
|
||||
{
|
||||
// SMP registers
|
||||
uint8_t out_ports [port_count];
|
||||
uint8_t regs [reg_count*2];
|
||||
memcpy( out_ports, ®S [r_cpuio0], sizeof out_ports );
|
||||
save_regs( regs );
|
||||
copier.copy( regs, sizeof regs );
|
||||
copier.copy( out_ports, sizeof out_ports );
|
||||
|
||||
memcpy(REGS, regs, reg_count);
|
||||
memcpy(REGS_IN, regs + reg_count, reg_count);
|
||||
|
||||
regs_loaded();
|
||||
memcpy( ®S [r_cpuio0], out_ports, sizeof out_ports );
|
||||
}
|
||||
|
||||
// CPU registers
|
||||
SPC_COPY( uint16_t, m.cpu_regs.pc );
|
||||
SPC_COPY( uint8_t, m.cpu_regs.a );
|
||||
SPC_COPY( uint8_t, m.cpu_regs.x );
|
||||
SPC_COPY( uint8_t, m.cpu_regs.y );
|
||||
SPC_COPY( uint8_t, m.cpu_regs.psw );
|
||||
SPC_COPY( uint8_t, m.cpu_regs.sp );
|
||||
copier.extra();
|
||||
|
||||
SPC_COPY( int16_t, m.spc_time );
|
||||
SPC_COPY( int16_t, m.dsp_time );
|
||||
|
||||
// DSP
|
||||
dsp.copy_state( io, copy );
|
||||
|
||||
// Timers
|
||||
for ( int i = 0; i < timer_count; i++ )
|
||||
{
|
||||
Timer* t = &m.timers [i];
|
||||
SPC_COPY( int16_t, t->next_time );
|
||||
SPC_COPY( uint8_t, t->divider );
|
||||
copier.extra();
|
||||
}
|
||||
copier.extra();
|
||||
}
|
||||
#endif
|
1220
Core/SPC_CPU.h
1220
Core/SPC_CPU.h
File diff suppressed because it is too large
Load diff
|
@ -708,8 +708,9 @@ ECHO_CLOCK( 28 )
|
|||
}
|
||||
inline void SPC_DSP::echo_write( int ch )
|
||||
{
|
||||
if ( !(m.t_echo_enabled & 0x20) )
|
||||
SET_LE16A( ECHO_PTR( ch ), m.t_echo_out [ch] );
|
||||
if(!(m.t_echo_enabled & 0x20) && _echoWriteEnabled) {
|
||||
SET_LE16A(ECHO_PTR(ch), m.t_echo_out[ch]);
|
||||
}
|
||||
m.t_echo_out [ch] = 0;
|
||||
}
|
||||
ECHO_CLOCK( 29 )
|
||||
|
@ -785,22 +786,14 @@ PHASE(31) V(V4,0) V(V1,2)\
|
|||
|
||||
#if !SPC_DSP_CUSTOM_RUN
|
||||
|
||||
void SPC_DSP::run( int clocks_remain )
|
||||
void SPC_DSP::run()
|
||||
{
|
||||
require( clocks_remain > 0 );
|
||||
|
||||
int const phase = m.phase;
|
||||
m.phase = (phase + clocks_remain) & 31;
|
||||
switch ( phase )
|
||||
m.phase = (m.phase + 1) & 31;
|
||||
switch (m.phase)
|
||||
{
|
||||
loop:
|
||||
|
||||
#define PHASE( n ) if ( n && !--clocks_remain ) break; case n:
|
||||
#define PHASE( n ) if ( n ) break; case n:
|
||||
GEN_DSP_TIMING
|
||||
#undef PHASE
|
||||
|
||||
if ( --clocks_remain )
|
||||
goto loop;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -42,8 +42,10 @@ public:
|
|||
|
||||
// Runs DSP for specified number of clocks (~1024000 per second). Every 32 clocks
|
||||
// a pair of samples is be generated.
|
||||
void run( int clock_count );
|
||||
void run();
|
||||
|
||||
void setEchoWriteEnabled(bool enabled);
|
||||
|
||||
// Sound control
|
||||
|
||||
// Mutes voices corresponding to non-zero bits in mask (issues repeated KOFF events).
|
||||
|
@ -187,6 +189,8 @@ private:
|
|||
};
|
||||
state_t m;
|
||||
|
||||
bool _echoWriteEnabled = true;
|
||||
|
||||
void init_counter();
|
||||
void run_counters();
|
||||
unsigned read_counter( int rate );
|
||||
|
@ -271,6 +275,8 @@ inline void SPC_DSP::write( int addr, int data )
|
|||
}
|
||||
}
|
||||
|
||||
inline void SPC_DSP::setEchoWriteEnabled(bool enabled) { _echoWriteEnabled = enabled; }
|
||||
|
||||
inline void SPC_DSP::mute_voices( int mask ) { m.mute_mask = mask; }
|
||||
|
||||
inline bool SPC_DSP::check_kon()
|
||||
|
|
1426
Core/Spc.Instructions.cpp
Normal file
1426
Core/Spc.Instructions.cpp
Normal file
File diff suppressed because it is too large
Load diff
337
Core/Spc.cpp
337
Core/Spc.cpp
|
@ -1,92 +1,353 @@
|
|||
#include "stdafx.h"
|
||||
#include "Spc.h"
|
||||
#include "SNES_SPC.h"
|
||||
#include "Console.h"
|
||||
#include "MemoryManager.h"
|
||||
#include "SoundMixer.h"
|
||||
#include "SPC_DSP.h"
|
||||
#include "../Utilities/Serializer.h"
|
||||
|
||||
Spc::Spc(shared_ptr<Console> console, vector<uint8_t> &spcRomData)
|
||||
{
|
||||
_console = console;
|
||||
|
||||
memcpy(_spcBios, spcRomData.data(), 64);
|
||||
|
||||
_soundBuffer = new int16_t[Spc::SampleBufferSize];
|
||||
_immediateMode = false;
|
||||
_operandA = 0;
|
||||
_operandB = 0;
|
||||
|
||||
_spc = new SNES_SPC();
|
||||
_spc->init();
|
||||
_spc->init_rom(_spcBios);
|
||||
_spc->reset();
|
||||
_spc->set_output(_soundBuffer, Spc::SampleBufferSize >> 1);
|
||||
_dsp.reset(new SPC_DSP());
|
||||
_dsp->init(_ram);
|
||||
_dsp->reset();
|
||||
_dsp->set_output(_soundBuffer, Spc::SampleBufferSize >> 1);
|
||||
|
||||
_startFrameMasterClock = 0;
|
||||
memcpy(_spcBios, spcRomData.data(), 64);
|
||||
memset(_ram, 0, sizeof(_ram));
|
||||
|
||||
_state = {};
|
||||
_state.WriteEnabled = true;
|
||||
_state.TimersEnabled = true;
|
||||
_state.RomEnabled = true;
|
||||
_state.SP = 0xFF;
|
||||
_state.PC = ReadWord(Spc::ResetVector);
|
||||
}
|
||||
|
||||
Spc::~Spc()
|
||||
{
|
||||
delete _spc;
|
||||
delete[] _soundBuffer;
|
||||
}
|
||||
|
||||
void Spc::Reset()
|
||||
{
|
||||
_spc->soft_reset();
|
||||
_spc->set_output(_soundBuffer, Spc::SampleBufferSize >> 1);
|
||||
_startFrameMasterClock = _console->GetMemoryManager()->GetMasterClock();
|
||||
_state.Timer0.Reset();
|
||||
_state.Timer1.Reset();
|
||||
_state.Timer2.Reset();
|
||||
_state.PC = ReadWord(Spc::ResetVector);
|
||||
|
||||
_dsp->soft_reset();
|
||||
_dsp->set_output(_soundBuffer, Spc::SampleBufferSize >> 1);
|
||||
}
|
||||
|
||||
int Spc::GetSpcTime()
|
||||
void Spc::Idle()
|
||||
{
|
||||
uint64_t currentClock = _console->GetMemoryManager()->GetMasterClock();
|
||||
uint64_t elapsedClocks = currentClock - _startFrameMasterClock;
|
||||
return (int)(elapsedClocks * 1024000 / _console->GetMasterClockRate());
|
||||
IncCycleCount(-1);
|
||||
}
|
||||
|
||||
uint8_t Spc::Read(uint16_t addr)
|
||||
void Spc::DummyRead()
|
||||
{
|
||||
return _spc->read_port(GetSpcTime(), addr & 0x03);
|
||||
Read(_state.PC);
|
||||
}
|
||||
|
||||
void Spc::Write(uint32_t addr, uint8_t value)
|
||||
void Spc::DummyRead(uint16_t addr)
|
||||
{
|
||||
_spc->write_port(GetSpcTime(), addr & 0x03, value);
|
||||
Read(addr);
|
||||
}
|
||||
|
||||
void Spc::IncCycleCount(int32_t addr)
|
||||
{
|
||||
static constexpr uint8_t cpuWait[4] = { 2, 4, 10, 20 };
|
||||
static constexpr uint8_t timerMultiplier[4] = { 2, 4, 8, 16 };
|
||||
|
||||
uint8_t speedSelect;
|
||||
if(addr < 0 || ((addr & 0xFFF0) == 0x00F0) || (addr >= 0xFFC0 && _state.RomEnabled)) {
|
||||
//Use internal speed (bits 4-5) for idle cycles, register access or IPL rom access
|
||||
speedSelect = _state.InternalSpeed;
|
||||
} else {
|
||||
speedSelect = _state.ExternalSpeed;
|
||||
}
|
||||
|
||||
_state.Cycle += cpuWait[speedSelect];
|
||||
_dsp->run();
|
||||
|
||||
uint8_t timerInc = timerMultiplier[speedSelect];
|
||||
_state.Timer0.Run(timerInc);
|
||||
_state.Timer1.Run(timerInc);
|
||||
_state.Timer2.Run(timerInc);
|
||||
}
|
||||
|
||||
uint8_t Spc::Read(uint16_t addr, MemoryOperationType type)
|
||||
{
|
||||
IncCycleCount(addr);
|
||||
|
||||
if(addr >= 0xFFC0 && _state.RomEnabled) {
|
||||
return _spcBios[addr & 0x3F];
|
||||
}
|
||||
|
||||
switch(addr) {
|
||||
case 0xF0: return 0;
|
||||
case 0xF1: return 0;
|
||||
|
||||
case 0xF2: return _state.DspReg;
|
||||
case 0xF3: return _dsp->read(_state.DspReg & 0x7F);
|
||||
|
||||
case 0xF4: return _state.CpuRegs[0];
|
||||
case 0xF5: return _state.CpuRegs[1];
|
||||
case 0xF6: return _state.CpuRegs[2];
|
||||
case 0xF7: return _state.CpuRegs[3];
|
||||
|
||||
case 0xF8: return _state.RamReg[0];
|
||||
case 0xF9: return _state.RamReg[1];
|
||||
|
||||
case 0xFA: return 0;
|
||||
case 0xFB: return 0;
|
||||
case 0xFC: return 0;
|
||||
|
||||
case 0xFD: return _state.Timer0.GetOutput();
|
||||
case 0xFE: return _state.Timer1.GetOutput();
|
||||
case 0xFF: return _state.Timer2.GetOutput();
|
||||
}
|
||||
|
||||
return _ram[addr];
|
||||
}
|
||||
|
||||
void Spc::Write(uint16_t addr, uint8_t value, MemoryOperationType type)
|
||||
{
|
||||
IncCycleCount(addr);
|
||||
|
||||
//Writes always affect the underlying RAM
|
||||
if(_state.WriteEnabled) {
|
||||
_ram[addr] = value;
|
||||
}
|
||||
|
||||
switch(addr) {
|
||||
case 0xF0:
|
||||
if(!CheckFlag(SpcFlags::DirectPage)) {
|
||||
_state.InternalSpeed = (value >> 6) & 0x03;
|
||||
_state.ExternalSpeed = (value >> 4) & 0x03;
|
||||
_state.TimersEnabled = (value & 0x09) == 0x08;
|
||||
_state.WriteEnabled = value & 0x02;
|
||||
|
||||
_dsp->setEchoWriteEnabled(_state.WriteEnabled);
|
||||
|
||||
_state.Timer0.SetGlobalEnabled(_state.TimersEnabled);
|
||||
_state.Timer1.SetGlobalEnabled(_state.TimersEnabled);
|
||||
_state.Timer2.SetGlobalEnabled(_state.TimersEnabled);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xF1:
|
||||
if(value & SpcControlFlags::ClearPortsA) {
|
||||
_state.CpuRegs[0] = _state.CpuRegs[1] = 0;
|
||||
}
|
||||
if(value & SpcControlFlags::ClearPortsB) {
|
||||
_state.CpuRegs[2] = _state.CpuRegs[3] = 0;
|
||||
}
|
||||
|
||||
_state.Timer0.SetEnabled((value & SpcControlFlags::Timer0) != 0);
|
||||
_state.Timer1.SetEnabled((value & SpcControlFlags::Timer1) != 0);
|
||||
_state.Timer2.SetEnabled((value & SpcControlFlags::Timer2) != 0);
|
||||
|
||||
_state.RomEnabled = (value & SpcControlFlags::EnableRom) != 0;
|
||||
break;
|
||||
|
||||
case 0xF2: _state.DspReg = value; break;
|
||||
case 0xF3:
|
||||
if(_state.DspReg < 128) {
|
||||
_dsp->write(_state.DspReg, value);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xF4: _state.OutputReg[0] = value; break;
|
||||
case 0xF5: _state.OutputReg[1] = value; break;
|
||||
case 0xF6: _state.OutputReg[2] = value; break;
|
||||
case 0xF7: _state.OutputReg[3] = value; break;
|
||||
case 0xF8: _state.RamReg[0] = value; break;
|
||||
case 0xF9: _state.RamReg[1] = value; break;
|
||||
|
||||
case 0xFA: _state.Timer0.SetTarget(value); break;
|
||||
case 0xFB: _state.Timer1.SetTarget(value); break;
|
||||
case 0xFC: _state.Timer2.SetTarget(value); break;
|
||||
|
||||
case 0xFD: break;
|
||||
case 0xFE: break;
|
||||
case 0xFF: break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Spc::CpuReadRegister(uint16_t addr)
|
||||
{
|
||||
Run();
|
||||
return _state.OutputReg[addr & 0x03];
|
||||
}
|
||||
|
||||
void Spc::CpuWriteRegister(uint32_t addr, uint8_t value)
|
||||
{
|
||||
Run();
|
||||
_state.CpuRegs[addr & 0x03] = value;
|
||||
}
|
||||
|
||||
void Spc::Run()
|
||||
{
|
||||
int64_t masterClock = _console->GetMemoryManager()->GetMasterClock();
|
||||
//TODO: This will overflow after 100+ hours, needs to be fixed
|
||||
uint64_t targetCycle = (masterClock * (uint64_t)1024000 / (uint64_t)_console->GetMasterClockRate()) * 2;
|
||||
while(_state.Cycle < targetCycle) {
|
||||
Exec();
|
||||
}
|
||||
}
|
||||
|
||||
void Spc::ProcessEndFrame()
|
||||
{
|
||||
_spc->end_frame(GetSpcTime());
|
||||
Run();
|
||||
|
||||
int sampleCount = _spc->sample_count();
|
||||
int sampleCount = _dsp->sample_count();
|
||||
_console->GetSoundMixer()->PlayAudioBuffer(_soundBuffer, sampleCount / 2);
|
||||
_spc->set_output(_soundBuffer, Spc::SampleBufferSize >> 1);
|
||||
|
||||
uint64_t remainder = (_console->GetMemoryManager()->GetMasterClock() - _startFrameMasterClock) * 1024000 % _console->GetMasterClockRate() / 1024000;
|
||||
_startFrameMasterClock = _console->GetMemoryManager()->GetMasterClock() - remainder;
|
||||
_dsp->set_output(_soundBuffer, Spc::SampleBufferSize >> 1);
|
||||
}
|
||||
|
||||
void Spc::Serialize(Serializer &s)
|
||||
{
|
||||
s.Stream(_startFrameMasterClock);
|
||||
s.Stream(_state.A, _state.Cycle, _state.PC, _state.PS, _state.SP, _state.X, _state.Y);
|
||||
s.Stream(_state.CpuRegs[0], _state.CpuRegs[1], _state.CpuRegs[2], _state.CpuRegs[3]);
|
||||
s.Stream(_state.OutputReg[0], _state.OutputReg[1], _state.OutputReg[2], _state.OutputReg[3]);
|
||||
s.Stream(_state.RamReg[0], _state.RamReg[1]);
|
||||
s.Stream(_state.ExternalSpeed, _state.InternalSpeed, _state.WriteEnabled, _state.TimersEnabled);
|
||||
s.Stream(_state.DspReg, _state.RomEnabled);
|
||||
|
||||
uint8_t state[SNES_SPC::state_size];
|
||||
memset(state, 0, SNES_SPC::state_size);
|
||||
_state.Timer0.Serialize(s);
|
||||
_state.Timer1.Serialize(s);
|
||||
_state.Timer2.Serialize(s);
|
||||
|
||||
ArrayInfo<uint8_t> ram { _ram, Spc::SpcRamSize };
|
||||
s.Stream(ram, _state.DspReg, _state.RomEnabled);
|
||||
|
||||
uint8_t dspState[SPC_DSP::state_size];
|
||||
memset(dspState, 0, SPC_DSP::state_size);
|
||||
if(s.IsSaving()) {
|
||||
uint8_t *out = state;
|
||||
_spc->copy_state(&out, [](uint8_t** output, void* in, size_t size) {
|
||||
uint8_t *out = dspState;
|
||||
_dsp->copy_state(&out, [](uint8_t** output, void* in, size_t size) {
|
||||
memcpy(*output, in, size);
|
||||
*output += size;
|
||||
});
|
||||
|
||||
s.StreamArray(state, SNES_SPC::state_size);
|
||||
s.StreamArray(dspState, SPC_DSP::state_size);
|
||||
} else {
|
||||
s.StreamArray(state, SNES_SPC::state_size);
|
||||
s.StreamArray(dspState, SPC_DSP::state_size);
|
||||
|
||||
uint8_t *in = state;
|
||||
_spc->copy_state(&in, [](uint8_t** input, void* output, size_t size) {
|
||||
uint8_t *in = dspState;
|
||||
_dsp->copy_state(&in, [](uint8_t** input, void* output, size_t size) {
|
||||
memcpy(output, *input, size);
|
||||
*input += size;
|
||||
});
|
||||
|
||||
_spc->set_output(_soundBuffer, Spc::SampleBufferSize >> 1);
|
||||
_dsp->set_output(_soundBuffer, Spc::SampleBufferSize >> 1);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Spc::GetOpCode()
|
||||
{
|
||||
return Read(_state.PC++, MemoryOperationType::ExecOpCode);
|
||||
}
|
||||
|
||||
uint8_t Spc::ReadOperandByte()
|
||||
{
|
||||
return Read(_state.PC++, MemoryOperationType::ExecOperand);
|
||||
}
|
||||
|
||||
uint16_t Spc::ReadWord(uint16_t addr, MemoryOperationType type)
|
||||
{
|
||||
uint8_t lsb = Read(addr, type);
|
||||
uint8_t msb = Read(addr + 1, type);
|
||||
return (msb << 8) | lsb;
|
||||
}
|
||||
|
||||
void Spc::ClearFlags(uint8_t flags)
|
||||
{
|
||||
_state.PS &= ~flags;
|
||||
}
|
||||
|
||||
void Spc::SetFlags(uint8_t flags)
|
||||
{
|
||||
_state.PS |= flags;
|
||||
}
|
||||
|
||||
bool Spc::CheckFlag(uint8_t flag)
|
||||
{
|
||||
return (_state.PS & flag) == flag;
|
||||
}
|
||||
|
||||
void Spc::SetZeroNegativeFlags(uint8_t value)
|
||||
{
|
||||
ClearFlags(SpcFlags::Zero | SpcFlags::Negative);
|
||||
if(value == 0) {
|
||||
SetFlags(SpcFlags::Zero);
|
||||
} else if(value & 0x80) {
|
||||
SetFlags(SpcFlags::Negative);
|
||||
}
|
||||
}
|
||||
|
||||
void Spc::SetZeroNegativeFlags16(uint16_t value)
|
||||
{
|
||||
ClearFlags(SpcFlags::Zero | SpcFlags::Negative);
|
||||
if(value == 0) {
|
||||
SetFlags(SpcFlags::Zero);
|
||||
} else if(value & 0x8000) {
|
||||
SetFlags(SpcFlags::Negative);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Spc::GetByteValue()
|
||||
{
|
||||
if(_immediateMode) {
|
||||
return (uint8_t)_operandA;
|
||||
} else {
|
||||
return Read(_operandA);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t Spc::GetWordValue()
|
||||
{
|
||||
if(_immediateMode) {
|
||||
return (uint16_t)_operandA;
|
||||
} else {
|
||||
return ReadWord(_operandA);
|
||||
}
|
||||
}
|
||||
|
||||
void Spc::Push(uint8_t value)
|
||||
{
|
||||
Write(0x100 | _state.SP, value);
|
||||
_state.SP--;
|
||||
}
|
||||
|
||||
uint8_t Spc::Pop()
|
||||
{
|
||||
_state.SP++;
|
||||
return Read(0x100 | _state.SP);
|
||||
}
|
||||
|
||||
void Spc::PushWord(uint16_t value)
|
||||
{
|
||||
Push(value >> 8);
|
||||
Push((uint8_t)value);
|
||||
}
|
||||
|
||||
uint16_t Spc::PopWord()
|
||||
{
|
||||
uint8_t lo = Pop();
|
||||
uint8_t hi = Pop();
|
||||
return lo | hi << 8;
|
||||
}
|
||||
|
||||
uint16_t Spc::GetDirectAddress(uint8_t offset)
|
||||
{
|
||||
return (CheckFlag(SpcFlags::DirectPage) ? 0x100 : 0) + offset;
|
||||
}
|
||||
|
|
230
Core/Spc.h
230
Core/Spc.h
|
@ -1,31 +1,245 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "SpcTypes.h"
|
||||
#include "CpuTypes.h"
|
||||
#include "SpcTimer.h"
|
||||
#include "../Utilities/ISerializable.h"
|
||||
|
||||
class Console;
|
||||
struct SNES_SPC;
|
||||
class SPC_DSP;
|
||||
|
||||
class Spc : public ISerializable
|
||||
{
|
||||
private:
|
||||
static constexpr int SampleBufferSize = 0x100000;
|
||||
static constexpr int SpcRamSize = 0x10000;
|
||||
static constexpr uint16_t ResetVector = 0xFFFE;
|
||||
|
||||
shared_ptr<Console> _console;
|
||||
uint8_t _spcBios[64];
|
||||
SNES_SPC* _spc;
|
||||
int16_t *_soundBuffer;
|
||||
uint64_t _startFrameMasterClock = 0;
|
||||
unique_ptr<SPC_DSP> _dsp;
|
||||
|
||||
int GetSpcTime();
|
||||
bool _immediateMode;
|
||||
uint16_t _operandA;
|
||||
uint16_t _operandB;
|
||||
|
||||
SpcState _state;
|
||||
uint8_t _ram[Spc::SpcRamSize];
|
||||
uint8_t _spcBios[64];
|
||||
|
||||
int16_t *_soundBuffer;
|
||||
//Store operations
|
||||
void STA();
|
||||
void STX();
|
||||
void STY();
|
||||
void STW();
|
||||
|
||||
void STA_AutoIncX();
|
||||
void LDA_AutoIncX();
|
||||
|
||||
//Load operations
|
||||
void LDA();
|
||||
void LDX();
|
||||
void LDY();
|
||||
void LDW();
|
||||
|
||||
//Transfer
|
||||
void TXA();
|
||||
void TYA();
|
||||
void TAX();
|
||||
void TAY();
|
||||
void TSX();
|
||||
void TXS();
|
||||
|
||||
void STC();
|
||||
void LDC();
|
||||
void MOV();
|
||||
|
||||
//Arithmetic
|
||||
uint8_t Add(uint8_t a, uint8_t b);
|
||||
uint8_t Sub(uint8_t a, uint8_t b);
|
||||
|
||||
void ADC();
|
||||
void ADC_Acc();
|
||||
void ADDW();
|
||||
void SBC();
|
||||
void SBC_Acc();
|
||||
void SUBW();
|
||||
|
||||
void Compare(uint8_t a, uint8_t b);
|
||||
void CMP();
|
||||
void CMP_Acc();
|
||||
void CPX();
|
||||
void CPY();
|
||||
void CMPW();
|
||||
|
||||
//Increment/decrement
|
||||
void INC();
|
||||
void INC_Acc();
|
||||
void INX();
|
||||
void INY();
|
||||
void INCW();
|
||||
|
||||
void DEC();
|
||||
void DEC_Acc();
|
||||
void DEX();
|
||||
void DEY();
|
||||
void DECW();
|
||||
|
||||
void MUL();
|
||||
void DIV();
|
||||
|
||||
//Decimal
|
||||
void DAA();
|
||||
void DAS();
|
||||
|
||||
//Logical
|
||||
void AND();
|
||||
void AND_Acc();
|
||||
void OR();
|
||||
void OR_Acc();
|
||||
void EOR();
|
||||
void EOR_Acc();
|
||||
|
||||
void SetCarry(uint8_t carry);
|
||||
void OR1();
|
||||
void NOR1();
|
||||
void AND1();
|
||||
void NAND1();
|
||||
void EOR1();
|
||||
void NOT1();
|
||||
|
||||
//Shift/rotate
|
||||
uint8_t ShiftLeft(uint8_t value);
|
||||
uint8_t RollLeft(uint8_t value);
|
||||
uint8_t ShiftRight(uint8_t value);
|
||||
uint8_t RollRight(uint8_t value);
|
||||
|
||||
void ASL();
|
||||
void ASL_Acc();
|
||||
void LSR();
|
||||
void LSR_Acc();
|
||||
void ROL();
|
||||
void ROL_Acc();
|
||||
void ROR();
|
||||
void ROR_Acc();
|
||||
void XCN();
|
||||
|
||||
//Branch operations
|
||||
void Branch();
|
||||
void BRA();
|
||||
void BEQ();
|
||||
void BNE();
|
||||
void BCS();
|
||||
void BCC();
|
||||
void BVS();
|
||||
void BVC();
|
||||
void BMI();
|
||||
void BPL();
|
||||
void BBS();
|
||||
void BBC();
|
||||
void CBNE();
|
||||
void DBNZ();
|
||||
void DBNZ_Y();
|
||||
void JMP();
|
||||
|
||||
//Flag operations
|
||||
void CLRC();
|
||||
void SETC();
|
||||
void NOTC();
|
||||
void CLRV();
|
||||
void CLRP();
|
||||
void SETP();
|
||||
void EI();
|
||||
void DI();
|
||||
|
||||
void TSET1();
|
||||
void TCLR1();
|
||||
|
||||
template<uint8_t bit> void SET1();
|
||||
template<uint8_t bit> void CLR1();
|
||||
template<uint8_t bit> void BBS();
|
||||
template<uint8_t bit> void BBC();
|
||||
|
||||
//Subroutine operations
|
||||
void PCALL();
|
||||
void JSR();
|
||||
void RTS();
|
||||
void BRK();
|
||||
void RTI();
|
||||
|
||||
template<uint8_t offset> void TCALL();
|
||||
|
||||
//Stack operations
|
||||
void PHA();
|
||||
void PHX();
|
||||
void PHY();
|
||||
void PHP();
|
||||
|
||||
void PLA();
|
||||
void PLX();
|
||||
void PLY();
|
||||
void PLP();
|
||||
|
||||
//Other operations
|
||||
void NOP();
|
||||
void SLEEP();
|
||||
void STOP();
|
||||
|
||||
void Idle();
|
||||
void DummyRead();
|
||||
void DummyRead(uint16_t addr);
|
||||
uint8_t Read(uint16_t addr, MemoryOperationType type = MemoryOperationType::Read);
|
||||
uint16_t ReadWord(uint16_t addr, MemoryOperationType type = MemoryOperationType::Read);
|
||||
void Write(uint16_t addr, uint8_t value, MemoryOperationType type = MemoryOperationType::Write);
|
||||
|
||||
void Addr_Dir();
|
||||
void Addr_DirIdxX();
|
||||
void Addr_DirIdxY();
|
||||
void Addr_DirToDir();
|
||||
void Addr_DirImm();
|
||||
void Addr_IndX();
|
||||
void Addr_IndXToIndY();
|
||||
void Addr_Abs();
|
||||
void Addr_AbsBit();
|
||||
void Addr_AbsIdxX();
|
||||
void Addr_AbsIdxY();
|
||||
void Addr_AbsIdxXInd();
|
||||
void Addr_DirIdxXInd();
|
||||
void Addr_DirIndIdxY();
|
||||
void Addr_Rel();
|
||||
void Addr_Imm();
|
||||
|
||||
void ClearFlags(uint8_t flags);
|
||||
void SetFlags(uint8_t flags);
|
||||
bool CheckFlag(uint8_t flag);
|
||||
void SetZeroNegativeFlags(uint8_t value);
|
||||
void SetZeroNegativeFlags16(uint16_t value);
|
||||
|
||||
uint8_t GetByteValue();
|
||||
uint16_t GetWordValue();
|
||||
|
||||
void Push(uint8_t value);
|
||||
uint8_t Pop();
|
||||
void PushWord(uint16_t value);
|
||||
uint16_t PopWord();
|
||||
|
||||
uint16_t GetDirectAddress(uint8_t offset);
|
||||
|
||||
uint8_t GetOpCode();
|
||||
uint8_t ReadOperandByte();
|
||||
|
||||
void IncCycleCount(int32_t addr);
|
||||
void Exec();
|
||||
|
||||
public:
|
||||
Spc(shared_ptr<Console> console, vector<uint8_t> &spcRomData);
|
||||
~Spc();
|
||||
|
||||
void Run();
|
||||
void Reset();
|
||||
|
||||
uint8_t Read(uint16_t addr);
|
||||
void Write(uint32_t addr, uint8_t value);
|
||||
uint8_t CpuReadRegister(uint16_t addr);
|
||||
void CpuWriteRegister(uint32_t addr, uint8_t value);
|
||||
|
||||
void ProcessEndFrame();
|
||||
|
||||
|
|
87
Core/SpcTimer.h
Normal file
87
Core/SpcTimer.h
Normal file
|
@ -0,0 +1,87 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "../Utilities/Serializer.h"
|
||||
|
||||
template<uint8_t rate>
|
||||
class SpcTimer
|
||||
{
|
||||
private:
|
||||
bool _enabled = false;
|
||||
bool _timersEnabled = true;
|
||||
uint8_t _output = 0x0F; //"On power on, all three TnOUT have the value $F. On reset, they are $0."
|
||||
uint8_t _stage0 = 0;
|
||||
uint8_t _stage1 = 0;
|
||||
uint8_t _prevStage1 = 0;
|
||||
uint8_t _stage2 = 0;
|
||||
uint8_t _target = 0;
|
||||
|
||||
void ClockTimer()
|
||||
{
|
||||
uint8_t currentState = _stage1;
|
||||
if(!_timersEnabled) {
|
||||
//All timers are disabled
|
||||
currentState = 0;
|
||||
}
|
||||
|
||||
uint8_t prevState = _prevStage1;
|
||||
_prevStage1 = currentState;
|
||||
if(!_enabled || !prevState || currentState) {
|
||||
//Only clock on 1->0 transitions, when the timer is enabled
|
||||
return;
|
||||
}
|
||||
|
||||
if(++_stage2 == _target) {
|
||||
_stage2 = 0;
|
||||
_output++;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void Reset()
|
||||
{
|
||||
_output = 0;
|
||||
}
|
||||
|
||||
void SetEnabled(bool enabled)
|
||||
{
|
||||
if(!_enabled && enabled) {
|
||||
_stage2 = 0;
|
||||
_output = 0;
|
||||
}
|
||||
_enabled = enabled;
|
||||
}
|
||||
|
||||
void SetGlobalEnabled(bool enabled)
|
||||
{
|
||||
_timersEnabled = enabled;
|
||||
ClockTimer();
|
||||
}
|
||||
|
||||
void Run(uint8_t step)
|
||||
{
|
||||
_stage0 += step;
|
||||
if(_stage0 >= rate) {
|
||||
_stage1 ^= 0x01;
|
||||
_stage0 -= rate;
|
||||
|
||||
ClockTimer();
|
||||
}
|
||||
}
|
||||
|
||||
void SetTarget(uint8_t target)
|
||||
{
|
||||
_target = target;
|
||||
}
|
||||
|
||||
uint8_t GetOutput()
|
||||
{
|
||||
uint8_t value = _output & 0x0F;
|
||||
_output = 0;
|
||||
return value;
|
||||
}
|
||||
|
||||
void Serialize(Serializer &s)
|
||||
{
|
||||
s.Stream(_stage0, _stage1, _stage2, _output, _target, _enabled, _timersEnabled, _prevStage1);
|
||||
}
|
||||
};
|
70
Core/SpcTypes.h
Normal file
70
Core/SpcTypes.h
Normal file
|
@ -0,0 +1,70 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "SpcTimer.h"
|
||||
|
||||
struct SpcState
|
||||
{
|
||||
uint64_t Cycle;
|
||||
uint16_t PC;
|
||||
uint8_t A;
|
||||
uint8_t X;
|
||||
uint8_t Y;
|
||||
uint8_t SP;
|
||||
uint8_t PS;
|
||||
|
||||
bool WriteEnabled;
|
||||
bool RomEnabled;
|
||||
uint8_t InternalSpeed;
|
||||
uint8_t ExternalSpeed;
|
||||
bool TimersEnabled;
|
||||
|
||||
uint8_t DspReg;
|
||||
uint8_t OutputReg[4];
|
||||
uint8_t RamReg[2];
|
||||
uint8_t CpuRegs[4];
|
||||
|
||||
SpcTimer<128> Timer0;
|
||||
SpcTimer<128> Timer1;
|
||||
SpcTimer<16> Timer2;
|
||||
};
|
||||
|
||||
namespace SpcFlags {
|
||||
enum SpcFlags : uint8_t
|
||||
{
|
||||
Carry = 0x01,
|
||||
Zero = 0x02,
|
||||
IrqEnable = 0x04, //unused
|
||||
HalfCarry = 0x08,
|
||||
Break = 0x10, /* Set by the BRK instruction */
|
||||
DirectPage = 0x20, /* Selects page 0 or 1 for direct mode addressing */
|
||||
Overflow = 0x40,
|
||||
Negative = 0x80
|
||||
};
|
||||
}
|
||||
|
||||
namespace SpcControlFlags {
|
||||
enum SpcControlFlags : uint8_t
|
||||
{
|
||||
Timer0 = 0x01,
|
||||
Timer1 = 0x02,
|
||||
Timer2 = 0x04,
|
||||
|
||||
ClearPortsA = 0x10,
|
||||
ClearPortsB = 0x20,
|
||||
|
||||
EnableRom = 0x80
|
||||
};
|
||||
}
|
||||
|
||||
namespace SpcTestFlags {
|
||||
enum SpcTestFlags : uint8_t
|
||||
{
|
||||
TimersDisabled = 0x01,
|
||||
WriteEnabled = 0x02,
|
||||
Unknown = 0x04,
|
||||
TimersEnabled = 0x08,
|
||||
|
||||
ExternalSpeed = 0x30,
|
||||
InternalSpeed = 0xC0,
|
||||
};
|
||||
}
|
83
Core/dsp.h
83
Core/dsp.h
|
@ -1,83 +0,0 @@
|
|||
/* SNES SPC-700 DSP emulator C interface (also usable from C++) */
|
||||
|
||||
/* snes_spc 0.9.0 */
|
||||
#ifndef DSP_H
|
||||
#define DSP_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct SPC_DSP SPC_DSP;
|
||||
|
||||
/* Creates new DSP emulator. NULL if out of memory. */
|
||||
SPC_DSP* spc_dsp_new( void );
|
||||
|
||||
/* Frees DSP emulator */
|
||||
void spc_dsp_delete( SPC_DSP* );
|
||||
|
||||
/* Initializes DSP and has it use the 64K RAM provided */
|
||||
void spc_dsp_init( SPC_DSP*, void* ram_64k );
|
||||
|
||||
/* Sets destination for output samples. If out is NULL or out_size is 0,
|
||||
doesn't generate any. */
|
||||
typedef short spc_dsp_sample_t;
|
||||
void spc_dsp_set_output( SPC_DSP*, spc_dsp_sample_t* out, int out_size );
|
||||
|
||||
/* Number of samples written to output since it was last set, always
|
||||
a multiple of 2. Undefined if more samples were generated than
|
||||
output buffer could hold. */
|
||||
int spc_dsp_sample_count( SPC_DSP const* );
|
||||
|
||||
|
||||
/**** Emulation *****/
|
||||
|
||||
/* Resets DSP to power-on state */
|
||||
void spc_dsp_reset( SPC_DSP* );
|
||||
|
||||
/* Emulates pressing reset switch on SNES */
|
||||
void spc_dsp_soft_reset( SPC_DSP* );
|
||||
|
||||
/* Reads/writes DSP registers. For accuracy, you must first call spc_dsp_run() */
|
||||
/* to catch the DSP up to present. */
|
||||
int spc_dsp_read ( SPC_DSP const*, int addr );
|
||||
void spc_dsp_write( SPC_DSP*, int addr, int data );
|
||||
|
||||
/* Runs DSP for specified number of clocks (~1024000 per second). Every 32 clocks */
|
||||
/* a pair of samples is be generated. */
|
||||
void spc_dsp_run( SPC_DSP*, int clock_count );
|
||||
|
||||
|
||||
/**** Sound control *****/
|
||||
|
||||
/* Mutes voices corresponding to non-zero bits in mask. Reduces emulation accuracy. */
|
||||
enum { spc_dsp_voice_count = 8 };
|
||||
void spc_dsp_mute_voices( SPC_DSP*, int mask );
|
||||
|
||||
/* If true, prevents channels and global volumes from being phase-negated.
|
||||
Only supported by fast DSP; has no effect on accurate DSP. */
|
||||
void spc_dsp_disable_surround( SPC_DSP*, int disable );
|
||||
|
||||
|
||||
/**** State save/load *****/
|
||||
|
||||
/* Resets DSP and uses supplied values to initialize registers */
|
||||
enum { spc_dsp_register_count = 128 };
|
||||
void spc_dsp_load( SPC_DSP*, unsigned char const regs [spc_dsp_register_count] );
|
||||
|
||||
/* Saves/loads exact emulator state (accurate DSP only) */
|
||||
enum { spc_dsp_state_size = 640 }; /* maximum space needed when saving */
|
||||
typedef void (*spc_dsp_copy_func_t)( unsigned char** io, void* state, size_t );
|
||||
void spc_dsp_copy_state( SPC_DSP*, unsigned char** io, spc_dsp_copy_func_t );
|
||||
|
||||
/* Returns non-zero if new key-on events occurred since last call (accurate DSP only) */
|
||||
int spc_dsp_check_kon( SPC_DSP* );
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
Loading…
Add table
Reference in a new issue