Clean up new GME somewhat

CQTexperiment
Christopher Snowhill 2022-01-04 03:42:18 -08:00
parent fc38295d02
commit e4e6da1a94
45 changed files with 6237 additions and 2693 deletions

View File

@ -82,6 +82,7 @@
17C8F2470CBED286008D969D /* Sms_Apu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1DE0CBED286008D969D /* Sms_Apu.h */; };
17C8F24F0CBED286008D969D /* Spc_Emu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1E60CBED286008D969D /* Spc_Emu.cpp */; };
17C8F2500CBED286008D969D /* Spc_Emu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1E70CBED286008D969D /* Spc_Emu.h */; };
8302AF4F2784668C0066143E /* vrc7tone.h in Headers */ = {isa = PBXBuildFile; fileRef = 8302AF4E2784668C0066143E /* vrc7tone.h */; };
83489CBB2783015300BDCEA2 /* gme_types.h in Headers */ = {isa = PBXBuildFile; fileRef = 83489CB12783015200BDCEA2 /* gme_types.h */; };
83489CBC2783015300BDCEA2 /* nes_cpu_io.h in Headers */ = {isa = PBXBuildFile; fileRef = 83489CB22783015300BDCEA2 /* nes_cpu_io.h */; };
83489CBD2783015300BDCEA2 /* hes_cpu_io.h in Headers */ = {isa = PBXBuildFile; fileRef = 83489CB32783015300BDCEA2 /* hes_cpu_io.h */; };
@ -103,7 +104,6 @@
83489CE22783CAC100BDCEA2 /* emu2413_NESpatches.txt in Resources */ = {isa = PBXBuildFile; fileRef = 83489CDC2783CAC100BDCEA2 /* emu2413_NESpatches.txt */; };
83489CE32783CAC100BDCEA2 /* emu2413.h in Headers */ = {isa = PBXBuildFile; fileRef = 83489CDD2783CAC100BDCEA2 /* emu2413.h */; };
83489CE52783CAC100BDCEA2 /* emu2413.c in Sources */ = {isa = PBXBuildFile; fileRef = 83489CDF2783CAC100BDCEA2 /* emu2413.c */; };
83489CE92783CADC00BDCEA2 /* vrc7tone.h in Headers */ = {isa = PBXBuildFile; fileRef = 83489CE62783CADC00BDCEA2 /* vrc7tone.h */; };
83489CEA2783CADC00BDCEA2 /* panning.c in Sources */ = {isa = PBXBuildFile; fileRef = 83489CE72783CADC00BDCEA2 /* panning.c */; };
83489CEB2783CADC00BDCEA2 /* panning.h in Headers */ = {isa = PBXBuildFile; fileRef = 83489CE82783CADC00BDCEA2 /* panning.h */; };
83489CED2783D86700BDCEA2 /* mamedef.h in Headers */ = {isa = PBXBuildFile; fileRef = 83489CEC2783D86700BDCEA2 /* mamedef.h */; };
@ -204,6 +204,7 @@
17C8F1DE0CBED286008D969D /* Sms_Apu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Sms_Apu.h; path = gme/Sms_Apu.h; sourceTree = "<group>"; };
17C8F1E60CBED286008D969D /* Spc_Emu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Spc_Emu.cpp; path = gme/Spc_Emu.cpp; sourceTree = "<group>"; };
17C8F1E70CBED286008D969D /* Spc_Emu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Spc_Emu.h; path = gme/Spc_Emu.h; sourceTree = "<group>"; };
8302AF4E2784668C0066143E /* vrc7tone.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vrc7tone.h; sourceTree = "<group>"; };
833F68361CDBCAB200AFB9F0 /* es */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = "<group>"; };
83489CB12783015200BDCEA2 /* gme_types.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gme_types.h; path = gme/gme_types.h; sourceTree = "<group>"; };
83489CB22783015300BDCEA2 /* nes_cpu_io.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = nes_cpu_io.h; path = gme/nes_cpu_io.h; sourceTree = "<group>"; };
@ -226,7 +227,6 @@
83489CDC2783CAC100BDCEA2 /* emu2413_NESpatches.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = emu2413_NESpatches.txt; sourceTree = "<group>"; };
83489CDD2783CAC100BDCEA2 /* emu2413.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = emu2413.h; sourceTree = "<group>"; };
83489CDF2783CAC100BDCEA2 /* emu2413.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = emu2413.c; sourceTree = "<group>"; };
83489CE62783CADC00BDCEA2 /* vrc7tone.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vrc7tone.h; sourceTree = "<group>"; };
83489CE72783CADC00BDCEA2 /* panning.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = panning.c; sourceTree = "<group>"; };
83489CE82783CADC00BDCEA2 /* panning.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = panning.h; sourceTree = "<group>"; };
83489CEC2783D86700BDCEA2 /* mamedef.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mamedef.h; sourceTree = "<group>"; };
@ -432,7 +432,6 @@
8370B70B17F615FE001A4D7A /* Spc_Filter.h */,
8370B70C17F615FE001A4D7A /* Spc_Sfm.cpp */,
8370B70D17F615FE001A4D7A /* Spc_Sfm.h */,
83489CE62783CADC00BDCEA2 /* vrc7tone.h */,
);
name = Source;
sourceTree = "<group>";
@ -456,6 +455,7 @@
83489CEC2783D86700BDCEA2 /* mamedef.h */,
83489CE72783CADC00BDCEA2 /* panning.c */,
83489CE82783CADC00BDCEA2 /* panning.h */,
8302AF4E2784668C0066143E /* vrc7tone.h */,
);
path = ext;
sourceTree = "<group>";
@ -467,8 +467,7 @@
83FC5D36181B47FB00B917E5 /* dsp */,
83FC5D40181B47FB00B917E5 /* smp */,
);
name = higan;
path = gme/higan;
path = higan;
sourceTree = "<group>";
};
83FC5D36181B47FB00B917E5 /* dsp */ = {
@ -545,6 +544,7 @@
83FC5DAB181B8B1900B917E5 /* registers.hpp in Headers */,
17C8F20E0CBED286008D969D /* Gb_Cpu.h in Headers */,
83489CC22783015300BDCEA2 /* sap_cpu_io.h in Headers */,
8302AF4F2784668C0066143E /* vrc7tone.h in Headers */,
83489CBD2783015300BDCEA2 /* hes_cpu_io.h in Headers */,
17C8F2100CBED286008D969D /* Gb_Oscs.h in Headers */,
17C8F2120CBED286008D969D /* Gbs_Emu.h in Headers */,
@ -565,7 +565,6 @@
17C8F2260CBED286008D969D /* Kss_Scc_Apu.h in Headers */,
17C8F2290CBED286008D969D /* M3u_Playlist.h in Headers */,
17C8F22B0CBED286008D969D /* Multi_Buffer.h in Headers */,
83489CE92783CADC00BDCEA2 /* vrc7tone.h in Headers */,
17C8F22D0CBED286008D969D /* Music_Emu.h in Headers */,
83489CED2783D86700BDCEA2 /* mamedef.h in Headers */,
17C8F22F0CBED286008D969D /* Nes_Apu.h in Headers */,

View File

@ -1,9 +1,23 @@
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "Bml_Parser.h"
/* Copyright (C) 2013-2022 Christopher Snowhill. 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 */
const char * strchr_limited( const char * in, const char * end, char c )
{
while ( in < end && *in != c ) ++in;
@ -166,12 +180,15 @@ Bml_Node & Bml_Node::walkToNode(const char *path, bool use_indexes)
{
for ( std::vector<Bml_Node>::iterator it = node->children.begin(); it != node->children.end(); ++it )
{
if ( array_index_start - path == strlen(it->name) &&
if ( size_t (array_index_start - path) == strlen(it->name) &&
strncmp( it->name, path, array_index_start - path ) == 0 )
{
next_node = &(*it);
if ( array_index == 0 )
{
item_found = true;
if ( array_index == 0 ) break;
break;
}
--array_index;
}
if (array_index)
@ -183,7 +200,7 @@ Bml_Node & Bml_Node::walkToNode(const char *path, bool use_indexes)
for ( std::vector<Bml_Node>::iterator it = node->children.end(); it != node->children.begin(); )
{
--it;
if ( next_separator - path == strlen(it->name) &&
if ( size_t (next_separator - path) == strlen(it->name) &&
strncmp( it->name, path, next_separator - path ) == 0 )
{
next_node = &(*it);
@ -215,7 +232,7 @@ Bml_Node const& Bml_Node::walkToNode(const char *path) const
while ( *path )
{
bool item_found = false;
size_t array_index = ~0;
size_t array_index = 0;
const char * array_index_start = strchr( path, '[' );
const char * next_separator = strchr( path, ':' );
if ( !next_separator ) next_separator = path + strlen(path);
@ -230,12 +247,15 @@ Bml_Node const& Bml_Node::walkToNode(const char *path) const
}
for ( std::vector<Bml_Node>::const_iterator it = node->children.begin(), ite = node->children.end(); it != ite; ++it )
{
if ( array_index_start - path == strlen(it->name) &&
if ( size_t (array_index_start - path) == strlen(it->name) &&
strncmp( it->name, path, array_index_start - path ) == 0 )
{
next_node = &(*it);
if ( array_index == 0 )
{
item_found = true;
if ( array_index == 0 ) break;
break;
}
--array_index;
}
}
@ -258,7 +278,7 @@ void Bml_Parser::parseDocument( const char * source, size_t max_length )
document.clear();
size_t last_indent = ~0;
size_t last_indent = ~0ULL;
Bml_Node node;
@ -282,7 +302,7 @@ void Bml_Parser::parseDocument( const char * source, size_t max_length )
indent++;
}
if ( last_indent == ~0 ) last_indent = indent;
if ( last_indent == ~0ULL ) last_indent = indent;
if ( indent > last_indent )
{

View File

@ -1,3 +1,5 @@
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#ifndef BML_PARSER_H
#define BML_PARSER_H

View File

@ -104,6 +104,10 @@ if (USE_GME_NSF OR USE_GME_NSFE)
Nes_Namco_Apu.cpp
Nes_Oscs.cpp
Nes_Vrc6_Apu.cpp
Nes_Fds_Apu.cpp
Nes_Vrc7_Apu.cpp
../ext/emu2413.c
../ext/panning.c
Nsf_Emu.cpp
)
endif()
@ -124,11 +128,19 @@ endif()
if (USE_GME_SPC)
set(libgme_SRCS ${libgme_SRCS}
../higan/processor/spc700/spc700.cpp
../higan/smp/memory.cpp
../higan/smp/timing.cpp
../higan/smp/smp.cpp
../higan/dsp/dsp.cpp
../higan/dsp/SPC_DSP.cpp
Snes_Spc.cpp
Spc_Cpu.cpp
Spc_Dsp.cpp
Spc_Emu.cpp
Spc_Filter.cpp
Bml_Parser.cpp
Spc_Sfm.cpp
)
if (GME_SPC_ISOLATED_ECHO_BUFFER)
add_definitions(-DSPC_ISOLATED_ECHO_BUFFER)

View File

@ -875,7 +875,7 @@ possibly_out_of_time:
case 0xD6: // DEC zp,x
data = uint8_t (data + x);/*FALLTHRU*/
case 0xC6: // DEC zp
nz = (unsigned) -1;
nz = (uint_fast16_t)-1;
add_nz_zp:
nz += READ_LOW( data );
write_nz_zp:
@ -900,7 +900,7 @@ possibly_out_of_time:
case 0xCE: // DEC abs
data = GET_ADDR();
dec_ptr:
nz = (unsigned) -1;
nz = (uint_fast16_t) -1;
inc_common:
FLUSH_TIME();
nz += READ( data );
@ -1037,7 +1037,7 @@ possibly_out_of_time:
// Flags
case 0x38: // SEC
c = (unsigned) ~0;
c = (uint_fast16_t) ~0;
goto loop;
case 0x18: // CLC

View File

@ -367,7 +367,6 @@ static void parse_comment( char* in, M3u_Playlist::info_t& info, char *& last_co
const char* text = skip_white( in );
if ( *text )
{
char saved = *in;
*in = 0;
if ( !strcmp( "TITLE" , field ) ) info.title = text;
else if ( !strcmp( "ARTIST" , field ) ) info.artist = text;

View File

@ -1,4 +1,4 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Nes_Fds_Apu.h"

View File

@ -1,6 +1,7 @@
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
// NES FDS sound chip emulator
// $package
#ifndef NES_FDS_APU_H
#define NES_FDS_APU_H

View File

@ -1,6 +1,7 @@
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
// NES MMC5 sound chip emulator
// Nes_Snd_Emu $vers
#ifndef NES_MMC5_APU_H
#define NES_MMC5_APU_H
@ -52,7 +53,7 @@ inline void Nes_Mmc5_Apu::write_register( blip_time_t time, unsigned addr, int d
#ifdef BLARGG_DEBUG_H
default:
dprintf( "Unmapped MMC5 APU write: $%04X <- $%02X\n", addr, data );
debug_printf( "Unmapped MMC5 APU write: $%04X <- $%02X\n", addr, data );
#endif
}
}

View File

@ -1,3 +1,5 @@
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
// Konami VRC7 sound chip emulator
#ifndef NES_VRC7_APU_H

View File

@ -60,8 +60,6 @@ Nsf_Emu::Nsf_Emu()
mmc5 = 0;
vrc7 = 0;
apu_names = 0;
set_type( gme_nsf_type );
set_silence_lookahead( 6 );
apu.dmc_reader( pcm_read, this );
@ -96,11 +94,6 @@ void Nsf_Emu::unload()
}
#endif
{
delete [] apu_names;
apu_names = 0;
}
rom.clear();
Music_Emu::unload();
}
@ -202,12 +195,7 @@ blargg_err_t Nsf_Emu::init_sound()
Nes_Vrc7_Apu::osc_count;
#endif
if ( apu_names )
{
delete [] apu_names;
}
apu_names = new char* [count_total];
apu_names.resize( count_total );
int count = 0;
@ -324,7 +312,7 @@ blargg_err_t Nsf_Emu::init_sound()
}
set_voice_count( count );
set_voice_names( apu_names );
set_voice_names( &apu_names[0] );
if ( namco ) namco->volume( adjusted_gain );
if ( vrc6 ) vrc6 ->volume( adjusted_gain );
@ -428,70 +416,39 @@ void Nsf_Emu::set_voice( int i, Blip_Buffer* buf, Blip_Buffer*, Blip_Buffer* )
i -= Nes_Apu::osc_count;
#if !NSF_EMU_APU_ONLY
{
if ( vrc6 )
{
if ( i < Nes_Vrc6_Apu::osc_count )
{
// put saw first
if ( --i < 0 )
i = 2;
vrc6->osc_output( i, buf );
return;
#define HANDLE_CHIP(class, object) \
if ( object ) \
{ \
if ( i < class::osc_count ) \
{ \
object->osc_output( i, buf ); \
return; \
} \
i -= class::osc_count; \
}
i -= Nes_Vrc6_Apu::osc_count;
#define HANDLE_CHIP_VRC6(class, object) \
if ( object ) \
{ \
if ( i < class::osc_count ) \
{ \
/* put saw first */ \
if ( --i < 0 ) \
i = 2; \
object->osc_output( i, buf ); \
return; \
} \
i -= class::osc_count; \
}
if ( namco )
{
if ( i < Nes_Namco_Apu::osc_count )
{
namco->osc_output( i, buf );
return;
}
i -= Nes_Namco_Apu::osc_count;
}
if ( fme7 )
{
if ( i < Nes_Fme7_Apu::osc_count )
{
fme7->osc_output( i, buf );
return;
}
i -= Nes_Fme7_Apu::osc_count;
}
if ( fds )
{
if ( i < Nes_Fds_Apu::osc_count )
{
fds->osc_output( i, buf );
return;
}
i -= Nes_Fds_Apu::osc_count;
}
if ( mmc5 )
{
if ( i < Nes_Mmc5_Apu::osc_count )
{
mmc5->osc_output( i, buf );
return;
}
i -= Nes_Mmc5_Apu::osc_count;
}
if ( vrc7 )
{
if ( i < Nes_Vrc7_Apu::osc_count )
{
vrc7->osc_output( i, buf );
return;
}
i -= Nes_Vrc7_Apu::osc_count;
}
HANDLE_CHIP_VRC6(Nes_Vrc6_Apu, vrc6);
HANDLE_CHIP(Nes_Namco_Apu, namco);
HANDLE_CHIP(Nes_Fme7_Apu, fme7);
HANDLE_CHIP(Nes_Fds_Apu, fds);
HANDLE_CHIP(Nes_Mmc5_Apu, mmc5);
HANDLE_CHIP(Nes_Vrc7_Apu, vrc7);
}
#undef HANDLE_CHIP
#undef HANDLE_CHIP_VRC6
#endif
}

View File

@ -98,7 +98,7 @@ private:
class Nes_Mmc5_Apu* mmc5;
class Nes_Vrc7_Apu* vrc7;
Nes_Apu apu;
char** apu_names;
blargg_vector<const char*> apu_names;
static int pcm_read( void*, nes_addr_t );
blargg_err_t init_sound();

View File

@ -0,0 +1,383 @@
// SPC emulation support: init, sample buffering, reset, SPC loading
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#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.ram + 0x10000, cpu_pad_fill, sizeof m.ram.padding1 );
}
// 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()
{
// Allows playback of dodgy Super Mario World mod SPCs
#ifndef SPC_ISOLATED_ECHO_BUFFER
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 );
}
#endif
}
//// 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, 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 );
}

View File

@ -0,0 +1,283 @@
// SNES SPC-700 APU emulator
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#ifndef SNES_SPC_H
#define SNES_SPC_H
#include "Spc_Dsp.h"
#include "blargg_endian.h"
#include <stdint.h>
struct Snes_Spc {
public:
// 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:
// TODO: document
struct regs_t
{
uint16_t pc;
uint8_t a;
uint8_t x;
uint8_t y;
uint8_t psw;
uint8_t sp;
};
regs_t& smp_regs() { return m.cpu_regs; }
uint8_t* smp_ram() { return m.ram.ram; }
void run_until( time_t t ) { run_until_( t ); }
public:
BLARGG_DISABLE_NOTHROW
// 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];
regs_t 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 -- but this is
// still undefined behavior! TODO: remove and instead properly
// guard usage of emulated memory
uint8_t padding1 [0x100];
alignas(uint16_t) uint8_t ram [0x10000 + 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, uint16_t addr );
void cpu_write_smp_reg ( int data, rel_time_t, uint16_t addr );
void cpu_write_high ( int data, uint8_t i );
void cpu_write ( int data, uint16_t addr, rel_time_t );
int cpu_read_smp_reg ( int i, rel_time_t );
int cpu_read ( uint16_t addr, rel_time_t );
unsigned CPU_mem_bit ( uint16_t 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

View File

@ -0,0 +1,554 @@
// Core SPC emulation: CPU, timers, SMP registers, memory
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#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 )
debug_printf( "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
// Read/write handlers are 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, uint16_t 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 )
{
//debug_printf( "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 )
debug_printf( "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 )
debug_printf( "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, uint16_t 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, uint8_t i )
{
assert ( i < rom_size );
m.hi_ram [i] = (uint8_t) data;
if ( m.rom_enabled )
RAM [i + rom_addr] = m.rom [i]; // restore overwritten ROM
}
void Snes_Spc::cpu_write( int data, uint16_t addr, rel_time_t time )
{
MEM_ACCESS( time, addr )
// RAM
RAM [addr] = (uint8_t) data;
if ( addr >= 0xF0 ) // 64%
{
const uint16_t reg = addr - 0xF0;
// $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, &REGS [r_cpuio0] );
#endif
// Registers other than $F2 and $F4-$F7
if ( reg != 2 && (reg < 4 || reg > 7) ) // 36%
cpu_write_smp_reg( data, time, reg );
}
// High mem/address wrap-around
else if ( addr >= rom_addr ) // 1% in IPL ROM area or address wrapped around
cpu_write_high( data, addr - rom_addr );
}
}
//// 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( uint16_t 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 \
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 &REGS [r_cpuio0];\
}
#ifndef NDEBUG
// Used only for assert
int const cpu_lag_max = 12 - 1; // DIV YA,X takes 12 clocks
#endif
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"

1182
Frameworks/GME/gme/Spc_Cpu.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,712 @@
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Spc_Dsp.h"
#include "blargg_endian.h"
#include <string.h>
/* Copyright (C) 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"
#ifdef BLARGG_ENABLE_OPTIMIZER
#include BLARGG_ENABLE_OPTIMIZER
#endif
#if INT_MAX < 0x7FFFFFFF
#error "Requires that int type have at least 32 bits"
#endif
// TODO: add to blargg_endian.h
#define GET_LE16SA( addr ) ((int16_t) GET_LE16( addr ))
#define GET_LE16A( addr ) GET_LE16( addr )
#define SET_LE16A( addr, data ) SET_LE16( addr, data )
static uint8_t const initial_regs [Spc_Dsp::register_count] =
{
0x45,0x8B,0x5A,0x9A,0xE4,0x82,0x1B,0x78,0x00,0x00,0xAA,0x96,0x89,0x0E,0xE0,0x80,
0x2A,0x49,0x3D,0xBA,0x14,0xA0,0xAC,0xC5,0x00,0x00,0x51,0xBB,0x9C,0x4E,0x7B,0xFF,
0xF4,0xFD,0x57,0x32,0x37,0xD9,0x42,0x22,0x00,0x00,0x5B,0x3C,0x9F,0x1B,0x87,0x9A,
0x6F,0x27,0xAF,0x7B,0xE5,0x68,0x0A,0xD9,0x00,0x00,0x9A,0xC5,0x9C,0x4E,0x7B,0xFF,
0xEA,0x21,0x78,0x4F,0xDD,0xED,0x24,0x14,0x00,0x00,0x77,0xB1,0xD1,0x36,0xC1,0x67,
0x52,0x57,0x46,0x3D,0x59,0xF4,0x87,0xA4,0x00,0x00,0x7E,0x44,0x9C,0x4E,0x7B,0xFF,
0x75,0xF5,0x06,0x97,0x10,0xC3,0x24,0xBB,0x00,0x00,0x7B,0x7A,0xE0,0x60,0x12,0x0F,
0xF7,0x74,0x1C,0xE5,0x39,0x3D,0x73,0xC1,0x00,0x00,0x7A,0xB3,0xFF,0x4E,0x7B,0xFF
};
// if ( io < -32768 ) io = -32768;
// if ( io > 32767 ) io = 32767;
#define CLAMP16( io )\
{\
if ( (int16_t) io != io )\
io = (io >> 31) ^ 0x7FFF;\
}
// Access global DSP register
#define REG(n) m.regs [r_##n]
// Access voice DSP register
#define VREG(r,n) r [v_##n]
#define WRITE_SAMPLES( l, r, out ) \
{\
out [0] = l;\
out [1] = r;\
out += 2;\
if ( out >= m.out_end )\
{\
check( out == m.out_end );\
check( m.out_end != &m.extra [extra_size] || \
(m.extra <= m.out_begin && m.extra < &m.extra [extra_size]) );\
out = m.extra;\
m.out_end = &m.extra [extra_size];\
}\
}\
void Spc_Dsp::set_output( sample_t* out, int size )
{
require( (size & 1) == 0 ); // must be even
if ( !out )
{
out = m.extra;
size = extra_size;
}
m.out_begin = out;
m.out = out;
m.out_end = out + size;
}
// Volume registers and efb are signed! Easy to forget int8_t cast.
// Prefixes are to avoid accidental use of locals with same names.
// Interleved gauss table (to improve cache coherency)
// interleved_gauss [i] = gauss [(i & 1) * 256 + 255 - (i >> 1 & 0xFF)]
static short const interleved_gauss [512] =
{
370,1305, 366,1305, 362,1304, 358,1304, 354,1304, 351,1304, 347,1304, 343,1303,
339,1303, 336,1303, 332,1302, 328,1302, 325,1301, 321,1300, 318,1300, 314,1299,
311,1298, 307,1297, 304,1297, 300,1296, 297,1295, 293,1294, 290,1293, 286,1292,
283,1291, 280,1290, 276,1288, 273,1287, 270,1286, 267,1284, 263,1283, 260,1282,
257,1280, 254,1279, 251,1277, 248,1275, 245,1274, 242,1272, 239,1270, 236,1269,
233,1267, 230,1265, 227,1263, 224,1261, 221,1259, 218,1257, 215,1255, 212,1253,
210,1251, 207,1248, 204,1246, 201,1244, 199,1241, 196,1239, 193,1237, 191,1234,
188,1232, 186,1229, 183,1227, 180,1224, 178,1221, 175,1219, 173,1216, 171,1213,
168,1210, 166,1207, 163,1205, 161,1202, 159,1199, 156,1196, 154,1193, 152,1190,
150,1186, 147,1183, 145,1180, 143,1177, 141,1174, 139,1170, 137,1167, 134,1164,
132,1160, 130,1157, 128,1153, 126,1150, 124,1146, 122,1143, 120,1139, 118,1136,
117,1132, 115,1128, 113,1125, 111,1121, 109,1117, 107,1113, 106,1109, 104,1106,
102,1102, 100,1098, 99,1094, 97,1090, 95,1086, 94,1082, 92,1078, 90,1074,
89,1070, 87,1066, 86,1061, 84,1057, 83,1053, 81,1049, 80,1045, 78,1040,
77,1036, 76,1032, 74,1027, 73,1023, 71,1019, 70,1014, 69,1010, 67,1005,
66,1001, 65, 997, 64, 992, 62, 988, 61, 983, 60, 978, 59, 974, 58, 969,
56, 965, 55, 960, 54, 955, 53, 951, 52, 946, 51, 941, 50, 937, 49, 932,
48, 927, 47, 923, 46, 918, 45, 913, 44, 908, 43, 904, 42, 899, 41, 894,
40, 889, 39, 884, 38, 880, 37, 875, 36, 870, 36, 865, 35, 860, 34, 855,
33, 851, 32, 846, 32, 841, 31, 836, 30, 831, 29, 826, 29, 821, 28, 816,
27, 811, 27, 806, 26, 802, 25, 797, 24, 792, 24, 787, 23, 782, 23, 777,
22, 772, 21, 767, 21, 762, 20, 757, 20, 752, 19, 747, 19, 742, 18, 737,
17, 732, 17, 728, 16, 723, 16, 718, 15, 713, 15, 708, 15, 703, 14, 698,
14, 693, 13, 688, 13, 683, 12, 678, 12, 674, 11, 669, 11, 664, 11, 659,
10, 654, 10, 649, 10, 644, 9, 640, 9, 635, 9, 630, 8, 625, 8, 620,
8, 615, 7, 611, 7, 606, 7, 601, 6, 596, 6, 592, 6, 587, 6, 582,
5, 577, 5, 573, 5, 568, 5, 563, 4, 559, 4, 554, 4, 550, 4, 545,
4, 540, 3, 536, 3, 531, 3, 527, 3, 522, 3, 517, 2, 513, 2, 508,
2, 504, 2, 499, 2, 495, 2, 491, 2, 486, 1, 482, 1, 477, 1, 473,
1, 469, 1, 464, 1, 460, 1, 456, 1, 451, 1, 447, 1, 443, 1, 439,
0, 434, 0, 430, 0, 426, 0, 422, 0, 418, 0, 414, 0, 410, 0, 405,
0, 401, 0, 397, 0, 393, 0, 389, 0, 385, 0, 381, 0, 378, 0, 374,
};
//// Counters
#define RATE( rate, div )\
(rate >= div ? rate / div * 8 - 1 : rate - 1)
static unsigned const counter_mask [32] =
{
RATE( 2,2), RATE(2048,4), RATE(1536,3),
RATE(1280,5), RATE(1024,4), RATE( 768,3),
RATE( 640,5), RATE( 512,4), RATE( 384,3),
RATE( 320,5), RATE( 256,4), RATE( 192,3),
RATE( 160,5), RATE( 128,4), RATE( 96,3),
RATE( 80,5), RATE( 64,4), RATE( 48,3),
RATE( 40,5), RATE( 32,4), RATE( 24,3),
RATE( 20,5), RATE( 16,4), RATE( 12,3),
RATE( 10,5), RATE( 8,4), RATE( 6,3),
RATE( 5,5), RATE( 4,4), RATE( 3,3),
RATE( 2,4),
RATE( 1,4)
};
#undef RATE
inline void Spc_Dsp::init_counter()
{
// counters start out with this synchronization
m.counters [0] = 1;
m.counters [1] = 0;
m.counters [2] = -0x20u;
m.counters [3] = 0x0B;
int n = 2;
for ( int i = 1; i < 32; i++ )
{
m.counter_select [i] = &m.counters [n];
if ( !--n )
n = 3;
}
m.counter_select [ 0] = &m.counters [0];
m.counter_select [30] = &m.counters [2];
}
inline void Spc_Dsp::run_counter( int i )
{
int n = m.counters [i];
if ( !(n-- & 7) )
n -= 6 - i;
m.counters [i] = n;
}
#define READ_COUNTER( rate )\
(*m.counter_select [rate] & counter_mask [rate])
//// Emulation
void Spc_Dsp::run( int clock_count )
{
int new_phase = m.phase + clock_count;
int count = new_phase >> 5;
m.phase = new_phase & 31;
if ( !count )
return;
uint8_t* const ram = m.ram;
#ifdef SPC_ISOLATED_ECHO_BUFFER
uint8_t* const echo_ram = m.echo_ram;
#endif
uint8_t const* const dir = &ram [REG(dir) * 0x100];
int const slow_gaussian = (REG(pmon) >> 1) | REG(non);
int const noise_rate = REG(flg) & 0x1F;
// Global volume
int mvoll = (int8_t) REG(mvoll);
int mvolr = (int8_t) REG(mvolr);
if ( mvoll * mvolr < m.surround_threshold )
mvoll = -mvoll; // eliminate surround
do
{
// KON/KOFF reading
if ( (m.every_other_sample ^= 1) != 0 )
{
m.new_kon &= ~m.kon;
m.kon = m.new_kon;
m.t_koff = REG(koff);
}
run_counter( 1 );
run_counter( 2 );
run_counter( 3 );
// Noise
if ( !READ_COUNTER( noise_rate ) )
{
int feedback = (m.noise << 13) ^ (m.noise << 14);
m.noise = (feedback & 0x4000) ^ (m.noise >> 1);
}
// Voices
int pmon_input = 0;
int main_out_l = 0;
int main_out_r = 0;
int echo_out_l = 0;
int echo_out_r = 0;
voice_t* v = m.voices;
uint8_t* v_regs = m.regs;
int vbit = 1;
do
{
#define SAMPLE_PTR(i) GET_LE16A( &dir [VREG(v_regs,srcn) * 4 + i * 2] )
int brr_header = ram [v->brr_addr];
int kon_delay = v->kon_delay;
// Pitch
int pitch = GET_LE16A( &VREG(v_regs,pitchl) ) & 0x3FFF;
if ( REG(pmon) & vbit )
pitch += ((pmon_input >> 5) * pitch) >> 10;
// KON phases
if ( --kon_delay >= 0 )
{
v->kon_delay = kon_delay;
// Get ready to start BRR decoding on next sample
if ( kon_delay == 4 )
{
v->brr_addr = SAMPLE_PTR( 0 );
v->brr_offset = 1;
v->buf_pos = v->buf;
brr_header = 0; // header is ignored on this sample
}
// Envelope is never run during KON
v->env = 0;
v->hidden_env = 0;
// Disable BRR decoding until last three samples
v->interp_pos = (kon_delay & 3 ? 0x4000 : 0);
// Pitch is never added during KON
pitch = 0;
}
int env = v->env;
// Gaussian interpolation
{
int output = 0;
VREG(v_regs,envx) = (uint8_t) (env >> 4);
if ( env )
{
// Make pointers into gaussian based on fractional position between samples
int offset = (unsigned) v->interp_pos >> 3 & 0x1FE;
short const* fwd = interleved_gauss + offset;
short const* rev = interleved_gauss + 510 - offset; // mirror left half of gaussian
int const* in = &v->buf_pos [(unsigned) v->interp_pos >> 12];
if ( !(slow_gaussian & vbit) ) // 99%
{
// Faster approximation when exact sample value isn't necessary for pitch mod
output = (fwd [0] * in [0] +
fwd [1] * in [1] +
rev [1] * in [2] +
rev [0] * in [3]) >> 11;
output = (output * env) >> 11;
}
else
{
output = (int16_t) (m.noise * 2);
if ( !(REG(non) & vbit) )
{
output = (fwd [0] * in [0]) >> 11;
output += (fwd [1] * in [1]) >> 11;
output += (rev [1] * in [2]) >> 11;
output = (int16_t) output;
output += (rev [0] * in [3]) >> 11;
CLAMP16( output );
output &= ~1;
}
output = (output * env) >> 11 & ~1;
}
// Output
int l = output * v->volume [0];
int r = output * v->volume [1];
main_out_l += l;
main_out_r += r;
if ( REG(eon) & vbit )
{
echo_out_l += l;
echo_out_r += r;
}
}
pmon_input = output;
VREG(v_regs,outx) = (uint8_t) (output >> 8);
}
// Soft reset or end of sample
if ( REG(flg) & 0x80 || (brr_header & 3) == 1 )
{
v->env_mode = env_release;
env = 0;
}
if ( m.every_other_sample )
{
// KOFF
if ( m.t_koff & vbit )
v->env_mode = env_release;
// KON
if ( m.kon & vbit )
{
v->kon_delay = 5;
v->env_mode = env_attack;
REG(endx) &= ~vbit;
}
}
// Envelope
if ( !v->kon_delay )
{
if ( v->env_mode == env_release ) // 97%
{
env -= 0x8;
v->env = env;
if ( env <= 0 )
{
v->env = 0;
goto skip_brr; // no BRR decoding for you!
}
}
else // 3%
{
int rate;
int const adsr0 = VREG(v_regs,adsr0);
int env_data = VREG(v_regs,adsr1);
if ( adsr0 >= 0x80 ) // 97% ADSR
{
if ( v->env_mode > env_decay ) // 89%
{
env--;
env -= env >> 8;
rate = env_data & 0x1F;
// optimized handling
v->hidden_env = env;
if ( READ_COUNTER( rate ) )
goto exit_env;
v->env = env;
goto exit_env;
}
else if ( v->env_mode == env_decay )
{
env--;
env -= env >> 8;
rate = (adsr0 >> 3 & 0x0E) + 0x10;
}
else // env_attack
{
rate = (adsr0 & 0x0F) * 2 + 1;
env += rate < 31 ? 0x20 : 0x400;
}
}
else // GAIN
{
int mode;
env_data = VREG(v_regs,gain);
mode = env_data >> 5;
if ( mode < 4 ) // direct
{
env = env_data * 0x10;
rate = 31;
}
else
{
rate = env_data & 0x1F;
if ( mode == 4 ) // 4: linear decrease
{
env -= 0x20;
}
else if ( mode < 6 ) // 5: exponential decrease
{
env--;
env -= env >> 8;
}
else // 6,7: linear increase
{
env += 0x20;
if ( mode > 6 && (unsigned) v->hidden_env >= 0x600 )
env += 0x8 - 0x20; // 7: two-slope linear increase
}
}
}
// Sustain level
if ( (env >> 8) == (env_data >> 5) && v->env_mode == env_decay )
v->env_mode = env_sustain;
v->hidden_env = env;
// unsigned cast because linear decrease going negative also triggers this
if ( (unsigned) env > 0x7FF )
{
env = (env < 0 ? 0 : 0x7FF);
if ( v->env_mode == env_attack )
v->env_mode = env_decay;
}
if ( !READ_COUNTER( rate ) )
v->env = env; // nothing else is controlled by the counter
}
}
exit_env:
{
// Apply pitch
int old_pos = v->interp_pos;
int interp_pos = (old_pos & 0x3FFF) + pitch;
if ( interp_pos > 0x7FFF )
interp_pos = 0x7FFF;
v->interp_pos = interp_pos;
// BRR decode if necessary
if ( old_pos >= 0x4000 )
{
// Arrange the four input nybbles in 0xABCD order for easy decoding
int nybbles = ram [(v->brr_addr + v->brr_offset) & 0xFFFF] * 0x100 +
ram [(v->brr_addr + v->brr_offset + 1) & 0xFFFF];
// Advance read position
int const brr_block_size = 9;
int brr_offset = v->brr_offset;
if ( (brr_offset += 2) >= brr_block_size )
{
// Next BRR block
int brr_addr = (v->brr_addr + brr_block_size) & 0xFFFF;
assert( brr_offset == brr_block_size );
if ( brr_header & 1 )
{
brr_addr = SAMPLE_PTR( 1 );
if ( !v->kon_delay )
REG(endx) |= vbit;
}
v->brr_addr = brr_addr;
brr_offset = 1;
}
v->brr_offset = brr_offset;
// Decode
// 0: >>1 1: <<0 2: <<1 ... 12: <<11 13-15: >>4 <<11
static unsigned char const shifts [16 * 2] = {
13,12,12,12,12,12,12,12,12,12,12, 12, 12, 16, 16, 16,
0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 11, 11, 11
};
int const scale = brr_header >> 4;
int const right_shift = shifts [scale];
int const left_shift = shifts [scale + 16];
// Write to next four samples in circular buffer
int* pos = v->buf_pos;
int* end;
// Decode four samples
for ( end = pos + 4; pos < end; pos++, nybbles <<= 4 )
{
// Extract upper nybble and scale appropriately. Every cast is
// necessary to maintain correctness and avoid undef behavior
int s = int16_t(uint16_t((int16_t) nybbles >> right_shift) << left_shift);
// Apply IIR filter (8 is the most commonly used)
int const filter = brr_header & 0x0C;
int const p1 = pos [brr_buf_size - 1];
int const p2 = pos [brr_buf_size - 2] >> 1;
if ( filter >= 8 )
{
s += p1;
s -= p2;
if ( filter == 8 ) // s += p1 * 0.953125 - p2 * 0.46875
{
s += p2 >> 4;
s += (p1 * -3) >> 6;
}
else // s += p1 * 0.8984375 - p2 * 0.40625
{
s += (p1 * -13) >> 7;
s += (p2 * 3) >> 4;
}
}
else if ( filter ) // s += p1 * 0.46875
{
s += p1 >> 1;
s += (-p1) >> 5;
}
// Adjust and write sample
CLAMP16( s );
s = (int16_t) (s * 2);
pos [brr_buf_size] = pos [0] = s; // second copy simplifies wrap-around
}
if ( pos >= &v->buf [brr_buf_size] )
pos = v->buf;
v->buf_pos = pos;
}
}
skip_brr:
// Next voice
vbit <<= 1;
v_regs += 0x10;
v++;
}
while ( vbit < 0x100 );
// Echo position
int echo_offset = m.echo_offset;
#ifdef SPC_ISOLATED_ECHO_BUFFER
// And here, we win no awards for accuracy, but gain playback of dodgy Super Mario World mod SPCs
uint8_t* const echo_ptr = &echo_ram [(REG(esa) * 0x100 + echo_offset) & 0xFFFF];
#else
uint8_t* const echo_ptr = &ram [(REG(esa) * 0x100 + echo_offset) & 0xFFFF];
#endif
if ( !echo_offset )
m.echo_length = (REG(edl) & 0x0F) * 0x800;
echo_offset += 4;
if ( echo_offset >= m.echo_length )
echo_offset = 0;
m.echo_offset = echo_offset;
// FIR
int echo_in_l = GET_LE16SA( echo_ptr + 0 );
int echo_in_r = GET_LE16SA( echo_ptr + 2 );
int (*echo_hist_pos) [2] = m.echo_hist_pos;
if ( ++echo_hist_pos >= &m.echo_hist [echo_hist_size] )
echo_hist_pos = m.echo_hist;
m.echo_hist_pos = echo_hist_pos;
echo_hist_pos [0] [0] = echo_hist_pos [8] [0] = echo_in_l;
echo_hist_pos [0] [1] = echo_hist_pos [8] [1] = echo_in_r;
#define CALC_FIR_( i, in ) ((in) * (int8_t) REG(fir + i * 0x10))
echo_in_l = CALC_FIR_( 7, echo_in_l );
echo_in_r = CALC_FIR_( 7, echo_in_r );
#define CALC_FIR( i, ch ) CALC_FIR_( i, echo_hist_pos [i + 1] [ch] )
#define DO_FIR( i )\
echo_in_l += CALC_FIR( i, 0 );\
echo_in_r += CALC_FIR( i, 1 );
DO_FIR( 0 );
DO_FIR( 1 );
DO_FIR( 2 );
#if defined (__MWERKS__) && __MWERKS__ < 0x3200
__eieio(); // keeps compiler from stupidly "caching" things in memory
#endif
DO_FIR( 3 );
DO_FIR( 4 );
DO_FIR( 5 );
DO_FIR( 6 );
// Echo out
if ( !(REG(flg) & 0x20) )
{
int l = (echo_out_l >> 7) + ((echo_in_l * (int8_t) REG(efb)) >> 14);
int r = (echo_out_r >> 7) + ((echo_in_r * (int8_t) REG(efb)) >> 14);
// just to help pass more validation tests
#if SPC_MORE_ACCURACY
l &= ~1;
r &= ~1;
#endif
CLAMP16( l );
CLAMP16( r );
SET_LE16A( echo_ptr + 0, l );
SET_LE16A( echo_ptr + 2, r );
}
// Sound out
int l = (main_out_l * mvoll + echo_in_l * (int8_t) REG(evoll)) >> 14;
int r = (main_out_r * mvolr + echo_in_r * (int8_t) REG(evolr)) >> 14;
CLAMP16( l );
CLAMP16( r );
if ( (REG(flg) & 0x40) )
{
l = 0;
r = 0;
}
sample_t* out = m.out;
WRITE_SAMPLES( l, r, out );
m.out = out;
}
while ( --count );
}
//// Setup
void Spc_Dsp::mute_voices( int mask )
{
m.mute_mask = mask;
for ( int i = 0; i < voice_count; i++ )
{
m.voices [i].enabled = (mask >> i & 1) - 1;
update_voice_vol( i * 0x10 );
}
}
void Spc_Dsp::init( void* ram_64k )
{
m.ram = (uint8_t*) ram_64k;
mute_voices( 0 );
disable_surround( false );
set_output( 0, 0 );
reset();
#ifndef NDEBUG
// be sure this sign-extends
assert( (int16_t) 0x8000 == -0x8000 );
// be sure right shift preserves sign
assert( (-1 >> 1) == -1 );
// check clamp macro
int i;
i = +0x8000; CLAMP16( i ); assert( i == +0x7FFF );
i = -0x8001; CLAMP16( i ); assert( i == -0x8000 );
blargg_verify_byte_order();
#endif
}
void Spc_Dsp::soft_reset_common()
{
require( m.ram ); // init() must have been called already
m.noise = 0x4000;
m.echo_hist_pos = m.echo_hist;
m.every_other_sample = 1;
m.echo_offset = 0;
m.phase = 0;
init_counter();
}
void Spc_Dsp::soft_reset()
{
REG(flg) = 0xE0;
soft_reset_common();
}
void Spc_Dsp::load( uint8_t const regs [register_count] )
{
memcpy( m.regs, regs, sizeof m.regs );
memset( &m.regs [register_count], 0, offsetof (state_t,ram) - register_count );
// Internal state
int i;
for ( i = voice_count; --i >= 0; )
{
voice_t& v = m.voices [i];
v.brr_offset = 1;
v.buf_pos = v.buf;
}
m.new_kon = REG(kon);
mute_voices( m.mute_mask );
soft_reset_common();
}
void Spc_Dsp::reset() { load( initial_regs ); }

View File

@ -0,0 +1,212 @@
// Fast SNES SPC-700 DSP emulator (about 3x speed of accurate one)
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#ifndef SPC_DSP_H
#define SPC_DSP_H
#include "blargg_common.h"
struct Spc_Dsp {
public:
// Setup
// Initializes DSP and has it use the 64K RAM provided
void init( void* ram_64k );
// Sets destination for output samples. If out is NULL or out_size is 0,
// doesn't generate any.
typedef short sample_t;
void set_output( 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 sample_count() const;
// Emulation
// Resets DSP to power-on state
void reset();
// Emulates pressing reset switch on SNES
void soft_reset();
// Reads/writes DSP registers. For accuracy, you must first call spc_run_dsp()
// to catch the DSP up to present.
int read ( int addr ) const;
void write( 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 run( int clock_count );
// Sound control
// Mutes voices corresponding to non-zero bits in mask (overrides VxVOL with 0).
// Reduces emulation accuracy.
enum { voice_count = 8 };
void mute_voices( int mask );
// If true, prevents channels and global volumes from being phase-negated
void disable_surround( bool disable = true );
// State
// Resets DSP and uses supplied values to initialize registers
enum { register_count = 128 };
void load( uint8_t const regs [register_count] );
// DSP register addresses
// Global registers
enum {
r_mvoll = 0x0C, r_mvolr = 0x1C,
r_evoll = 0x2C, r_evolr = 0x3C,
r_kon = 0x4C, r_koff = 0x5C,
r_flg = 0x6C, r_endx = 0x7C,
r_efb = 0x0D, r_pmon = 0x2D,
r_non = 0x3D, r_eon = 0x4D,
r_dir = 0x5D, r_esa = 0x6D,
r_edl = 0x7D,
r_fir = 0x0F // 8 coefficients at 0x0F, 0x1F ... 0x7F
};
// Voice registers
enum {
v_voll = 0x00, v_volr = 0x01,
v_pitchl = 0x02, v_pitchh = 0x03,
v_srcn = 0x04, v_adsr0 = 0x05,
v_adsr1 = 0x06, v_gain = 0x07,
v_envx = 0x08, v_outx = 0x09
};
public:
enum { extra_size = 16 };
sample_t* extra() { return m.extra; }
sample_t const* out_pos() const { return m.out; }
public:
BLARGG_DISABLE_NOTHROW
enum { echo_hist_size = 8 };
enum env_mode_t { env_release, env_attack, env_decay, env_sustain };
enum { brr_buf_size = 12 };
struct voice_t
{
int buf [brr_buf_size*2];// decoded samples (twice the size to simplify wrap handling)
int* buf_pos; // place in buffer where next samples will be decoded
int interp_pos; // relative fractional position in sample (0x1000 = 1.0)
int brr_addr; // address of current BRR block
int brr_offset; // current decoding offset in BRR block
int kon_delay; // KON delay/current setup phase
env_mode_t env_mode;
int env; // current envelope level
int hidden_env; // used by GAIN mode 7, very obscure quirk
int volume [2]; // copy of volume from DSP registers, with surround disabled
int enabled; // -1 if enabled, 0 if muted
};
private:
struct state_t
{
uint8_t regs [register_count];
#ifdef SPC_ISOLATED_ECHO_BUFFER
// Echo buffer, for dodgy SPC rips that were only made to work in dodgy emulators
uint8_t echo_ram [64 * 1024];
#endif
// Echo history keeps most recent 8 samples (twice the size to simplify wrap handling)
int echo_hist [echo_hist_size * 2] [2];
int (*echo_hist_pos) [2]; // &echo_hist [0 to 7]
int every_other_sample; // toggles every sample
int kon; // KON value when last checked
int noise;
int echo_offset; // offset from ESA in echo buffer
int echo_length; // number of bytes that echo_offset will stop at
int phase; // next clock cycle to run (0-31)
unsigned counters [4];
int new_kon;
int t_koff;
voice_t voices [voice_count];
unsigned* counter_select [32];
// non-emulation state
uint8_t* ram; // 64K shared RAM between DSP and SMP
int mute_mask;
int surround_threshold;
sample_t* out;
sample_t* out_end;
sample_t* out_begin;
sample_t extra [extra_size];
};
state_t m;
void init_counter();
void run_counter( int );
void soft_reset_common();
void write_outline( int addr, int data );
void update_voice_vol( int addr );
};
#include <assert.h>
inline int Spc_Dsp::sample_count() const { return m.out - m.out_begin; }
inline int Spc_Dsp::read( int addr ) const
{
assert( (unsigned) addr < register_count );
return m.regs [addr];
}
inline void Spc_Dsp::update_voice_vol( int addr )
{
int l = (int8_t) m.regs [addr + v_voll];
int r = (int8_t) m.regs [addr + v_volr];
if ( l * r < m.surround_threshold )
{
// signs differ, so negate those that are negative
l ^= l >> 7;
r ^= r >> 7;
}
voice_t& v = m.voices [addr >> 4];
int enabled = v.enabled;
v.volume [0] = l & enabled;
v.volume [1] = r & enabled;
}
inline void Spc_Dsp::write( int addr, int data )
{
assert( (unsigned) addr < register_count );
m.regs [addr] = (uint8_t) data;
int low = addr & 0x0F;
if ( low < 0x2 ) // voice volumes
{
update_voice_vol( low ^ addr );
}
else if ( low == 0xC )
{
if ( addr == r_kon )
m.new_kon = (uint8_t) data;
if ( addr == r_endx ) // always cleared, regardless of data written
m.regs [r_endx] = 0;
}
}
inline void Spc_Dsp::disable_surround( bool disable )
{
m.surround_threshold = disable ? 0 : -0x4000;
}
#define SPC_NO_COPY_STATE_FUNCS 1
#define SPC_LESS_ACCURATE 1
#endif

View File

@ -1,8 +1,21 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Spc_Emu.h"
#include "blargg_endian.h"
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#ifdef RARDLL
#define PASCAL
#define CALLBACK
#define LONG long
#define HANDLE void *
#define LPARAM intptr_t
#define UINT __attribute__((unused)) unsigned int
#include <dll.hpp>
#endif
/* Copyright (C) 2004-2009 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
@ -26,7 +39,6 @@ Spc_Emu::Spc_Emu( gme_type_t type )
{
set_type( type );
set_voice_count( SuperFamicom::SPC_DSP::voice_count );
static const char* const names [SuperFamicom::SPC_DSP::voice_count] = {
"DSP 1", "DSP 2", "DSP 3", "DSP 4", "DSP 5", "DSP 6", "DSP 7", "DSP 8"
};
@ -34,7 +46,6 @@ Spc_Emu::Spc_Emu( gme_type_t type )
set_gain( 1.4 );
enable_filter( false );
enable_echo( true );
}
@ -49,7 +60,20 @@ byte const* Spc_Emu::trailer() const { return &file_data [min( file_size, spc_si
long Spc_Emu::trailer_size() const { return max( 0L, file_size - spc_size ); }
static void get_spc_xid6( byte const begin [], long size, track_info_t* out )
byte const* Rsn_Emu::trailer( int track ) const
{
const byte *track_data = spc[track];
long track_size = spc[track + 1] - spc[track];
return &track_data [min( track_size, spc_size )];
}
long Rsn_Emu::trailer_size( int track ) const
{
long track_size = spc[track + 1] - spc[track];
return max( 0L, track_size - spc_size );
}
static void get_spc_xid6( byte const* begin, long size, track_info_t* out )
{
// header
byte const* end = begin + size;
@ -62,13 +86,13 @@ static void get_spc_xid6( byte const begin [], long size, track_info_t* out )
byte const* in = begin + 8;
if ( end - in > info_size )
{
debug_printf( "SPC: Extra data after xid6\n" );
debug_printf( "Extra data after SPC xid6 info\n" );
end = in + info_size;
}
int year = 0;
char copyright [256 + 5];
long copyright_len = 0;
int copyright_len = 0;
int const year_len = 5;
int disc = 0, track = 0;
@ -82,12 +106,12 @@ static void get_spc_xid6( byte const begin [], long size, track_info_t* out )
in += 4;
if ( len > end - in )
{
debug_printf( "SPC: xid6 goes past end" );
check( false );
break; // block goes past end of data
}
// handle specific block types
char* field = NULL;
char* field = 0;
switch ( id )
{
case 0x01: field = out->song; break;
@ -110,7 +134,7 @@ static void get_spc_xid6( byte const begin [], long size, track_info_t* out )
out->intro_length = get_le32( in ) / 64;
if ( out->length > 0 )
{
int loop = out->length - out->intro_length;
long loop = out->length - out->intro_length;
if ( loop >= 2000 )
out->loop_length = loop;
}
@ -134,7 +158,7 @@ static void get_spc_xid6( byte const begin [], long size, track_info_t* out )
default:
if ( id < 0x01 || (id > 0x07 && id < 0x10) ||
(id > 0x14 && id < 0x30) || id > 0x36 )
debug_printf( "SPC: Unknown xid6 block: %X\n", (int) id );
debug_printf( "Unknown SPC xid6 block: %X\n", (int) id );
break;
}
if ( field )
@ -154,7 +178,7 @@ static void get_spc_xid6( byte const begin [], long size, track_info_t* out )
{
// ...but some files have no padding
in = unaligned;
//dprintf( "SPC: xid6 info tag wasn't properly padded to align\n" );
debug_printf( "SPC info tag wasn't properly padded to align\n" );
break;
}
}
@ -164,7 +188,6 @@ static void get_spc_xid6( byte const begin [], long size, track_info_t* out )
if ( year )
{
*--p = ' ';
// avoid using bloated printf
for ( int n = 4; n--; )
{
*--p = char (year % 10 + '0');
@ -198,11 +221,11 @@ static void get_spc_xid6( byte const begin [], long size, track_info_t* out )
check( in == end );
}
static void get_spc_info( Spc_Emu::header_t const& h, byte const xid6 [], long xid6_size,
static void get_spc_info( Spc_Emu::header_t const& h, byte const* xid6, long xid6_size,
track_info_t* out )
{
// decode length (can be in text or binary format, sometimes ambiguous ugh)
int len_secs = 0;
long len_secs = 0;
int i;
for ( i = 0; i < 3; i++ )
{
@ -261,9 +284,10 @@ blargg_err_t Spc_Emu::track_info_( track_info_t* out, int ) const
return 0;
}
void Spc_Emu::enable_accuracy_(bool enable)
blargg_err_t Rsn_Emu::track_info_( track_info_t* out, int track ) const
{
(void)enable;
get_spc_info( header( track ), trailer( track ), trailer_size( track ), out );
return 0;
}
static blargg_err_t check_spc_header( void const* header )
@ -276,25 +300,25 @@ static blargg_err_t check_spc_header( void const* header )
struct Spc_File : Gme_Info_
{
Spc_Emu::header_t header;
blargg_vector<byte> data;
blargg_vector<byte> xid6;
Spc_File() { set_type( gme_spc_type ); }
Spc_File( gme_type_t type ) { set_type( type ); }
Spc_File() : Spc_File( gme_spc_type ) {}
blargg_err_t load_( Data_Reader& in )
{
long file_size = in.remain();
if ( is_archive )
return 0;
if ( file_size < 0x10180 )
return gme_wrong_file_type;
RETURN_ERR( in.read( &header, Spc_Emu::header_size ) );
RETURN_ERR( in.read( &header, head_size ) );
RETURN_ERR( check_spc_header( header.tag ) );
long const xid6_offset = 0x10200;
RETURN_ERR( data.resize( min( xid6_offset - Spc_Emu::header_size, file_size - Spc_Emu::header_size ) ) );
RETURN_ERR( in.read( data.begin(), data.end() - data.begin() ) );
int xid6_size = file_size - xid6_offset;
long xid6_size = file_size - spc_size;
if ( xid6_size > 0 )
{
RETURN_ERR( xid6.resize( xid6_size ) );
RETURN_ERR( in.skip( spc_size - head_size ) );
RETURN_ERR( in.read( xid6.begin(), xid6.size() ) );
}
return 0;
@ -313,11 +337,113 @@ static Music_Emu* new_spc_file() { return BLARGG_NEW Spc_File; }
static gme_type_t_ const gme_spc_type_ = { "Super Nintendo", 1, &new_spc_emu, &new_spc_file, "SPC", 0 };
extern gme_type_t const gme_spc_type = &gme_spc_type_;
#ifdef RARDLL
static int CALLBACK call_rsn(UINT msg, LPARAM UserData, LPARAM P1, LPARAM P2)
{
byte **bp = (byte **)UserData;
unsigned char *addr = (unsigned char *)P1;
memcpy( *bp, addr, P2 );
*bp += P2;
return 0;
}
#endif
struct Rsn_File : Spc_File
{
blargg_vector<byte*> spc;
Rsn_File() : Spc_File( gme_rsn_type ) { is_archive = true; }
blargg_err_t load_archive( const char* path )
{
#ifdef RARDLL
struct RAROpenArchiveData data = {
.ArcName = (char *)path,
.OpenMode = RAR_OM_LIST, .OpenResult = 0,
.CmtBuf = 0, .CmtBufSize = 0, .CmtSize = 0, .CmtState = 0
};
// get the size of all unpacked headers combined
long pos = 0;
int count = 0;
unsigned biggest = 0;
blargg_vector<byte> temp;
HANDLE PASCAL rar = RAROpenArchive( &data );
struct RARHeaderData head;
for ( ; RARReadHeader( rar, &head ) == ERAR_SUCCESS; count++ )
{
RARProcessFile( rar, RAR_SKIP, 0, 0 );
long xid6_size = head.UnpSize - spc_size;
if ( xid6_size > 0 )
pos += xid6_size;
pos += head_size;
biggest = max( biggest, head.UnpSize );
}
xid6.resize( pos );
spc.resize( count + 1 );
temp.resize( biggest );
RARCloseArchive( rar );
// copy the headers/xid6 and index them
byte *bp;
data.OpenMode = RAR_OM_EXTRACT;
rar = RAROpenArchive( &data );
RARSetCallback( rar, call_rsn, (intptr_t)&bp );
for ( count = 0, pos = 0; RARReadHeader( rar, &head ) == ERAR_SUCCESS; )
{
bp = &temp[0];
RARProcessFile( rar, RAR_TEST, 0, 0 );
if ( !check_spc_header( bp - head.UnpSize ) )
{
spc[count++] = &xid6[pos];
memcpy( &xid6[pos], &temp[0], head_size );
pos += head_size;
long xid6_size = head.UnpSize - spc_size;
if ( xid6_size > 0 )
{
memcpy( &xid6[pos], &temp[spc_size], xid6_size );
pos += xid6_size;
}
}
}
spc[count] = &xid6[pos];
set_track_count( count );
RARCloseArchive( rar );
return 0;
#else
(void) path;
return gme_wrong_file_type;
#endif
}
blargg_err_t track_info_( track_info_t* out, int track ) const
{
if ( static_cast<size_t>(track) >= spc.size() )
return "Invalid track";
long xid6_size = spc[track + 1] - ( spc[track] + head_size );
get_spc_info(
*(Spc_Emu::header_t const*) spc[track],
spc[track] + head_size, xid6_size, out
);
return 0;
}
};
static Music_Emu* new_rsn_emu () { return BLARGG_NEW Rsn_Emu ; }
static Music_Emu* new_rsn_file() { return BLARGG_NEW Rsn_File; }
static gme_type_t_ const gme_rsn_type_ = { "Super Nintendo", 0, &new_rsn_emu, &new_rsn_file, "RSN", 0 };
extern gme_type_t const gme_rsn_type = &gme_rsn_type_;
// Setup
blargg_err_t Spc_Emu::set_sample_rate_( long sample_rate )
{
smp.power();
enable_accuracy( false );
if ( sample_rate != native_sample_rate )
{
RETURN_ERR( resampler.buffer_size( native_sample_rate / 20 * 2 ) );
@ -326,6 +452,13 @@ blargg_err_t Spc_Emu::set_sample_rate_( long sample_rate )
return 0;
}
void Spc_Emu::enable_accuracy_( bool b )
{
Music_Emu::enable_accuracy_( b );
filter.enable( b );
if ( b ) enable_echo();
}
void Spc_Emu::mute_voices_( int m )
{
Music_Emu::mute_voices_( m );
@ -336,12 +469,13 @@ void Spc_Emu::mute_voices_( int m )
blargg_err_t Spc_Emu::load_mem_( byte const* in, long size )
{
assert( offsetof (header_t,unused2 [46]) == header_size );
if ( size < 0x10180 )
return gme_wrong_file_type;
file_data = in;
file_size = size;
set_voice_count( SuperFamicom::SPC_DSP::voice_count );
if ( is_archive )
return 0;
if ( size < 0x10180 )
return gme_wrong_file_type;
return check_spc_header( in );
}
@ -370,29 +504,48 @@ blargg_err_t Spc_Emu::start_track_( int track )
smp.regs.p = header.psw;
smp.regs.s = header.sp;
memcpy( smp.apuram, ptr, 0x10000 );
memcpy( smp.apuram, ptr, sizeof smp.apuram );
// clear input ports that contain out port data from dump
memset( smp.apuram + 0xF4, 0, 4 );
memcpy( smp.sfm_last, ptr + 0xF4, 4 );
static const uint8_t regs_to_copy[][2] = { {0xFC,0xFF}, {0xFB,0xFF}, {0xFA,0xFF}, {0xF9,0xFF}, {0xF8,0xFF}, {0xF2,0xFF}, {0xF1,0x87} };
for (auto n : regs_to_copy) smp.op_buswrite( n[0], ptr[ n[0] ] & n[1] );
static const uint8_t regs_to_copy[][2] = {
{0xFC,0xFF}, {0xFB,0xFF}, {0xFA,0xFF}, {0xF9,0xFF},
{0xF8,0xFF}, {0xF2,0xFF}, {0xF1,0x87}
};
for (auto n : regs_to_copy)
smp.op_buswrite( n[0], ptr[ n[0] ] & n[1] );
smp.timer0.stage3_ticks = ptr[ 0xFD ] & 0x0F;
smp.timer1.stage3_ticks = ptr[ 0xFE ] & 0x0F;
smp.timer2.stage3_ticks = ptr[ 0xFF ] & 0x0F;
ptr += 0x10000;
ptr += sizeof smp.apuram;
smp.dsp.spc_dsp.load( ptr );
#if 1
// clear fake echo ram
memset( &smp.dsp.spc_dsp.m.echo_ram, 0xFF, sizeof smp.dsp.spc_dsp.m.echo_ram );
#else
if ( !(smp.dsp.read( SuperFamicom::SPC_DSP::r_flg ) & 0x20) )
{
// if echo enabled, clear echo buffer
int addr = 0x100 * smp.dsp.read( SuperFamicom::SPC_DSP::r_esa );
int end = addr + 0x800 * (smp.dsp.read( SuperFamicom::SPC_DSP::r_edl ) & 0x0F);
if ( end > 0x10000 )
end = 0x10000;
memset( &smp.apuram [addr], 0xFF, end - addr );
}
#endif
filter.set_gain( (int) (gain() * SPC_Filter::gain_unit) );
track_info_t spc_info;
RETURN_ERR( track_info_( &spc_info, track ) );
// Set a default track length, need a non-zero fadeout
if ( autoload_playback_limit() && ( spc_info.length > 0 ) )
set_fade ( spc_info.length, 50 );
return 0;
}
@ -407,7 +560,7 @@ blargg_err_t Spc_Emu::skip_( long count )
{
if ( sample_rate() != native_sample_rate )
{
count = (long) (count * resampler.ratio()) & ~1;
count = long (count * resampler.ratio()) & ~1;
count -= resampler.skip_input( count );
}
@ -422,7 +575,7 @@ blargg_err_t Spc_Emu::skip_( long count )
// eliminate pop due to resampler
if ( sample_rate() != native_sample_rate )
{
const long resampler_latency = 64;
const int resampler_latency = 64;
sample_t buf [resampler_latency];
return play_( resampler_latency, buf );
}
@ -430,7 +583,7 @@ blargg_err_t Spc_Emu::skip_( long count )
return 0;
}
blargg_err_t Spc_Emu::play_( long count, sample_t out [] )
blargg_err_t Spc_Emu::play_( long count, sample_t* out )
{
if ( sample_rate() == native_sample_rate )
return play_and_filter( count, out );
@ -449,3 +602,60 @@ blargg_err_t Spc_Emu::play_( long count, sample_t out [] )
check( remain == 0 );
return 0;
}
blargg_err_t Rsn_Emu::load_archive( const char* path )
{
#ifdef RARDLL
struct RAROpenArchiveData data = {
.ArcName = (char *)path,
.OpenMode = RAR_OM_LIST, .OpenResult = 0,
.CmtBuf = 0, .CmtBufSize = 0, .CmtSize = 0, .CmtState = 0
};
// get the file count and unpacked size
long pos = 0;
int count = 0;
HANDLE PASCAL rar = RAROpenArchive( &data );
struct RARHeaderData head;
for ( ; RARReadHeader( rar, &head ) == ERAR_SUCCESS; count++ )
{
RARProcessFile( rar, RAR_SKIP, 0, 0 );
pos += head.UnpSize;
}
rsn.resize( pos );
spc.resize( count + 1 );
RARCloseArchive( rar );
// copy the stream and index the tracks
byte *bp = &rsn[0];
data.OpenMode = RAR_OM_EXTRACT;
rar = RAROpenArchive( &data );
RARSetCallback( rar, call_rsn, (intptr_t)&bp );
for ( count = 0, pos = 0; RARReadHeader( rar, &head ) == ERAR_SUCCESS; )
{
RARProcessFile( rar, RAR_TEST, 0, 0 );
if ( !check_spc_header( bp - head.UnpSize ) )
spc[count++] = &rsn[pos];
pos += head.UnpSize;
}
spc[count] = &rsn[pos];
set_track_count( count );
RARCloseArchive( rar );
return 0;
#else
(void) path;
return gme_wrong_file_type;
#endif
}
blargg_err_t Rsn_Emu::start_track_( int track )
{
if ( static_cast<size_t>(track) >= spc.size() )
return "Invalid track requested";
file_data = spc[track];
file_size = spc[track + 1] - spc[track];
return Spc_Emu::start_track_( track );
}
Rsn_Emu::~Rsn_Emu() { }

View File

@ -1,20 +1,13 @@
// Super Nintendo SPC music file emulator
// Game_Music_Emu $vers
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#ifndef SPC_EMU_H
#define SPC_EMU_H
#include "Music_Emu.h"
#include "higan/smp/smp.hpp"
#include "Spc_Filter.h"
#if GME_SPC_FAST_RESAMPLER
#include "Upsampler.h"
typedef Upsampler Spc_Emu_Resampler;
#else
#include "Fir_Resampler.h"
typedef Fir_Resampler<24> Spc_Emu_Resampler;
#endif
#include "Music_Emu.h"
#include "../higan/smp/smp.hpp"
#include "Spc_Filter.h"
class Spc_Emu : public Music_Emu {
public:
@ -22,22 +15,6 @@ public:
// handled by resampling the 32kHz output; emulation accuracy is not affected.
enum { native_sample_rate = 32000 };
// Disables annoying pseudo-surround effect some music uses
void disable_surround( bool disable = true ) { smp.dsp.disable_surround( disable ); }
// Enables gaussian, cubic or sinc interpolation
void interpolation_level( int level = 0 ) { smp.dsp.spc_dsp.interpolation_level( level ); }
// Enables an analog signal simulation filter
void enable_filter( bool enable = true ) { _enable_filter = enable; if (enable) filter.clear(); }
// Enables native echo
void enable_echo( bool enable = true ) { smp.dsp.spc_dsp.enable_echo( enable ); }
virtual void mute_effects( bool mute ) { enable_echo(!mute); }
SuperFamicom::SMP const* get_smp() const;
SuperFamicom::SMP * get_smp();
// SPC file header
enum { header_size = 0x100 };
struct header_t
@ -64,6 +41,20 @@ public:
// Header for currently loaded file
header_t const& header() const { return *(header_t const*) file_data; }
// Prevents channels and global volumes from being phase-negated
void disable_surround( bool disable = true );
// Enables gaussian=0, cubic=1 or sinc=2 interpolation
// Or negative levels for worse quality, linear=-1 or nearest=-2
void interpolation_level( int level = 0 );
// Enables native echo
void enable_echo( bool enable = true );
void mute_effects( bool mute );
SuperFamicom::SMP const* get_smp() const;
SuperFamicom::SMP * get_smp();
static gme_type_t static_type() { return gme_spc_type; }
public:
@ -74,12 +65,10 @@ public:
byte const* trailer() const; // use track_info()
long trailer_size() const;
// Implementation
public:
Spc_Emu( gme_type_t );
Spc_Emu() : Spc_Emu( gme_spc_type ) {}
~Spc_Emu();
protected:
blargg_err_t load_mem_( byte const*, long );
blargg_err_t track_info_( track_info_t*, int track ) const;
@ -92,18 +81,35 @@ protected:
void enable_accuracy_( bool );
byte const* file_data;
long file_size;
private:
Spc_Emu_Resampler resampler;
Fir_Resampler<24> resampler;
SPC_Filter filter;
SuperFamicom::SMP smp;
bool _enable_filter;
blargg_err_t play_and_filter( long count, sample_t out [] );
};
inline void Spc_Emu::disable_surround( bool disable ) { smp.dsp.disable_surround( disable ); }
inline void Spc_Emu::interpolation_level( int level ) { smp.dsp.spc_dsp.interpolation_level( level ); }
inline void Spc_Emu::enable_echo( bool enable ) { smp.dsp.spc_dsp.enable_echo( enable ); }
inline void Spc_Emu::mute_effects( bool mute ) { enable_echo(!mute); }
inline SuperFamicom::SMP const* Spc_Emu::get_smp() const { return &smp; }
inline SuperFamicom::SMP * Spc_Emu::get_smp() { return &smp; }
class Rsn_Emu : public Spc_Emu {
public:
Rsn_Emu() : Spc_Emu( gme_rsn_type ) { is_archive = true; }
~Rsn_Emu();
blargg_err_t load_archive( const char* );
header_t const& header( int track ) const { return *(header_t const*) spc[track]; }
byte const* trailer( int ) const; // use track_info()
long trailer_size( int ) const;
protected:
blargg_err_t track_info_( track_info_t*, int ) const;
blargg_err_t start_track_( int );
private:
blargg_vector<byte> rsn;
blargg_vector<byte*> spc;
};
#endif

View File

@ -1,4 +1,4 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Spc_Sfm.h"
@ -6,16 +6,17 @@
#include <stdio.h>
/* Copyright (C) 2004-2013 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 */
/* Copyright (C) 2013-2022 Christopher Snowhill. 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"
@ -36,7 +37,6 @@ Sfm_Emu::Sfm_Emu()
set_gain( 1.4 );
set_max_initial_silence( 30 );
set_silence_lookahead( 30 ); // Some SFMs may have a lot of initialization code
enable_filter( false );
enable_echo( true );
}
@ -123,7 +123,7 @@ struct Sfm_File : Gme_Info_
blargg_err_t load_( Data_Reader& in )
{
int file_size = in.remain();
long file_size = in.remain();
if ( file_size < Sfm_Emu::sfm_min_file_size )
return gme_wrong_file_type;
RETURN_ERR( data.resize( file_size ) );
@ -169,6 +169,13 @@ blargg_err_t Sfm_Emu::set_sample_rate_( long sample_rate )
return 0;
}
void Sfm_Emu::enable_accuracy_( bool b )
{
Music_Emu::enable_accuracy_( b );
filter.enable( b );
if ( b ) enable_echo();
}
void Sfm_Emu::mute_voices_( int m )
{
Music_Emu::mute_voices_( m );
@ -232,6 +239,7 @@ blargg_err_t Sfm_Emu::start_track_( int track )
smp.reset();
memcpy( smp.apuram, ptr + 8 + metadata_size, 65536 );
memcpy( smp.dsp.spc_dsp.m.echo_ram, ptr + 8 + metadata_size, 65536 );
memcpy( smp.dsp.spc_dsp.m.regs, ptr + 8 + metadata_size + 65536, 128 );
@ -282,7 +290,7 @@ blargg_err_t Sfm_Emu::start_track_( int track )
value = metadata.enumValue("smp:ports");
if (value)
{
for (int i = 0; i < _countof(smp.sfm_last); i++)
for (unsigned long i = 0; i < _countof(smp.sfm_last); i++)
{
smp.sfm_last[i] = strtol(value, &end, 10);
if (*end == ',')
@ -440,7 +448,7 @@ blargg_err_t Sfm_Emu::start_track_( int track )
blargg_err_t Sfm_Emu::play_and_filter( long count, sample_t out [] )
{
smp.render( out, count );
if ( _enable_filter ) filter.run( out, count );
filter.run( out, count );
return 0;
}
@ -448,7 +456,7 @@ blargg_err_t Sfm_Emu::skip_( long count )
{
if ( sample_rate() != native_sample_rate )
{
count = (long) (count * resampler.ratio()) & ~1;
count = long (count * resampler.ratio()) & ~1;
count -= resampler.skip_input( count );
}

View File

@ -1,23 +1,17 @@
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
// Super Nintendo SFM music file emulator
// Game_Music_Emu $vers
#ifndef SPC_SFM_H
#define SPC_SFM_H
#include "Fir_Resampler.h"
#include "Music_Emu.h"
#include "higan/smp/smp.hpp"
#include "../higan/smp/smp.hpp"
#include "Spc_Filter.h"
#include "Bml_Parser.h"
#if GME_SPC_FAST_RESAMPLER
#include "Upsampler.h"
typedef Upsampler Spc_Emu_Resampler;
#else
#include "Fir_Resampler.h"
typedef Fir_Resampler<24> Spc_Emu_Resampler;
#endif
class Sfm_Emu : public Music_Emu {
public:
// Minimum allowed file size
@ -31,24 +25,21 @@ public:
blargg_err_t serialize( std::vector<uint8_t> & out );
// Disables annoying pseudo-surround effect some music uses
void disable_surround( bool disable = true ) { smp.dsp.disable_surround( disable ); }
void disable_surround( bool disable = true );
// Enables gaussian, cubic or sinc interpolation
void interpolation_level( int level = 0 ) { smp.dsp.spc_dsp.interpolation_level( level ); }
// Enables an analog signal simulation filter
void enable_filter( bool enable = true ) { _enable_filter = enable; if (enable) filter.clear(); }
// Enables gaussian=0, cubic=1 or sinc=2 interpolation
// Or sets worse quality, linear=-1, nearest=-2
void interpolation_level( int level = 0 );
// Enables native echo
void enable_echo(bool enable = true) { smp.dsp.spc_dsp.enable_echo(enable); }
virtual void mute_effects(bool mute) { enable_echo(!mute); }
void enable_echo(bool enable = true);
void mute_effects(bool mute);
SuperFamicom::SMP const* get_smp() const;
SuperFamicom::SMP * get_smp();
static gme_type_t static_type() { return gme_sfm_type; }
// Implementation
public:
Sfm_Emu();
~Sfm_Emu();
@ -68,20 +59,20 @@ protected:
long file_size;
private:
Spc_Emu_Resampler resampler;
Fir_Resampler<24> resampler;
SPC_Filter filter;
SuperFamicom::SMP smp;
bool _enable_filter;
Bml_Parser metadata;
blargg_err_t play_and_filter( long count, sample_t out [] );
};
inline void Sfm_Emu::disable_surround( bool disable ) { smp.dsp.disable_surround( disable ); }
inline void Sfm_Emu::interpolation_level( int level ) { smp.dsp.spc_dsp.interpolation_level( level ); }
inline void Sfm_Emu::enable_echo(bool enable) { smp.dsp.spc_dsp.enable_echo(enable); }
inline void Sfm_Emu::mute_effects(bool mute) { enable_echo(!mute); }
inline SuperFamicom::SMP const* Sfm_Emu::get_smp() const { return &smp; }
inline SuperFamicom::SMP * Sfm_Emu::get_smp() { return &smp; }
inline void Sfm_Emu::enable_accuracy_(bool enable) { (void)enable; }
#endif // SPC_SFM_H

View File

@ -4,7 +4,7 @@
#define BLARGG_CONFIG_H
// Uncomment to use zlib for transparent decompression of gzipped files
#define HAVE_ZLIB_H
//#define HAVE_ZLIB_H
// Uncomment and edit list to support only the listed game music types,
// so that the others don't get linked in at all.
@ -33,7 +33,7 @@
//#define BLARGG_BIG_ENDIAN 1
// Uncomment if you get errors in the bool section of blargg_common.h
#define BLARGG_COMPILER_HAS_BOOL 1
//#define BLARGG_COMPILER_HAS_BOOL 1
#define debug_printf(a, ...)

View File

@ -55,6 +55,7 @@ gme_type_t const* gme_type_list()
#endif
#ifdef USE_GME_SPC
gme_spc_type,
gme_rsn_type,
gme_sfm_type,
#endif
#ifdef USE_GME_VGM
@ -82,6 +83,8 @@ const char* gme_identify_header( void const* header )
case BLARGG_4CHAR('N','S','F','E'): return "NSFE";
case BLARGG_4CHAR('S','A','P',0x0D): return "SAP";
case BLARGG_4CHAR('S','N','E','S'): return "SPC";
case BLARGG_4CHAR('R','a','r','!'): return "RSN";
case BLARGG_4CHAR('V','g','m',' '): return "VGM";
}
if (get_be16(header) == BLARGG_2CHAR(0x1F, 0x8B))
return "VGZ";

View File

@ -195,6 +195,7 @@ extern BLARGG_EXPORT const gme_type_t
gme_nsfe_type,
gme_sap_type,
gme_spc_type,
gme_rsn_type,
gme_sfm_type,
gme_vgm_type,
gme_vgz_type;

View File

@ -2,7 +2,7 @@
#include "SPC_DSP.h"
#include "blargg_endian.h"
#include "../../gme/blargg_endian.h"
#include <string.h>
/* Copyright (C) 2007 Shay Green. This module is free software; you
@ -16,7 +16,7 @@ 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"
#include "../../gme/blargg_source.h"
namespace SuperFamicom {
@ -992,7 +992,7 @@ VOICE_CLOCK(V9_V6_V3) { voice_V9(v); voice_V6(v+1); voice_V3(v+2); }
//// Echo
// Current echo buffer pointer for left/right channel
#define ECHO_PTR( ch ) (&m.ram [m.t_echo_ptr + ch * 2])
#define ECHO_PTR( ch ) (&m.echo_ram [m.t_echo_ptr + ch * 2])
// Sample in echo history buffer, where 0 is the oldest
#define ECHO_FIR( i ) (m.echo_hist_pos [i])

View File

@ -4,7 +4,7 @@
#ifndef SPC_DSP_H
#define SPC_DSP_H
#include "blargg_common.h"
#include "../../gme/blargg_common.h"
extern "C" { typedef void (*dsp_copy_func_t)( unsigned char** io, void* state, size_t ); }
@ -128,6 +128,9 @@ public:
{
uint8_t regs [register_count];
// Echo buffer, for dodgy SPC rips that were only made to work in dodgy emulators
uint8_t echo_ram[64 * 1024];
// Echo history keeps most recent 8 samples (twice the size to simplify wrap handling)
int echo_hist [echo_hist_size * 2] [2];
int (*echo_hist_pos) [2]; // &echo_hist [0 to 7]

View File

@ -3,7 +3,7 @@
#include "SPC_DSP.h"
#include "blargg_common.h"
#include "../../gme/blargg_common.h"
namespace SuperFamicom {

View File

@ -53,6 +53,7 @@ uint8_t SPC700::op_inc(uint8_t x) {
}
uint8_t SPC700::op_ld(uint8_t x, uint8_t y) {
(void)x;
regs.p.n = y & 0x80;
regs.p.z = y == 0;
return y;
@ -96,6 +97,7 @@ uint8_t SPC700::op_sbc(uint8_t x, uint8_t y) {
}
uint8_t SPC700::op_st(uint8_t x, uint8_t y) {
(void)x;
return y;
}
@ -119,6 +121,7 @@ uint16_t SPC700::op_cpw(uint16_t x, uint16_t y) {
}
uint16_t SPC700::op_ldw(uint16_t x, uint16_t y) {
(void)x;
regs.p.n = y & 0x8000;
regs.p.z = y == 0;
return y;

View File

@ -1,7 +1,7 @@
#ifndef _higan_smp_h_
#define _higan_smp_h_
#include "blargg_common.h"
#include "../../gme/blargg_common.h"
#include "../processor/spc700/spc700.hpp"