Clean up new GME somewhat

This commit is contained in:
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,357 +1,377 @@
#include <string.h> // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include <stdlib.h>
#include <stdio.h> #include <string.h>
#include <stdlib.h>
#include "Bml_Parser.h" #include <stdio.h>
const char * strchr_limited( const char * in, const char * end, char c ) #include "Bml_Parser.h"
{
while ( in < end && *in != c ) ++in; /* Copyright (C) 2013-2022 Christopher Snowhill. This module is free
if ( in < end ) return in; software; you can redistribute it and/or modify it under the terms of
else return 0; 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
Bml_Node Bml_Node::emptyNode; useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
Bml_Node::Bml_Node() 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,
name = 0; write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
value = 0; Floor, Boston, MA 02110-1301 USA */
}
const char * strchr_limited( const char * in, const char * end, char c )
Bml_Node::Bml_Node(char const* name, size_t max_length) {
{ while ( in < end && *in != c ) ++in;
size_t length = 0; if ( in < end ) return in;
char const* ptr = name; else return 0;
while (*ptr && length < max_length) { ++ptr; ++length; } }
this->name = new char[ length + 1 ];
memcpy( this->name, name, length ); Bml_Node Bml_Node::emptyNode;
this->name[ length ] = '\0';
value = 0; Bml_Node::Bml_Node()
} {
name = 0;
Bml_Node::Bml_Node(const Bml_Node &in) value = 0;
{ }
size_t length;
name = 0; Bml_Node::Bml_Node(char const* name, size_t max_length)
if (in.name) {
{ size_t length = 0;
length = strlen(in.name); char const* ptr = name;
name = new char[length + 1]; while (*ptr && length < max_length) { ++ptr; ++length; }
memcpy(name, in.name, length + 1); this->name = new char[ length + 1 ];
} memcpy( this->name, name, length );
value = 0; this->name[ length ] = '\0';
if (in.value) value = 0;
{ }
length = strlen(in.value);
value = new char[length + 1]; Bml_Node::Bml_Node(const Bml_Node &in)
memcpy(value, in.value, length + 1); {
} size_t length;
children = in.children; name = 0;
} if (in.name)
{
Bml_Node::~Bml_Node() length = strlen(in.name);
{ name = new char[length + 1];
delete [] name; memcpy(name, in.name, length + 1);
delete [] value; }
} value = 0;
if (in.value)
void Bml_Node::clear() {
{ length = strlen(in.value);
delete [] name; value = new char[length + 1];
delete [] value; memcpy(value, in.value, length + 1);
}
name = 0; children = in.children;
value = 0; }
children.resize( 0 );
} Bml_Node::~Bml_Node()
{
void Bml_Node::setLine(const char *line, size_t max_length) delete [] name;
{ delete [] value;
delete [] name; }
delete [] value;
void Bml_Node::clear()
name = 0; {
value = 0; delete [] name;
delete [] value;
size_t length = 0;
const char * end = line; name = 0;
while (*end && length < max_length) ++end; value = 0;
children.resize( 0 );
const char * line_end = strchr_limited(line, end, '\n'); }
if ( !line_end ) line_end = end;
void Bml_Node::setLine(const char *line, size_t max_length)
const char * first_letter = line; {
while ( first_letter < line_end && *first_letter <= 0x20 ) first_letter++; delete [] name;
delete [] value;
const char * colon = strchr_limited(first_letter, line_end, ':');
const char * last_letter = line_end - 1; name = 0;
value = 0;
if (colon)
{ size_t length = 0;
const char * first_value_letter = colon + 1; const char * end = line;
while (first_value_letter < line_end && *first_value_letter <= 0x20) first_value_letter++; while (*end && length < max_length) ++end;
last_letter = line_end - 1;
while (last_letter > first_value_letter && *last_letter <= 0x20) last_letter--; const char * line_end = strchr_limited(line, end, '\n');
if ( !line_end ) line_end = end;
value = new char[last_letter - first_value_letter + 2];
memcpy(value, first_value_letter, last_letter - first_value_letter + 1); const char * first_letter = line;
value[last_letter - first_value_letter + 1] = '\0'; while ( first_letter < line_end && *first_letter <= 0x20 ) first_letter++;
last_letter = colon - 1; const char * colon = strchr_limited(first_letter, line_end, ':');
} const char * last_letter = line_end - 1;
while (last_letter > first_letter && *last_letter <= 0x20) last_letter--; if (colon)
{
name = new char[last_letter - first_letter + 2]; const char * first_value_letter = colon + 1;
memcpy(name, first_letter, last_letter - first_letter + 1); while (first_value_letter < line_end && *first_value_letter <= 0x20) first_value_letter++;
name[last_letter - first_letter + 1] = '\0'; last_letter = line_end - 1;
} while (last_letter > first_value_letter && *last_letter <= 0x20) last_letter--;
Bml_Node& Bml_Node::addChild(const Bml_Node &child) value = new char[last_letter - first_value_letter + 2];
{ memcpy(value, first_value_letter, last_letter - first_value_letter + 1);
children.push_back(child); value[last_letter - first_value_letter + 1] = '\0';
return *(children.end() - 1);
} last_letter = colon - 1;
}
const char * Bml_Node::getName() const
{ while (last_letter > first_letter && *last_letter <= 0x20) last_letter--;
return name;
} name = new char[last_letter - first_letter + 2];
memcpy(name, first_letter, last_letter - first_letter + 1);
const char * Bml_Node::getValue() const name[last_letter - first_letter + 1] = '\0';
{ }
return value;
} Bml_Node& Bml_Node::addChild(const Bml_Node &child)
{
void Bml_Node::setValue(char const* value) children.push_back(child);
{ return *(children.end() - 1);
delete [] this->value; }
size_t length = strlen( value ) + 1;
this->value = new char[ length ]; const char * Bml_Node::getName() const
memcpy( this->value, value, length ); {
} return name;
}
size_t Bml_Node::getChildCount() const
{ const char * Bml_Node::getValue() const
return children.size(); {
} return value;
}
Bml_Node const& Bml_Node::getChild(size_t index) const
{ void Bml_Node::setValue(char const* value)
return children[index]; {
} delete [] this->value;
size_t length = strlen( value ) + 1;
Bml_Node & Bml_Node::walkToNode(const char *path, bool use_indexes) this->value = new char[ length ];
{ memcpy( this->value, value, length );
Bml_Node * next_node; }
Bml_Node * node = this;
while ( *path ) size_t Bml_Node::getChildCount() const
{ {
bool item_found = false; return children.size();
size_t array_index = 0; }
const char * array_index_start = strchr( path, '[' );
const char * next_separator = strchr( path, ':' ); Bml_Node const& Bml_Node::getChild(size_t index) const
if ( !next_separator ) next_separator = path + strlen(path); {
if ( use_indexes && array_index_start && array_index_start < next_separator ) return children[index];
{ }
char * temp;
array_index = strtoul( array_index_start + 1, &temp, 10 ); Bml_Node & Bml_Node::walkToNode(const char *path, bool use_indexes)
} {
else Bml_Node * next_node;
{ Bml_Node * node = this;
array_index_start = next_separator; while ( *path )
} {
if ( use_indexes ) bool item_found = false;
{ size_t array_index = 0;
for ( std::vector<Bml_Node>::iterator it = node->children.begin(); it != node->children.end(); ++it ) const char * array_index_start = strchr( path, '[' );
{ const char * next_separator = strchr( path, ':' );
if ( array_index_start - path == strlen(it->name) && if ( !next_separator ) next_separator = path + strlen(path);
strncmp( it->name, path, array_index_start - path ) == 0 ) if ( use_indexes && array_index_start && array_index_start < next_separator )
{ {
next_node = &(*it); char * temp;
item_found = true; array_index = strtoul( array_index_start + 1, &temp, 10 );
if ( array_index == 0 ) break; }
--array_index; else
} {
if (array_index) array_index_start = next_separator;
item_found = false; }
} if ( use_indexes )
} {
else for ( std::vector<Bml_Node>::iterator it = node->children.begin(); it != node->children.end(); ++it )
{ {
for ( std::vector<Bml_Node>::iterator it = node->children.end(); it != node->children.begin(); ) if ( size_t (array_index_start - path) == strlen(it->name) &&
{ strncmp( it->name, path, array_index_start - path ) == 0 )
--it; {
if ( next_separator - path == strlen(it->name) && next_node = &(*it);
strncmp( it->name, path, next_separator - path ) == 0 ) if ( array_index == 0 )
{ {
next_node = &(*it); item_found = true;
item_found = true; break;
break; }
} --array_index;
} }
} if (array_index)
if ( !item_found ) item_found = false;
{ }
Bml_Node child( path, next_separator - path ); }
node = &(node->addChild( child )); else
} {
else for ( std::vector<Bml_Node>::iterator it = node->children.end(); it != node->children.begin(); )
node = next_node; {
if ( *next_separator ) --it;
{ if ( size_t (next_separator - path) == strlen(it->name) &&
path = next_separator + 1; strncmp( it->name, path, next_separator - path ) == 0 )
} {
else break; next_node = &(*it);
} item_found = true;
return *node; break;
} }
}
Bml_Node const& Bml_Node::walkToNode(const char *path) const }
{ if ( !item_found )
Bml_Node const* next_node; {
Bml_Node const* node = this; Bml_Node child( path, next_separator - path );
while ( *path ) node = &(node->addChild( child ));
{ }
bool item_found = false; else
size_t array_index = ~0; node = next_node;
const char * array_index_start = strchr( path, '[' ); if ( *next_separator )
const char * next_separator = strchr( path, ':' ); {
if ( !next_separator ) next_separator = path + strlen(path); path = next_separator + 1;
if ( array_index_start && array_index_start < next_separator ) }
{ else break;
char * temp; }
array_index = strtoul( array_index_start + 1, &temp, 10 ); return *node;
} }
else
{ Bml_Node const& Bml_Node::walkToNode(const char *path) const
array_index_start = next_separator; {
} Bml_Node const* next_node;
for ( std::vector<Bml_Node>::const_iterator it = node->children.begin(), ite = node->children.end(); it != ite; ++it ) Bml_Node const* node = this;
{ while ( *path )
if ( array_index_start - path == strlen(it->name) && {
strncmp( it->name, path, array_index_start - path ) == 0 ) bool item_found = false;
{ size_t array_index = 0;
next_node = &(*it); const char * array_index_start = strchr( path, '[' );
item_found = true; const char * next_separator = strchr( path, ':' );
if ( array_index == 0 ) break; if ( !next_separator ) next_separator = path + strlen(path);
--array_index; if ( array_index_start && array_index_start < next_separator )
} {
} char * temp;
if ( !item_found ) return emptyNode; array_index = strtoul( array_index_start + 1, &temp, 10 );
node = next_node; }
if ( *next_separator ) else
{ {
path = next_separator + 1; array_index_start = next_separator;
} }
else break; for ( std::vector<Bml_Node>::const_iterator it = node->children.begin(), ite = node->children.end(); it != ite; ++it )
} {
return *node; if ( size_t (array_index_start - path) == strlen(it->name) &&
} strncmp( it->name, path, array_index_start - path ) == 0 )
{
void Bml_Parser::parseDocument( const char * source, size_t max_length ) next_node = &(*it);
{ if ( array_index == 0 )
std::vector<size_t> indents; {
std::string last_name; item_found = true;
std::string current_path; break;
}
document.clear(); --array_index;
}
size_t last_indent = ~0; }
if ( !item_found ) return emptyNode;
Bml_Node node; node = next_node;
if ( *next_separator )
size_t length = 0; {
const char * end = source; path = next_separator + 1;
while ( *end && length < max_length ) { ++end; ++length; } }
else break;
while ( source < end ) }
{ return *node;
const char * line_end = strchr_limited( source, end, '\n' ); }
if ( !line_end ) line_end = end;
void Bml_Parser::parseDocument( const char * source, size_t max_length )
if ( node.getName() ) last_name = node.getName(); {
std::vector<size_t> indents;
node.setLine( source, line_end - source ); std::string last_name;
std::string current_path;
size_t indent = 0;
while ( source < line_end && *source <= 0x20 ) document.clear();
{
source++; size_t last_indent = ~0ULL;
indent++;
} Bml_Node node;
if ( last_indent == ~0 ) last_indent = indent; size_t length = 0;
const char * end = source;
if ( indent > last_indent ) while ( *end && length < max_length ) { ++end; ++length; }
{
indents.push_back( last_indent ); while ( source < end )
last_indent = indent; {
if ( current_path.length() ) current_path += ":"; const char * line_end = strchr_limited( source, end, '\n' );
current_path += last_name; if ( !line_end ) line_end = end;
}
else if ( indent < last_indent ) if ( node.getName() ) last_name = node.getName();
{
while ( last_indent > indent && indents.size() ) node.setLine( source, line_end - source );
{
last_indent = *(indents.end() - 1); size_t indent = 0;
indents.pop_back(); while ( source < line_end && *source <= 0x20 )
size_t colon = current_path.find_last_of( ':' ); {
if ( colon != std::string::npos ) current_path.resize( colon ); source++;
else current_path.resize( 0 ); indent++;
} }
last_indent = indent;
} if ( last_indent == ~0ULL ) last_indent = indent;
document.walkToNode( current_path.c_str() ).addChild( node ); if ( indent > last_indent )
{
source = line_end; indents.push_back( last_indent );
while ( *source && *source == '\n' ) source++; last_indent = indent;
} if ( current_path.length() ) current_path += ":";
} current_path += last_name;
}
const char * Bml_Parser::enumValue(std::string const& path) const else if ( indent < last_indent )
{ {
return document.walkToNode(path.c_str()).getValue(); while ( last_indent > indent && indents.size() )
} {
last_indent = *(indents.end() - 1);
void Bml_Parser::setValue(std::string const& path, const char *value) indents.pop_back();
{ size_t colon = current_path.find_last_of( ':' );
document.walkToNode(path.c_str(), true).setValue(value); if ( colon != std::string::npos ) current_path.resize( colon );
} else current_path.resize( 0 );
}
void Bml_Parser::setValue(std::string const& path, long value) last_indent = indent;
{ }
std::ostringstream str;
str << value; document.walkToNode( current_path.c_str() ).addChild( node );
setValue( path, str.str().c_str() );
} source = line_end;
while ( *source && *source == '\n' ) source++;
void Bml_Parser::serialize(std::string & out) const }
{ }
std::ostringstream strOut;
serialize(strOut, &document, 0); const char * Bml_Parser::enumValue(std::string const& path) const
out = strOut.str(); {
} return document.walkToNode(path.c_str()).getValue();
}
void Bml_Parser::serialize(std::ostringstream & out, Bml_Node const* node, unsigned int indent) const
{ void Bml_Parser::setValue(std::string const& path, const char *value)
for (unsigned i = 1; i < indent; ++i) out << " "; {
document.walkToNode(path.c_str(), true).setValue(value);
if ( indent ) }
{
out << node->getName(); void Bml_Parser::setValue(std::string const& path, long value)
if (node->getValue() && strlen(node->getValue())) out << ":" << node->getValue(); {
out << std::endl; std::ostringstream str;
} str << value;
setValue( path, str.str().c_str() );
for (size_t i = 0, j = node->getChildCount(); i < j; ++i) }
{
Bml_Node const& child = node->getChild(i); void Bml_Parser::serialize(std::string & out) const
if ( (!child.getValue() || !strlen(child.getValue())) && !child.getChildCount() ) {
continue; std::ostringstream strOut;
serialize( out, &child, indent + 1 ); serialize(strOut, &document, 0);
if ( indent == 0 ) out << std::endl; out = strOut.str();
} }
}
void Bml_Parser::serialize(std::ostringstream & out, Bml_Node const* node, unsigned int indent) const
{
for (unsigned i = 1; i < indent; ++i) out << " ";
if ( indent )
{
out << node->getName();
if (node->getValue() && strlen(node->getValue())) out << ":" << node->getValue();
out << std::endl;
}
for (size_t i = 0, j = node->getChildCount(); i < j; ++i)
{
Bml_Node const& child = node->getChild(i);
if ( (!child.getValue() || !strlen(child.getValue())) && !child.getChildCount() )
continue;
serialize( out, &child, indent + 1 );
if ( indent == 0 ) out << std::endl;
}
}

View file

@ -1,61 +1,63 @@
#ifndef BML_PARSER_H // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#define BML_PARSER_H
#ifndef BML_PARSER_H
#include <vector> #define BML_PARSER_H
#include <string>
#include <sstream> #include <vector>
#include <string>
class Bml_Node #include <sstream>
{
char * name; class Bml_Node
char * value; {
char * name;
std::vector<Bml_Node> children; char * value;
static Bml_Node emptyNode; std::vector<Bml_Node> children;
public: static Bml_Node emptyNode;
Bml_Node();
Bml_Node(char const* name, size_t max_length = ~0UL); public:
Bml_Node(Bml_Node const& in); Bml_Node();
Bml_Node(char const* name, size_t max_length = ~0UL);
~Bml_Node(); Bml_Node(Bml_Node const& in);
void clear(); ~Bml_Node();
void setLine(const char * line, size_t max_length = ~0UL); void clear();
Bml_Node& addChild(Bml_Node const& child);
void setLine(const char * line, size_t max_length = ~0UL);
const char * getName() const; Bml_Node& addChild(Bml_Node const& child);
const char * getValue() const;
const char * getName() const;
void setValue(char const* value); const char * getValue() const;
size_t getChildCount() const; void setValue(char const* value);
Bml_Node const& getChild(size_t index) const;
size_t getChildCount() const;
Bml_Node & walkToNode( const char * path, bool use_indexes = false ); Bml_Node const& getChild(size_t index) const;
Bml_Node const& walkToNode( const char * path ) const;
}; Bml_Node & walkToNode( const char * path, bool use_indexes = false );
Bml_Node const& walkToNode( const char * path ) const;
class Bml_Parser };
{
Bml_Node document; class Bml_Parser
{
public: Bml_Node document;
Bml_Parser() { }
public:
void parseDocument(const char * document, size_t max_length = ~0UL); Bml_Parser() { }
const char * enumValue(std::string const& path) const; void parseDocument(const char * document, size_t max_length = ~0UL);
void setValue(std::string const& path, long value); const char * enumValue(std::string const& path) const;
void setValue(std::string const& path, const char * value);
void setValue(std::string const& path, long value);
void serialize(std::string & out) const; void setValue(std::string const& path, const char * value);
private: void serialize(std::string & out) const;
void serialize(std::ostringstream & out, Bml_Node const* node, unsigned int indent) const;
}; private:
void serialize(std::ostringstream & out, Bml_Node const* node, unsigned int indent) const;
#endif // BML_PARSER_H };
#endif // 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

@ -183,24 +183,24 @@ blargg_err_t Gme_File::track_info( track_info_t* out, int track ) const
out->loop_length = -1; out->loop_length = -1;
out->intro_length = -1; out->intro_length = -1;
out->fade_length = -1; out->fade_length = -1;
out->play_length = -1; out->play_length = -1;
out->repeat_count = -1; out->repeat_count = -1;
out->song [0] = 0; out->song [0] = 0;
out->game [0] = 0; out->game [0] = 0;
out->author [0] = 0; out->author [0] = 0;
out->composer [0] = 0; out->composer [0] = 0;
out->engineer [0] = 0; out->engineer [0] = 0;
out->sequencer [0] = 0; out->sequencer [0] = 0;
out->tagger [0] = 0; out->tagger [0] = 0;
out->copyright [0] = 0; out->copyright [0] = 0;
out->date [0] = 0; out->date [0] = 0;
out->comment [0] = 0; out->comment [0] = 0;
out->dumper [0] = 0; out->dumper [0] = 0;
out->system [0] = 0; out->system [0] = 0;
out->disc [0] = 0; out->disc [0] = 0;
out->track [0] = 0; out->track [0] = 0;
out->ost [0] = 0; out->ost [0] = 0;
copy_field_( out->system, type()->system ); copy_field_( out->system, type()->system );
@ -214,13 +214,13 @@ blargg_err_t Gme_File::track_info( track_info_t* out, int track ) const
M3u_Playlist::info_t const& i = playlist.info(); M3u_Playlist::info_t const& i = playlist.info();
copy_field_( out->game , i.title ); copy_field_( out->game , i.title );
copy_field_( out->author, i.artist ); copy_field_( out->author, i.artist );
copy_field_( out->engineer, i.engineer ); copy_field_( out->engineer, i.engineer );
copy_field_( out->composer, i.composer ); copy_field_( out->composer, i.composer );
copy_field_( out->sequencer, i.sequencer ); copy_field_( out->sequencer, i.sequencer );
copy_field_( out->copyright, i.copyright ); copy_field_( out->copyright, i.copyright );
copy_field_( out->dumper, i.ripping ); copy_field_( out->dumper, i.ripping );
copy_field_( out->tagger, i.tagging ); copy_field_( out->tagger, i.tagging );
copy_field_( out->date, i.date ); copy_field_( out->date, i.date );
M3u_Playlist::entry_t const& e = playlist [track]; M3u_Playlist::entry_t const& e = playlist [track];
copy_field_( out->song, e.name ); copy_field_( out->song, e.name );
@ -228,7 +228,7 @@ blargg_err_t Gme_File::track_info( track_info_t* out, int track ) const
if ( e.intro >= 0 ) out->intro_length = e.intro; if ( e.intro >= 0 ) out->intro_length = e.intro;
if ( e.loop >= 0 ) out->loop_length = e.loop; if ( e.loop >= 0 ) out->loop_length = e.loop;
if ( e.fade >= 0 ) out->fade_length = e.fade; if ( e.fade >= 0 ) out->fade_length = e.fade;
if ( e.repeat >= 0 ) out->repeat_count = e.repeat; if ( e.repeat >= 0 ) out->repeat_count = e.repeat;
} }
return 0; return 0;
} }

View file

@ -33,28 +33,28 @@ struct track_info_t
long intro_length; long intro_length;
long loop_length; long loop_length;
long fade_length; long fade_length;
long repeat_count; long repeat_count;
/* Length if available, otherwise intro_length+loop_length*2 if available, /* Length if available, otherwise intro_length+loop_length*2 if available,
* otherwise a default of 150000 (2.5 minutes) */ * otherwise a default of 150000 (2.5 minutes) */
long play_length; long play_length;
/* empty string if not available */ /* empty string if not available */
char system [256]; char system [256];
char game [256]; char game [256];
char song [256]; char song [256];
char author [256]; char author [256];
char composer [256]; char composer [256];
char engineer [256]; char engineer [256];
char sequencer [256]; char sequencer [256];
char tagger [256]; char tagger [256];
char copyright [256]; char copyright [256];
char date [256]; char date [256];
char comment [256]; char comment [256];
char dumper [256]; char dumper [256];
char disc [256]; char disc [256];
char track [256]; char track [256];
char ost [256]; char ost [256];
}; };
enum { gme_max_field = 255 }; enum { gme_max_field = 255 };

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

@ -334,9 +334,9 @@ static void parse_comment( char* in, M3u_Playlist::info_t& info, char *& last_co
{ {
in = skip_white( in + 1 ); in = skip_white( in + 1 );
const char* field = in; const char* field = in;
if ( *field != '@' ) if ( *field != '@' )
while ( *in && *in != ':' ) while ( *in && *in != ':' )
in++; in++;
if ( *in == ':' ) if ( *in == ':' )
{ {
@ -348,9 +348,9 @@ static void parse_comment( char* in, M3u_Playlist::info_t& info, char *& last_co
else if ( !strcmp( "Engineer" , field ) ) info.engineer = text; else if ( !strcmp( "Engineer" , field ) ) info.engineer = text;
else if ( !strcmp( "Ripping" , field ) ) info.ripping = text; else if ( !strcmp( "Ripping" , field ) ) info.ripping = text;
else if ( !strcmp( "Tagging" , field ) ) info.tagging = text; else if ( !strcmp( "Tagging" , field ) ) info.tagging = text;
else if ( !strcmp( "Game" , field ) ) info.title = text; else if ( !strcmp( "Game" , field ) ) info.title = text;
else if ( !strcmp( "Artist" , field ) ) info.artist = text; else if ( !strcmp( "Artist" , field ) ) info.artist = text;
else if ( !strcmp( "Copyright", field ) ) info.copyright = text; else if ( !strcmp( "Copyright", field ) ) info.copyright = text;
else else
text = 0; text = 0;
if ( text ) if ( text )
@ -358,44 +358,43 @@ static void parse_comment( char* in, M3u_Playlist::info_t& info, char *& last_co
*in = ':'; *in = ':';
} }
} }
else if ( *field == '@' ) else if ( *field == '@' )
{ {
++field; ++field;
in = (char*)field; in = (char*)field;
while ( *in && *in > ' ' ) while ( *in && *in > ' ' )
in++; in++;
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; else if ( !strcmp( "DATE" , field ) ) info.date = text;
else if ( !strcmp( "DATE" , field ) ) info.date = text; else if ( !strcmp( "COMPOSER" , field ) ) info.composer = text;
else if ( !strcmp( "COMPOSER" , field ) ) info.composer = text; else if ( !strcmp( "SEQUENCER", field ) ) info.sequencer = text;
else if ( !strcmp( "SEQUENCER", field ) ) info.sequencer = text; else if ( !strcmp( "ENGINEER" , field ) ) info.engineer = text;
else if ( !strcmp( "ENGINEER" , field ) ) info.engineer = text; else if ( !strcmp( "RIPPER" , field ) ) info.ripping = text;
else if ( !strcmp( "RIPPER" , field ) ) info.ripping = text; else if ( !strcmp( "TAGGER" , field ) ) info.tagging = text;
else if ( !strcmp( "TAGGER" , field ) ) info.tagging = text; else
else text = 0;
text = 0; if ( text )
if ( text ) {
{ last_comment_value = (char*)text;
last_comment_value = (char*)text; return;
return; }
} }
} }
} else if ( last_comment_value )
else if ( last_comment_value ) {
{ size_t len = strlen( last_comment_value );
size_t len = strlen( last_comment_value ); last_comment_value[ len ] = ',';
last_comment_value[ len ] = ','; last_comment_value[ len + 1 ] = ' ';
last_comment_value[ len + 1 ] = ' '; size_t field_len = strlen( field );
size_t field_len = strlen( field ); memmove( last_comment_value + len + 2, field, field_len );
memmove( last_comment_value + len + 2, field, field_len ); last_comment_value[ len + 2 + field_len ] = 0;
last_comment_value[ len + 2 + field_len ] = 0; return;
return; }
}
if ( first ) if ( first )
info.title = field; info.title = field;
@ -404,14 +403,14 @@ static void parse_comment( char* in, M3u_Playlist::info_t& info, char *& last_co
blargg_err_t M3u_Playlist::parse_() blargg_err_t M3u_Playlist::parse_()
{ {
info_.title = ""; info_.title = "";
info_.artist = ""; info_.artist = "";
info_.date = ""; info_.date = "";
info_.composer = ""; info_.composer = "";
info_.sequencer = ""; info_.sequencer = "";
info_.engineer = ""; info_.engineer = "";
info_.ripping = ""; info_.ripping = "";
info_.tagging = ""; info_.tagging = "";
info_.copyright = ""; info_.copyright = "";
int const CR = 13; int const CR = 13;
int const LF = 10; int const LF = 10;
@ -423,7 +422,7 @@ blargg_err_t M3u_Playlist::parse_()
int line = 0; int line = 0;
int count = 0; int count = 0;
char* in = data.begin(); char* in = data.begin();
char* last_comment_value = 0; char* last_comment_value = 0;
while ( in < data.end() ) while ( in < data.end() )
{ {
// find end of line and terminate it // find end of line and terminate it
@ -456,7 +455,7 @@ blargg_err_t M3u_Playlist::parse_()
first_error_ = line; first_error_ = line;
first_comment = false; first_comment = false;
} }
else last_comment_value = 0; else last_comment_value = 0;
} }
if ( count <= 0 ) if ( count <= 0 )
return "Not an m3u playlist"; return "Not an m3u playlist";

View file

@ -21,14 +21,14 @@ public:
struct info_t struct info_t
{ {
const char* title; const char* title;
const char* artist; const char* artist;
const char* date; const char* date;
const char* composer; const char* composer;
const char* sequencer; const char* sequencer;
const char* engineer; const char* engineer;
const char* ripping; const char* ripping;
const char* tagging; const char* tagging;
const char* copyright; const char* copyright;
}; };
info_t const& info() const { return info_; } info_t const& info() const { return info_; }

View file

@ -1,282 +1,282 @@
// 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"
/* Copyright (C) 2006 Shay Green. This module is free software; you /* Copyright (C) 2006 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
General Public License as published by the Free Software Foundation; either 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 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 module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 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 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 "blargg_source.h"
#include <string.h> #include <string.h>
int const fract_range = 65536; int const fract_range = 65536;
void Nes_Fds_Apu::reset() void Nes_Fds_Apu::reset()
{ {
memset( regs_, 0, sizeof regs_ ); memset( regs_, 0, sizeof regs_ );
memset( mod_wave, 0, sizeof mod_wave ); memset( mod_wave, 0, sizeof mod_wave );
last_time = 0; last_time = 0;
env_delay = 0; env_delay = 0;
sweep_delay = 0; sweep_delay = 0;
wave_pos = 0; wave_pos = 0;
last_amp = 0; last_amp = 0;
wave_fract = fract_range; wave_fract = fract_range;
mod_fract = fract_range; mod_fract = fract_range;
mod_pos = 0; mod_pos = 0;
mod_write_pos = 0; mod_write_pos = 0;
static byte const initial_regs [0x0B] = { static byte const initial_regs [0x0B] = {
0x80, // disable envelope 0x80, // disable envelope
0, 0, 0xC0, // disable wave and lfo 0, 0, 0xC0, // disable wave and lfo
0x80, // disable sweep 0x80, // disable sweep
0, 0, 0x80, // disable modulation 0, 0, 0x80, // disable modulation
0, 0, 0xFF // LFO period // TODO: use 0xE8 as FDS ROM does? 0, 0, 0xFF // LFO period // TODO: use 0xE8 as FDS ROM does?
}; };
for ( int i = 0; i < (int) sizeof initial_regs; i++ ) for ( int i = 0; i < (int) sizeof initial_regs; i++ )
{ {
// two writes to set both gain and period for envelope registers // two writes to set both gain and period for envelope registers
write_( io_addr + wave_size + i, 0 ); write_( io_addr + wave_size + i, 0 );
write_( io_addr + wave_size + i, initial_regs [i] ); write_( io_addr + wave_size + i, initial_regs [i] );
} }
} }
void Nes_Fds_Apu::write_( unsigned addr, int data ) void Nes_Fds_Apu::write_( unsigned addr, int data )
{ {
unsigned reg = addr - io_addr; unsigned reg = addr - io_addr;
if ( reg < io_size ) if ( reg < io_size )
{ {
if ( reg < wave_size ) if ( reg < wave_size )
{ {
if ( regs (0x4089) & 0x80 ) if ( regs (0x4089) & 0x80 )
regs_ [reg] = data & wave_sample_max; regs_ [reg] = data & wave_sample_max;
} }
else else
{ {
regs_ [reg] = data; regs_ [reg] = data;
switch ( addr ) switch ( addr )
{ {
case 0x4080: case 0x4080:
if ( data & 0x80 ) if ( data & 0x80 )
env_gain = data & 0x3F; env_gain = data & 0x3F;
else else
env_speed = (data & 0x3F) + 1; env_speed = (data & 0x3F) + 1;
break; break;
case 0x4084: case 0x4084:
if ( data & 0x80 ) if ( data & 0x80 )
sweep_gain = data & 0x3F; sweep_gain = data & 0x3F;
else else
sweep_speed = (data & 0x3F) + 1; sweep_speed = (data & 0x3F) + 1;
break; break;
case 0x4085: case 0x4085:
mod_pos = mod_write_pos; mod_pos = mod_write_pos;
regs (0x4085) = data & 0x7F; regs (0x4085) = data & 0x7F;
break; break;
case 0x4088: case 0x4088:
if ( regs (0x4087) & 0x80 ) if ( regs (0x4087) & 0x80 )
{ {
int pos = mod_write_pos; int pos = mod_write_pos;
data &= 0x07; data &= 0x07;
mod_wave [pos ] = data; mod_wave [pos ] = data;
mod_wave [pos + 1] = data; mod_wave [pos + 1] = data;
mod_write_pos = (pos + 2) & (wave_size - 1); mod_write_pos = (pos + 2) & (wave_size - 1);
mod_pos = (mod_pos + 2) & (wave_size - 1); mod_pos = (mod_pos + 2) & (wave_size - 1);
} }
break; break;
} }
} }
} }
} }
void Nes_Fds_Apu::set_tempo( double t ) void Nes_Fds_Apu::set_tempo( double t )
{ {
lfo_tempo = lfo_base_tempo; lfo_tempo = lfo_base_tempo;
if ( t != 1.0 ) if ( t != 1.0 )
{ {
lfo_tempo = int ((double) lfo_base_tempo / t + 0.5); lfo_tempo = int ((double) lfo_base_tempo / t + 0.5);
if ( lfo_tempo <= 0 ) if ( lfo_tempo <= 0 )
lfo_tempo = 1; lfo_tempo = 1;
} }
} }
void Nes_Fds_Apu::run_until( blip_time_t final_end_time ) void Nes_Fds_Apu::run_until( blip_time_t final_end_time )
{ {
int const wave_freq = (regs (0x4083) & 0x0F) * 0x100 + regs (0x4082); int const wave_freq = (regs (0x4083) & 0x0F) * 0x100 + regs (0x4082);
Blip_Buffer* const output_ = this->output_; Blip_Buffer* const output_ = this->output_;
if ( wave_freq && output_ && !((regs (0x4089) | regs (0x4083)) & 0x80) ) if ( wave_freq && output_ && !((regs (0x4089) | regs (0x4083)) & 0x80) )
{ {
output_->set_modified(); output_->set_modified();
// master_volume // master_volume
#define MVOL_ENTRY( percent ) (master_vol_max * percent + 50) / 100 #define MVOL_ENTRY( percent ) (master_vol_max * percent + 50) / 100
static unsigned char const master_volumes [4] = { static unsigned char const master_volumes [4] = {
MVOL_ENTRY( 100 ), MVOL_ENTRY( 67 ), MVOL_ENTRY( 50 ), MVOL_ENTRY( 40 ) MVOL_ENTRY( 100 ), MVOL_ENTRY( 67 ), MVOL_ENTRY( 50 ), MVOL_ENTRY( 40 )
}; };
int const master_volume = master_volumes [regs (0x4089) & 0x03]; int const master_volume = master_volumes [regs (0x4089) & 0x03];
// lfo_period // lfo_period
blip_time_t lfo_period = regs (0x408A) * lfo_tempo; blip_time_t lfo_period = regs (0x408A) * lfo_tempo;
if ( regs (0x4083) & 0x40 ) if ( regs (0x4083) & 0x40 )
lfo_period = 0; lfo_period = 0;
// sweep setup // sweep setup
blip_time_t sweep_time = last_time + sweep_delay; blip_time_t sweep_time = last_time + sweep_delay;
blip_time_t const sweep_period = lfo_period * sweep_speed; blip_time_t const sweep_period = lfo_period * sweep_speed;
if ( !sweep_period || regs (0x4084) & 0x80 ) if ( !sweep_period || regs (0x4084) & 0x80 )
sweep_time = final_end_time; sweep_time = final_end_time;
// envelope setup // envelope setup
blip_time_t env_time = last_time + env_delay; blip_time_t env_time = last_time + env_delay;
blip_time_t const env_period = lfo_period * env_speed; blip_time_t const env_period = lfo_period * env_speed;
if ( !env_period || regs (0x4080) & 0x80 ) if ( !env_period || regs (0x4080) & 0x80 )
env_time = final_end_time; env_time = final_end_time;
// modulation // modulation
int mod_freq = 0; int mod_freq = 0;
if ( !(regs (0x4087) & 0x80) ) if ( !(regs (0x4087) & 0x80) )
mod_freq = (regs (0x4087) & 0x0F) * 0x100 + regs (0x4086); mod_freq = (regs (0x4087) & 0x0F) * 0x100 + regs (0x4086);
blip_time_t end_time = last_time; blip_time_t end_time = last_time;
do do
{ {
// sweep // sweep
if ( sweep_time <= end_time ) if ( sweep_time <= end_time )
{ {
sweep_time += sweep_period; sweep_time += sweep_period;
int mode = regs (0x4084) >> 5 & 2; int mode = regs (0x4084) >> 5 & 2;
int new_sweep_gain = sweep_gain + mode - 1; int new_sweep_gain = sweep_gain + mode - 1;
if ( (unsigned) new_sweep_gain <= (unsigned) 0x80 >> mode ) if ( (unsigned) new_sweep_gain <= (unsigned) 0x80 >> mode )
sweep_gain = new_sweep_gain; sweep_gain = new_sweep_gain;
else else
regs (0x4084) |= 0x80; // optimization only regs (0x4084) |= 0x80; // optimization only
} }
// envelope // envelope
if ( env_time <= end_time ) if ( env_time <= end_time )
{ {
env_time += env_period; env_time += env_period;
int mode = regs (0x4080) >> 5 & 2; int mode = regs (0x4080) >> 5 & 2;
int new_env_gain = env_gain + mode - 1; int new_env_gain = env_gain + mode - 1;
if ( (unsigned) new_env_gain <= (unsigned) 0x80 >> mode ) if ( (unsigned) new_env_gain <= (unsigned) 0x80 >> mode )
env_gain = new_env_gain; env_gain = new_env_gain;
else else
regs (0x4080) |= 0x80; // optimization only regs (0x4080) |= 0x80; // optimization only
} }
// new end_time // new end_time
blip_time_t const start_time = end_time; blip_time_t const start_time = end_time;
end_time = final_end_time; end_time = final_end_time;
if ( end_time > env_time ) end_time = env_time; if ( end_time > env_time ) end_time = env_time;
if ( end_time > sweep_time ) end_time = sweep_time; if ( end_time > sweep_time ) end_time = sweep_time;
// frequency modulation // frequency modulation
int freq = wave_freq; int freq = wave_freq;
if ( mod_freq ) if ( mod_freq )
{ {
// time of next modulation clock // time of next modulation clock
blip_time_t mod_time = start_time + (mod_fract + mod_freq - 1) / mod_freq; blip_time_t mod_time = start_time + (mod_fract + mod_freq - 1) / mod_freq;
if ( end_time > mod_time ) if ( end_time > mod_time )
end_time = mod_time; end_time = mod_time;
// run modulator up to next clock and save old sweep_bias // run modulator up to next clock and save old sweep_bias
int sweep_bias = regs (0x4085); int sweep_bias = regs (0x4085);
mod_fract -= (end_time - start_time) * mod_freq; mod_fract -= (end_time - start_time) * mod_freq;
if ( mod_fract <= 0 ) if ( mod_fract <= 0 )
{ {
mod_fract += fract_range; mod_fract += fract_range;
check( (unsigned) mod_fract <= fract_range ); check( (unsigned) mod_fract <= fract_range );
static short const mod_table [8] = { 0, +1, +2, +4, 0, -4, -2, -1 }; static short const mod_table [8] = { 0, +1, +2, +4, 0, -4, -2, -1 };
int mod = mod_wave [mod_pos]; int mod = mod_wave [mod_pos];
mod_pos = (mod_pos + 1) & (wave_size - 1); mod_pos = (mod_pos + 1) & (wave_size - 1);
int new_sweep_bias = (sweep_bias + mod_table [mod]) & 0x7F; int new_sweep_bias = (sweep_bias + mod_table [mod]) & 0x7F;
if ( mod == 4 ) if ( mod == 4 )
new_sweep_bias = 0; new_sweep_bias = 0;
regs (0x4085) = new_sweep_bias; regs (0x4085) = new_sweep_bias;
} }
// apply frequency modulation // apply frequency modulation
sweep_bias = (sweep_bias ^ 0x40) - 0x40; sweep_bias = (sweep_bias ^ 0x40) - 0x40;
int factor = sweep_bias * sweep_gain; int factor = sweep_bias * sweep_gain;
int extra = factor & 0x0F; int extra = factor & 0x0F;
factor >>= 4; factor >>= 4;
if ( extra ) if ( extra )
{ {
factor--; factor--;
if ( sweep_bias >= 0 ) if ( sweep_bias >= 0 )
factor += 3; factor += 3;
} }
if ( factor > 193 ) factor -= 258; if ( factor > 193 ) factor -= 258;
if ( factor < -64 ) factor += 256; if ( factor < -64 ) factor += 256;
freq += (freq * factor) >> 6; freq += (freq * factor) >> 6;
if ( freq <= 0 ) if ( freq <= 0 )
continue; continue;
} }
// wave // wave
int wave_fract = this->wave_fract; int wave_fract = this->wave_fract;
blip_time_t delay = (wave_fract + freq - 1) / freq; blip_time_t delay = (wave_fract + freq - 1) / freq;
blip_time_t time = start_time + delay; blip_time_t time = start_time + delay;
if ( time <= end_time ) if ( time <= end_time )
{ {
// at least one wave clock within start_time...end_time // at least one wave clock within start_time...end_time
blip_time_t const min_delay = fract_range / freq; blip_time_t const min_delay = fract_range / freq;
int wave_pos = this->wave_pos; int wave_pos = this->wave_pos;
int volume = env_gain; int volume = env_gain;
if ( volume > vol_max ) if ( volume > vol_max )
volume = vol_max; volume = vol_max;
volume *= master_volume; volume *= master_volume;
int const min_fract = min_delay * freq; int const min_fract = min_delay * freq;
do do
{ {
// clock wave // clock wave
int amp = regs_ [wave_pos] * volume; int amp = regs_ [wave_pos] * volume;
wave_pos = (wave_pos + 1) & (wave_size - 1); wave_pos = (wave_pos + 1) & (wave_size - 1);
int delta = amp - last_amp; int delta = amp - last_amp;
if ( delta ) if ( delta )
{ {
last_amp = amp; last_amp = amp;
synth.offset_inline( time, delta, output_ ); synth.offset_inline( time, delta, output_ );
} }
wave_fract += fract_range - delay * freq; wave_fract += fract_range - delay * freq;
check( unsigned (fract_range - wave_fract) < freq ); check( unsigned (fract_range - wave_fract) < freq );
// delay until next clock // delay until next clock
delay = min_delay; delay = min_delay;
if ( wave_fract > min_fract ) if ( wave_fract > min_fract )
delay++; delay++;
check( delay && delay == (wave_fract + freq - 1) / freq ); check( delay && delay == (wave_fract + freq - 1) / freq );
time += delay; time += delay;
} }
while ( time <= end_time ); // TODO: using < breaks things, but <= is wrong while ( time <= end_time ); // TODO: using < breaks things, but <= is wrong
this->wave_pos = wave_pos; this->wave_pos = wave_pos;
} }
this->wave_fract = wave_fract - (end_time - (time - delay)) * freq; this->wave_fract = wave_fract - (end_time - (time - delay)) * freq;
check( this->wave_fract > 0 ); check( this->wave_fract > 0 );
} }
while ( end_time < final_end_time ); while ( end_time < final_end_time );
env_delay = env_time - final_end_time; check( env_delay >= 0 ); env_delay = env_time - final_end_time; check( env_delay >= 0 );
sweep_delay = sweep_time - final_end_time; check( sweep_delay >= 0 ); sweep_delay = sweep_time - final_end_time; check( sweep_delay >= 0 );
} }
last_time = final_end_time; last_time = final_end_time;
} }

View file

@ -1,128 +1,129 @@
// NES FDS sound chip emulator // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
// $package // NES FDS sound chip emulator
#ifndef NES_FDS_APU_H
#define NES_FDS_APU_H #ifndef NES_FDS_APU_H
#define NES_FDS_APU_H
#include "blargg_common.h"
#include "Blip_Buffer.h" #include "blargg_common.h"
#include "Blip_Buffer.h"
class Nes_Fds_Apu {
public: class Nes_Fds_Apu {
// setup public:
void set_tempo( double ); // setup
enum { osc_count = 1 }; void set_tempo( double );
void volume( double ); enum { osc_count = 1 };
void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); } void volume( double );
void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
// emulation
void reset(); // emulation
enum { io_addr = 0x4040 }; void reset();
enum { io_size = 0x53 }; enum { io_addr = 0x4040 };
void write( blip_time_t time, unsigned addr, int data ); enum { io_size = 0x53 };
int read( blip_time_t time, unsigned addr ); void write( blip_time_t time, unsigned addr, int data );
void end_frame( blip_time_t ); int read( blip_time_t time, unsigned addr );
void end_frame( blip_time_t );
public:
Nes_Fds_Apu(); public:
void write_( unsigned addr, int data ); Nes_Fds_Apu();
BLARGG_DISABLE_NOTHROW void write_( unsigned addr, int data );
BLARGG_DISABLE_NOTHROW
void osc_output( int, Blip_Buffer* );
private: void osc_output( int, Blip_Buffer* );
enum { wave_size = 0x40 }; private:
enum { master_vol_max = 10 }; enum { wave_size = 0x40 };
enum { vol_max = 0x20 }; enum { master_vol_max = 10 };
enum { wave_sample_max = 0x3F }; enum { vol_max = 0x20 };
enum { wave_sample_max = 0x3F };
unsigned char regs_ [io_size];// last written value to registers
unsigned char regs_ [io_size];// last written value to registers
enum { lfo_base_tempo = 8 };
int lfo_tempo; // normally 8; adjusted by set_tempo() enum { lfo_base_tempo = 8 };
int lfo_tempo; // normally 8; adjusted by set_tempo()
int env_delay;
int env_speed; int env_delay;
int env_gain; int env_speed;
int env_gain;
int sweep_delay;
int sweep_speed; int sweep_delay;
int sweep_gain; int sweep_speed;
int sweep_gain;
int wave_pos;
int last_amp; int wave_pos;
blip_time_t wave_fract; int last_amp;
blip_time_t wave_fract;
int mod_fract;
int mod_pos; int mod_fract;
int mod_write_pos; int mod_pos;
unsigned char mod_wave [wave_size]; int mod_write_pos;
unsigned char mod_wave [wave_size];
// synthesis
blip_time_t last_time; // synthesis
Blip_Buffer* output_; blip_time_t last_time;
Blip_Synth<blip_med_quality,1> synth; Blip_Buffer* output_;
Blip_Synth<blip_med_quality,1> synth;
// allow access to registers by absolute address (i.e. 0x4080)
unsigned char& regs( unsigned addr ) { return regs_ [addr - io_addr]; } // allow access to registers by absolute address (i.e. 0x4080)
unsigned char& regs( unsigned addr ) { return regs_ [addr - io_addr]; }
void run_until( blip_time_t );
}; void run_until( blip_time_t );
};
inline void Nes_Fds_Apu::volume( double v )
{ inline void Nes_Fds_Apu::volume( double v )
synth.volume( 0.14 / master_vol_max / vol_max / wave_sample_max * v ); {
} synth.volume( 0.14 / master_vol_max / vol_max / wave_sample_max * v );
}
inline void Nes_Fds_Apu::osc_output( int i, Blip_Buffer* buf )
{ inline void Nes_Fds_Apu::osc_output( int i, Blip_Buffer* buf )
assert( (unsigned) i < osc_count ); {
output_ = buf; assert( (unsigned) i < osc_count );
} output_ = buf;
}
inline void Nes_Fds_Apu::end_frame( blip_time_t end_time )
{ inline void Nes_Fds_Apu::end_frame( blip_time_t end_time )
if ( end_time > last_time ) {
run_until( end_time ); if ( end_time > last_time )
last_time -= end_time; run_until( end_time );
assert( last_time >= 0 ); last_time -= end_time;
} assert( last_time >= 0 );
}
inline void Nes_Fds_Apu::write( blip_time_t time, unsigned addr, int data )
{ inline void Nes_Fds_Apu::write( blip_time_t time, unsigned addr, int data )
run_until( time ); {
write_( addr, data ); run_until( time );
} write_( addr, data );
}
inline int Nes_Fds_Apu::read( blip_time_t time, unsigned addr )
{ inline int Nes_Fds_Apu::read( blip_time_t time, unsigned addr )
run_until( time ); {
run_until( time );
int result = 0xFF;
switch ( addr ) int result = 0xFF;
{ switch ( addr )
case 0x4090: {
result = env_gain; case 0x4090:
break; result = env_gain;
break;
case 0x4092:
result = sweep_gain; case 0x4092:
break; result = sweep_gain;
break;
default:
unsigned i = addr - io_addr; default:
if ( i < wave_size ) unsigned i = addr - io_addr;
result = regs_ [i]; if ( i < wave_size )
} result = regs_ [i];
}
return result | 0x40;
} return result | 0x40;
}
inline Nes_Fds_Apu::Nes_Fds_Apu()
{ inline Nes_Fds_Apu::Nes_Fds_Apu()
lfo_tempo = lfo_base_tempo; {
osc_output( 0, NULL ); lfo_tempo = lfo_base_tempo;
volume( 1.0 ); osc_output( 0, NULL );
reset(); volume( 1.0 );
} reset();
}
#endif
#endif

View file

@ -1,60 +1,61 @@
// NES MMC5 sound chip emulator // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
// Nes_Snd_Emu $vers // NES MMC5 sound chip emulator
#ifndef NES_MMC5_APU_H
#define NES_MMC5_APU_H #ifndef NES_MMC5_APU_H
#define NES_MMC5_APU_H
#include "blargg_common.h"
#include "Nes_Apu.h" #include "blargg_common.h"
#include "Nes_Apu.h"
class Nes_Mmc5_Apu : public Nes_Apu {
public: class Nes_Mmc5_Apu : public Nes_Apu {
enum { regs_addr = 0x5000 }; public:
enum { regs_size = 0x16 }; enum { regs_addr = 0x5000 };
enum { regs_size = 0x16 };
enum { osc_count = 3 };
void write_register( blip_time_t, unsigned addr, int data ); enum { osc_count = 3 };
void osc_output( int i, Blip_Buffer* ); void write_register( blip_time_t, unsigned addr, int data );
void osc_output( int i, Blip_Buffer* );
enum { exram_size = 1024 };
unsigned char exram [exram_size]; enum { exram_size = 1024 };
}; unsigned char exram [exram_size];
};
inline void Nes_Mmc5_Apu::osc_output( int i, Blip_Buffer* b )
{ inline void Nes_Mmc5_Apu::osc_output( int i, Blip_Buffer* b )
// in: square 1, square 2, PCM {
// out: square 1, square 2, skipped, skipped, PCM // in: square 1, square 2, PCM
assert( (unsigned) i < osc_count ); // out: square 1, square 2, skipped, skipped, PCM
if ( i > 1 ) assert( (unsigned) i < osc_count );
i += 2; if ( i > 1 )
Nes_Apu::osc_output( i, b ); i += 2;
} Nes_Apu::osc_output( i, b );
}
inline void Nes_Mmc5_Apu::write_register( blip_time_t time, unsigned addr, int data )
{ inline void Nes_Mmc5_Apu::write_register( blip_time_t time, unsigned addr, int data )
switch ( addr ) {
{ switch ( addr )
case 0x5015: // channel enables {
data &= 0x03; // enable the square waves only case 0x5015: // channel enables
// fall through data &= 0x03; // enable the square waves only
case 0x5000: // Square 1 // fall through
case 0x5002: case 0x5000: // Square 1
case 0x5003: case 0x5002:
case 0x5004: // Square 2 case 0x5003:
case 0x5006: case 0x5004: // Square 2
case 0x5007: case 0x5006:
case 0x5011: // DAC case 0x5007:
Nes_Apu::write_register( time, addr - 0x1000, data ); case 0x5011: // DAC
break; Nes_Apu::write_register( time, addr - 0x1000, data );
break;
case 0x5010: // some things write to this for some reason
break; case 0x5010: // some things write to this for some reason
break;
#ifdef BLARGG_DEBUG_H
default: #ifdef BLARGG_DEBUG_H
dprintf( "Unmapped MMC5 APU write: $%04X <- $%02X\n", addr, data ); default:
#endif debug_printf( "Unmapped MMC5 APU write: $%04X <- $%02X\n", addr, data );
} #endif
} }
}
#endif
#endif

View file

@ -1,218 +1,218 @@
#include "Nes_Vrc7_Apu.h" #include "Nes_Vrc7_Apu.h"
extern "C" { extern "C" {
#include "../ext/emu2413.h" #include "../ext/emu2413.h"
} }
#include <string.h> #include <string.h>
#include "blargg_source.h" #include "blargg_source.h"
static unsigned char vrc7_inst[(16 + 3) * 8] = static unsigned char vrc7_inst[(16 + 3) * 8] =
{ {
#include "../ext/vrc7tone.h" #include "../ext/vrc7tone.h"
}; };
int const period = 36; // NES CPU clocks per FM clock int const period = 36; // NES CPU clocks per FM clock
Nes_Vrc7_Apu::Nes_Vrc7_Apu() Nes_Vrc7_Apu::Nes_Vrc7_Apu()
{ {
opll = 0; opll = 0;
} }
blargg_err_t Nes_Vrc7_Apu::init() blargg_err_t Nes_Vrc7_Apu::init()
{ {
CHECK_ALLOC( opll = OPLL_new( 3579545, 3579545 / 72 ) ); CHECK_ALLOC( opll = OPLL_new( 3579545, 3579545 / 72 ) );
OPLL_SetChipMode((OPLL *) opll, 1); OPLL_SetChipMode((OPLL *) opll, 1);
OPLL_setPatch((OPLL *) opll, vrc7_inst); OPLL_setPatch((OPLL *) opll, vrc7_inst);
set_output( 0 ); set_output( 0 );
volume( 1.0 ); volume( 1.0 );
reset(); reset();
return 0; return 0;
} }
Nes_Vrc7_Apu::~Nes_Vrc7_Apu() Nes_Vrc7_Apu::~Nes_Vrc7_Apu()
{ {
if ( opll ) if ( opll )
OPLL_delete( (OPLL *) opll ); OPLL_delete( (OPLL *) opll );
} }
void Nes_Vrc7_Apu::set_output( Blip_Buffer* buf ) void Nes_Vrc7_Apu::set_output( Blip_Buffer* buf )
{ {
for ( int i = 0; i < osc_count; ++i ) for ( int i = 0; i < osc_count; ++i )
oscs [i].output = buf; oscs [i].output = buf;
output_changed(); output_changed();
} }
void Nes_Vrc7_Apu::output_changed() void Nes_Vrc7_Apu::output_changed()
{ {
mono.output = oscs [0].output; mono.output = oscs [0].output;
for ( int i = osc_count; --i; ) for ( int i = osc_count; --i; )
{ {
if ( mono.output != oscs [i].output ) if ( mono.output != oscs [i].output )
{ {
mono.output = 0; mono.output = 0;
break; break;
} }
} }
if ( mono.output ) if ( mono.output )
{ {
for ( int i = osc_count; --i; ) for ( int i = osc_count; --i; )
{ {
mono.last_amp += oscs [i].last_amp; mono.last_amp += oscs [i].last_amp;
oscs [i].last_amp = 0; oscs [i].last_amp = 0;
} }
} }
} }
void Nes_Vrc7_Apu::reset() void Nes_Vrc7_Apu::reset()
{ {
addr = 0; addr = 0;
next_time = 0; next_time = 0;
mono.last_amp = 0; mono.last_amp = 0;
for ( int i = osc_count; --i >= 0; ) for ( int i = osc_count; --i >= 0; )
{ {
Vrc7_Osc& osc = oscs [i]; Vrc7_Osc& osc = oscs [i];
osc.last_amp = 0; osc.last_amp = 0;
for ( int j = 0; j < 3; ++j ) for ( int j = 0; j < 3; ++j )
osc.regs [j] = 0; osc.regs [j] = 0;
} }
OPLL_reset( (OPLL *) opll ); OPLL_reset( (OPLL *) opll );
} }
void Nes_Vrc7_Apu::write_reg( int data ) void Nes_Vrc7_Apu::write_reg( int data )
{ {
addr = data; addr = data;
} }
void Nes_Vrc7_Apu::write_data( blip_time_t time, int data ) void Nes_Vrc7_Apu::write_data( blip_time_t time, int data )
{ {
int type = (addr >> 4) - 1; int type = (addr >> 4) - 1;
int chan = addr & 15; int chan = addr & 15;
if ( (unsigned) type < 3 && chan < osc_count ) if ( (unsigned) type < 3 && chan < osc_count )
oscs [chan].regs [type] = data; oscs [chan].regs [type] = data;
if ( addr < 0x08 ) if ( addr < 0x08 )
inst [addr] = data; inst [addr] = data;
if ( time > next_time ) if ( time > next_time )
run_until( time ); run_until( time );
OPLL_writeIO( (OPLL *) opll, 0, addr ); OPLL_writeIO( (OPLL *) opll, 0, addr );
OPLL_writeIO( (OPLL *) opll, 1, data ); OPLL_writeIO( (OPLL *) opll, 1, data );
} }
void Nes_Vrc7_Apu::end_frame( blip_time_t time ) void Nes_Vrc7_Apu::end_frame( blip_time_t time )
{ {
if ( time > next_time ) if ( time > next_time )
run_until( time ); run_until( time );
next_time -= time; next_time -= time;
assert( next_time >= 0 ); assert( next_time >= 0 );
for ( int i = osc_count; --i >= 0; ) for ( int i = osc_count; --i >= 0; )
{ {
Blip_Buffer* output = oscs [i].output; Blip_Buffer* output = oscs [i].output;
if ( output ) if ( output )
output->set_modified(); output->set_modified();
} }
} }
void Nes_Vrc7_Apu::save_snapshot( vrc7_snapshot_t* out ) const void Nes_Vrc7_Apu::save_snapshot( vrc7_snapshot_t* out ) const
{ {
out->latch = addr; out->latch = addr;
out->delay = next_time; out->delay = next_time;
for ( int i = osc_count; --i >= 0; ) for ( int i = osc_count; --i >= 0; )
{ {
for ( int j = 0; j < 3; ++j ) for ( int j = 0; j < 3; ++j )
out->regs [i] [j] = oscs [i].regs [j]; out->regs [i] [j] = oscs [i].regs [j];
} }
memcpy( out->inst, inst, 8 ); memcpy( out->inst, inst, 8 );
} }
void Nes_Vrc7_Apu::load_snapshot( vrc7_snapshot_t const& in ) void Nes_Vrc7_Apu::load_snapshot( vrc7_snapshot_t const& in )
{ {
assert( offsetof (vrc7_snapshot_t,delay) == 28 - 1 ); assert( offsetof (vrc7_snapshot_t,delay) == 28 - 1 );
reset(); reset();
next_time = in.delay; next_time = in.delay;
write_reg( in.latch ); write_reg( in.latch );
int i; int i;
for ( i = 0; i < osc_count; ++i ) for ( i = 0; i < osc_count; ++i )
{ {
for ( int j = 0; j < 3; ++j ) for ( int j = 0; j < 3; ++j )
oscs [i].regs [j] = in.regs [i] [j]; oscs [i].regs [j] = in.regs [i] [j];
} }
memcpy( inst, in.inst, 8 ); memcpy( inst, in.inst, 8 );
for ( i = 0; i < 8; ++i ) for ( i = 0; i < 8; ++i )
{ {
OPLL_writeIO( (OPLL *) opll, 0, i ); OPLL_writeIO( (OPLL *) opll, 0, i );
OPLL_writeIO( (OPLL *) opll, 1, in.inst [i] ); OPLL_writeIO( (OPLL *) opll, 1, in.inst [i] );
} }
for ( i = 0; i < 3; ++i ) for ( i = 0; i < 3; ++i )
{ {
for ( int j = 0; j < 6; ++j ) for ( int j = 0; j < 6; ++j )
{ {
OPLL_writeIO( (OPLL *) opll, 0, 0x10 + i * 0x10 + j ); OPLL_writeIO( (OPLL *) opll, 0, 0x10 + i * 0x10 + j );
OPLL_writeIO( (OPLL *) opll, 1, oscs [j].regs [i] ); OPLL_writeIO( (OPLL *) opll, 1, oscs [j].regs [i] );
} }
} }
} }
void Nes_Vrc7_Apu::run_until( blip_time_t end_time ) void Nes_Vrc7_Apu::run_until( blip_time_t end_time )
{ {
require( end_time > next_time ); require( end_time > next_time );
blip_time_t time = next_time; blip_time_t time = next_time;
void* opll = this->opll; // cache void* opll = this->opll; // cache
Blip_Buffer* const mono_output = mono.output; Blip_Buffer* const mono_output = mono.output;
e_int32 buffer [2]; e_int32 buffer [2];
e_int32* buffers[2] = {&buffer[0], &buffer[1]}; e_int32* buffers[2] = {&buffer[0], &buffer[1]};
if ( mono_output ) if ( mono_output )
{ {
// optimal case // optimal case
do do
{ {
OPLL_calc_stereo( (OPLL *) opll, buffers, 1, -1 ); OPLL_calc_stereo( (OPLL *) opll, buffers, 1, -1 );
int amp = buffer [0] + buffer [1]; int amp = buffer [0] + buffer [1];
int delta = amp - mono.last_amp; int delta = amp - mono.last_amp;
if ( delta ) if ( delta )
{ {
mono.last_amp = amp; mono.last_amp = amp;
synth.offset_inline( time, delta, mono_output ); synth.offset_inline( time, delta, mono_output );
} }
time += period; time += period;
} }
while ( time < end_time ); while ( time < end_time );
} }
else else
{ {
mono.last_amp = 0; mono.last_amp = 0;
do do
{ {
OPLL_advance( (OPLL *) opll ); OPLL_advance( (OPLL *) opll );
for ( int i = 0; i < osc_count; ++i ) for ( int i = 0; i < osc_count; ++i )
{ {
Vrc7_Osc& osc = oscs [i]; Vrc7_Osc& osc = oscs [i];
if ( osc.output ) if ( osc.output )
{ {
OPLL_calc_stereo( (OPLL *) opll, buffers, 1, i ); OPLL_calc_stereo( (OPLL *) opll, buffers, 1, i );
int amp = buffer [0] + buffer [1]; int amp = buffer [0] + buffer [1];
int delta = amp - osc.last_amp; int delta = amp - osc.last_amp;
if ( delta ) if ( delta )
{ {
osc.last_amp = amp; osc.last_amp = amp;
synth.offset( time, delta, osc.output ); synth.offset( time, delta, osc.output );
} }
} }
} }
time += period; time += period;
} }
while ( time < end_time ); while ( time < end_time );
} }
next_time = time; next_time = time;
} }

View file

@ -1,82 +1,84 @@
// Konami VRC7 sound chip emulator // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#ifndef NES_VRC7_APU_H // Konami VRC7 sound chip emulator
#define NES_VRC7_APU_H
#ifndef NES_VRC7_APU_H
#include "blargg_common.h" #define NES_VRC7_APU_H
#include "Blip_Buffer.h"
#include "blargg_common.h"
struct vrc7_snapshot_t; #include "Blip_Buffer.h"
class Nes_Vrc7_Apu { struct vrc7_snapshot_t;
public:
blargg_err_t init(); class Nes_Vrc7_Apu {
public:
// See Nes_Apu.h for reference blargg_err_t init();
void reset();
void volume( double ); // See Nes_Apu.h for reference
void treble_eq( blip_eq_t const& ); void reset();
void set_output( Blip_Buffer* ); void volume( double );
enum { osc_count = 6 }; void treble_eq( blip_eq_t const& );
void osc_output( int index, Blip_Buffer* ); void set_output( Blip_Buffer* );
void end_frame( blip_time_t ); enum { osc_count = 6 };
void save_snapshot( vrc7_snapshot_t* ) const; void osc_output( int index, Blip_Buffer* );
void load_snapshot( vrc7_snapshot_t const& ); void end_frame( blip_time_t );
void save_snapshot( vrc7_snapshot_t* ) const;
void write_reg( int reg ); void load_snapshot( vrc7_snapshot_t const& );
void write_data( blip_time_t, int data );
void write_reg( int reg );
public: void write_data( blip_time_t, int data );
Nes_Vrc7_Apu();
~Nes_Vrc7_Apu(); public:
BLARGG_DISABLE_NOTHROW Nes_Vrc7_Apu();
private: ~Nes_Vrc7_Apu();
// noncopyable BLARGG_DISABLE_NOTHROW
Nes_Vrc7_Apu( const Nes_Vrc7_Apu& ); private:
Nes_Vrc7_Apu& operator = ( const Nes_Vrc7_Apu& ); // noncopyable
Nes_Vrc7_Apu( const Nes_Vrc7_Apu& );
struct Vrc7_Osc Nes_Vrc7_Apu& operator = ( const Nes_Vrc7_Apu& );
{
uint8_t regs [3]; struct Vrc7_Osc
Blip_Buffer* output; {
int last_amp; uint8_t regs [3];
}; Blip_Buffer* output;
int last_amp;
Vrc7_Osc oscs [osc_count]; };
uint8_t kon;
uint8_t inst [8]; Vrc7_Osc oscs [osc_count];
void* opll; uint8_t kon;
int addr; uint8_t inst [8];
blip_time_t next_time; void* opll;
struct { int addr;
Blip_Buffer* output; blip_time_t next_time;
int last_amp; struct {
} mono; Blip_Buffer* output;
int last_amp;
Blip_Synth<blip_med_quality,1> synth; } mono;
void run_until( blip_time_t ); Blip_Synth<blip_med_quality,1> synth;
void output_changed();
}; void run_until( blip_time_t );
void output_changed();
struct vrc7_snapshot_t };
{
uint8_t latch; struct vrc7_snapshot_t
uint8_t inst [8]; {
uint8_t regs [6] [3]; uint8_t latch;
uint8_t delay; uint8_t inst [8];
}; uint8_t regs [6] [3];
uint8_t delay;
inline void Nes_Vrc7_Apu::osc_output( int i, Blip_Buffer* buf ) };
{
assert( (unsigned) i < osc_count ); inline void Nes_Vrc7_Apu::osc_output( int i, Blip_Buffer* buf )
oscs [i].output = buf; {
output_changed(); assert( (unsigned) i < osc_count );
} oscs [i].output = buf;
output_changed();
// DB2LIN_AMP_BITS == 11, * 2 }
inline void Nes_Vrc7_Apu::volume( double v ) { synth.volume( 1.0 / 3 / 4096 * v ); }
// DB2LIN_AMP_BITS == 11, * 2
inline void Nes_Vrc7_Apu::treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); } inline void Nes_Vrc7_Apu::volume( double v ) { synth.volume( 1.0 / 3 / 4096 * v ); }
#endif inline void Nes_Vrc7_Apu::treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
#endif

View file

@ -11,9 +11,9 @@
#include "Nes_Namco_Apu.h" #include "Nes_Namco_Apu.h"
#include "Nes_Vrc6_Apu.h" #include "Nes_Vrc6_Apu.h"
#include "Nes_Fme7_Apu.h" #include "Nes_Fme7_Apu.h"
#include "Nes_Fds_Apu.h" #include "Nes_Fds_Apu.h"
#include "Nes_Mmc5_Apu.h" #include "Nes_Mmc5_Apu.h"
#include "Nes_Vrc7_Apu.h" #include "Nes_Vrc7_Apu.h"
#endif #endif
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you /* Copyright (C) 2003-2006 Shay Green. This module is free software; you
@ -56,12 +56,10 @@ Nsf_Emu::Nsf_Emu()
vrc6 = 0; vrc6 = 0;
namco = 0; namco = 0;
fme7 = 0; fme7 = 0;
fds = 0; fds = 0;
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 );
@ -84,22 +82,17 @@ void Nsf_Emu::unload()
delete fme7; delete fme7;
fme7 = 0; fme7 = 0;
delete fds; delete fds;
fds = 0; fds = 0;
delete mmc5; delete mmc5;
mmc5 = 0; mmc5 = 0;
delete vrc7; delete vrc7;
vrc7 = 0; vrc7 = 0;
} }
#endif #endif
{
delete [] apu_names;
apu_names = 0;
}
rom.clear(); rom.clear();
Music_Emu::unload(); Music_Emu::unload();
@ -194,31 +187,26 @@ blargg_err_t Nsf_Emu::init_sound()
set_warning( "Uses unsupported audio expansion hardware" ); set_warning( "Uses unsupported audio expansion hardware" );
#ifdef NSF_EMU_APU_ONLY #ifdef NSF_EMU_APU_ONLY
int const count_total = Nes_Apu::osc_count; int const count_total = Nes_Apu::osc_count;
#else #else
int const count_total = Nes_Apu::osc_count + Nes_Namco_Apu::osc_count + int const count_total = Nes_Apu::osc_count + Nes_Namco_Apu::osc_count +
Nes_Vrc6_Apu::osc_count + Nes_Fme7_Apu::osc_count + Nes_Vrc6_Apu::osc_count + Nes_Fme7_Apu::osc_count +
Nes_Fds_Apu::osc_count + Nes_Mmc5_Apu::osc_count + Nes_Fds_Apu::osc_count + Nes_Mmc5_Apu::osc_count +
Nes_Vrc7_Apu::osc_count; Nes_Vrc7_Apu::osc_count;
#endif #endif
if ( apu_names ) apu_names.resize( count_total );
{
delete [] apu_names; int count = 0;
}
apu_names = new char* [count_total];
int count = 0;
{ {
apu_names[count + 0] = "Square 1"; apu_names[count + 0] = "Square 1";
apu_names[count + 1] = "Square 2"; apu_names[count + 1] = "Square 2";
apu_names[count + 2] = "Triangle"; apu_names[count + 2] = "Triangle";
apu_names[count + 3] = "Noise"; apu_names[count + 3] = "Noise";
apu_names[count + 4] = "DMC"; apu_names[count + 4] = "DMC";
count += Nes_Apu::osc_count; count += Nes_Apu::osc_count;
} }
static int const types [] = { static int const types [] = {
wave_type | 1, wave_type | 2, wave_type | 0, wave_type | 1, wave_type | 2, wave_type | 0,
@ -238,35 +226,35 @@ blargg_err_t Nsf_Emu::init_sound()
} }
#else #else
{ {
if ( header_.chip_flags & vrc6_flag ) if ( header_.chip_flags & vrc6_flag )
{ {
vrc6 = BLARGG_NEW Nes_Vrc6_Apu; vrc6 = BLARGG_NEW Nes_Vrc6_Apu;
CHECK_ALLOC( vrc6 ); CHECK_ALLOC( vrc6 );
adjusted_gain *= 0.75; adjusted_gain *= 0.75;
apu_names[count + 0] = "Saw Wave";
apu_names[count + 1] = "Square 3";
apu_names[count + 2] = "Square 4";
count += Nes_Vrc6_Apu::osc_count;
}
apu_names[count + 0] = "Saw Wave";
apu_names[count + 1] = "Square 3";
apu_names[count + 2] = "Square 4";
count += Nes_Vrc6_Apu::osc_count;
}
if ( header_.chip_flags & namco_flag ) if ( header_.chip_flags & namco_flag )
{ {
namco = BLARGG_NEW Nes_Namco_Apu; namco = BLARGG_NEW Nes_Namco_Apu;
CHECK_ALLOC( namco ); CHECK_ALLOC( namco );
adjusted_gain *= 0.75; adjusted_gain *= 0.75;
apu_names[count + 0] = "Wave 1"; apu_names[count + 0] = "Wave 1";
apu_names[count + 1] = "Wave 2"; apu_names[count + 1] = "Wave 2";
apu_names[count + 2] = "Wave 3"; apu_names[count + 2] = "Wave 3";
apu_names[count + 3] = "Wave 4"; apu_names[count + 3] = "Wave 4";
apu_names[count + 4] = "Wave 5"; apu_names[count + 4] = "Wave 5";
apu_names[count + 5] = "Wave 6"; apu_names[count + 5] = "Wave 6";
apu_names[count + 6] = "Wave 7"; apu_names[count + 6] = "Wave 7";
apu_names[count + 7] = "Wave 8"; apu_names[count + 7] = "Wave 8";
count += Nes_Namco_Apu::osc_count; count += Nes_Namco_Apu::osc_count;
} }
if ( header_.chip_flags & fme7_flag ) if ( header_.chip_flags & fme7_flag )
@ -274,64 +262,64 @@ blargg_err_t Nsf_Emu::init_sound()
fme7 = BLARGG_NEW Nes_Fme7_Apu; fme7 = BLARGG_NEW Nes_Fme7_Apu;
CHECK_ALLOC( fme7 ); CHECK_ALLOC( fme7 );
adjusted_gain *= 0.75; adjusted_gain *= 0.75;
apu_names[count + 0] = "Square 3"; apu_names[count + 0] = "Square 3";
apu_names[count + 1] = "Square 4"; apu_names[count + 1] = "Square 4";
apu_names[count + 2] = "Square 5"; apu_names[count + 2] = "Square 5";
count += Nes_Fme7_Apu::osc_count; count += Nes_Fme7_Apu::osc_count;
} }
if ( header_.chip_flags & fds_flag )
{
fds = BLARGG_NEW Nes_Fds_Apu;
CHECK_ALLOC( fds );
adjusted_gain *= 0.75;
apu_names[count + 0] = "Wave"; if ( header_.chip_flags & fds_flag )
{
count += Nes_Fds_Apu::osc_count; fds = BLARGG_NEW Nes_Fds_Apu;
} CHECK_ALLOC( fds );
adjusted_gain *= 0.75;
if ( header_.chip_flags & mmc5_flag )
{ apu_names[count + 0] = "Wave";
mmc5 = BLARGG_NEW Nes_Mmc5_Apu;
CHECK_ALLOC( mmc5 ); count += Nes_Fds_Apu::osc_count;
adjusted_gain *= 0.75; }
apu_names[count + 0] = "Square 3"; if ( header_.chip_flags & mmc5_flag )
apu_names[count + 1] = "Square 4"; {
apu_names[count + 2] = "PCM"; mmc5 = BLARGG_NEW Nes_Mmc5_Apu;
CHECK_ALLOC( mmc5 );
count += Nes_Mmc5_Apu::osc_count; adjusted_gain *= 0.75;
}
apu_names[count + 0] = "Square 3";
if ( header_.chip_flags & vrc7_flag ) apu_names[count + 1] = "Square 4";
{ apu_names[count + 2] = "PCM";
vrc7 = BLARGG_NEW Nes_Vrc7_Apu;
CHECK_ALLOC( vrc7 ); count += Nes_Mmc5_Apu::osc_count;
RETURN_ERR( vrc7->init() ); }
adjusted_gain *= 0.75;
if ( header_.chip_flags & vrc7_flag )
apu_names[count + 0] = "FM 1"; {
apu_names[count + 1] = "FM 2"; vrc7 = BLARGG_NEW Nes_Vrc7_Apu;
apu_names[count + 2] = "FM 3"; CHECK_ALLOC( vrc7 );
apu_names[count + 3] = "FM 4"; RETURN_ERR( vrc7->init() );
apu_names[count + 4] = "FM 5"; adjusted_gain *= 0.75;
apu_names[count + 5] = "FM 6";
apu_names[count + 0] = "FM 1";
count += Nes_Vrc7_Apu::osc_count; apu_names[count + 1] = "FM 2";
} apu_names[count + 2] = "FM 3";
apu_names[count + 3] = "FM 4";
set_voice_count( count ); apu_names[count + 4] = "FM 5";
set_voice_names( apu_names ); apu_names[count + 5] = "FM 6";
count += Nes_Vrc7_Apu::osc_count;
}
set_voice_count( count );
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 );
if ( fme7 ) fme7 ->volume( adjusted_gain ); if ( fme7 ) fme7 ->volume( adjusted_gain );
if ( fds ) fds ->volume( adjusted_gain ); if ( fds ) fds ->volume( adjusted_gain );
if ( mmc5 ) mmc5 ->volume( adjusted_gain ); if ( mmc5 ) mmc5 ->volume( adjusted_gain );
if ( vrc7 ) vrc7 ->volume( adjusted_gain ); if ( vrc7 ) vrc7 ->volume( adjusted_gain );
} }
#endif #endif
@ -411,9 +399,9 @@ void Nsf_Emu::update_eq( blip_eq_t const& eq )
if ( namco ) namco->treble_eq( eq ); if ( namco ) namco->treble_eq( eq );
if ( vrc6 ) vrc6 ->treble_eq( eq ); if ( vrc6 ) vrc6 ->treble_eq( eq );
if ( fme7 ) fme7 ->treble_eq( eq ); if ( fme7 ) fme7 ->treble_eq( eq );
if ( fds ) fds ->treble_eq( eq ); if ( fds ) fds ->treble_eq( eq );
if ( mmc5 ) mmc5 ->treble_eq( eq ); if ( mmc5 ) mmc5 ->treble_eq( eq );
if ( vrc7 ) vrc7 ->treble_eq( eq ); if ( vrc7 ) vrc7 ->treble_eq( eq );
} }
#endif #endif
} }
@ -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 ( object ) \
{ \
if ( i < class::osc_count ) \
{ \
object->osc_output( i, buf ); \
return; \
} \
i -= class::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 ( vrc6 ) HANDLE_CHIP_VRC6(Nes_Vrc6_Apu, vrc6);
{ HANDLE_CHIP(Nes_Namco_Apu, namco);
if ( i < Nes_Vrc6_Apu::osc_count ) HANDLE_CHIP(Nes_Fme7_Apu, fme7);
{ HANDLE_CHIP(Nes_Fds_Apu, fds);
// put saw first HANDLE_CHIP(Nes_Mmc5_Apu, mmc5);
if ( --i < 0 ) HANDLE_CHIP(Nes_Vrc7_Apu, vrc7);
i = 2;
vrc6->osc_output( i, buf );
return;
}
i -= Nes_Vrc6_Apu::osc_count;
}
if ( namco )
{
if ( i < Nes_Namco_Apu::osc_count )
{
namco->osc_output( i, buf );
return;
}
i -= Nes_Namco_Apu::osc_count;
}
if ( fme7 )
{
if ( i < Nes_Fme7_Apu::osc_count )
{
fme7->osc_output( i, buf );
return;
}
i -= Nes_Fme7_Apu::osc_count;
}
if ( fds )
{
if ( i < Nes_Fds_Apu::osc_count )
{
fds->osc_output( i, buf );
return;
}
i -= Nes_Fds_Apu::osc_count;
}
if ( mmc5 )
{
if ( i < Nes_Mmc5_Apu::osc_count )
{
mmc5->osc_output( i, buf );
return;
}
i -= Nes_Mmc5_Apu::osc_count;
}
if ( vrc7 )
{
if ( i < Nes_Vrc7_Apu::osc_count )
{
vrc7->osc_output( i, buf );
return;
}
i -= Nes_Vrc7_Apu::osc_count;
}
} }
#undef HANDLE_CHIP
#undef HANDLE_CHIP_VRC6
#endif #endif
} }
@ -503,15 +460,15 @@ void Nsf_Emu::cpu_write_misc( nes_addr_t addr, int data )
{ {
#if !NSF_EMU_APU_ONLY #if !NSF_EMU_APU_ONLY
{ {
if ( fds ) if ( fds )
{ {
if ( (unsigned) (addr - fds->io_addr) < fds->io_size ) if ( (unsigned) (addr - fds->io_addr) < fds->io_size )
{ {
fds->write( time(), addr, data); fds->write( time(), addr, data);
return; return;
} }
} }
if ( namco ) if ( namco )
{ {
switch ( addr ) switch ( addr )
@ -550,44 +507,44 @@ void Nsf_Emu::cpu_write_misc( nes_addr_t addr, int data )
return; return;
} }
} }
if ( mmc5 ) if ( mmc5 )
{ {
if ( (unsigned) (addr - mmc5->regs_addr) < mmc5->regs_size) if ( (unsigned) (addr - mmc5->regs_addr) < mmc5->regs_size)
{ {
mmc5->write_register( time(), addr, data ); mmc5->write_register( time(), addr, data );
return; return;
} }
int m = addr - 0x5205; int m = addr - 0x5205;
if ( (unsigned) m < 2 ) if ( (unsigned) m < 2 )
{ {
mmc5_mul [m] = data; mmc5_mul [m] = data;
return; return;
} }
int i = addr - 0x5C00; int i = addr - 0x5C00;
if ( (unsigned) i < mmc5->exram_size ) if ( (unsigned) i < mmc5->exram_size )
{ {
mmc5->exram [i] = data; mmc5->exram [i] = data;
return; return;
} }
} }
if ( vrc7 ) if ( vrc7 )
{ {
if ( addr == 0x9010 ) if ( addr == 0x9010 )
{ {
vrc7->write_reg( data ); vrc7->write_reg( data );
return; return;
} }
if ( (unsigned) (addr - 0x9028) <= 0x08 ) if ( (unsigned) (addr - 0x9028) <= 0x08 )
{ {
vrc7->write_data( time(), data ); vrc7->write_data( time(), data );
return; return;
} }
} }
} }
#endif #endif
@ -625,20 +582,20 @@ blargg_err_t Nsf_Emu::start_track_( int track )
apu.write_register( 0, 0x4015, 0x0F ); apu.write_register( 0, 0x4015, 0x0F );
apu.write_register( 0, 0x4017, (header_.speed_flags & 0x10) ? 0x80 : 0 ); apu.write_register( 0, 0x4017, (header_.speed_flags & 0x10) ? 0x80 : 0 );
#if !NSF_EMU_APU_ONLY #if !NSF_EMU_APU_ONLY
if ( mmc5 ) if ( mmc5 )
{ {
mmc5_mul [0] = 0; mmc5_mul [0] = 0;
mmc5_mul [1] = 0; mmc5_mul [1] = 0;
memset( mmc5->exram, 0, mmc5->exram_size ); memset( mmc5->exram, 0, mmc5->exram_size );
} }
{ {
if ( namco ) namco->reset(); if ( namco ) namco->reset();
if ( vrc6 ) vrc6 ->reset(); if ( vrc6 ) vrc6 ->reset();
if ( fme7 ) fme7 ->reset(); if ( fme7 ) fme7 ->reset();
if ( fds ) fds ->reset(); if ( fds ) fds ->reset();
if ( mmc5 ) mmc5 ->reset(); if ( mmc5 ) mmc5 ->reset();
if ( vrc7 ) vrc7 ->reset(); if ( vrc7 ) vrc7 ->reset();
} }
#endif #endif
@ -724,9 +681,9 @@ blargg_err_t Nsf_Emu::run_clocks( blip_time_t& duration, int )
if ( namco ) namco->end_frame( duration ); if ( namco ) namco->end_frame( duration );
if ( vrc6 ) vrc6 ->end_frame( duration ); if ( vrc6 ) vrc6 ->end_frame( duration );
if ( fme7 ) fme7 ->end_frame( duration ); if ( fme7 ) fme7 ->end_frame( duration );
if ( fds ) fds ->end_frame( duration ); if ( fds ) fds ->end_frame( duration );
if ( mmc5 ) mmc5 ->end_frame( duration ); if ( mmc5 ) mmc5 ->end_frame( duration );
if ( vrc7 ) vrc7 ->end_frame( duration ); if ( vrc7 ) vrc7 ->end_frame( duration );
} }
#endif #endif

View file

@ -89,16 +89,16 @@ public: private: friend class Nes_Cpu;
enum { badop_addr = bank_select_addr }; enum { badop_addr = bank_select_addr };
private: private:
byte mmc5_mul [2]; byte mmc5_mul [2];
class Nes_Namco_Apu* namco; class Nes_Namco_Apu* namco;
class Nes_Vrc6_Apu* vrc6; class Nes_Vrc6_Apu* vrc6;
class Nes_Fme7_Apu* fme7; class Nes_Fme7_Apu* fme7;
class Nes_Fds_Apu* fds; class Nes_Fds_Apu* fds;
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

File diff suppressed because it is too large Load diff

View file

@ -1,109 +1,115 @@
// 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 "Fir_Resampler.h"
#include "higan/smp/smp.hpp" #include "Music_Emu.h"
#include "Spc_Filter.h" #include "../higan/smp/smp.hpp"
#include "Spc_Filter.h"
#if GME_SPC_FAST_RESAMPLER
#include "Upsampler.h" class Spc_Emu : public Music_Emu {
typedef Upsampler Spc_Emu_Resampler; public:
#else // The Super Nintendo hardware samples at 32kHz. Other sample rates are
#include "Fir_Resampler.h" // handled by resampling the 32kHz output; emulation accuracy is not affected.
typedef Fir_Resampler<24> Spc_Emu_Resampler; enum { native_sample_rate = 32000 };
#endif
// SPC file header
class Spc_Emu : public Music_Emu { enum { header_size = 0x100 };
public: struct header_t
// The Super Nintendo hardware samples at 32kHz. Other sample rates are {
// handled by resampling the 32kHz output; emulation accuracy is not affected. char tag [35];
enum { native_sample_rate = 32000 }; byte format;
byte version;
// Disables annoying pseudo-surround effect some music uses byte pc [2];
void disable_surround( bool disable = true ) { smp.dsp.disable_surround( disable ); } byte a, x, y, psw, sp;
byte unused [2];
// Enables gaussian, cubic or sinc interpolation char song [32];
void interpolation_level( int level = 0 ) { smp.dsp.spc_dsp.interpolation_level( level ); } char game [32];
char dumper [16];
// Enables an analog signal simulation filter char comment [32];
void enable_filter( bool enable = true ) { _enable_filter = enable; if (enable) filter.clear(); } byte date [11];
byte len_secs [3];
// Enables native echo byte fade_msec [4];
void enable_echo( bool enable = true ) { smp.dsp.spc_dsp.enable_echo( enable ); } char author [32]; // sometimes first char should be skipped (see official SPC spec)
virtual void mute_effects( bool mute ) { enable_echo(!mute); } byte mute_mask;
byte emulator;
SuperFamicom::SMP const* get_smp() const; byte unused2 [46];
SuperFamicom::SMP * get_smp(); };
// SPC file header // Header for currently loaded file
enum { header_size = 0x100 }; header_t const& header() const { return *(header_t const*) file_data; }
struct header_t
{ // Prevents channels and global volumes from being phase-negated
char tag [35]; void disable_surround( bool disable = true );
byte format;
byte version; // Enables gaussian=0, cubic=1 or sinc=2 interpolation
byte pc [ 2]; // Or negative levels for worse quality, linear=-1 or nearest=-2
byte a, x, y, psw, sp; void interpolation_level( int level = 0 );
byte unused [ 2];
char song [32]; // Enables native echo
char game [32]; void enable_echo( bool enable = true );
char dumper [16]; void mute_effects( bool mute );
char comment [32];
byte date [11]; SuperFamicom::SMP const* get_smp() const;
byte len_secs [ 3]; SuperFamicom::SMP * get_smp();
byte fade_msec [ 4];
char author [32]; // sometimes first char should be skipped (see official SPC spec) static gme_type_t static_type() { return gme_spc_type; }
byte mute_mask;
byte emulator; public:
byte unused2 [46]; // deprecated
}; using Music_Emu::load;
blargg_err_t load( header_t const& h, Data_Reader& in ) // use Remaining_Reader
// Header for currently loaded file { return load_remaining_( &h, sizeof h, in ); }
header_t const& header() const { return *(header_t const*) file_data; } byte const* trailer() const; // use track_info()
long trailer_size() const;
static gme_type_t static_type() { return gme_spc_type; }
public:
public: Spc_Emu( gme_type_t );
// deprecated Spc_Emu() : Spc_Emu( gme_spc_type ) {}
using Music_Emu::load; ~Spc_Emu();
blargg_err_t load( header_t const& h, Data_Reader& in ) // use Remaining_Reader protected:
{ return load_remaining_( &h, sizeof h, in ); } blargg_err_t load_mem_( byte const*, long );
byte const* trailer() const; // use track_info() blargg_err_t track_info_( track_info_t*, int track ) const;
long trailer_size() const; blargg_err_t set_sample_rate_( long );
blargg_err_t start_track_( int );
// Implementation blargg_err_t play_( long, sample_t* );
public: blargg_err_t skip_( long );
Spc_Emu( gme_type_t ); void mute_voices_( int );
Spc_Emu() : Spc_Emu( gme_spc_type ) {} void set_tempo_( double );
~Spc_Emu(); void enable_accuracy_( bool );
byte const* file_data;
protected: long file_size;
blargg_err_t load_mem_( byte const*, long ); private:
blargg_err_t track_info_( track_info_t*, int track ) const; Fir_Resampler<24> resampler;
blargg_err_t set_sample_rate_( long ); SPC_Filter filter;
blargg_err_t start_track_( int ); SuperFamicom::SMP smp;
blargg_err_t play_( long, sample_t* );
blargg_err_t skip_( long ); blargg_err_t play_and_filter( long count, sample_t out [] );
void mute_voices_( int ); };
void set_tempo_( double );
void enable_accuracy_( bool ); inline void Spc_Emu::disable_surround( bool disable ) { smp.dsp.disable_surround( disable ); }
byte const* file_data; inline void Spc_Emu::interpolation_level( int level ) { smp.dsp.spc_dsp.interpolation_level( level ); }
long file_size; 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); }
private: inline SuperFamicom::SMP const* Spc_Emu::get_smp() const { return &smp; }
Spc_Emu_Resampler resampler; inline SuperFamicom::SMP * Spc_Emu::get_smp() { return &smp; }
SPC_Filter filter;
SuperFamicom::SMP smp; class Rsn_Emu : public Spc_Emu {
public:
bool _enable_filter; Rsn_Emu() : Spc_Emu( gme_rsn_type ) { is_archive = true; }
~Rsn_Emu();
blargg_err_t play_and_filter( long count, sample_t out [] ); 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()
inline SuperFamicom::SMP const* Spc_Emu::get_smp() const { return &smp; } long trailer_size( int ) const;
inline SuperFamicom::SMP * Spc_Emu::get_smp() { return &smp; } protected:
blargg_err_t track_info_( track_info_t*, int ) const;
#endif blargg_err_t start_track_( int );
private:
blargg_vector<byte> rsn;
blargg_vector<byte*> spc;
};
#endif

View file

@ -1,492 +1,500 @@
// 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"
#include "blargg_endian.h" #include "blargg_endian.h"
#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"
#ifndef _countof
#define _countof(x) (sizeof((x))/sizeof((x)[0])) #ifndef _countof
#endif #define _countof(x) (sizeof((x))/sizeof((x)[0]))
#endif
// TODO: support Spc_Filter's bass
// TODO: support Spc_Filter's bass
Sfm_Emu::Sfm_Emu()
{ Sfm_Emu::Sfm_Emu()
set_type( gme_sfm_type ); {
set_voice_count( SuperFamicom::SPC_DSP::voice_count ); set_type( gme_sfm_type );
static const char* const names [SuperFamicom::SPC_DSP::voice_count] = { set_voice_count( SuperFamicom::SPC_DSP::voice_count );
"DSP 1", "DSP 2", "DSP 3", "DSP 4", "DSP 5", "DSP 6", "DSP 7", "DSP 8" 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"
set_voice_names( names ); };
set_gain( 1.4 ); set_voice_names( names );
set_max_initial_silence( 30 ); set_gain( 1.4 );
set_silence_lookahead( 30 ); // Some SFMs may have a lot of initialization code set_max_initial_silence( 30 );
enable_filter( false ); set_silence_lookahead( 30 ); // Some SFMs may have a lot of initialization code
enable_echo( true ); enable_echo( true );
} }
Sfm_Emu::~Sfm_Emu() { } Sfm_Emu::~Sfm_Emu() { }
// Track info // Track info
static void copy_field( char* out, size_t size, const Bml_Parser& in, char const* in_path ) static void copy_field( char* out, size_t size, const Bml_Parser& in, char const* in_path )
{ {
const char * value = in.enumValue( in_path ); const char * value = in.enumValue( in_path );
if ( value ) strncpy( out, value, size - 1 ), out[ size - 1 ] = 0; if ( value ) strncpy( out, value, size - 1 ), out[ size - 1 ] = 0;
else out[ 0 ] = 0; else out[ 0 ] = 0;
} }
static void copy_info( track_info_t* out, const Bml_Parser& in ) static void copy_info( track_info_t* out, const Bml_Parser& in )
{ {
copy_field( out->song, sizeof(out->song), in, "information:title" ); copy_field( out->song, sizeof(out->song), in, "information:title" );
copy_field( out->game, sizeof(out->game), in, "information:game" ); copy_field( out->game, sizeof(out->game), in, "information:game" );
copy_field( out->author, sizeof(out->author), in, "information:author" ); copy_field( out->author, sizeof(out->author), in, "information:author" );
copy_field( out->composer, sizeof(out->composer), in, "information:composer" ); copy_field( out->composer, sizeof(out->composer), in, "information:composer" );
copy_field( out->copyright, sizeof(out->copyright), in, "information:copyright" ); copy_field( out->copyright, sizeof(out->copyright), in, "information:copyright" );
copy_field( out->date, sizeof(out->date), in, "information:date" ); copy_field( out->date, sizeof(out->date), in, "information:date" );
copy_field( out->track, sizeof(out->track), in, "information:track" ); copy_field( out->track, sizeof(out->track), in, "information:track" );
copy_field( out->disc, sizeof(out->disc), in, "information:disc" ); copy_field( out->disc, sizeof(out->disc), in, "information:disc" );
copy_field( out->dumper, sizeof(out->dumper), in, "information:dumper" ); copy_field( out->dumper, sizeof(out->dumper), in, "information:dumper" );
char * end; char * end;
const char * value = in.enumValue( "timing:length" ); const char * value = in.enumValue( "timing:length" );
if ( value ) if ( value )
out->length = strtoul( value, &end, 10 ); out->length = strtoul( value, &end, 10 );
else else
out->length = 0; out->length = 0;
value = in.enumValue( "timing:fade" ); value = in.enumValue( "timing:fade" );
if ( value ) if ( value )
out->fade_length = strtoul( value, &end, 10 ); out->fade_length = strtoul( value, &end, 10 );
else else
out->fade_length = -1; out->fade_length = -1;
} }
blargg_err_t Sfm_Emu::track_info_( track_info_t* out, int ) const blargg_err_t Sfm_Emu::track_info_( track_info_t* out, int ) const
{ {
copy_info( out, metadata ); copy_info( out, metadata );
return 0; return 0;
} }
static void set_track_info( const track_info_t* in, Bml_Parser& out ) static void set_track_info( const track_info_t* in, Bml_Parser& out )
{ {
out.setValue( "information:title", in->song ); out.setValue( "information:title", in->song );
out.setValue( "information:game", in->game ); out.setValue( "information:game", in->game );
out.setValue( "information:author", in->author ); out.setValue( "information:author", in->author );
out.setValue( "information:composer", in->composer ); out.setValue( "information:composer", in->composer );
out.setValue( "information:copyright", in->copyright ); out.setValue( "information:copyright", in->copyright );
out.setValue( "information:date", in->date ); out.setValue( "information:date", in->date );
out.setValue( "information:track", in->track ); out.setValue( "information:track", in->track );
out.setValue( "information:disc", in->disc ); out.setValue( "information:disc", in->disc );
out.setValue( "information:dumper", in->dumper ); out.setValue( "information:dumper", in->dumper );
out.setValue( "timing:length", in->length ); out.setValue( "timing:length", in->length );
out.setValue( "timing:fade", in->fade_length ); out.setValue( "timing:fade", in->fade_length );
} }
blargg_err_t Sfm_Emu::set_track_info_( const track_info_t* in, int ) blargg_err_t Sfm_Emu::set_track_info_( const track_info_t* in, int )
{ {
::set_track_info(in, metadata); ::set_track_info(in, metadata);
return 0; return 0;
} }
static blargg_err_t check_sfm_header( void const* header ) static blargg_err_t check_sfm_header( void const* header )
{ {
if ( memcmp( header, "SFM1", 4 ) ) if ( memcmp( header, "SFM1", 4 ) )
return gme_wrong_file_type; return gme_wrong_file_type;
return 0; return 0;
} }
struct Sfm_File : Gme_Info_ struct Sfm_File : Gme_Info_
{ {
blargg_vector<byte> data; blargg_vector<byte> data;
Bml_Parser metadata; Bml_Parser metadata;
unsigned long original_metadata_size; unsigned long original_metadata_size;
Sfm_File() { set_type( gme_sfm_type ); } Sfm_File() { set_type( gme_sfm_type ); }
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 ) );
RETURN_ERR( in.read( data.begin(), data.end() - data.begin() ) ); RETURN_ERR( in.read( data.begin(), data.end() - data.begin() ) );
RETURN_ERR( check_sfm_header( data.begin() ) ); RETURN_ERR( check_sfm_header( data.begin() ) );
if ( file_size < 8 ) if ( file_size < 8 )
return "SFM file too small"; return "SFM file too small";
int metadata_size = get_le32( data.begin() + 4 ); int metadata_size = get_le32( data.begin() + 4 );
metadata.parseDocument( (const char *)data.begin() + 8, metadata_size ); metadata.parseDocument( (const char *)data.begin() + 8, metadata_size );
original_metadata_size = metadata_size; original_metadata_size = metadata_size;
return 0; return 0;
} }
blargg_err_t track_info_( track_info_t* out, int ) const blargg_err_t track_info_( track_info_t* out, int ) const
{ {
copy_info( out, metadata ); copy_info( out, metadata );
return 0; return 0;
} }
blargg_err_t set_track_info_( const track_info_t* in, int ) blargg_err_t set_track_info_( const track_info_t* in, int )
{ {
::set_track_info( in, metadata ); ::set_track_info( in, metadata );
return 0; return 0;
} }
}; };
static Music_Emu* new_sfm_emu () { return BLARGG_NEW Sfm_Emu ; } static Music_Emu* new_sfm_emu () { return BLARGG_NEW Sfm_Emu ; }
static Music_Emu* new_sfm_file() { return BLARGG_NEW Sfm_File; } static Music_Emu* new_sfm_file() { return BLARGG_NEW Sfm_File; }
static gme_type_t_ const gme_sfm_type_ = { "Super Nintendo with log", 1, &new_sfm_emu, &new_sfm_file, "SFM", 0 }; static gme_type_t_ const gme_sfm_type_ = { "Super Nintendo with log", 1, &new_sfm_emu, &new_sfm_file, "SFM", 0 };
extern gme_type_t const gme_sfm_type = &gme_sfm_type_; extern gme_type_t const gme_sfm_type = &gme_sfm_type_;
// Setup // Setup
blargg_err_t Sfm_Emu::set_sample_rate_( long sample_rate ) blargg_err_t Sfm_Emu::set_sample_rate_( long sample_rate )
{ {
smp.power(); smp.power();
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 ) );
resampler.time_ratio( (double) native_sample_rate / sample_rate, 0.9965 ); resampler.time_ratio( (double) native_sample_rate / sample_rate, 0.9965 );
} }
return 0; return 0;
} }
void Sfm_Emu::mute_voices_( int m ) void Sfm_Emu::enable_accuracy_( bool b )
{ {
Music_Emu::mute_voices_( m ); Music_Emu::enable_accuracy_( b );
for ( int i = 0, j = 1; i < 8; ++i, j <<= 1 ) filter.enable( b );
smp.dsp.channel_enable( i, !( m & j ) ); if ( b ) enable_echo();
} }
blargg_err_t Sfm_Emu::load_mem_( byte const in [], long size ) void Sfm_Emu::mute_voices_( int m )
{ {
if ( size < Sfm_Emu::sfm_min_file_size ) Music_Emu::mute_voices_( m );
return gme_wrong_file_type; for ( int i = 0, j = 1; i < 8; ++i, j <<= 1 )
smp.dsp.channel_enable( i, !( m & j ) );
file_data = in; }
file_size = size;
blargg_err_t Sfm_Emu::load_mem_( byte const in [], long size )
RETURN_ERR( check_sfm_header( in ) ); {
if ( size < Sfm_Emu::sfm_min_file_size )
const byte * ptr = file_data; return gme_wrong_file_type;
int metadata_size = get_le32(ptr + 4);
if ( file_size < metadata_size + Sfm_Emu::sfm_min_file_size ) file_data = in;
return "SFM file too small"; file_size = size;
metadata.parseDocument((const char *) ptr + 8, metadata_size);
RETURN_ERR( check_sfm_header( in ) );
return 0;
} const byte * ptr = file_data;
int metadata_size = get_le32(ptr + 4);
// Emulation if ( file_size < metadata_size + Sfm_Emu::sfm_min_file_size )
return "SFM file too small";
void Sfm_Emu::set_tempo_( double t ) metadata.parseDocument((const char *) ptr + 8, metadata_size);
{
smp.set_tempo( t ); return 0;
} }
// (n ? n : 256) // Emulation
#define IF_0_THEN_256( n ) ((uint8_t) ((n) - 1) + 1)
void Sfm_Emu::set_tempo_( double t )
#define META_ENUM_INT(n,d) (value = metadata.enumValue(n), value ? strtol(value, &end, 10) : (d)) {
smp.set_tempo( t );
static const byte ipl_rom[0x40] = }
{
0xCD, 0xEF, 0xBD, 0xE8, 0x00, 0xC6, 0x1D, 0xD0, // (n ? n : 256)
0xFC, 0x8F, 0xAA, 0xF4, 0x8F, 0xBB, 0xF5, 0x78, #define IF_0_THEN_256( n ) ((uint8_t) ((n) - 1) + 1)
0xCC, 0xF4, 0xD0, 0xFB, 0x2F, 0x19, 0xEB, 0xF4,
0xD0, 0xFC, 0x7E, 0xF4, 0xD0, 0x0B, 0xE4, 0xF5, #define META_ENUM_INT(n,d) (value = metadata.enumValue(n), value ? strtol(value, &end, 10) : (d))
0xCB, 0xF4, 0xD7, 0x00, 0xFC, 0xD0, 0xF3, 0xAB,
0x01, 0x10, 0xEF, 0x7E, 0xF4, 0x10, 0xEB, 0xBA, static const byte ipl_rom[0x40] =
0xF6, 0xDA, 0x00, 0xBA, 0xF4, 0xC4, 0xF4, 0xDD, {
0x5D, 0xD0, 0xDB, 0x1F, 0x00, 0x00, 0xC0, 0xFF 0xCD, 0xEF, 0xBD, 0xE8, 0x00, 0xC6, 0x1D, 0xD0,
}; 0xFC, 0x8F, 0xAA, 0xF4, 0x8F, 0xBB, 0xF5, 0x78,
0xCC, 0xF4, 0xD0, 0xFB, 0x2F, 0x19, 0xEB, 0xF4,
blargg_err_t Sfm_Emu::start_track_( int track ) 0xD0, 0xFC, 0x7E, 0xF4, 0xD0, 0x0B, 0xE4, 0xF5,
{ 0xCB, 0xF4, 0xD7, 0x00, 0xFC, 0xD0, 0xF3, 0xAB,
RETURN_ERR( Music_Emu::start_track_( track ) ); 0x01, 0x10, 0xEF, 0x7E, 0xF4, 0x10, 0xEB, 0xBA,
resampler.clear(); 0xF6, 0xDA, 0x00, 0xBA, 0xF4, 0xC4, 0xF4, 0xDD,
filter.clear(); 0x5D, 0xD0, 0xDB, 0x1F, 0x00, 0x00, 0xC0, 0xFF
const byte * ptr = file_data; };
int metadata_size = get_le32(ptr + 4);
blargg_err_t Sfm_Emu::start_track_( int track )
memcpy( smp.iplrom, ipl_rom, 64 ); {
RETURN_ERR( Music_Emu::start_track_( track ) );
smp.reset(); resampler.clear();
filter.clear();
memcpy( smp.apuram, ptr + 8 + metadata_size, 65536 ); const byte * ptr = file_data;
int metadata_size = get_le32(ptr + 4);
memcpy( smp.dsp.spc_dsp.m.regs, ptr + 8 + metadata_size + 65536, 128 );
memcpy( smp.iplrom, ipl_rom, 64 );
const uint8_t* log_begin = ptr + 8 + metadata_size + 65536 + 128;
const uint8_t* log_end = ptr + file_size; smp.reset();
size_t loop_begin = log_end - log_begin;
memcpy( smp.apuram, ptr + 8 + metadata_size, 65536 );
char * end; memcpy( smp.dsp.spc_dsp.m.echo_ram, ptr + 8 + metadata_size, 65536 );
const char * value;
memcpy( smp.dsp.spc_dsp.m.regs, ptr + 8 + metadata_size + 65536, 128 );
loop_begin = META_ENUM_INT("timing:loopstart", loop_begin);
const uint8_t* log_begin = ptr + 8 + metadata_size + 65536 + 128;
smp.set_sfm_queue( log_begin, log_end, log_begin + loop_begin ); const uint8_t* log_end = ptr + file_size;
size_t loop_begin = log_end - log_begin;
uint32_t test = META_ENUM_INT("smp:test", 0);
smp.status.clock_speed = (test >> 6) & 3; char * end;
smp.status.timer_speed = (test >> 4) & 3; const char * value;
smp.status.timers_enable = test & 0x08;
smp.status.ram_disable = test & 0x04; loop_begin = META_ENUM_INT("timing:loopstart", loop_begin);
smp.status.ram_writable = test & 0x02;
smp.status.timers_disable = test & 0x01; smp.set_sfm_queue( log_begin, log_end, log_begin + loop_begin );
smp.status.iplrom_enable = META_ENUM_INT("smp:iplrom",1); uint32_t test = META_ENUM_INT("smp:test", 0);
smp.status.dsp_addr = META_ENUM_INT("smp:dspaddr",0); smp.status.clock_speed = (test >> 6) & 3;
smp.status.timer_speed = (test >> 4) & 3;
value = metadata.enumValue("smp:ram"); smp.status.timers_enable = test & 0x08;
if (value) smp.status.ram_disable = test & 0x04;
{ smp.status.ram_writable = test & 0x02;
smp.status.ram00f8 = strtol(value, &end, 10); smp.status.timers_disable = test & 0x01;
if (*end)
{ smp.status.iplrom_enable = META_ENUM_INT("smp:iplrom",1);
value = end + 1; smp.status.dsp_addr = META_ENUM_INT("smp:dspaddr",0);
smp.status.ram00f9 = strtol(value, &end, 10);
} value = metadata.enumValue("smp:ram");
} if (value)
{
std::string name; smp.status.ram00f8 = strtol(value, &end, 10);
std::ostringstream oss; if (*end)
{
name = "smp:regs:"; value = end + 1;
smp.regs.pc = META_ENUM_INT(name + "pc", 0xffc0); smp.status.ram00f9 = strtol(value, &end, 10);
smp.regs.a = META_ENUM_INT(name + "a", 0x00); }
smp.regs.x = META_ENUM_INT(name + "x", 0x00); }
smp.regs.y = META_ENUM_INT(name + "y", 0x00);
smp.regs.s = META_ENUM_INT(name + "s", 0xef); std::string name;
smp.regs.p = META_ENUM_INT(name + "psw", 0x02); std::ostringstream oss;
value = metadata.enumValue("smp:ports"); name = "smp:regs:";
if (value) smp.regs.pc = META_ENUM_INT(name + "pc", 0xffc0);
{ smp.regs.a = META_ENUM_INT(name + "a", 0x00);
for (int i = 0; i < _countof(smp.sfm_last); i++) smp.regs.x = META_ENUM_INT(name + "x", 0x00);
{ smp.regs.y = META_ENUM_INT(name + "y", 0x00);
smp.sfm_last[i] = strtol(value, &end, 10); smp.regs.s = META_ENUM_INT(name + "s", 0xef);
if (*end == ',') smp.regs.p = META_ENUM_INT(name + "psw", 0x02);
value = end + 1;
else value = metadata.enumValue("smp:ports");
break; if (value)
} {
} for (unsigned long i = 0; i < _countof(smp.sfm_last); i++)
{
for (int i = 0; i < 3; ++i) smp.sfm_last[i] = strtol(value, &end, 10);
{ if (*end == ',')
SuperFamicom::SMP::Timer<192> &t = (i == 0 ? smp.timer0 : (i == 1 ? smp.timer1 : *(SuperFamicom::SMP::Timer<192>*)&smp.timer2)); value = end + 1;
oss.str(""); else
oss.clear(); break;
oss << "smp:timer[" << i << "]:"; }
name = oss.str(); }
value = metadata.enumValue(name + "enable");
if (value) for (int i = 0; i < 3; ++i)
{ {
t.enable = !!strtol(value, &end, 10); SuperFamicom::SMP::Timer<192> &t = (i == 0 ? smp.timer0 : (i == 1 ? smp.timer1 : *(SuperFamicom::SMP::Timer<192>*)&smp.timer2));
} oss.str("");
value = metadata.enumValue(name + "target"); oss.clear();
if (value) oss << "smp:timer[" << i << "]:";
{ name = oss.str();
t.target = strtol(value, &end, 10); value = metadata.enumValue(name + "enable");
} if (value)
value = metadata.enumValue(name + "stage"); {
if (value) t.enable = !!strtol(value, &end, 10);
{ }
t.stage0_ticks = strtol(value, &end, 10); value = metadata.enumValue(name + "target");
if (*end != ',') break; if (value)
value = end + 1; {
t.stage1_ticks = strtol(value, &end, 10); t.target = strtol(value, &end, 10);
if (*end != ',') break; }
value = end + 1; value = metadata.enumValue(name + "stage");
t.stage2_ticks = strtol(value, &end, 10); if (value)
if (*end != ',') break; {
value = end + 1; t.stage0_ticks = strtol(value, &end, 10);
t.stage3_ticks = strtol(value, &end, 10); if (*end != ',') break;
} value = end + 1;
value = metadata.enumValue(name + "line"); t.stage1_ticks = strtol(value, &end, 10);
if (value) if (*end != ',') break;
{ value = end + 1;
t.current_line = !!strtol(value, &end, 10); t.stage2_ticks = strtol(value, &end, 10);
} if (*end != ',') break;
} value = end + 1;
t.stage3_ticks = strtol(value, &end, 10);
smp.dsp.clock = META_ENUM_INT("dsp:clock", 0) * 4096; }
value = metadata.enumValue(name + "line");
smp.dsp.spc_dsp.m.echo_hist_pos = &smp.dsp.spc_dsp.m.echo_hist[META_ENUM_INT("dsp:echohistaddr", 0)]; if (value)
{
value = metadata.enumValue("dsp:echohistdata"); t.current_line = !!strtol(value, &end, 10);
if (value) }
{ }
for (int i = 0; i < 8; ++i)
{ smp.dsp.clock = META_ENUM_INT("dsp:clock", 0) * 4096;
smp.dsp.spc_dsp.m.echo_hist[i][0] = strtol(value, &end, 10);
value = strchr(value, ','); smp.dsp.spc_dsp.m.echo_hist_pos = &smp.dsp.spc_dsp.m.echo_hist[META_ENUM_INT("dsp:echohistaddr", 0)];
if (!value) break;
++value; value = metadata.enumValue("dsp:echohistdata");
smp.dsp.spc_dsp.m.echo_hist[i][1] = strtol(value, &end, 10); if (value)
value = strchr(value, ','); {
if (!value) break; for (int i = 0; i < 8; ++i)
++value; {
} smp.dsp.spc_dsp.m.echo_hist[i][0] = strtol(value, &end, 10);
} value = strchr(value, ',');
if (!value) break;
smp.dsp.spc_dsp.m.phase = META_ENUM_INT("dsp:sample", 0); ++value;
smp.dsp.spc_dsp.m.kon = META_ENUM_INT("dsp:kon", 0); smp.dsp.spc_dsp.m.echo_hist[i][1] = strtol(value, &end, 10);
smp.dsp.spc_dsp.m.noise = META_ENUM_INT("dsp:noise", 0); value = strchr(value, ',');
smp.dsp.spc_dsp.m.counter = META_ENUM_INT("dsp:counter", 0); if (!value) break;
smp.dsp.spc_dsp.m.echo_offset = META_ENUM_INT("dsp:echooffset", 0); ++value;
smp.dsp.spc_dsp.m.echo_length = META_ENUM_INT("dsp:echolength", 0); }
smp.dsp.spc_dsp.m.new_kon = META_ENUM_INT("dsp:koncache", 0); }
smp.dsp.spc_dsp.m.endx_buf = META_ENUM_INT("dsp:endx", 0);
smp.dsp.spc_dsp.m.envx_buf = META_ENUM_INT("dsp:envx", 0); smp.dsp.spc_dsp.m.phase = META_ENUM_INT("dsp:sample", 0);
smp.dsp.spc_dsp.m.outx_buf = META_ENUM_INT("dsp:outx", 0); smp.dsp.spc_dsp.m.kon = META_ENUM_INT("dsp:kon", 0);
smp.dsp.spc_dsp.m.t_pmon = META_ENUM_INT("dsp:pmon", 0); smp.dsp.spc_dsp.m.noise = META_ENUM_INT("dsp:noise", 0);
smp.dsp.spc_dsp.m.t_non = META_ENUM_INT("dsp:non", 0); smp.dsp.spc_dsp.m.counter = META_ENUM_INT("dsp:counter", 0);
smp.dsp.spc_dsp.m.t_eon = META_ENUM_INT("dsp:eon", 0); smp.dsp.spc_dsp.m.echo_offset = META_ENUM_INT("dsp:echooffset", 0);
smp.dsp.spc_dsp.m.t_dir = META_ENUM_INT("dsp:dir", 0); smp.dsp.spc_dsp.m.echo_length = META_ENUM_INT("dsp:echolength", 0);
smp.dsp.spc_dsp.m.t_koff = META_ENUM_INT("dsp:koff", 0); smp.dsp.spc_dsp.m.new_kon = META_ENUM_INT("dsp:koncache", 0);
smp.dsp.spc_dsp.m.t_brr_next_addr = META_ENUM_INT("dsp:brrnext", 0); smp.dsp.spc_dsp.m.endx_buf = META_ENUM_INT("dsp:endx", 0);
smp.dsp.spc_dsp.m.t_adsr0 = META_ENUM_INT("dsp:adsr0", 0); smp.dsp.spc_dsp.m.envx_buf = META_ENUM_INT("dsp:envx", 0);
smp.dsp.spc_dsp.m.t_brr_header = META_ENUM_INT("dsp:brrheader", 0); smp.dsp.spc_dsp.m.outx_buf = META_ENUM_INT("dsp:outx", 0);
smp.dsp.spc_dsp.m.t_brr_byte = META_ENUM_INT("dsp:brrdata", 0); smp.dsp.spc_dsp.m.t_pmon = META_ENUM_INT("dsp:pmon", 0);
smp.dsp.spc_dsp.m.t_srcn = META_ENUM_INT("dsp:srcn", 0); smp.dsp.spc_dsp.m.t_non = META_ENUM_INT("dsp:non", 0);
smp.dsp.spc_dsp.m.t_esa = META_ENUM_INT("dsp:esa", 0); smp.dsp.spc_dsp.m.t_eon = META_ENUM_INT("dsp:eon", 0);
smp.dsp.spc_dsp.m.t_echo_enabled = !META_ENUM_INT("dsp:echodisable", 0); smp.dsp.spc_dsp.m.t_dir = META_ENUM_INT("dsp:dir", 0);
smp.dsp.spc_dsp.m.t_dir_addr = META_ENUM_INT("dsp:diraddr", 0); smp.dsp.spc_dsp.m.t_koff = META_ENUM_INT("dsp:koff", 0);
smp.dsp.spc_dsp.m.t_pitch = META_ENUM_INT("dsp:pitch", 0); smp.dsp.spc_dsp.m.t_brr_next_addr = META_ENUM_INT("dsp:brrnext", 0);
smp.dsp.spc_dsp.m.t_output = META_ENUM_INT("dsp:output", 0); smp.dsp.spc_dsp.m.t_adsr0 = META_ENUM_INT("dsp:adsr0", 0);
smp.dsp.spc_dsp.m.t_looped = META_ENUM_INT("dsp:looped", 0); smp.dsp.spc_dsp.m.t_brr_header = META_ENUM_INT("dsp:brrheader", 0);
smp.dsp.spc_dsp.m.t_echo_ptr = META_ENUM_INT("dsp:echoaddr", 0); smp.dsp.spc_dsp.m.t_brr_byte = META_ENUM_INT("dsp:brrdata", 0);
smp.dsp.spc_dsp.m.t_srcn = META_ENUM_INT("dsp:srcn", 0);
smp.dsp.spc_dsp.m.t_esa = META_ENUM_INT("dsp:esa", 0);
#define META_ENUM_LEVELS(n, o) \ smp.dsp.spc_dsp.m.t_echo_enabled = !META_ENUM_INT("dsp:echodisable", 0);
value = metadata.enumValue(n); \ smp.dsp.spc_dsp.m.t_dir_addr = META_ENUM_INT("dsp:diraddr", 0);
if (value) \ smp.dsp.spc_dsp.m.t_pitch = META_ENUM_INT("dsp:pitch", 0);
{ \ smp.dsp.spc_dsp.m.t_output = META_ENUM_INT("dsp:output", 0);
(o)[0] = strtol(value, &end, 10); \ smp.dsp.spc_dsp.m.t_looped = META_ENUM_INT("dsp:looped", 0);
if (*end) \ smp.dsp.spc_dsp.m.t_echo_ptr = META_ENUM_INT("dsp:echoaddr", 0);
{ \
value = end + 1; \
(o)[1] = strtol(value, &end, 10); \ #define META_ENUM_LEVELS(n, o) \
} \ value = metadata.enumValue(n); \
} if (value) \
{ \
META_ENUM_LEVELS("dsp:mainout", smp.dsp.spc_dsp.m.t_main_out); (o)[0] = strtol(value, &end, 10); \
META_ENUM_LEVELS("dsp:echoout", smp.dsp.spc_dsp.m.t_echo_out); if (*end) \
META_ENUM_LEVELS("dsp:echoin", smp.dsp.spc_dsp.m.t_echo_in); { \
value = end + 1; \
#undef META_ENUM_LEVELS (o)[1] = strtol(value, &end, 10); \
} \
for (int i = 0; i < 8; ++i) }
{
oss.str(""); META_ENUM_LEVELS("dsp:mainout", smp.dsp.spc_dsp.m.t_main_out);
oss.clear(); META_ENUM_LEVELS("dsp:echoout", smp.dsp.spc_dsp.m.t_echo_out);
oss << "dsp:voice[" << i << "]:"; META_ENUM_LEVELS("dsp:echoin", smp.dsp.spc_dsp.m.t_echo_in);
name = oss.str();
SuperFamicom::SPC_DSP::voice_t & voice = smp.dsp.spc_dsp.m.voices[i]; #undef META_ENUM_LEVELS
value = metadata.enumValue(name + "brrhistaddr");
if (value) for (int i = 0; i < 8; ++i)
{ {
voice.buf_pos = strtol(value, &end, 10); oss.str("");
} oss.clear();
value = metadata.enumValue(name + "brrhistdata"); oss << "dsp:voice[" << i << "]:";
if (value) name = oss.str();
{ SuperFamicom::SPC_DSP::voice_t & voice = smp.dsp.spc_dsp.m.voices[i];
for (int j = 0; j < SuperFamicom::SPC_DSP::brr_buf_size; ++j) value = metadata.enumValue(name + "brrhistaddr");
{ if (value)
voice.buf[j] = voice.buf[j + SuperFamicom::SPC_DSP::brr_buf_size] = strtol(value, &end, 10); {
if (!*end) break; voice.buf_pos = strtol(value, &end, 10);
value = end + 1; }
} value = metadata.enumValue(name + "brrhistdata");
} if (value)
voice.interp_pos = META_ENUM_INT(name + "interpaddr",0); {
voice.brr_addr = META_ENUM_INT(name + "brraddr",0); for (int j = 0; j < SuperFamicom::SPC_DSP::brr_buf_size; ++j)
voice.brr_offset = META_ENUM_INT(name + "brroffset",0); {
voice.vbit = META_ENUM_INT(name + "vbit",0); voice.buf[j] = voice.buf[j + SuperFamicom::SPC_DSP::brr_buf_size] = strtol(value, &end, 10);
voice.regs = &smp.dsp.spc_dsp.m.regs[META_ENUM_INT(name + "vidx",0)]; if (!*end) break;
voice.kon_delay = META_ENUM_INT(name + "kondelay", 0); value = end + 1;
voice.env_mode = (SuperFamicom::SPC_DSP::env_mode_t) META_ENUM_INT(name + "envmode", 0); }
voice.env = META_ENUM_INT(name + "env", 0); }
voice.t_envx_out = META_ENUM_INT(name + "envxout", 0); voice.interp_pos = META_ENUM_INT(name + "interpaddr",0);
voice.hidden_env = META_ENUM_INT(name + "envcache", 0); voice.brr_addr = META_ENUM_INT(name + "brraddr",0);
} voice.brr_offset = META_ENUM_INT(name + "brroffset",0);
voice.vbit = META_ENUM_INT(name + "vbit",0);
filter.set_gain( (int) (gain() * SPC_Filter::gain_unit) ); voice.regs = &smp.dsp.spc_dsp.m.regs[META_ENUM_INT(name + "vidx",0)];
return 0; voice.kon_delay = META_ENUM_INT(name + "kondelay", 0);
} voice.env_mode = (SuperFamicom::SPC_DSP::env_mode_t) META_ENUM_INT(name + "envmode", 0);
voice.env = META_ENUM_INT(name + "env", 0);
#undef META_ENUM_INT voice.t_envx_out = META_ENUM_INT(name + "envxout", 0);
voice.hidden_env = META_ENUM_INT(name + "envcache", 0);
blargg_err_t Sfm_Emu::play_and_filter( long count, sample_t out [] ) }
{
smp.render( out, count ); filter.set_gain( (int) (gain() * SPC_Filter::gain_unit) );
if ( _enable_filter ) filter.run( out, count ); return 0;
return 0; }
}
#undef META_ENUM_INT
blargg_err_t Sfm_Emu::skip_( long count )
{ blargg_err_t Sfm_Emu::play_and_filter( long count, sample_t out [] )
if ( sample_rate() != native_sample_rate ) {
{ smp.render( out, count );
count = (long) (count * resampler.ratio()) & ~1; filter.run( out, count );
count -= resampler.skip_input( count ); return 0;
} }
// TODO: shouldn't skip be adjusted for the 64 samples read afterwards? blargg_err_t Sfm_Emu::skip_( long count )
{
if ( count > 0 ) if ( sample_rate() != native_sample_rate )
{ {
smp.skip( count ); count = long (count * resampler.ratio()) & ~1;
filter.clear(); count -= resampler.skip_input( count );
} }
if ( sample_rate() != native_sample_rate ) // TODO: shouldn't skip be adjusted for the 64 samples read afterwards?
{
// eliminate pop due to resampler if ( count > 0 )
const long resampler_latency = 64; {
sample_t buf [resampler_latency]; smp.skip( count );
return play_( resampler_latency, buf ); filter.clear();
} }
return 0; if ( sample_rate() != native_sample_rate )
} {
// eliminate pop due to resampler
blargg_err_t Sfm_Emu::play_( long count, sample_t out [] ) const long resampler_latency = 64;
{ sample_t buf [resampler_latency];
if ( sample_rate() == native_sample_rate ) return play_( resampler_latency, buf );
return play_and_filter( count, out ); }
long remain = count; return 0;
while ( remain > 0 ) }
{
remain -= resampler.read( &out [count - remain], remain ); blargg_err_t Sfm_Emu::play_( long count, sample_t out [] )
if ( remain > 0 ) {
{ if ( sample_rate() == native_sample_rate )
int n = resampler.max_write(); return play_and_filter( count, out );
RETURN_ERR( play_and_filter( n, resampler.buffer() ) );
resampler.write( n ); long remain = count;
} while ( remain > 0 )
} {
check( remain == 0 ); remain -= resampler.read( &out [count - remain], remain );
return 0; if ( remain > 0 )
} {
int n = resampler.max_write();
RETURN_ERR( play_and_filter( n, resampler.buffer() ) );
resampler.write( n );
}
}
check( remain == 0 );
return 0;
}

View file

@ -1,87 +1,78 @@
// Super Nintendo SFM music file emulator // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
// Game_Music_Emu $vers // Super Nintendo SFM music file emulator
#ifndef SPC_SFM_H
#define SPC_SFM_H #ifndef SPC_SFM_H
#define SPC_SFM_H
#include "Music_Emu.h"
#include "higan/smp/smp.hpp" #include "Fir_Resampler.h"
#include "Spc_Filter.h" #include "Music_Emu.h"
#include "../higan/smp/smp.hpp"
#include "Bml_Parser.h" #include "Spc_Filter.h"
#if GME_SPC_FAST_RESAMPLER #include "Bml_Parser.h"
#include "Upsampler.h"
typedef Upsampler Spc_Emu_Resampler; class Sfm_Emu : public Music_Emu {
#else public:
#include "Fir_Resampler.h" // Minimum allowed file size
typedef Fir_Resampler<24> Spc_Emu_Resampler; enum { sfm_min_file_size = 8 + 65536 + 128 };
#endif
// The Super Nintendo hardware samples at 32kHz. Other sample rates are
class Sfm_Emu : public Music_Emu { // handled by resampling the 32kHz output; emulation accuracy is not affected.
public: enum { native_sample_rate = 32000 };
// Minimum allowed file size
enum { sfm_min_file_size = 8 + 65536 + 128 }; // This will serialize the current state of the emulator into a new SFM file
blargg_err_t serialize( std::vector<uint8_t> & out );
// The Super Nintendo hardware samples at 32kHz. Other sample rates are
// handled by resampling the 32kHz output; emulation accuracy is not affected. // Disables annoying pseudo-surround effect some music uses
enum { native_sample_rate = 32000 }; void disable_surround( bool disable = true );
// This will serialize the current state of the emulator into a new SFM file // Enables gaussian=0, cubic=1 or sinc=2 interpolation
blargg_err_t serialize( std::vector<uint8_t> & out ); // Or sets worse quality, linear=-1, nearest=-2
void interpolation_level( int level = 0 );
// Disables annoying pseudo-surround effect some music uses
void disable_surround( bool disable = true ) { smp.dsp.disable_surround( disable ); } // Enables native echo
void enable_echo(bool enable = true);
// Enables gaussian, cubic or sinc interpolation void mute_effects(bool mute);
void interpolation_level( int level = 0 ) { smp.dsp.spc_dsp.interpolation_level( level ); }
SuperFamicom::SMP const* get_smp() const;
// Enables an analog signal simulation filter SuperFamicom::SMP * get_smp();
void enable_filter( bool enable = true ) { _enable_filter = enable; if (enable) filter.clear(); }
static gme_type_t static_type() { return gme_sfm_type; }
// Enables native echo
void enable_echo(bool enable = true) { smp.dsp.spc_dsp.enable_echo(enable); } public:
virtual void mute_effects(bool mute) { enable_echo(!mute); } Sfm_Emu();
~Sfm_Emu();
SuperFamicom::SMP const* get_smp() const;
SuperFamicom::SMP * get_smp(); protected:
blargg_err_t load_mem_( byte const [], long );
static gme_type_t static_type() { return gme_sfm_type; } blargg_err_t track_info_( track_info_t*, int track ) const;
blargg_err_t set_track_info_( const track_info_t*, int track );
// Implementation blargg_err_t set_sample_rate_( long );
public: blargg_err_t start_track_( int );
Sfm_Emu(); blargg_err_t play_( long, sample_t [] );
~Sfm_Emu(); blargg_err_t skip_( long );
void mute_voices_( int );
protected: void set_tempo_( double );
blargg_err_t load_mem_( byte const [], long ); void enable_accuracy_( bool );
blargg_err_t track_info_( track_info_t*, int track ) const; byte const* file_data;
blargg_err_t set_track_info_( const track_info_t*, int track ); long file_size;
blargg_err_t set_sample_rate_( long );
blargg_err_t start_track_( int ); private:
blargg_err_t play_( long, sample_t [] ); Fir_Resampler<24> resampler;
blargg_err_t skip_( long ); SPC_Filter filter;
void mute_voices_( int ); SuperFamicom::SMP smp;
void set_tempo_( double );
void enable_accuracy_( bool ); Bml_Parser metadata;
byte const* file_data;
long file_size; blargg_err_t play_and_filter( long count, sample_t out [] );
};
private:
Spc_Emu_Resampler resampler; inline void Sfm_Emu::disable_surround( bool disable ) { smp.dsp.disable_surround( disable ); }
SPC_Filter filter; inline void Sfm_Emu::interpolation_level( int level ) { smp.dsp.spc_dsp.interpolation_level( level ); }
SuperFamicom::SMP smp; 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); }
bool _enable_filter; inline SuperFamicom::SMP const* Sfm_Emu::get_smp() const { return &smp; }
inline SuperFamicom::SMP * Sfm_Emu::get_smp() { return &smp; }
Bml_Parser metadata;
#endif // SPC_SFM_H
blargg_err_t play_and_filter( long count, sample_t out [] );
};
inline SuperFamicom::SMP const* Sfm_Emu::get_smp() const { return &smp; }
inline SuperFamicom::SMP * Sfm_Emu::get_smp() { return &smp; }
inline void Sfm_Emu::enable_accuracy_(bool enable) { (void)enable; }
#endif // SPC_SFM_H

View file

@ -4,7 +4,7 @@
#define BLARGG_CONFIG_H #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,7 +55,8 @@ gme_type_t const* gme_type_list()
#endif #endif
#ifdef USE_GME_SPC #ifdef USE_GME_SPC
gme_spc_type, gme_spc_type,
gme_sfm_type, gme_rsn_type,
gme_sfm_type,
#endif #endif
#ifdef USE_GME_VGM #ifdef USE_GME_VGM
gme_vgm_type, gme_vgm_type,
@ -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,7 +195,8 @@ 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_sfm_type, gme_rsn_type,
gme_sfm_type,
gme_vgm_type, gme_vgm_type,
gme_vgz_type; gme_vgz_type;

View file

@ -3,8 +3,8 @@
#if !NSF_EMU_APU_ONLY #if !NSF_EMU_APU_ONLY
#include "Nes_Namco_Apu.h" #include "Nes_Namco_Apu.h"
#include "Nes_Fds_Apu.h" #include "Nes_Fds_Apu.h"
#include "Nes_Mmc5_Apu.h" #include "Nes_Mmc5_Apu.h"
#endif #endif
#include "blargg_source.h" #include "blargg_source.h"
@ -31,17 +31,17 @@ int Nsf_Emu::cpu_read( nes_addr_t addr )
#if !NSF_EMU_APU_ONLY #if !NSF_EMU_APU_ONLY
if ( addr == Nes_Namco_Apu::data_reg_addr && namco ) if ( addr == Nes_Namco_Apu::data_reg_addr && namco )
return namco->read_data(); return namco->read_data();
if ( (unsigned) (addr - Nes_Fds_Apu::io_addr) < Nes_Fds_Apu::io_size && fds ) if ( (unsigned) (addr - Nes_Fds_Apu::io_addr) < Nes_Fds_Apu::io_size && fds )
return fds->read( time(), addr ); return fds->read( time(), addr );
i = addr - 0x5C00; i = addr - 0x5C00;
if ( (unsigned) i < mmc5->exram_size && mmc5 ) if ( (unsigned) i < mmc5->exram_size && mmc5 )
return mmc5->exram [i]; return mmc5->exram [i];
i = addr - 0x5205; i = addr - 0x5205;
if ( (unsigned) i < 2 && mmc5 ) if ( (unsigned) i < 2 && mmc5 )
return ((mmc5_mul [0] * mmc5_mul [1]) >> (i * 8)) & 0xFF; return ((mmc5_mul [0] * mmc5_mul [1]) >> (i * 8)) & 0xFF;
#endif #endif
result = addr >> 8; // simulate open bus result = addr >> 8; // simulate open bus

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"