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 */; }; 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 */; }; 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 */; }; 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 */; }; 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 */; }; 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 */; }; 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 */; }; 83489CE22783CAC100BDCEA2 /* emu2413_NESpatches.txt in Resources */ = {isa = PBXBuildFile; fileRef = 83489CDC2783CAC100BDCEA2 /* emu2413_NESpatches.txt */; };
83489CE32783CAC100BDCEA2 /* emu2413.h in Headers */ = {isa = PBXBuildFile; fileRef = 83489CDD2783CAC100BDCEA2 /* emu2413.h */; }; 83489CE32783CAC100BDCEA2 /* emu2413.h in Headers */ = {isa = PBXBuildFile; fileRef = 83489CDD2783CAC100BDCEA2 /* emu2413.h */; };
83489CE52783CAC100BDCEA2 /* emu2413.c in Sources */ = {isa = PBXBuildFile; fileRef = 83489CDF2783CAC100BDCEA2 /* emu2413.c */; }; 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 */; }; 83489CEA2783CADC00BDCEA2 /* panning.c in Sources */ = {isa = PBXBuildFile; fileRef = 83489CE72783CADC00BDCEA2 /* panning.c */; };
83489CEB2783CADC00BDCEA2 /* panning.h in Headers */ = {isa = PBXBuildFile; fileRef = 83489CE82783CADC00BDCEA2 /* panning.h */; }; 83489CEB2783CADC00BDCEA2 /* panning.h in Headers */ = {isa = PBXBuildFile; fileRef = 83489CE82783CADC00BDCEA2 /* panning.h */; };
83489CED2783D86700BDCEA2 /* mamedef.h in Headers */ = {isa = PBXBuildFile; fileRef = 83489CEC2783D86700BDCEA2 /* mamedef.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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 83489CEC2783D86700BDCEA2 /* mamedef.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mamedef.h; sourceTree = "<group>"; };
@ -432,7 +432,6 @@
8370B70B17F615FE001A4D7A /* Spc_Filter.h */, 8370B70B17F615FE001A4D7A /* Spc_Filter.h */,
8370B70C17F615FE001A4D7A /* Spc_Sfm.cpp */, 8370B70C17F615FE001A4D7A /* Spc_Sfm.cpp */,
8370B70D17F615FE001A4D7A /* Spc_Sfm.h */, 8370B70D17F615FE001A4D7A /* Spc_Sfm.h */,
83489CE62783CADC00BDCEA2 /* vrc7tone.h */,
); );
name = Source; name = Source;
sourceTree = "<group>"; sourceTree = "<group>";
@ -456,6 +455,7 @@
83489CEC2783D86700BDCEA2 /* mamedef.h */, 83489CEC2783D86700BDCEA2 /* mamedef.h */,
83489CE72783CADC00BDCEA2 /* panning.c */, 83489CE72783CADC00BDCEA2 /* panning.c */,
83489CE82783CADC00BDCEA2 /* panning.h */, 83489CE82783CADC00BDCEA2 /* panning.h */,
8302AF4E2784668C0066143E /* vrc7tone.h */,
); );
path = ext; path = ext;
sourceTree = "<group>"; sourceTree = "<group>";
@ -467,8 +467,7 @@
83FC5D36181B47FB00B917E5 /* dsp */, 83FC5D36181B47FB00B917E5 /* dsp */,
83FC5D40181B47FB00B917E5 /* smp */, 83FC5D40181B47FB00B917E5 /* smp */,
); );
name = higan; path = higan;
path = gme/higan;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
83FC5D36181B47FB00B917E5 /* dsp */ = { 83FC5D36181B47FB00B917E5 /* dsp */ = {
@ -545,6 +544,7 @@
83FC5DAB181B8B1900B917E5 /* registers.hpp in Headers */, 83FC5DAB181B8B1900B917E5 /* registers.hpp in Headers */,
17C8F20E0CBED286008D969D /* Gb_Cpu.h in Headers */, 17C8F20E0CBED286008D969D /* Gb_Cpu.h in Headers */,
83489CC22783015300BDCEA2 /* sap_cpu_io.h in Headers */, 83489CC22783015300BDCEA2 /* sap_cpu_io.h in Headers */,
8302AF4F2784668C0066143E /* vrc7tone.h in Headers */,
83489CBD2783015300BDCEA2 /* hes_cpu_io.h in Headers */, 83489CBD2783015300BDCEA2 /* hes_cpu_io.h in Headers */,
17C8F2100CBED286008D969D /* Gb_Oscs.h in Headers */, 17C8F2100CBED286008D969D /* Gb_Oscs.h in Headers */,
17C8F2120CBED286008D969D /* Gbs_Emu.h in Headers */, 17C8F2120CBED286008D969D /* Gbs_Emu.h in Headers */,
@ -565,7 +565,6 @@
17C8F2260CBED286008D969D /* Kss_Scc_Apu.h in Headers */, 17C8F2260CBED286008D969D /* Kss_Scc_Apu.h in Headers */,
17C8F2290CBED286008D969D /* M3u_Playlist.h in Headers */, 17C8F2290CBED286008D969D /* M3u_Playlist.h in Headers */,
17C8F22B0CBED286008D969D /* Multi_Buffer.h in Headers */, 17C8F22B0CBED286008D969D /* Multi_Buffer.h in Headers */,
83489CE92783CADC00BDCEA2 /* vrc7tone.h in Headers */,
17C8F22D0CBED286008D969D /* Music_Emu.h in Headers */, 17C8F22D0CBED286008D969D /* Music_Emu.h in Headers */,
83489CED2783D86700BDCEA2 /* mamedef.h in Headers */, 83489CED2783D86700BDCEA2 /* mamedef.h in Headers */,
17C8F22F0CBED286008D969D /* Nes_Apu.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 <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include "Bml_Parser.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 ) const char * strchr_limited( const char * in, const char * end, char c )
{ {
while ( in < end && *in != c ) ++in; 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 ) 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 ) strncmp( it->name, path, array_index_start - path ) == 0 )
{ {
next_node = &(*it); next_node = &(*it);
if ( array_index == 0 )
{
item_found = true; item_found = true;
if ( array_index == 0 ) break; break;
}
--array_index; --array_index;
} }
if (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(); ) for ( std::vector<Bml_Node>::iterator it = node->children.end(); it != node->children.begin(); )
{ {
--it; --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 ) strncmp( it->name, path, next_separator - path ) == 0 )
{ {
next_node = &(*it); next_node = &(*it);
@ -215,7 +232,7 @@ Bml_Node const& Bml_Node::walkToNode(const char *path) const
while ( *path ) while ( *path )
{ {
bool item_found = false; bool item_found = false;
size_t array_index = ~0; size_t array_index = 0;
const char * array_index_start = strchr( path, '[' ); const char * array_index_start = strchr( path, '[' );
const char * next_separator = strchr( path, ':' ); const char * next_separator = strchr( path, ':' );
if ( !next_separator ) next_separator = path + strlen(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 ) 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 ) strncmp( it->name, path, array_index_start - path ) == 0 )
{ {
next_node = &(*it); next_node = &(*it);
if ( array_index == 0 )
{
item_found = true; item_found = true;
if ( array_index == 0 ) break; break;
}
--array_index; --array_index;
} }
} }
@ -258,7 +278,7 @@ void Bml_Parser::parseDocument( const char * source, size_t max_length )
document.clear(); document.clear();
size_t last_indent = ~0; size_t last_indent = ~0ULL;
Bml_Node node; Bml_Node node;
@ -282,7 +302,7 @@ void Bml_Parser::parseDocument( const char * source, size_t max_length )
indent++; indent++;
} }
if ( last_indent == ~0 ) last_indent = indent; if ( last_indent == ~0ULL ) last_indent = indent;
if ( indent > last_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 #ifndef BML_PARSER_H
#define 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_Namco_Apu.cpp
Nes_Oscs.cpp Nes_Oscs.cpp
Nes_Vrc6_Apu.cpp Nes_Vrc6_Apu.cpp
Nes_Fds_Apu.cpp
Nes_Vrc7_Apu.cpp
../ext/emu2413.c
../ext/panning.c
Nsf_Emu.cpp Nsf_Emu.cpp
) )
endif() endif()
@ -124,11 +128,19 @@ endif()
if (USE_GME_SPC) if (USE_GME_SPC)
set(libgme_SRCS ${libgme_SRCS} 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 Snes_Spc.cpp
Spc_Cpu.cpp Spc_Cpu.cpp
Spc_Dsp.cpp Spc_Dsp.cpp
Spc_Emu.cpp Spc_Emu.cpp
Spc_Filter.cpp Spc_Filter.cpp
Bml_Parser.cpp
Spc_Sfm.cpp
) )
if (GME_SPC_ISOLATED_ECHO_BUFFER) if (GME_SPC_ISOLATED_ECHO_BUFFER)
add_definitions(-DSPC_ISOLATED_ECHO_BUFFER) add_definitions(-DSPC_ISOLATED_ECHO_BUFFER)

View File

@ -875,7 +875,7 @@ possibly_out_of_time:
case 0xD6: // DEC zp,x case 0xD6: // DEC zp,x
data = uint8_t (data + x);/*FALLTHRU*/ data = uint8_t (data + x);/*FALLTHRU*/
case 0xC6: // DEC zp case 0xC6: // DEC zp
nz = (unsigned) -1; nz = (uint_fast16_t)-1;
add_nz_zp: add_nz_zp:
nz += READ_LOW( data ); nz += READ_LOW( data );
write_nz_zp: write_nz_zp:
@ -900,7 +900,7 @@ possibly_out_of_time:
case 0xCE: // DEC abs case 0xCE: // DEC abs
data = GET_ADDR(); data = GET_ADDR();
dec_ptr: dec_ptr:
nz = (unsigned) -1; nz = (uint_fast16_t) -1;
inc_common: inc_common:
FLUSH_TIME(); FLUSH_TIME();
nz += READ( data ); nz += READ( data );
@ -1037,7 +1037,7 @@ possibly_out_of_time:
// Flags // Flags
case 0x38: // SEC case 0x38: // SEC
c = (unsigned) ~0; c = (uint_fast16_t) ~0;
goto loop; goto loop;
case 0x18: // CLC 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 ); const char* text = skip_white( in );
if ( *text ) if ( *text )
{ {
char saved = *in;
*in = 0; *in = 0;
if ( !strcmp( "TITLE" , field ) ) info.title = text; if ( !strcmp( "TITLE" , field ) ) info.title = text;
else if ( !strcmp( "ARTIST" , field ) ) info.artist = 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" #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 // NES FDS sound chip emulator
// $package
#ifndef NES_FDS_APU_H #ifndef NES_FDS_APU_H
#define 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 MMC5 sound chip emulator
// Nes_Snd_Emu $vers
#ifndef NES_MMC5_APU_H #ifndef NES_MMC5_APU_H
#define 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 #ifdef BLARGG_DEBUG_H
default: default:
dprintf( "Unmapped MMC5 APU write: $%04X <- $%02X\n", addr, data ); debug_printf( "Unmapped MMC5 APU write: $%04X <- $%02X\n", addr, data );
#endif #endif
} }
} }

View File

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

View File

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

View File

@ -98,7 +98,7 @@ private:
class Nes_Mmc5_Apu* mmc5; class Nes_Mmc5_Apu* mmc5;
class Nes_Vrc7_Apu* vrc7; class Nes_Vrc7_Apu* vrc7;
Nes_Apu apu; Nes_Apu apu;
char** apu_names; blargg_vector<const char*> apu_names;
static int pcm_read( void*, nes_addr_t ); static int pcm_read( void*, nes_addr_t );
blargg_err_t init_sound(); 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 "Spc_Emu.h"
#include "blargg_endian.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 /* 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 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_type( type );
set_voice_count( SuperFamicom::SPC_DSP::voice_count );
static const char* const names [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" "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 ); set_gain( 1.4 );
enable_filter( false );
enable_echo( true ); 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 ); } 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 // header
byte const* end = begin + size; 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; byte const* in = begin + 8;
if ( end - in > info_size ) 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; end = in + info_size;
} }
int year = 0; int year = 0;
char copyright [256 + 5]; char copyright [256 + 5];
long copyright_len = 0; int copyright_len = 0;
int const year_len = 5; int const year_len = 5;
int disc = 0, track = 0; 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; in += 4;
if ( len > end - in ) if ( len > end - in )
{ {
debug_printf( "SPC: xid6 goes past end" ); check( false );
break; // block goes past end of data break; // block goes past end of data
} }
// handle specific block types // handle specific block types
char* field = NULL; char* field = 0;
switch ( id ) switch ( id )
{ {
case 0x01: field = out->song; break; 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; out->intro_length = get_le32( in ) / 64;
if ( out->length > 0 ) if ( out->length > 0 )
{ {
int loop = out->length - out->intro_length; long loop = out->length - out->intro_length;
if ( loop >= 2000 ) if ( loop >= 2000 )
out->loop_length = loop; out->loop_length = loop;
} }
@ -134,7 +158,7 @@ static void get_spc_xid6( byte const begin [], long size, track_info_t* out )
default: default:
if ( id < 0x01 || (id > 0x07 && id < 0x10) || if ( id < 0x01 || (id > 0x07 && id < 0x10) ||
(id > 0x14 && id < 0x30) || id > 0x36 ) (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; break;
} }
if ( field ) 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 // ...but some files have no padding
in = unaligned; 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; break;
} }
} }
@ -164,7 +188,6 @@ static void get_spc_xid6( byte const begin [], long size, track_info_t* out )
if ( year ) if ( year )
{ {
*--p = ' '; *--p = ' ';
// avoid using bloated printf
for ( int n = 4; n--; ) for ( int n = 4; n--; )
{ {
*--p = char (year % 10 + '0'); *--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 ); 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 ) track_info_t* out )
{ {
// decode length (can be in text or binary format, sometimes ambiguous ugh) // decode length (can be in text or binary format, sometimes ambiguous ugh)
int len_secs = 0; long len_secs = 0;
int i; int i;
for ( i = 0; i < 3; 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; 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 ) 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_ struct Spc_File : Gme_Info_
{ {
Spc_Emu::header_t header; Spc_Emu::header_t header;
blargg_vector<byte> data;
blargg_vector<byte> xid6; 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 ) blargg_err_t load_( Data_Reader& in )
{ {
long file_size = in.remain(); long file_size = in.remain();
if ( is_archive )
return 0;
if ( file_size < 0x10180 ) if ( file_size < 0x10180 )
return gme_wrong_file_type; 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 ) ); RETURN_ERR( check_spc_header( header.tag ) );
long const xid6_offset = 0x10200; long xid6_size = file_size - spc_size;
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;
if ( xid6_size > 0 ) if ( xid6_size > 0 )
{ {
RETURN_ERR( xid6.resize( xid6_size ) ); RETURN_ERR( xid6.resize( xid6_size ) );
RETURN_ERR( in.skip( spc_size - head_size ) );
RETURN_ERR( in.read( xid6.begin(), xid6.size() ) ); RETURN_ERR( in.read( xid6.begin(), xid6.size() ) );
} }
return 0; 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 }; 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_; 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 // Setup
blargg_err_t Spc_Emu::set_sample_rate_( long sample_rate ) blargg_err_t Spc_Emu::set_sample_rate_( long sample_rate )
{ {
smp.power(); smp.power();
enable_accuracy( false );
if ( sample_rate != native_sample_rate ) if ( sample_rate != native_sample_rate )
{ {
RETURN_ERR( resampler.buffer_size( native_sample_rate / 20 * 2 ) ); 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; 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 ) void Spc_Emu::mute_voices_( int m )
{ {
Music_Emu::mute_voices_( 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 ) blargg_err_t Spc_Emu::load_mem_( byte const* in, long size )
{ {
assert( offsetof (header_t,unused2 [46]) == header_size ); assert( offsetof (header_t,unused2 [46]) == header_size );
if ( size < 0x10180 )
return gme_wrong_file_type;
file_data = in; file_data = in;
file_size = size; 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 ); 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.p = header.psw;
smp.regs.s = header.sp; 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 ); memset( smp.apuram + 0xF4, 0, 4 );
memcpy( smp.sfm_last, ptr + 0xF4, 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} }; static const uint8_t regs_to_copy[][2] = {
for (auto n : regs_to_copy) smp.op_buswrite( n[0], ptr[ n[0] ] & n[1] ); {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.timer0.stage3_ticks = ptr[ 0xFD ] & 0x0F;
smp.timer1.stage3_ticks = ptr[ 0xFE ] & 0x0F; smp.timer1.stage3_ticks = ptr[ 0xFE ] & 0x0F;
smp.timer2.stage3_ticks = ptr[ 0xFF ] & 0x0F; smp.timer2.stage3_ticks = ptr[ 0xFF ] & 0x0F;
ptr += 0x10000; ptr += sizeof smp.apuram;
smp.dsp.spc_dsp.load( ptr ); 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 ( !(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 addr = 0x100 * smp.dsp.read( SuperFamicom::SPC_DSP::r_esa );
int end = addr + 0x800 * (smp.dsp.read( SuperFamicom::SPC_DSP::r_edl ) & 0x0F); int end = addr + 0x800 * (smp.dsp.read( SuperFamicom::SPC_DSP::r_edl ) & 0x0F);
if ( end > 0x10000 ) if ( end > 0x10000 )
end = 0x10000; end = 0x10000;
memset( &smp.apuram [addr], 0xFF, end - addr ); memset( &smp.apuram [addr], 0xFF, end - addr );
} }
#endif
filter.set_gain( (int) (gain() * SPC_Filter::gain_unit) ); 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; return 0;
} }
@ -407,7 +560,7 @@ blargg_err_t Spc_Emu::skip_( long count )
{ {
if ( sample_rate() != native_sample_rate ) if ( sample_rate() != native_sample_rate )
{ {
count = (long) (count * resampler.ratio()) & ~1; count = long (count * resampler.ratio()) & ~1;
count -= resampler.skip_input( count ); count -= resampler.skip_input( count );
} }
@ -422,7 +575,7 @@ blargg_err_t Spc_Emu::skip_( long count )
// eliminate pop due to resampler // eliminate pop due to resampler
if ( sample_rate() != native_sample_rate ) if ( sample_rate() != native_sample_rate )
{ {
const long resampler_latency = 64; const int resampler_latency = 64;
sample_t buf [resampler_latency]; sample_t buf [resampler_latency];
return play_( resampler_latency, buf ); return play_( resampler_latency, buf );
} }
@ -430,7 +583,7 @@ blargg_err_t Spc_Emu::skip_( long count )
return 0; 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 ) if ( sample_rate() == native_sample_rate )
return play_and_filter( count, out ); return play_and_filter( count, out );
@ -449,3 +602,60 @@ blargg_err_t Spc_Emu::play_( long count, sample_t out [] )
check( remain == 0 ); check( remain == 0 );
return 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 // Super Nintendo SPC music file emulator
// Game_Music_Emu $vers // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#ifndef SPC_EMU_H #ifndef SPC_EMU_H
#define 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" #include "Fir_Resampler.h"
typedef Fir_Resampler<24> Spc_Emu_Resampler; #include "Music_Emu.h"
#endif #include "../higan/smp/smp.hpp"
#include "Spc_Filter.h"
class Spc_Emu : public Music_Emu { class Spc_Emu : public Music_Emu {
public: public:
@ -22,22 +15,6 @@ public:
// handled by resampling the 32kHz output; emulation accuracy is not affected. // handled by resampling the 32kHz output; emulation accuracy is not affected.
enum { native_sample_rate = 32000 }; 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 // SPC file header
enum { header_size = 0x100 }; enum { header_size = 0x100 };
struct header_t struct header_t
@ -64,6 +41,20 @@ public:
// Header for currently loaded file // Header for currently loaded file
header_t const& header() const { return *(header_t const*) file_data; } 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; } static gme_type_t static_type() { return gme_spc_type; }
public: public:
@ -74,12 +65,10 @@ public:
byte const* trailer() const; // use track_info() byte const* trailer() const; // use track_info()
long trailer_size() const; long trailer_size() const;
// Implementation
public: public:
Spc_Emu( gme_type_t ); Spc_Emu( gme_type_t );
Spc_Emu() : Spc_Emu( gme_spc_type ) {} Spc_Emu() : Spc_Emu( gme_spc_type ) {}
~Spc_Emu(); ~Spc_Emu();
protected: protected:
blargg_err_t load_mem_( byte const*, long ); blargg_err_t load_mem_( byte const*, long );
blargg_err_t track_info_( track_info_t*, int track ) const; blargg_err_t track_info_( track_info_t*, int track ) const;
@ -92,18 +81,35 @@ protected:
void enable_accuracy_( bool ); void enable_accuracy_( bool );
byte const* file_data; byte const* file_data;
long file_size; long file_size;
private: private:
Spc_Emu_Resampler resampler; Fir_Resampler<24> resampler;
SPC_Filter filter; SPC_Filter filter;
SuperFamicom::SMP smp; SuperFamicom::SMP smp;
bool _enable_filter;
blargg_err_t play_and_filter( long count, sample_t out [] ); 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 const* Spc_Emu::get_smp() const { return &smp; }
inline SuperFamicom::SMP * Spc_Emu::get_smp() { 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 #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" #include "Spc_Sfm.h"
@ -6,16 +6,17 @@
#include <stdio.h> #include <stdio.h>
/* Copyright (C) 2004-2013 Shay Green. This module is free software; you /* Copyright (C) 2013-2022 Christopher Snowhill. This module is free
can redistribute it and/or modify it under the terms of the GNU Lesser software; you can redistribute it and/or modify it under the terms of
General Public License as published by the Free Software Foundation; either the GNU Lesser General Public License as published by the Free Software
version 2.1 of the License, or (at your option) any later version. This Foundation; either version 2.1 of the License, or (at your option) any
module is distributed in the hope that it will be useful, but WITHOUT ANY later version. This module is distributed in the hope that it will be
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
details. You should have received a copy of the GNU Lesser General Public General Public License for more details. You should have received a copy
License along with this module; if not, write to the Free Software Foundation, of the GNU Lesser General Public License along with this module; if not,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
@ -36,7 +37,6 @@ Sfm_Emu::Sfm_Emu()
set_gain( 1.4 ); set_gain( 1.4 );
set_max_initial_silence( 30 ); set_max_initial_silence( 30 );
set_silence_lookahead( 30 ); // Some SFMs may have a lot of initialization code set_silence_lookahead( 30 ); // Some SFMs may have a lot of initialization code
enable_filter( false );
enable_echo( true ); enable_echo( true );
} }
@ -123,7 +123,7 @@ struct Sfm_File : Gme_Info_
blargg_err_t load_( Data_Reader& in ) 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 ) if ( file_size < Sfm_Emu::sfm_min_file_size )
return gme_wrong_file_type; return gme_wrong_file_type;
RETURN_ERR( data.resize( file_size ) ); RETURN_ERR( data.resize( file_size ) );
@ -169,6 +169,13 @@ blargg_err_t Sfm_Emu::set_sample_rate_( long sample_rate )
return 0; 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 ) void Sfm_Emu::mute_voices_( int m )
{ {
Music_Emu::mute_voices_( m ); Music_Emu::mute_voices_( m );
@ -232,6 +239,7 @@ blargg_err_t Sfm_Emu::start_track_( int track )
smp.reset(); smp.reset();
memcpy( smp.apuram, ptr + 8 + metadata_size, 65536 ); 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 ); 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"); value = metadata.enumValue("smp:ports");
if (value) 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); smp.sfm_last[i] = strtol(value, &end, 10);
if (*end == ',') 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 [] ) blargg_err_t Sfm_Emu::play_and_filter( long count, sample_t out [] )
{ {
smp.render( out, count ); smp.render( out, count );
if ( _enable_filter ) filter.run( out, count ); filter.run( out, count );
return 0; return 0;
} }
@ -448,7 +456,7 @@ blargg_err_t Sfm_Emu::skip_( long count )
{ {
if ( sample_rate() != native_sample_rate ) if ( sample_rate() != native_sample_rate )
{ {
count = (long) (count * resampler.ratio()) & ~1; count = long (count * resampler.ratio()) & ~1;
count -= resampler.skip_input( count ); 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 // Super Nintendo SFM music file emulator
// Game_Music_Emu $vers
#ifndef SPC_SFM_H #ifndef SPC_SFM_H
#define SPC_SFM_H #define SPC_SFM_H
#include "Fir_Resampler.h"
#include "Music_Emu.h" #include "Music_Emu.h"
#include "higan/smp/smp.hpp" #include "../higan/smp/smp.hpp"
#include "Spc_Filter.h" #include "Spc_Filter.h"
#include "Bml_Parser.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 { class Sfm_Emu : public Music_Emu {
public: public:
// Minimum allowed file size // Minimum allowed file size
@ -31,24 +25,21 @@ public:
blargg_err_t serialize( std::vector<uint8_t> & out ); blargg_err_t serialize( std::vector<uint8_t> & out );
// Disables annoying pseudo-surround effect some music uses // 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 // Enables gaussian=0, cubic=1 or sinc=2 interpolation
void interpolation_level( int level = 0 ) { smp.dsp.spc_dsp.interpolation_level( level ); } // Or sets worse quality, linear=-1, nearest=-2
void interpolation_level( int level = 0 );
// Enables an analog signal simulation filter
void enable_filter( bool enable = true ) { _enable_filter = enable; if (enable) filter.clear(); }
// Enables native echo // Enables native echo
void enable_echo(bool enable = true) { smp.dsp.spc_dsp.enable_echo(enable); } void enable_echo(bool enable = true);
virtual void mute_effects(bool mute) { enable_echo(!mute); } void mute_effects(bool mute);
SuperFamicom::SMP const* get_smp() const; SuperFamicom::SMP const* get_smp() const;
SuperFamicom::SMP * get_smp(); SuperFamicom::SMP * get_smp();
static gme_type_t static_type() { return gme_sfm_type; } static gme_type_t static_type() { return gme_sfm_type; }
// Implementation
public: public:
Sfm_Emu(); Sfm_Emu();
~Sfm_Emu(); ~Sfm_Emu();
@ -68,20 +59,20 @@ protected:
long file_size; long file_size;
private: private:
Spc_Emu_Resampler resampler; Fir_Resampler<24> resampler;
SPC_Filter filter; SPC_Filter filter;
SuperFamicom::SMP smp; SuperFamicom::SMP smp;
bool _enable_filter;
Bml_Parser metadata; Bml_Parser metadata;
blargg_err_t play_and_filter( long count, sample_t out [] ); 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 const* Sfm_Emu::get_smp() const { return &smp; }
inline SuperFamicom::SMP * Sfm_Emu::get_smp() { 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 #endif // SPC_SFM_H

View File

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

View File

@ -55,6 +55,7 @@ gme_type_t const* gme_type_list()
#endif #endif
#ifdef USE_GME_SPC #ifdef USE_GME_SPC
gme_spc_type, gme_spc_type,
gme_rsn_type,
gme_sfm_type, gme_sfm_type,
#endif #endif
#ifdef USE_GME_VGM #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('N','S','F','E'): return "NSFE";
case BLARGG_4CHAR('S','A','P',0x0D): return "SAP"; case BLARGG_4CHAR('S','A','P',0x0D): return "SAP";
case BLARGG_4CHAR('S','N','E','S'): return "SPC"; 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)) if (get_be16(header) == BLARGG_2CHAR(0x1F, 0x8B))
return "VGZ"; return "VGZ";

View File

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

View File

@ -2,7 +2,7 @@
#include "SPC_DSP.h" #include "SPC_DSP.h"
#include "blargg_endian.h" #include "../../gme/blargg_endian.h"
#include <string.h> #include <string.h>
/* Copyright (C) 2007 Shay Green. This module is free software; you /* 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, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "../../gme/blargg_source.h"
namespace SuperFamicom { namespace SuperFamicom {
@ -992,7 +992,7 @@ VOICE_CLOCK(V9_V6_V3) { voice_V9(v); voice_V6(v+1); voice_V3(v+2); }
//// Echo //// Echo
// Current echo buffer pointer for left/right channel // 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 // Sample in echo history buffer, where 0 is the oldest
#define ECHO_FIR( i ) (m.echo_hist_pos [i]) #define ECHO_FIR( i ) (m.echo_hist_pos [i])

View File

@ -4,7 +4,7 @@
#ifndef SPC_DSP_H #ifndef SPC_DSP_H
#define 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 ); } 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]; 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) // 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 [echo_hist_size * 2] [2];
int (*echo_hist_pos) [2]; // &echo_hist [0 to 7] int (*echo_hist_pos) [2]; // &echo_hist [0 to 7]

View File

@ -3,7 +3,7 @@
#include "SPC_DSP.h" #include "SPC_DSP.h"
#include "blargg_common.h" #include "../../gme/blargg_common.h"
namespace SuperFamicom { 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) { uint8_t SPC700::op_ld(uint8_t x, uint8_t y) {
(void)x;
regs.p.n = y & 0x80; regs.p.n = y & 0x80;
regs.p.z = y == 0; regs.p.z = y == 0;
return y; 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) { uint8_t SPC700::op_st(uint8_t x, uint8_t y) {
(void)x;
return y; 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) { uint16_t SPC700::op_ldw(uint16_t x, uint16_t y) {
(void)x;
regs.p.n = y & 0x8000; regs.p.n = y & 0x8000;
regs.p.z = y == 0; regs.p.z = y == 0;
return y; return y;

View File

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