Clean up new GME somewhat

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

View File

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

View File

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

View File

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

View File

@ -183,24 +183,24 @@ blargg_err_t Gme_File::track_info( track_info_t* out, int track ) const
out->loop_length = -1;
out->intro_length = -1;
out->fade_length = -1;
out->play_length = -1;
out->repeat_count = -1;
out->play_length = -1;
out->repeat_count = -1;
out->song [0] = 0;
out->game [0] = 0;
out->author [0] = 0;
out->composer [0] = 0;
out->engineer [0] = 0;
out->sequencer [0] = 0;
out->tagger [0] = 0;
out->composer [0] = 0;
out->engineer [0] = 0;
out->sequencer [0] = 0;
out->tagger [0] = 0;
out->copyright [0] = 0;
out->date [0] = 0;
out->date [0] = 0;
out->comment [0] = 0;
out->dumper [0] = 0;
out->system [0] = 0;
out->disc [0] = 0;
out->track [0] = 0;
out->ost [0] = 0;
out->disc [0] = 0;
out->track [0] = 0;
out->ost [0] = 0;
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();
copy_field_( out->game , i.title );
copy_field_( out->author, i.artist );
copy_field_( out->engineer, i.engineer );
copy_field_( out->composer, i.composer );
copy_field_( out->sequencer, i.sequencer );
copy_field_( out->copyright, i.copyright );
copy_field_( out->dumper, i.ripping );
copy_field_( out->tagger, i.tagging );
copy_field_( out->date, i.date );
copy_field_( out->engineer, i.engineer );
copy_field_( out->composer, i.composer );
copy_field_( out->sequencer, i.sequencer );
copy_field_( out->copyright, i.copyright );
copy_field_( out->dumper, i.ripping );
copy_field_( out->tagger, i.tagging );
copy_field_( out->date, i.date );
M3u_Playlist::entry_t const& e = playlist [track];
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.loop >= 0 ) out->loop_length = e.loop;
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;
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,282 +1,282 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Nes_Fds_Apu.h"
/* 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
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"
#include <string.h>
int const fract_range = 65536;
void Nes_Fds_Apu::reset()
{
memset( regs_, 0, sizeof regs_ );
memset( mod_wave, 0, sizeof mod_wave );
last_time = 0;
env_delay = 0;
sweep_delay = 0;
wave_pos = 0;
last_amp = 0;
wave_fract = fract_range;
mod_fract = fract_range;
mod_pos = 0;
mod_write_pos = 0;
static byte const initial_regs [0x0B] = {
0x80, // disable envelope
0, 0, 0xC0, // disable wave and lfo
0x80, // disable sweep
0, 0, 0x80, // disable modulation
0, 0, 0xFF // LFO period // TODO: use 0xE8 as FDS ROM does?
};
for ( int i = 0; i < (int) sizeof initial_regs; i++ )
{
// two writes to set both gain and period for envelope registers
write_( io_addr + wave_size + i, 0 );
write_( io_addr + wave_size + i, initial_regs [i] );
}
}
void Nes_Fds_Apu::write_( unsigned addr, int data )
{
unsigned reg = addr - io_addr;
if ( reg < io_size )
{
if ( reg < wave_size )
{
if ( regs (0x4089) & 0x80 )
regs_ [reg] = data & wave_sample_max;
}
else
{
regs_ [reg] = data;
switch ( addr )
{
case 0x4080:
if ( data & 0x80 )
env_gain = data & 0x3F;
else
env_speed = (data & 0x3F) + 1;
break;
case 0x4084:
if ( data & 0x80 )
sweep_gain = data & 0x3F;
else
sweep_speed = (data & 0x3F) + 1;
break;
case 0x4085:
mod_pos = mod_write_pos;
regs (0x4085) = data & 0x7F;
break;
case 0x4088:
if ( regs (0x4087) & 0x80 )
{
int pos = mod_write_pos;
data &= 0x07;
mod_wave [pos ] = data;
mod_wave [pos + 1] = data;
mod_write_pos = (pos + 2) & (wave_size - 1);
mod_pos = (mod_pos + 2) & (wave_size - 1);
}
break;
}
}
}
}
void Nes_Fds_Apu::set_tempo( double t )
{
lfo_tempo = lfo_base_tempo;
if ( t != 1.0 )
{
lfo_tempo = int ((double) lfo_base_tempo / t + 0.5);
if ( lfo_tempo <= 0 )
lfo_tempo = 1;
}
}
void Nes_Fds_Apu::run_until( blip_time_t final_end_time )
{
int const wave_freq = (regs (0x4083) & 0x0F) * 0x100 + regs (0x4082);
Blip_Buffer* const output_ = this->output_;
if ( wave_freq && output_ && !((regs (0x4089) | regs (0x4083)) & 0x80) )
{
output_->set_modified();
// master_volume
#define MVOL_ENTRY( percent ) (master_vol_max * percent + 50) / 100
static unsigned char const master_volumes [4] = {
MVOL_ENTRY( 100 ), MVOL_ENTRY( 67 ), MVOL_ENTRY( 50 ), MVOL_ENTRY( 40 )
};
int const master_volume = master_volumes [regs (0x4089) & 0x03];
// lfo_period
blip_time_t lfo_period = regs (0x408A) * lfo_tempo;
if ( regs (0x4083) & 0x40 )
lfo_period = 0;
// sweep setup
blip_time_t sweep_time = last_time + sweep_delay;
blip_time_t const sweep_period = lfo_period * sweep_speed;
if ( !sweep_period || regs (0x4084) & 0x80 )
sweep_time = final_end_time;
// envelope setup
blip_time_t env_time = last_time + env_delay;
blip_time_t const env_period = lfo_period * env_speed;
if ( !env_period || regs (0x4080) & 0x80 )
env_time = final_end_time;
// modulation
int mod_freq = 0;
if ( !(regs (0x4087) & 0x80) )
mod_freq = (regs (0x4087) & 0x0F) * 0x100 + regs (0x4086);
blip_time_t end_time = last_time;
do
{
// sweep
if ( sweep_time <= end_time )
{
sweep_time += sweep_period;
int mode = regs (0x4084) >> 5 & 2;
int new_sweep_gain = sweep_gain + mode - 1;
if ( (unsigned) new_sweep_gain <= (unsigned) 0x80 >> mode )
sweep_gain = new_sweep_gain;
else
regs (0x4084) |= 0x80; // optimization only
}
// envelope
if ( env_time <= end_time )
{
env_time += env_period;
int mode = regs (0x4080) >> 5 & 2;
int new_env_gain = env_gain + mode - 1;
if ( (unsigned) new_env_gain <= (unsigned) 0x80 >> mode )
env_gain = new_env_gain;
else
regs (0x4080) |= 0x80; // optimization only
}
// new end_time
blip_time_t const start_time = end_time;
end_time = final_end_time;
if ( end_time > env_time ) end_time = env_time;
if ( end_time > sweep_time ) end_time = sweep_time;
// frequency modulation
int freq = wave_freq;
if ( mod_freq )
{
// time of next modulation clock
blip_time_t mod_time = start_time + (mod_fract + mod_freq - 1) / mod_freq;
if ( end_time > mod_time )
end_time = mod_time;
// run modulator up to next clock and save old sweep_bias
int sweep_bias = regs (0x4085);
mod_fract -= (end_time - start_time) * mod_freq;
if ( mod_fract <= 0 )
{
mod_fract += fract_range;
check( (unsigned) mod_fract <= fract_range );
static short const mod_table [8] = { 0, +1, +2, +4, 0, -4, -2, -1 };
int mod = mod_wave [mod_pos];
mod_pos = (mod_pos + 1) & (wave_size - 1);
int new_sweep_bias = (sweep_bias + mod_table [mod]) & 0x7F;
if ( mod == 4 )
new_sweep_bias = 0;
regs (0x4085) = new_sweep_bias;
}
// apply frequency modulation
sweep_bias = (sweep_bias ^ 0x40) - 0x40;
int factor = sweep_bias * sweep_gain;
int extra = factor & 0x0F;
factor >>= 4;
if ( extra )
{
factor--;
if ( sweep_bias >= 0 )
factor += 3;
}
if ( factor > 193 ) factor -= 258;
if ( factor < -64 ) factor += 256;
freq += (freq * factor) >> 6;
if ( freq <= 0 )
continue;
}
// wave
int wave_fract = this->wave_fract;
blip_time_t delay = (wave_fract + freq - 1) / freq;
blip_time_t time = start_time + delay;
if ( time <= end_time )
{
// at least one wave clock within start_time...end_time
blip_time_t const min_delay = fract_range / freq;
int wave_pos = this->wave_pos;
int volume = env_gain;
if ( volume > vol_max )
volume = vol_max;
volume *= master_volume;
int const min_fract = min_delay * freq;
do
{
// clock wave
int amp = regs_ [wave_pos] * volume;
wave_pos = (wave_pos + 1) & (wave_size - 1);
int delta = amp - last_amp;
if ( delta )
{
last_amp = amp;
synth.offset_inline( time, delta, output_ );
}
wave_fract += fract_range - delay * freq;
check( unsigned (fract_range - wave_fract) < freq );
// delay until next clock
delay = min_delay;
if ( wave_fract > min_fract )
delay++;
check( delay && delay == (wave_fract + freq - 1) / freq );
time += delay;
}
while ( time <= end_time ); // TODO: using < breaks things, but <= is wrong
this->wave_pos = wave_pos;
}
this->wave_fract = wave_fract - (end_time - (time - delay)) * freq;
check( this->wave_fract > 0 );
}
while ( end_time < final_end_time );
env_delay = env_time - final_end_time; check( env_delay >= 0 );
sweep_delay = sweep_time - final_end_time; check( sweep_delay >= 0 );
}
last_time = final_end_time;
}
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Nes_Fds_Apu.h"
/* 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
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"
#include <string.h>
int const fract_range = 65536;
void Nes_Fds_Apu::reset()
{
memset( regs_, 0, sizeof regs_ );
memset( mod_wave, 0, sizeof mod_wave );
last_time = 0;
env_delay = 0;
sweep_delay = 0;
wave_pos = 0;
last_amp = 0;
wave_fract = fract_range;
mod_fract = fract_range;
mod_pos = 0;
mod_write_pos = 0;
static byte const initial_regs [0x0B] = {
0x80, // disable envelope
0, 0, 0xC0, // disable wave and lfo
0x80, // disable sweep
0, 0, 0x80, // disable modulation
0, 0, 0xFF // LFO period // TODO: use 0xE8 as FDS ROM does?
};
for ( int i = 0; i < (int) sizeof initial_regs; i++ )
{
// two writes to set both gain and period for envelope registers
write_( io_addr + wave_size + i, 0 );
write_( io_addr + wave_size + i, initial_regs [i] );
}
}
void Nes_Fds_Apu::write_( unsigned addr, int data )
{
unsigned reg = addr - io_addr;
if ( reg < io_size )
{
if ( reg < wave_size )
{
if ( regs (0x4089) & 0x80 )
regs_ [reg] = data & wave_sample_max;
}
else
{
regs_ [reg] = data;
switch ( addr )
{
case 0x4080:
if ( data & 0x80 )
env_gain = data & 0x3F;
else
env_speed = (data & 0x3F) + 1;
break;
case 0x4084:
if ( data & 0x80 )
sweep_gain = data & 0x3F;
else
sweep_speed = (data & 0x3F) + 1;
break;
case 0x4085:
mod_pos = mod_write_pos;
regs (0x4085) = data & 0x7F;
break;
case 0x4088:
if ( regs (0x4087) & 0x80 )
{
int pos = mod_write_pos;
data &= 0x07;
mod_wave [pos ] = data;
mod_wave [pos + 1] = data;
mod_write_pos = (pos + 2) & (wave_size - 1);
mod_pos = (mod_pos + 2) & (wave_size - 1);
}
break;
}
}
}
}
void Nes_Fds_Apu::set_tempo( double t )
{
lfo_tempo = lfo_base_tempo;
if ( t != 1.0 )
{
lfo_tempo = int ((double) lfo_base_tempo / t + 0.5);
if ( lfo_tempo <= 0 )
lfo_tempo = 1;
}
}
void Nes_Fds_Apu::run_until( blip_time_t final_end_time )
{
int const wave_freq = (regs (0x4083) & 0x0F) * 0x100 + regs (0x4082);
Blip_Buffer* const output_ = this->output_;
if ( wave_freq && output_ && !((regs (0x4089) | regs (0x4083)) & 0x80) )
{
output_->set_modified();
// master_volume
#define MVOL_ENTRY( percent ) (master_vol_max * percent + 50) / 100
static unsigned char const master_volumes [4] = {
MVOL_ENTRY( 100 ), MVOL_ENTRY( 67 ), MVOL_ENTRY( 50 ), MVOL_ENTRY( 40 )
};
int const master_volume = master_volumes [regs (0x4089) & 0x03];
// lfo_period
blip_time_t lfo_period = regs (0x408A) * lfo_tempo;
if ( regs (0x4083) & 0x40 )
lfo_period = 0;
// sweep setup
blip_time_t sweep_time = last_time + sweep_delay;
blip_time_t const sweep_period = lfo_period * sweep_speed;
if ( !sweep_period || regs (0x4084) & 0x80 )
sweep_time = final_end_time;
// envelope setup
blip_time_t env_time = last_time + env_delay;
blip_time_t const env_period = lfo_period * env_speed;
if ( !env_period || regs (0x4080) & 0x80 )
env_time = final_end_time;
// modulation
int mod_freq = 0;
if ( !(regs (0x4087) & 0x80) )
mod_freq = (regs (0x4087) & 0x0F) * 0x100 + regs (0x4086);
blip_time_t end_time = last_time;
do
{
// sweep
if ( sweep_time <= end_time )
{
sweep_time += sweep_period;
int mode = regs (0x4084) >> 5 & 2;
int new_sweep_gain = sweep_gain + mode - 1;
if ( (unsigned) new_sweep_gain <= (unsigned) 0x80 >> mode )
sweep_gain = new_sweep_gain;
else
regs (0x4084) |= 0x80; // optimization only
}
// envelope
if ( env_time <= end_time )
{
env_time += env_period;
int mode = regs (0x4080) >> 5 & 2;
int new_env_gain = env_gain + mode - 1;
if ( (unsigned) new_env_gain <= (unsigned) 0x80 >> mode )
env_gain = new_env_gain;
else
regs (0x4080) |= 0x80; // optimization only
}
// new end_time
blip_time_t const start_time = end_time;
end_time = final_end_time;
if ( end_time > env_time ) end_time = env_time;
if ( end_time > sweep_time ) end_time = sweep_time;
// frequency modulation
int freq = wave_freq;
if ( mod_freq )
{
// time of next modulation clock
blip_time_t mod_time = start_time + (mod_fract + mod_freq - 1) / mod_freq;
if ( end_time > mod_time )
end_time = mod_time;
// run modulator up to next clock and save old sweep_bias
int sweep_bias = regs (0x4085);
mod_fract -= (end_time - start_time) * mod_freq;
if ( mod_fract <= 0 )
{
mod_fract += fract_range;
check( (unsigned) mod_fract <= fract_range );
static short const mod_table [8] = { 0, +1, +2, +4, 0, -4, -2, -1 };
int mod = mod_wave [mod_pos];
mod_pos = (mod_pos + 1) & (wave_size - 1);
int new_sweep_bias = (sweep_bias + mod_table [mod]) & 0x7F;
if ( mod == 4 )
new_sweep_bias = 0;
regs (0x4085) = new_sweep_bias;
}
// apply frequency modulation
sweep_bias = (sweep_bias ^ 0x40) - 0x40;
int factor = sweep_bias * sweep_gain;
int extra = factor & 0x0F;
factor >>= 4;
if ( extra )
{
factor--;
if ( sweep_bias >= 0 )
factor += 3;
}
if ( factor > 193 ) factor -= 258;
if ( factor < -64 ) factor += 256;
freq += (freq * factor) >> 6;
if ( freq <= 0 )
continue;
}
// wave
int wave_fract = this->wave_fract;
blip_time_t delay = (wave_fract + freq - 1) / freq;
blip_time_t time = start_time + delay;
if ( time <= end_time )
{
// at least one wave clock within start_time...end_time
blip_time_t const min_delay = fract_range / freq;
int wave_pos = this->wave_pos;
int volume = env_gain;
if ( volume > vol_max )
volume = vol_max;
volume *= master_volume;
int const min_fract = min_delay * freq;
do
{
// clock wave
int amp = regs_ [wave_pos] * volume;
wave_pos = (wave_pos + 1) & (wave_size - 1);
int delta = amp - last_amp;
if ( delta )
{
last_amp = amp;
synth.offset_inline( time, delta, output_ );
}
wave_fract += fract_range - delay * freq;
check( unsigned (fract_range - wave_fract) < freq );
// delay until next clock
delay = min_delay;
if ( wave_fract > min_fract )
delay++;
check( delay && delay == (wave_fract + freq - 1) / freq );
time += delay;
}
while ( time <= end_time ); // TODO: using < breaks things, but <= is wrong
this->wave_pos = wave_pos;
}
this->wave_fract = wave_fract - (end_time - (time - delay)) * freq;
check( this->wave_fract > 0 );
}
while ( end_time < final_end_time );
env_delay = env_time - final_end_time; check( env_delay >= 0 );
sweep_delay = sweep_time - final_end_time; check( sweep_delay >= 0 );
}
last_time = final_end_time;
}

View File

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

View File

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

View File

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

View File

@ -1,82 +1,84 @@
// Konami VRC7 sound chip emulator
#ifndef NES_VRC7_APU_H
#define NES_VRC7_APU_H
#include "blargg_common.h"
#include "Blip_Buffer.h"
struct vrc7_snapshot_t;
class Nes_Vrc7_Apu {
public:
blargg_err_t init();
// See Nes_Apu.h for reference
void reset();
void volume( double );
void treble_eq( blip_eq_t const& );
void set_output( Blip_Buffer* );
enum { osc_count = 6 };
void osc_output( int index, Blip_Buffer* );
void end_frame( blip_time_t );
void save_snapshot( vrc7_snapshot_t* ) const;
void load_snapshot( vrc7_snapshot_t const& );
void write_reg( int reg );
void write_data( blip_time_t, int data );
public:
Nes_Vrc7_Apu();
~Nes_Vrc7_Apu();
BLARGG_DISABLE_NOTHROW
private:
// noncopyable
Nes_Vrc7_Apu( const Nes_Vrc7_Apu& );
Nes_Vrc7_Apu& operator = ( const Nes_Vrc7_Apu& );
struct Vrc7_Osc
{
uint8_t regs [3];
Blip_Buffer* output;
int last_amp;
};
Vrc7_Osc oscs [osc_count];
uint8_t kon;
uint8_t inst [8];
void* opll;
int addr;
blip_time_t next_time;
struct {
Blip_Buffer* output;
int last_amp;
} mono;
Blip_Synth<blip_med_quality,1> synth;
void run_until( blip_time_t );
void output_changed();
};
struct vrc7_snapshot_t
{
uint8_t latch;
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 );
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 ); }
inline void Nes_Vrc7_Apu::treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
#endif
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
// Konami VRC7 sound chip emulator
#ifndef NES_VRC7_APU_H
#define NES_VRC7_APU_H
#include "blargg_common.h"
#include "Blip_Buffer.h"
struct vrc7_snapshot_t;
class Nes_Vrc7_Apu {
public:
blargg_err_t init();
// See Nes_Apu.h for reference
void reset();
void volume( double );
void treble_eq( blip_eq_t const& );
void set_output( Blip_Buffer* );
enum { osc_count = 6 };
void osc_output( int index, Blip_Buffer* );
void end_frame( blip_time_t );
void save_snapshot( vrc7_snapshot_t* ) const;
void load_snapshot( vrc7_snapshot_t const& );
void write_reg( int reg );
void write_data( blip_time_t, int data );
public:
Nes_Vrc7_Apu();
~Nes_Vrc7_Apu();
BLARGG_DISABLE_NOTHROW
private:
// noncopyable
Nes_Vrc7_Apu( const Nes_Vrc7_Apu& );
Nes_Vrc7_Apu& operator = ( const Nes_Vrc7_Apu& );
struct Vrc7_Osc
{
uint8_t regs [3];
Blip_Buffer* output;
int last_amp;
};
Vrc7_Osc oscs [osc_count];
uint8_t kon;
uint8_t inst [8];
void* opll;
int addr;
blip_time_t next_time;
struct {
Blip_Buffer* output;
int last_amp;
} mono;
Blip_Synth<blip_med_quality,1> synth;
void run_until( blip_time_t );
void output_changed();
};
struct vrc7_snapshot_t
{
uint8_t latch;
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 );
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 ); }
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_Vrc6_Apu.h"
#include "Nes_Fme7_Apu.h"
#include "Nes_Fds_Apu.h"
#include "Nes_Mmc5_Apu.h"
#include "Nes_Vrc7_Apu.h"
#include "Nes_Fds_Apu.h"
#include "Nes_Mmc5_Apu.h"
#include "Nes_Vrc7_Apu.h"
#endif
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
@ -56,12 +56,10 @@ Nsf_Emu::Nsf_Emu()
vrc6 = 0;
namco = 0;
fme7 = 0;
fds = 0;
mmc5 = 0;
vrc7 = 0;
apu_names = 0;
fds = 0;
mmc5 = 0;
vrc7 = 0;
set_type( gme_nsf_type );
set_silence_lookahead( 6 );
apu.dmc_reader( pcm_read, this );
@ -84,22 +82,17 @@ void Nsf_Emu::unload()
delete fme7;
fme7 = 0;
delete fds;
fds = 0;
delete mmc5;
mmc5 = 0;
delete vrc7;
vrc7 = 0;
delete fds;
fds = 0;
delete mmc5;
mmc5 = 0;
delete vrc7;
vrc7 = 0;
}
#endif
{
delete [] apu_names;
apu_names = 0;
}
rom.clear();
Music_Emu::unload();
@ -194,31 +187,26 @@ blargg_err_t Nsf_Emu::init_sound()
set_warning( "Uses unsupported audio expansion hardware" );
#ifdef NSF_EMU_APU_ONLY
int const count_total = Nes_Apu::osc_count;
int const count_total = Nes_Apu::osc_count;
#else
int const count_total = Nes_Apu::osc_count + Nes_Namco_Apu::osc_count +
Nes_Vrc6_Apu::osc_count + Nes_Fme7_Apu::osc_count +
Nes_Fds_Apu::osc_count + Nes_Mmc5_Apu::osc_count +
Nes_Vrc7_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_Fds_Apu::osc_count + Nes_Mmc5_Apu::osc_count +
Nes_Vrc7_Apu::osc_count;
#endif
if ( apu_names )
{
delete [] apu_names;
}
apu_names = new char* [count_total];
int count = 0;
apu_names.resize( count_total );
int count = 0;
{
apu_names[count + 0] = "Square 1";
apu_names[count + 1] = "Square 2";
apu_names[count + 2] = "Triangle";
apu_names[count + 3] = "Noise";
apu_names[count + 4] = "DMC";
count += Nes_Apu::osc_count;
}
apu_names[count + 0] = "Square 1";
apu_names[count + 1] = "Square 2";
apu_names[count + 2] = "Triangle";
apu_names[count + 3] = "Noise";
apu_names[count + 4] = "DMC";
count += Nes_Apu::osc_count;
}
static int const types [] = {
wave_type | 1, wave_type | 2, wave_type | 0,
@ -238,35 +226,35 @@ blargg_err_t Nsf_Emu::init_sound()
}
#else
{
if ( header_.chip_flags & vrc6_flag )
{
vrc6 = BLARGG_NEW Nes_Vrc6_Apu;
CHECK_ALLOC( vrc6 );
adjusted_gain *= 0.75;
if ( header_.chip_flags & vrc6_flag )
{
vrc6 = BLARGG_NEW Nes_Vrc6_Apu;
CHECK_ALLOC( vrc6 );
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 )
{
namco = BLARGG_NEW Nes_Namco_Apu;
CHECK_ALLOC( namco );
adjusted_gain *= 0.75;
apu_names[count + 0] = "Wave 1";
apu_names[count + 1] = "Wave 2";
apu_names[count + 2] = "Wave 3";
apu_names[count + 3] = "Wave 4";
apu_names[count + 4] = "Wave 5";
apu_names[count + 5] = "Wave 6";
apu_names[count + 6] = "Wave 7";
apu_names[count + 7] = "Wave 8";
count += Nes_Namco_Apu::osc_count;
apu_names[count + 0] = "Wave 1";
apu_names[count + 1] = "Wave 2";
apu_names[count + 2] = "Wave 3";
apu_names[count + 3] = "Wave 4";
apu_names[count + 4] = "Wave 5";
apu_names[count + 5] = "Wave 6";
apu_names[count + 6] = "Wave 7";
apu_names[count + 7] = "Wave 8";
count += Nes_Namco_Apu::osc_count;
}
if ( header_.chip_flags & fme7_flag )
@ -274,64 +262,64 @@ blargg_err_t Nsf_Emu::init_sound()
fme7 = BLARGG_NEW Nes_Fme7_Apu;
CHECK_ALLOC( fme7 );
adjusted_gain *= 0.75;
apu_names[count + 0] = "Square 3";
apu_names[count + 1] = "Square 4";
apu_names[count + 2] = "Square 5";
count += Nes_Fme7_Apu::osc_count;
apu_names[count + 0] = "Square 3";
apu_names[count + 1] = "Square 4";
apu_names[count + 2] = "Square 5";
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";
count += Nes_Fds_Apu::osc_count;
}
if ( header_.chip_flags & mmc5_flag )
{
mmc5 = BLARGG_NEW Nes_Mmc5_Apu;
CHECK_ALLOC( mmc5 );
adjusted_gain *= 0.75;
apu_names[count + 0] = "Square 3";
apu_names[count + 1] = "Square 4";
apu_names[count + 2] = "PCM";
count += Nes_Mmc5_Apu::osc_count;
}
if ( header_.chip_flags & vrc7_flag )
{
vrc7 = BLARGG_NEW Nes_Vrc7_Apu;
CHECK_ALLOC( vrc7 );
RETURN_ERR( vrc7->init() );
adjusted_gain *= 0.75;
apu_names[count + 0] = "FM 1";
apu_names[count + 1] = "FM 2";
apu_names[count + 2] = "FM 3";
apu_names[count + 3] = "FM 4";
apu_names[count + 4] = "FM 5";
apu_names[count + 5] = "FM 6";
count += Nes_Vrc7_Apu::osc_count;
}
set_voice_count( count );
set_voice_names( apu_names );
if ( header_.chip_flags & fds_flag )
{
fds = BLARGG_NEW Nes_Fds_Apu;
CHECK_ALLOC( fds );
adjusted_gain *= 0.75;
apu_names[count + 0] = "Wave";
count += Nes_Fds_Apu::osc_count;
}
if ( header_.chip_flags & mmc5_flag )
{
mmc5 = BLARGG_NEW Nes_Mmc5_Apu;
CHECK_ALLOC( mmc5 );
adjusted_gain *= 0.75;
apu_names[count + 0] = "Square 3";
apu_names[count + 1] = "Square 4";
apu_names[count + 2] = "PCM";
count += Nes_Mmc5_Apu::osc_count;
}
if ( header_.chip_flags & vrc7_flag )
{
vrc7 = BLARGG_NEW Nes_Vrc7_Apu;
CHECK_ALLOC( vrc7 );
RETURN_ERR( vrc7->init() );
adjusted_gain *= 0.75;
apu_names[count + 0] = "FM 1";
apu_names[count + 1] = "FM 2";
apu_names[count + 2] = "FM 3";
apu_names[count + 3] = "FM 4";
apu_names[count + 4] = "FM 5";
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 ( vrc6 ) vrc6 ->volume( adjusted_gain );
if ( fme7 ) fme7 ->volume( adjusted_gain );
if ( fds ) fds ->volume( adjusted_gain );
if ( mmc5 ) mmc5 ->volume( adjusted_gain );
if ( vrc7 ) vrc7 ->volume( adjusted_gain );
if ( fds ) fds ->volume( adjusted_gain );
if ( mmc5 ) mmc5 ->volume( adjusted_gain );
if ( vrc7 ) vrc7 ->volume( adjusted_gain );
}
#endif
@ -411,9 +399,9 @@ void Nsf_Emu::update_eq( blip_eq_t const& eq )
if ( namco ) namco->treble_eq( eq );
if ( vrc6 ) vrc6 ->treble_eq( eq );
if ( fme7 ) fme7 ->treble_eq( eq );
if ( fds ) fds ->treble_eq( eq );
if ( mmc5 ) mmc5 ->treble_eq( eq );
if ( vrc7 ) vrc7 ->treble_eq( eq );
if ( fds ) fds ->treble_eq( eq );
if ( mmc5 ) mmc5 ->treble_eq( eq );
if ( vrc7 ) vrc7 ->treble_eq( eq );
}
#endif
}
@ -428,70 +416,39 @@ void Nsf_Emu::set_voice( int i, Blip_Buffer* buf, Blip_Buffer*, Blip_Buffer* )
i -= Nes_Apu::osc_count;
#if !NSF_EMU_APU_ONLY
#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 )
{
if ( i < Nes_Vrc6_Apu::osc_count )
{
// put saw first
if ( --i < 0 )
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;
}
HANDLE_CHIP_VRC6(Nes_Vrc6_Apu, vrc6);
HANDLE_CHIP(Nes_Namco_Apu, namco);
HANDLE_CHIP(Nes_Fme7_Apu, fme7);
HANDLE_CHIP(Nes_Fds_Apu, fds);
HANDLE_CHIP(Nes_Mmc5_Apu, mmc5);
HANDLE_CHIP(Nes_Vrc7_Apu, vrc7);
}
#undef HANDLE_CHIP
#undef HANDLE_CHIP_VRC6
#endif
}
@ -503,15 +460,15 @@ void Nsf_Emu::cpu_write_misc( nes_addr_t addr, int data )
{
#if !NSF_EMU_APU_ONLY
{
if ( fds )
{
if ( (unsigned) (addr - fds->io_addr) < fds->io_size )
{
fds->write( time(), addr, data);
return;
}
}
if ( fds )
{
if ( (unsigned) (addr - fds->io_addr) < fds->io_size )
{
fds->write( time(), addr, data);
return;
}
}
if ( namco )
{
switch ( addr )
@ -550,44 +507,44 @@ void Nsf_Emu::cpu_write_misc( nes_addr_t addr, int data )
return;
}
}
if ( mmc5 )
{
if ( (unsigned) (addr - mmc5->regs_addr) < mmc5->regs_size)
{
mmc5->write_register( time(), addr, data );
return;
}
int m = addr - 0x5205;
if ( (unsigned) m < 2 )
{
mmc5_mul [m] = data;
return;
}
int i = addr - 0x5C00;
if ( (unsigned) i < mmc5->exram_size )
{
mmc5->exram [i] = data;
return;
}
}
if ( vrc7 )
{
if ( addr == 0x9010 )
{
vrc7->write_reg( data );
return;
}
if ( (unsigned) (addr - 0x9028) <= 0x08 )
{
vrc7->write_data( time(), data );
return;
}
}
if ( mmc5 )
{
if ( (unsigned) (addr - mmc5->regs_addr) < mmc5->regs_size)
{
mmc5->write_register( time(), addr, data );
return;
}
int m = addr - 0x5205;
if ( (unsigned) m < 2 )
{
mmc5_mul [m] = data;
return;
}
int i = addr - 0x5C00;
if ( (unsigned) i < mmc5->exram_size )
{
mmc5->exram [i] = data;
return;
}
}
if ( vrc7 )
{
if ( addr == 0x9010 )
{
vrc7->write_reg( data );
return;
}
if ( (unsigned) (addr - 0x9028) <= 0x08 )
{
vrc7->write_data( time(), data );
return;
}
}
}
#endif
@ -625,20 +582,20 @@ blargg_err_t Nsf_Emu::start_track_( int track )
apu.write_register( 0, 0x4015, 0x0F );
apu.write_register( 0, 0x4017, (header_.speed_flags & 0x10) ? 0x80 : 0 );
#if !NSF_EMU_APU_ONLY
if ( mmc5 )
{
mmc5_mul [0] = 0;
mmc5_mul [1] = 0;
memset( mmc5->exram, 0, mmc5->exram_size );
}
if ( mmc5 )
{
mmc5_mul [0] = 0;
mmc5_mul [1] = 0;
memset( mmc5->exram, 0, mmc5->exram_size );
}
{
if ( namco ) namco->reset();
if ( vrc6 ) vrc6 ->reset();
if ( fme7 ) fme7 ->reset();
if ( fds ) fds ->reset();
if ( mmc5 ) mmc5 ->reset();
if ( vrc7 ) vrc7 ->reset();
if ( fds ) fds ->reset();
if ( mmc5 ) mmc5 ->reset();
if ( vrc7 ) vrc7 ->reset();
}
#endif
@ -724,9 +681,9 @@ blargg_err_t Nsf_Emu::run_clocks( blip_time_t& duration, int )
if ( namco ) namco->end_frame( duration );
if ( vrc6 ) vrc6 ->end_frame( duration );
if ( fme7 ) fme7 ->end_frame( duration );
if ( fds ) fds ->end_frame( duration );
if ( mmc5 ) mmc5 ->end_frame( duration );
if ( vrc7 ) vrc7 ->end_frame( duration );
if ( fds ) fds ->end_frame( duration );
if ( mmc5 ) mmc5 ->end_frame( duration );
if ( vrc7 ) vrc7 ->end_frame( duration );
}
#endif

View File

@ -89,16 +89,16 @@ public: private: friend class Nes_Cpu;
enum { badop_addr = bank_select_addr };
private:
byte mmc5_mul [2];
byte mmc5_mul [2];
class Nes_Namco_Apu* namco;
class Nes_Vrc6_Apu* vrc6;
class Nes_Fme7_Apu* fme7;
class Nes_Fds_Apu* fds;
class Nes_Mmc5_Apu* mmc5;
class Nes_Vrc7_Apu* vrc7;
class Nes_Fds_Apu* fds;
class Nes_Mmc5_Apu* mmc5;
class Nes_Vrc7_Apu* vrc7;
Nes_Apu apu;
char** apu_names;
blargg_vector<const char*> apu_names;
static int pcm_read( void*, nes_addr_t );
blargg_err_t init_sound();

View File

@ -0,0 +1,383 @@
// SPC emulation support: init, sample buffering, reset, SPC loading
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Snes_Spc.h"
#include <string.h>
/* Copyright (C) 2004-2007 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
#define RAM (m.ram.ram)
#define REGS (m.smp_regs [0])
#define REGS_IN (m.smp_regs [1])
// (n ? n : 256)
#define IF_0_THEN_256( n ) ((uint8_t) ((n) - 1) + 1)
//// Init
blargg_err_t Snes_Spc::init()
{
memset( &m, 0, sizeof m );
dsp.init( RAM );
m.tempo = tempo_unit;
// Most SPC music doesn't need ROM, and almost all the rest only rely
// on these two bytes
m.rom [0x3E] = 0xFF;
m.rom [0x3F] = 0xC0;
static unsigned char const cycle_table [128] =
{// 01 23 45 67 89 AB CD EF
0x28,0x47,0x34,0x36,0x26,0x54,0x54,0x68, // 0
0x48,0x47,0x45,0x56,0x55,0x65,0x22,0x46, // 1
0x28,0x47,0x34,0x36,0x26,0x54,0x54,0x74, // 2
0x48,0x47,0x45,0x56,0x55,0x65,0x22,0x38, // 3
0x28,0x47,0x34,0x36,0x26,0x44,0x54,0x66, // 4
0x48,0x47,0x45,0x56,0x55,0x45,0x22,0x43, // 5
0x28,0x47,0x34,0x36,0x26,0x44,0x54,0x75, // 6
0x48,0x47,0x45,0x56,0x55,0x55,0x22,0x36, // 7
0x28,0x47,0x34,0x36,0x26,0x54,0x52,0x45, // 8
0x48,0x47,0x45,0x56,0x55,0x55,0x22,0xC5, // 9
0x38,0x47,0x34,0x36,0x26,0x44,0x52,0x44, // A
0x48,0x47,0x45,0x56,0x55,0x55,0x22,0x34, // B
0x38,0x47,0x45,0x47,0x25,0x64,0x52,0x49, // C
0x48,0x47,0x56,0x67,0x45,0x55,0x22,0x83, // D
0x28,0x47,0x34,0x36,0x24,0x53,0x43,0x40, // E
0x48,0x47,0x45,0x56,0x34,0x54,0x22,0x60, // F
};
// unpack cycle table
for ( int i = 0; i < 128; i++ )
{
int n = cycle_table [i];
m.cycle_table [i * 2 + 0] = n >> 4;
m.cycle_table [i * 2 + 1] = n & 0x0F;
}
#if SPC_LESS_ACCURATE
memcpy( reg_times, reg_times_, sizeof reg_times );
#endif
reset();
return 0;
}
void Snes_Spc::init_rom( uint8_t const in [rom_size] )
{
memcpy( m.rom, in, sizeof m.rom );
}
void Snes_Spc::set_tempo( int t )
{
m.tempo = t;
int const timer2_shift = 4; // 64 kHz
int const other_shift = 3; // 8 kHz
#if SPC_DISABLE_TEMPO
m.timers [2].prescaler = timer2_shift;
m.timers [1].prescaler = timer2_shift + other_shift;
m.timers [0].prescaler = timer2_shift + other_shift;
#else
if ( !t )
t = 1;
int const timer2_rate = 1 << timer2_shift;
int rate = (timer2_rate * tempo_unit + (t >> 1)) / t;
if ( rate < timer2_rate / 4 )
rate = timer2_rate / 4; // max 4x tempo
m.timers [2].prescaler = rate;
m.timers [1].prescaler = rate << other_shift;
m.timers [0].prescaler = rate << other_shift;
#endif
}
// Timer registers have been loaded. Applies these to the timers. Does not
// reset timer prescalers or dividers.
void Snes_Spc::timers_loaded()
{
int i;
for ( i = 0; i < timer_count; i++ )
{
Timer* t = &m.timers [i];
t->period = IF_0_THEN_256( REGS [r_t0target + i] );
t->enabled = REGS [r_control] >> i & 1;
t->counter = REGS_IN [r_t0out + i] & 0x0F;
}
set_tempo( m.tempo );
}
// Loads registers from unified 16-byte format
void Snes_Spc::load_regs( uint8_t const in [reg_count] )
{
memcpy( REGS, in, reg_count );
memcpy( REGS_IN, REGS, reg_count );
// These always read back as 0
REGS_IN [r_test ] = 0;
REGS_IN [r_control ] = 0;
REGS_IN [r_t0target] = 0;
REGS_IN [r_t1target] = 0;
REGS_IN [r_t2target] = 0;
}
// RAM was just loaded from SPC, with $F0-$FF containing SMP registers
// and timer counts. Copies these to proper registers.
void Snes_Spc::ram_loaded()
{
m.rom_enabled = 0;
load_regs( &RAM [0xF0] );
// Put STOP instruction around memory to catch PC underflow/overflow
memset( m.ram.padding1, cpu_pad_fill, sizeof m.ram.padding1 );
memset( m.ram.ram + 0x10000, cpu_pad_fill, sizeof m.ram.padding1 );
}
// Registers were just loaded. Applies these new values.
void Snes_Spc::regs_loaded()
{
enable_rom( REGS [r_control] & 0x80 );
timers_loaded();
}
void Snes_Spc::reset_time_regs()
{
m.cpu_error = 0;
m.echo_accessed = 0;
m.spc_time = 0;
m.dsp_time = 0;
#if SPC_LESS_ACCURATE
m.dsp_time = clocks_per_sample + 1;
#endif
for ( int i = 0; i < timer_count; i++ )
{
Timer* t = &m.timers [i];
t->next_time = 1;
t->divider = 0;
}
regs_loaded();
m.extra_clocks = 0;
reset_buf();
}
void Snes_Spc::reset_common( int timer_counter_init )
{
int i;
for ( i = 0; i < timer_count; i++ )
REGS_IN [r_t0out + i] = timer_counter_init;
// Run IPL ROM
memset( &m.cpu_regs, 0, sizeof m.cpu_regs );
m.cpu_regs.pc = rom_addr;
REGS [r_test ] = 0x0A;
REGS [r_control] = 0xB0; // ROM enabled, clear ports
for ( i = 0; i < port_count; i++ )
REGS_IN [r_cpuio0 + i] = 0;
reset_time_regs();
}
void Snes_Spc::soft_reset()
{
reset_common( 0 );
dsp.soft_reset();
}
void Snes_Spc::reset()
{
memset( RAM, 0xFF, 0x10000 );
ram_loaded();
reset_common( 0x0F );
dsp.reset();
}
char const Snes_Spc::signature [signature_size + 1] =
"SNES-SPC700 Sound File Data v0.30\x1A\x1A";
blargg_err_t Snes_Spc::load_spc( void const* data, long size )
{
spc_file_t const* const spc = (spc_file_t const*) data;
// be sure compiler didn't insert any padding into fle_t
assert( sizeof (spc_file_t) == spc_min_file_size + 0x80 );
// Check signature and file size
if ( size < signature_size || memcmp( spc, signature, 27 ) )
return "Not an SPC file";
if ( size < spc_min_file_size )
return "Corrupt SPC file";
// CPU registers
m.cpu_regs.pc = spc->pch * 0x100 + spc->pcl;
m.cpu_regs.a = spc->a;
m.cpu_regs.x = spc->x;
m.cpu_regs.y = spc->y;
m.cpu_regs.psw = spc->psw;
m.cpu_regs.sp = spc->sp;
// RAM and registers
memcpy( RAM, spc->ram, 0x10000 );
ram_loaded();
// DSP registers
dsp.load( spc->dsp );
reset_time_regs();
return 0;
}
void Snes_Spc::clear_echo()
{
// Allows playback of dodgy Super Mario World mod SPCs
#ifndef SPC_ISOLATED_ECHO_BUFFER
if ( !(dsp.read( Spc_Dsp::r_flg ) & 0x20) )
{
int addr = 0x100 * dsp.read( Spc_Dsp::r_esa );
int end = addr + 0x800 * (dsp.read( Spc_Dsp::r_edl ) & 0x0F);
if ( end > 0x10000 )
end = 0x10000;
memset( &RAM [addr], 0xFF, end - addr );
}
#endif
}
//// Sample output
void Snes_Spc::reset_buf()
{
// Start with half extra buffer of silence
sample_t* out = m.extra_buf;
while ( out < &m.extra_buf [extra_size / 2] )
*out++ = 0;
m.extra_pos = out;
m.buf_begin = 0;
dsp.set_output( 0, 0 );
}
void Snes_Spc::set_output( sample_t* out, int size )
{
require( (size & 1) == 0 ); // size must be even
m.extra_clocks &= clocks_per_sample - 1;
if ( out )
{
sample_t const* out_end = out + size;
m.buf_begin = out;
m.buf_end = out_end;
// Copy extra to output
sample_t const* in = m.extra_buf;
while ( in < m.extra_pos && out < out_end )
*out++ = *in++;
// Handle output being full already
if ( out >= out_end )
{
// Have DSP write to remaining extra space
out = dsp.extra();
out_end = &dsp.extra() [extra_size];
// Copy any remaining extra samples as if DSP wrote them
while ( in < m.extra_pos )
*out++ = *in++;
assert( out <= out_end );
}
dsp.set_output( out, out_end - out );
}
else
{
reset_buf();
}
}
void Snes_Spc::save_extra()
{
// Get end pointers
sample_t const* main_end = m.buf_end; // end of data written to buf
sample_t const* dsp_end = dsp.out_pos(); // end of data written to dsp.extra()
if ( m.buf_begin <= dsp_end && dsp_end <= main_end )
{
main_end = dsp_end;
dsp_end = dsp.extra(); // nothing in DSP's extra
}
// Copy any extra samples at these ends into extra_buf
sample_t* out = m.extra_buf;
sample_t const* in;
for ( in = m.buf_begin + sample_count(); in < main_end; in++ )
*out++ = *in;
for ( in = dsp.extra(); in < dsp_end ; in++ )
*out++ = *in;
m.extra_pos = out;
assert( out <= &m.extra_buf [extra_size] );
}
blargg_err_t Snes_Spc::play( int count, sample_t* out )
{
require( (count & 1) == 0 ); // must be even
if ( count )
{
set_output( out, count );
end_frame( count * (clocks_per_sample / 2) );
}
const char* err = m.cpu_error;
m.cpu_error = 0;
return err;
}
blargg_err_t Snes_Spc::skip( int count )
{
#if SPC_LESS_ACCURATE
if ( count > 2 * sample_rate * 2 )
{
set_output( 0, 0 );
// Skip a multiple of 4 samples
time_t end = count;
count = (count & 3) + 1 * sample_rate * 2;
end = (end - count) * (clocks_per_sample / 2);
m.skipped_kon = 0;
m.skipped_koff = 0;
// Preserve DSP and timer synchronization
// TODO: verify that this really preserves it
int old_dsp_time = m.dsp_time + m.spc_time;
m.dsp_time = end - m.spc_time + skipping_time;
end_frame( end );
m.dsp_time = m.dsp_time - skipping_time + old_dsp_time;
dsp.write( Spc_Dsp::r_koff, m.skipped_koff & ~m.skipped_kon );
dsp.write( Spc_Dsp::r_kon , m.skipped_kon );
clear_echo();
}
#endif
return play( count, 0 );
}

View File

@ -0,0 +1,283 @@
// SNES SPC-700 APU emulator
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#ifndef SNES_SPC_H
#define SNES_SPC_H
#include "Spc_Dsp.h"
#include "blargg_endian.h"
#include <stdint.h>
struct Snes_Spc {
public:
// Must be called once before using
blargg_err_t init();
// Sample pairs generated per second
enum { sample_rate = 32000 };
// Emulator use
// Sets IPL ROM data. Library does not include ROM data. Most SPC music files
// don't need ROM, but a full emulator must provide this.
enum { rom_size = 0x40 };
void init_rom( uint8_t const rom [rom_size] );
// Sets destination for output samples
typedef short sample_t;
void set_output( sample_t* out, int out_size );
// Number of samples written to output since last set
int sample_count() const;
// Resets SPC to power-on state. This resets your output buffer, so you must
// call set_output() after this.
void reset();
// Emulates pressing reset switch on SNES. This resets your output buffer, so
// you must call set_output() after this.
void soft_reset();
// 1024000 SPC clocks per second, sample pair every 32 clocks
typedef int time_t;
enum { clock_rate = 1024000 };
enum { clocks_per_sample = 32 };
// Emulated port read/write at specified time
enum { port_count = 4 };
int read_port ( time_t, int port );
void write_port( time_t, int port, int data );
// Runs SPC to end_time and starts a new time frame at 0
void end_frame( time_t end_time );
// Sound control
// Mutes voices corresponding to non-zero bits in mask (issues repeated KOFF events).
// Reduces emulation accuracy.
enum { voice_count = 8 };
void mute_voices( int mask );
// If true, prevents channels and global volumes from being phase-negated.
// Only supported by fast DSP.
void disable_surround( bool disable = true );
// Sets tempo, where tempo_unit = normal, tempo_unit / 2 = half speed, etc.
enum { tempo_unit = 0x100 };
void set_tempo( int );
// SPC music files
// Loads SPC data into emulator
enum { spc_min_file_size = 0x10180 };
enum { spc_file_size = 0x10200 };
blargg_err_t load_spc( void const* in, long size );
// Clears echo region. Useful after loading an SPC as many have garbage in echo.
void clear_echo();
// Plays for count samples and write samples to out. Discards samples if out
// is NULL. Count must be a multiple of 2 since output is stereo.
blargg_err_t play( int count, sample_t* out );
// Skips count samples. Several times faster than play() when using fast DSP.
blargg_err_t skip( int count );
// State save/load (only available with accurate DSP)
#if !SPC_NO_COPY_STATE_FUNCS
// Saves/loads state
enum { state_size = 67 * 1024L }; // maximum space needed when saving
typedef Spc_Dsp::copy_func_t copy_func_t;
void copy_state( unsigned char** io, copy_func_t );
// Writes minimal header to spc_out
static void init_header( void* spc_out );
// Saves emulator state as SPC file data. Writes spc_file_size bytes to spc_out.
// Does not set up SPC header; use init_header() for that.
void save_spc( void* spc_out );
// Returns true if new key-on events occurred since last check. Useful for
// trimming silence while saving an SPC.
bool check_kon();
#endif
public:
// TODO: document
struct regs_t
{
uint16_t pc;
uint8_t a;
uint8_t x;
uint8_t y;
uint8_t psw;
uint8_t sp;
};
regs_t& smp_regs() { return m.cpu_regs; }
uint8_t* smp_ram() { return m.ram.ram; }
void run_until( time_t t ) { run_until_( t ); }
public:
BLARGG_DISABLE_NOTHROW
// Time relative to m_spc_time. Speeds up code a bit by eliminating need to
// constantly add m_spc_time to time from CPU. CPU uses time that ends at
// 0 to eliminate reloading end time every instruction. It pays off.
typedef int rel_time_t;
struct Timer
{
rel_time_t next_time; // time of next event
int prescaler;
int period;
int divider;
int enabled;
int counter;
};
enum { reg_count = 0x10 };
enum { timer_count = 3 };
enum { extra_size = Spc_Dsp::extra_size };
enum { signature_size = 35 };
private:
Spc_Dsp dsp;
#if SPC_LESS_ACCURATE
static signed char const reg_times_ [256];
signed char reg_times [256];
#endif
struct state_t
{
Timer timers [timer_count];
uint8_t smp_regs [2] [reg_count];
regs_t cpu_regs;
rel_time_t dsp_time;
time_t spc_time;
bool echo_accessed;
int tempo;
int skipped_kon;
int skipped_koff;
const char* cpu_error;
int extra_clocks;
sample_t* buf_begin;
sample_t const* buf_end;
sample_t* extra_pos;
sample_t extra_buf [extra_size];
int rom_enabled;
uint8_t rom [rom_size];
uint8_t hi_ram [rom_size];
unsigned char cycle_table [256];
struct
{
// padding to neutralize address overflow -- but this is
// still undefined behavior! TODO: remove and instead properly
// guard usage of emulated memory
uint8_t padding1 [0x100];
alignas(uint16_t) uint8_t ram [0x10000 + 0x100];
} ram;
};
state_t m;
enum { rom_addr = 0xFFC0 };
enum { skipping_time = 127 };
// Value that padding should be filled with
enum { cpu_pad_fill = 0xFF };
enum {
r_test = 0x0, r_control = 0x1,
r_dspaddr = 0x2, r_dspdata = 0x3,
r_cpuio0 = 0x4, r_cpuio1 = 0x5,
r_cpuio2 = 0x6, r_cpuio3 = 0x7,
r_f8 = 0x8, r_f9 = 0x9,
r_t0target = 0xA, r_t1target = 0xB, r_t2target = 0xC,
r_t0out = 0xD, r_t1out = 0xE, r_t2out = 0xF
};
void timers_loaded();
void enable_rom( int enable );
void reset_buf();
void save_extra();
void load_regs( uint8_t const in [reg_count] );
void ram_loaded();
void regs_loaded();
void reset_time_regs();
void reset_common( int timer_counter_init );
Timer* run_timer_ ( Timer* t, rel_time_t );
Timer* run_timer ( Timer* t, rel_time_t );
int dsp_read ( rel_time_t );
void dsp_write ( int data, rel_time_t );
void cpu_write_smp_reg_( int data, rel_time_t, uint16_t addr );
void cpu_write_smp_reg ( int data, rel_time_t, uint16_t addr );
void cpu_write_high ( int data, uint8_t i );
void cpu_write ( int data, uint16_t addr, rel_time_t );
int cpu_read_smp_reg ( int i, rel_time_t );
int cpu_read ( uint16_t addr, rel_time_t );
unsigned CPU_mem_bit ( uint16_t pc, rel_time_t );
bool check_echo_access ( int addr );
uint8_t* run_until_( time_t end_time );
struct spc_file_t
{
char signature [signature_size];
uint8_t has_id666;
uint8_t version;
uint8_t pcl, pch;
uint8_t a;
uint8_t x;
uint8_t y;
uint8_t psw;
uint8_t sp;
char text [212];
uint8_t ram [0x10000];
uint8_t dsp [128];
uint8_t unused [0x40];
uint8_t ipl_rom [0x40];
};
static char const signature [signature_size + 1];
void save_regs( uint8_t out [reg_count] );
};
#include <assert.h>
inline int Snes_Spc::sample_count() const { return (m.extra_clocks >> 5) * 2; }
inline int Snes_Spc::read_port( time_t t, int port )
{
assert( (unsigned) port < port_count );
return run_until_( t ) [port];
}
inline void Snes_Spc::write_port( time_t t, int port, int data )
{
assert( (unsigned) port < port_count );
run_until_( t ) [0x10 + port] = data;
}
inline void Snes_Spc::mute_voices( int mask ) { dsp.mute_voices( mask ); }
inline void Snes_Spc::disable_surround( bool disable ) { dsp.disable_surround( disable ); }
#if !SPC_NO_COPY_STATE_FUNCS
inline bool Snes_Spc::check_kon() { return dsp.check_kon(); }
#endif
#endif

View File

@ -0,0 +1,554 @@
// Core SPC emulation: CPU, timers, SMP registers, memory
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Snes_Spc.h"
#include <string.h>
/* Copyright (C) 2004-2007 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
#define RAM (m.ram.ram)
#define REGS (m.smp_regs [0])
#define REGS_IN (m.smp_regs [1])
// (n ? n : 256)
#define IF_0_THEN_256( n ) ((uint8_t) ((n) - 1) + 1)
// Note: SPC_MORE_ACCURACY exists mainly so I can run my validation tests, which
// do crazy echo buffer accesses.
#ifndef SPC_MORE_ACCURACY
#define SPC_MORE_ACCURACY 0
#endif
#ifdef BLARGG_ENABLE_OPTIMIZER
#include BLARGG_ENABLE_OPTIMIZER
#endif
//// Timers
#if SPC_DISABLE_TEMPO
#define TIMER_DIV( t, n ) ((n) >> t->prescaler)
#define TIMER_MUL( t, n ) ((n) << t->prescaler)
#else
#define TIMER_DIV( t, n ) ((n) / t->prescaler)
#define TIMER_MUL( t, n ) ((n) * t->prescaler)
#endif
Snes_Spc::Timer* Snes_Spc::run_timer_( Timer* t, rel_time_t time )
{
int elapsed = TIMER_DIV( t, time - t->next_time ) + 1;
t->next_time += TIMER_MUL( t, elapsed );
if ( t->enabled )
{
int remain = IF_0_THEN_256( t->period - t->divider );
int divider = t->divider + elapsed;
int over = elapsed - remain;
if ( over >= 0 )
{
int n = over / t->period;
t->counter = (t->counter + 1 + n) & 0x0F;
divider = over - n * t->period;
}
t->divider = (uint8_t) divider;
}
return t;
}
inline Snes_Spc::Timer* Snes_Spc::run_timer( Timer* t, rel_time_t time )
{
if ( time >= t->next_time )
t = run_timer_( t, time );
return t;
}
//// ROM
void Snes_Spc::enable_rom( int enable )
{
if ( m.rom_enabled != enable )
{
m.rom_enabled = enable;
if ( enable )
memcpy( m.hi_ram, &RAM [rom_addr], sizeof m.hi_ram );
memcpy( &RAM [rom_addr], (enable ? m.rom : m.hi_ram), rom_size );
// TODO: ROM can still get overwritten when DSP writes to echo buffer
}
}
//// DSP
#if SPC_LESS_ACCURATE
int const max_reg_time = 29;
signed char const Snes_Spc::reg_times_ [256] =
{
-1, 0,-11,-10,-15,-11, -2, -2, 4, 3, 14, 14, 26, 26, 14, 22,
2, 3, 0, 1,-12, 0, 1, 1, 7, 6, 14, 14, 27, 14, 14, 23,
5, 6, 3, 4, -1, 3, 4, 4, 10, 9, 14, 14, 26, -5, 14, 23,
8, 9, 6, 7, 2, 6, 7, 7, 13, 12, 14, 14, 27, -4, 14, 24,
11, 12, 9, 10, 5, 9, 10, 10, 16, 15, 14, 14, -2, -4, 14, 24,
14, 15, 12, 13, 8, 12, 13, 13, 19, 18, 14, 14, -2,-36, 14, 24,
17, 18, 15, 16, 11, 15, 16, 16, 22, 21, 14, 14, 28, -3, 14, 25,
20, 21, 18, 19, 14, 18, 19, 19, 25, 24, 14, 14, 14, 29, 14, 25,
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
};
#define RUN_DSP( time, offset ) \
int count = (time) - (offset) - m.dsp_time;\
if ( count >= 0 )\
{\
int clock_count = (count & ~(clocks_per_sample - 1)) + clocks_per_sample;\
m.dsp_time += clock_count;\
dsp.run( clock_count );\
}
#else
#define RUN_DSP( time, offset ) \
{\
int count = (time) - m.dsp_time;\
if ( !SPC_MORE_ACCURACY || count )\
{\
assert( count > 0 );\
m.dsp_time = (time);\
dsp.run( count );\
}\
}
#endif
int Snes_Spc::dsp_read( rel_time_t time )
{
RUN_DSP( time, reg_times [REGS [r_dspaddr] & 0x7F] );
int result = dsp.read( REGS [r_dspaddr] & 0x7F );
#ifdef SPC_DSP_READ_HOOK
SPC_DSP_READ_HOOK( spc_time + time, (REGS [r_dspaddr] & 0x7F), result );
#endif
return result;
}
inline void Snes_Spc::dsp_write( int data, rel_time_t time )
{
RUN_DSP( time, reg_times [REGS [r_dspaddr]] )
#if SPC_LESS_ACCURATE
else if ( m.dsp_time == skipping_time )
{
int r = REGS [r_dspaddr];
if ( r == Spc_Dsp::r_kon )
m.skipped_kon |= data & ~dsp.read( Spc_Dsp::r_koff );
if ( r == Spc_Dsp::r_koff )
{
m.skipped_koff |= data;
m.skipped_kon &= ~data;
}
}
#endif
#ifdef SPC_DSP_WRITE_HOOK
SPC_DSP_WRITE_HOOK( m.spc_time + time, REGS [r_dspaddr], (uint8_t) data );
#endif
if ( REGS [r_dspaddr] <= 0x7F )
dsp.write( REGS [r_dspaddr], data );
else if ( !SPC_MORE_ACCURACY )
debug_printf( "SPC wrote to DSP register > $7F\n" );
}
//// Memory access extras
#if SPC_MORE_ACCURACY
#define MEM_ACCESS( time, addr ) \
{\
if ( time >= m.dsp_time )\
{\
RUN_DSP( time, max_reg_time );\
}\
}
#elif !defined (NDEBUG)
// Debug-only check for read/write within echo buffer, since this might result in
// inaccurate emulation due to the DSP not being caught up to the present.
bool Snes_Spc::check_echo_access( int addr )
{
if ( !(dsp.read( Spc_Dsp::r_flg ) & 0x20) )
{
int start = 0x100 * dsp.read( Spc_Dsp::r_esa );
int size = 0x800 * (dsp.read( Spc_Dsp::r_edl ) & 0x0F);
int end = start + (size ? size : 4);
if ( start <= addr && addr < end )
{
if ( !m.echo_accessed )
{
m.echo_accessed = 1;
return true;
}
}
}
return false;
}
#define MEM_ACCESS( time, addr ) check( !check_echo_access( (uint16_t) addr ) );
#else
#define MEM_ACCESS( time, addr )
#endif
//// CPU write
#if SPC_MORE_ACCURACY
static unsigned char const glitch_probs [3] [256] =
{
0xC3,0x92,0x5B,0x1C,0xD1,0x92,0x5B,0x1C,0xDB,0x9C,0x72,0x18,0xCD,0x5C,0x38,0x0B,
0xE1,0x9C,0x74,0x17,0xCF,0x75,0x45,0x0C,0xCF,0x6E,0x4A,0x0D,0xA3,0x3A,0x1D,0x08,
0xDB,0xA0,0x82,0x19,0xD9,0x73,0x3C,0x0E,0xCB,0x76,0x52,0x0B,0xA5,0x46,0x1D,0x09,
0xDA,0x74,0x55,0x0F,0xA2,0x3F,0x21,0x05,0x9A,0x40,0x20,0x07,0x63,0x1E,0x10,0x01,
0xDF,0xA9,0x85,0x1D,0xD3,0x84,0x4B,0x0E,0xCF,0x6F,0x49,0x0F,0xB3,0x48,0x1E,0x05,
0xD8,0x77,0x52,0x12,0xB7,0x49,0x23,0x06,0xAA,0x45,0x28,0x07,0x7D,0x28,0x0F,0x07,
0xCC,0x7B,0x4A,0x0E,0xB2,0x4F,0x24,0x07,0xAD,0x43,0x2C,0x06,0x86,0x29,0x11,0x07,
0xAE,0x48,0x1F,0x0A,0x76,0x21,0x19,0x05,0x76,0x21,0x14,0x05,0x44,0x11,0x0B,0x01,
0xE7,0xAD,0x96,0x23,0xDC,0x86,0x59,0x0E,0xDC,0x7C,0x5F,0x15,0xBB,0x53,0x2E,0x09,
0xD6,0x7C,0x4A,0x16,0xBB,0x4A,0x25,0x08,0xB3,0x4F,0x28,0x0B,0x8E,0x23,0x15,0x08,
0xCF,0x7F,0x57,0x11,0xB5,0x4A,0x23,0x0A,0xAA,0x42,0x28,0x05,0x7D,0x22,0x12,0x03,
0xA6,0x49,0x28,0x09,0x82,0x2B,0x0D,0x04,0x7A,0x20,0x0F,0x04,0x3D,0x0F,0x09,0x03,
0xD1,0x7C,0x4C,0x0F,0xAF,0x4E,0x21,0x09,0xA8,0x46,0x2A,0x07,0x85,0x1F,0x0E,0x07,
0xA6,0x3F,0x26,0x07,0x7C,0x24,0x14,0x07,0x78,0x22,0x16,0x04,0x46,0x12,0x0A,0x02,
0xA6,0x41,0x2C,0x0A,0x7E,0x28,0x11,0x05,0x73,0x1B,0x14,0x05,0x3D,0x11,0x0A,0x02,
0x70,0x22,0x17,0x05,0x48,0x13,0x08,0x03,0x3C,0x07,0x0D,0x07,0x26,0x07,0x06,0x01,
0xE0,0x9F,0xDA,0x7C,0x4F,0x18,0x28,0x0D,0xE9,0x9F,0xDA,0x7C,0x4F,0x18,0x1F,0x07,
0xE6,0x97,0xD8,0x72,0x64,0x13,0x26,0x09,0xDC,0x67,0xA9,0x38,0x21,0x07,0x15,0x06,
0xE9,0x91,0xD2,0x6B,0x63,0x14,0x2B,0x0E,0xD6,0x61,0xB7,0x41,0x2B,0x0E,0x10,0x09,
0xCF,0x59,0xB0,0x2F,0x35,0x08,0x0F,0x07,0xB6,0x30,0x7A,0x21,0x17,0x07,0x09,0x03,
0xE7,0xA3,0xE5,0x6B,0x65,0x1F,0x34,0x09,0xD8,0x6B,0xBE,0x45,0x27,0x07,0x10,0x07,
0xDA,0x54,0xB1,0x39,0x2E,0x0E,0x17,0x08,0xA9,0x3C,0x86,0x22,0x16,0x06,0x07,0x03,
0xD4,0x51,0xBC,0x3D,0x38,0x0A,0x13,0x06,0xB2,0x37,0x79,0x1C,0x17,0x05,0x0E,0x06,
0xA7,0x31,0x74,0x1C,0x11,0x06,0x0C,0x02,0x6D,0x1A,0x38,0x10,0x0B,0x05,0x06,0x03,
0xEB,0x9A,0xE1,0x7A,0x6F,0x13,0x34,0x0E,0xE6,0x75,0xC5,0x45,0x3E,0x0B,0x1A,0x05,
0xD8,0x63,0xC1,0x40,0x3C,0x1B,0x19,0x06,0xB3,0x42,0x83,0x29,0x18,0x0A,0x08,0x04,
0xD4,0x58,0xBA,0x43,0x3F,0x0A,0x1F,0x09,0xB1,0x33,0x8A,0x1F,0x1F,0x06,0x0D,0x05,
0xAF,0x3C,0x7A,0x1F,0x16,0x08,0x0A,0x01,0x72,0x1B,0x52,0x0D,0x0B,0x09,0x06,0x01,
0xCF,0x63,0xB7,0x47,0x40,0x10,0x14,0x06,0xC0,0x41,0x96,0x20,0x1C,0x09,0x10,0x05,
0xA6,0x35,0x82,0x1A,0x20,0x0C,0x0E,0x04,0x80,0x1F,0x53,0x0F,0x0B,0x02,0x06,0x01,
0xA6,0x31,0x81,0x1B,0x1D,0x01,0x08,0x08,0x7B,0x20,0x4D,0x19,0x0E,0x05,0x07,0x03,
0x6B,0x17,0x49,0x07,0x0E,0x03,0x0A,0x05,0x37,0x0B,0x1F,0x06,0x04,0x02,0x07,0x01,
0xF0,0xD6,0xED,0xAD,0xEC,0xB1,0xEB,0x79,0xAC,0x22,0x47,0x1E,0x6E,0x1B,0x32,0x0A,
0xF0,0xD6,0xEA,0xA4,0xED,0xC4,0xDE,0x82,0x98,0x1F,0x50,0x13,0x52,0x15,0x2A,0x0A,
0xF1,0xD1,0xEB,0xA2,0xEB,0xB7,0xD8,0x69,0xA2,0x1F,0x5B,0x18,0x55,0x18,0x2C,0x0A,
0xED,0xB5,0xDE,0x7E,0xE6,0x85,0xD3,0x59,0x59,0x0F,0x2C,0x09,0x24,0x07,0x15,0x09,
0xF1,0xD6,0xEA,0xA0,0xEC,0xBB,0xDA,0x77,0xA9,0x23,0x58,0x14,0x5D,0x12,0x2F,0x09,
0xF1,0xC1,0xE3,0x86,0xE4,0x87,0xD2,0x4E,0x68,0x15,0x26,0x0B,0x27,0x09,0x15,0x02,
0xEE,0xA6,0xE0,0x5C,0xE0,0x77,0xC3,0x41,0x67,0x1B,0x3C,0x07,0x2A,0x06,0x19,0x07,
0xE4,0x75,0xC6,0x43,0xCC,0x50,0x95,0x23,0x35,0x09,0x14,0x04,0x15,0x05,0x0B,0x04,
0xEE,0xD6,0xED,0xAD,0xEC,0xB1,0xEB,0x79,0xAC,0x22,0x56,0x14,0x5A,0x12,0x26,0x0A,
0xEE,0xBB,0xE7,0x7E,0xE9,0x8D,0xCB,0x49,0x67,0x11,0x34,0x07,0x2B,0x0B,0x14,0x07,
0xED,0xA7,0xE5,0x76,0xE3,0x7E,0xC4,0x4B,0x77,0x14,0x34,0x08,0x27,0x07,0x14,0x04,
0xE7,0x8B,0xD2,0x4C,0xCA,0x56,0x9E,0x31,0x36,0x0C,0x11,0x07,0x14,0x04,0x0A,0x02,
0xF0,0x9B,0xEA,0x6F,0xE5,0x81,0xC4,0x43,0x74,0x10,0x30,0x0B,0x2D,0x08,0x1B,0x06,
0xE6,0x83,0xCA,0x48,0xD9,0x56,0xA7,0x23,0x3B,0x09,0x12,0x09,0x15,0x07,0x0A,0x03,
0xE5,0x5F,0xCB,0x3C,0xCF,0x48,0x91,0x22,0x31,0x0A,0x17,0x08,0x15,0x04,0x0D,0x02,
0xD1,0x43,0x91,0x20,0xA9,0x2D,0x54,0x12,0x17,0x07,0x09,0x02,0x0C,0x04,0x05,0x03,
};
#endif
// Read/write handlers are divided into multiple functions to keep rarely-used
// functionality separate so often-used functionality can be optimized better
// by compiler.
// If write isn't preceded by read, data has this added to it
int const no_read_before_write = 0x2000;
void Snes_Spc::cpu_write_smp_reg_( int data, rel_time_t time, uint16_t addr )
{
switch ( addr )
{
case r_t0target:
case r_t1target:
case r_t2target: {
Timer* t = &m.timers [addr - r_t0target];
int period = IF_0_THEN_256( data );
if ( t->period != period )
{
t = run_timer( t, time );
#if SPC_MORE_ACCURACY
// Insane behavior when target is written just after counter is
// clocked and counter matches new period and new period isn't 1, 2, 4, or 8
if ( t->divider == (period & 0xFF) &&
t->next_time == time + TIMER_MUL( t, 1 ) &&
((period - 1) | ~0x0F) & period )
{
//debug_printf( "SPC pathological timer target write\n" );
// If the period is 3, 5, or 9, there's a probability this behavior won't occur,
// based on the previous period
int prob = 0xFF;
int old_period = t->period & 0xFF;
if ( period == 3 ) prob = glitch_probs [0] [old_period];
if ( period == 5 ) prob = glitch_probs [1] [old_period];
if ( period == 9 ) prob = glitch_probs [2] [old_period];
// The glitch suppresses incrementing of one of the counter bits, based on
// the lowest set bit in the new period
int b = 1;
while ( !(period & b) )
b <<= 1;
if ( (rand() >> 4 & 0xFF) <= prob )
t->divider = (t->divider - b) & 0xFF;
}
#endif
t->period = period;
}
break;
}
case r_t0out:
case r_t1out:
case r_t2out:
if ( !SPC_MORE_ACCURACY )
debug_printf( "SPC wrote to counter %d\n", (int) addr - r_t0out );
if ( data < no_read_before_write / 2 )
run_timer( &m.timers [addr - r_t0out], time - 1 )->counter = 0;
break;
// Registers that act like RAM
case 0x8:
case 0x9:
REGS_IN [addr] = (uint8_t) data;
break;
case r_test:
if ( (uint8_t) data != 0x0A )
debug_printf( "SPC wrote to test register\n" );
break;
case r_control:
// port clears
if ( data & 0x10 )
{
REGS_IN [r_cpuio0] = 0;
REGS_IN [r_cpuio1] = 0;
}
if ( data & 0x20 )
{
REGS_IN [r_cpuio2] = 0;
REGS_IN [r_cpuio3] = 0;
}
// timers
{
for ( int i = 0; i < timer_count; i++ )
{
Timer* t = &m.timers [i];
int enabled = data >> i & 1;
if ( t->enabled != enabled )
{
t = run_timer( t, time );
t->enabled = enabled;
if ( enabled )
{
t->divider = 0;
t->counter = 0;
}
}
}
}
enable_rom( data & 0x80 );
break;
}
}
void Snes_Spc::cpu_write_smp_reg( int data, rel_time_t time, uint16_t addr )
{
if ( addr == r_dspdata ) // 99%
dsp_write( data, time );
else
cpu_write_smp_reg_( data, time, addr );
}
void Snes_Spc::cpu_write_high( int data, uint8_t i )
{
assert ( i < rom_size );
m.hi_ram [i] = (uint8_t) data;
if ( m.rom_enabled )
RAM [i + rom_addr] = m.rom [i]; // restore overwritten ROM
}
void Snes_Spc::cpu_write( int data, uint16_t addr, rel_time_t time )
{
MEM_ACCESS( time, addr )
// RAM
RAM [addr] = (uint8_t) data;
if ( addr >= 0xF0 ) // 64%
{
const uint16_t reg = addr - 0xF0;
// $F0-$FF
if ( reg < reg_count ) // 87%
{
REGS [reg] = (uint8_t) data;
// Ports
#ifdef SPC_PORT_WRITE_HOOK
if ( (unsigned) (reg - r_cpuio0) < port_count )
SPC_PORT_WRITE_HOOK( m.spc_time + time, (reg - r_cpuio0),
(uint8_t) data, &REGS [r_cpuio0] );
#endif
// Registers other than $F2 and $F4-$F7
if ( reg != 2 && (reg < 4 || reg > 7) ) // 36%
cpu_write_smp_reg( data, time, reg );
}
// High mem/address wrap-around
else if ( addr >= rom_addr ) // 1% in IPL ROM area or address wrapped around
cpu_write_high( data, addr - rom_addr );
}
}
//// CPU read
inline int Snes_Spc::cpu_read_smp_reg( int reg, rel_time_t time )
{
int result = REGS_IN [reg];
reg -= r_dspaddr;
// DSP addr and data
if ( (unsigned) reg <= 1 ) // 4% 0xF2 and 0xF3
{
result = REGS [r_dspaddr];
if ( (unsigned) reg == 1 )
result = dsp_read( time ); // 0xF3
}
return result;
}
int Snes_Spc::cpu_read( uint16_t addr, rel_time_t time )
{
MEM_ACCESS( time, addr )
// RAM
int result = RAM [addr];
int reg = addr - 0xF0;
if ( reg >= 0 ) // 40%
{
reg -= 0x10;
if ( (unsigned) reg >= 0xFF00 ) // 21%
{
reg += 0x10 - r_t0out;
// Timers
if ( (unsigned) reg < timer_count ) // 90%
{
Timer* t = &m.timers [reg];
if ( time >= t->next_time )
t = run_timer_( t, time );
result = t->counter;
t->counter = 0;
}
// Other registers
else if ( reg < 0 ) // 10%
{
result = cpu_read_smp_reg( reg + r_t0out, time );
}
else // 1%
{
assert( reg + (r_t0out + 0xF0 - 0x10000) < 0x100 );
result = cpu_read( reg + (r_t0out + 0xF0 - 0x10000), time );
}
}
}
return result;
}
//// Run
// Prefix and suffix for CPU emulator function
#define SPC_CPU_RUN_FUNC \
uint8_t* Snes_Spc::run_until_( time_t end_time )\
{\
rel_time_t rel_time = m.spc_time - end_time;\
assert( rel_time <= 0 );\
m.spc_time = end_time;\
m.dsp_time += rel_time;\
m.timers [0].next_time += rel_time;\
m.timers [1].next_time += rel_time;\
m.timers [2].next_time += rel_time;
#define SPC_CPU_RUN_FUNC_END \
m.spc_time += rel_time;\
m.dsp_time -= rel_time;\
m.timers [0].next_time -= rel_time;\
m.timers [1].next_time -= rel_time;\
m.timers [2].next_time -= rel_time;\
assert( m.spc_time <= end_time );\
return &REGS [r_cpuio0];\
}
#ifndef NDEBUG
// Used only for assert
int const cpu_lag_max = 12 - 1; // DIV YA,X takes 12 clocks
#endif
void Snes_Spc::end_frame( time_t end_time )
{
// Catch CPU up to as close to end as possible. If final instruction
// would exceed end, does NOT execute it and leaves m.spc_time < end.
if ( end_time > m.spc_time )
run_until_( end_time );
m.spc_time -= end_time;
m.extra_clocks += end_time;
// Greatest number of clocks early that emulation can stop early due to
// not being able to execute current instruction without going over
// allowed time.
assert( -cpu_lag_max <= m.spc_time && m.spc_time <= 0 );
// Catch timers up to CPU
for ( int i = 0; i < timer_count; i++ )
run_timer( &m.timers [i], 0 );
// Catch DSP up to CPU
if ( m.dsp_time < 0 )
{
RUN_DSP( 0, max_reg_time );
}
// Save any extra samples beyond what should be generated
if ( m.buf_begin )
save_extra();
}
// Inclusion here allows static memory access functions and better optimization
#include "Spc_Cpu.h"

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,492 +1,500 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Spc_Sfm.h"
#include "blargg_endian.h"
#include <stdio.h>
/* Copyright (C) 2004-2013 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
#ifndef _countof
#define _countof(x) (sizeof((x))/sizeof((x)[0]))
#endif
// TODO: support Spc_Filter's bass
Sfm_Emu::Sfm_Emu()
{
set_type( gme_sfm_type );
set_voice_count( SuperFamicom::SPC_DSP::voice_count );
static const char* const names [SuperFamicom::SPC_DSP::voice_count] = {
"DSP 1", "DSP 2", "DSP 3", "DSP 4", "DSP 5", "DSP 6", "DSP 7", "DSP 8"
};
set_voice_names( names );
set_gain( 1.4 );
set_max_initial_silence( 30 );
set_silence_lookahead( 30 ); // Some SFMs may have a lot of initialization code
enable_filter( false );
enable_echo( true );
}
Sfm_Emu::~Sfm_Emu() { }
// Track info
static void copy_field( char* out, size_t size, const Bml_Parser& in, char const* in_path )
{
const char * value = in.enumValue( in_path );
if ( value ) strncpy( out, value, size - 1 ), out[ size - 1 ] = 0;
else out[ 0 ] = 0;
}
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->game, sizeof(out->game), in, "information:game" );
copy_field( out->author, sizeof(out->author), in, "information:author" );
copy_field( out->composer, sizeof(out->composer), in, "information:composer" );
copy_field( out->copyright, sizeof(out->copyright), in, "information:copyright" );
copy_field( out->date, sizeof(out->date), in, "information:date" );
copy_field( out->track, sizeof(out->track), in, "information:track" );
copy_field( out->disc, sizeof(out->disc), in, "information:disc" );
copy_field( out->dumper, sizeof(out->dumper), in, "information:dumper" );
char * end;
const char * value = in.enumValue( "timing:length" );
if ( value )
out->length = strtoul( value, &end, 10 );
else
out->length = 0;
value = in.enumValue( "timing:fade" );
if ( value )
out->fade_length = strtoul( value, &end, 10 );
else
out->fade_length = -1;
}
blargg_err_t Sfm_Emu::track_info_( track_info_t* out, int ) const
{
copy_info( out, metadata );
return 0;
}
static void set_track_info( const track_info_t* in, Bml_Parser& out )
{
out.setValue( "information:title", in->song );
out.setValue( "information:game", in->game );
out.setValue( "information:author", in->author );
out.setValue( "information:composer", in->composer );
out.setValue( "information:copyright", in->copyright );
out.setValue( "information:date", in->date );
out.setValue( "information:track", in->track );
out.setValue( "information:disc", in->disc );
out.setValue( "information:dumper", in->dumper );
out.setValue( "timing:length", in->length );
out.setValue( "timing:fade", in->fade_length );
}
blargg_err_t Sfm_Emu::set_track_info_( const track_info_t* in, int )
{
::set_track_info(in, metadata);
return 0;
}
static blargg_err_t check_sfm_header( void const* header )
{
if ( memcmp( header, "SFM1", 4 ) )
return gme_wrong_file_type;
return 0;
}
struct Sfm_File : Gme_Info_
{
blargg_vector<byte> data;
Bml_Parser metadata;
unsigned long original_metadata_size;
Sfm_File() { set_type( gme_sfm_type ); }
blargg_err_t load_( Data_Reader& in )
{
int file_size = in.remain();
if ( file_size < Sfm_Emu::sfm_min_file_size )
return gme_wrong_file_type;
RETURN_ERR( data.resize( file_size ) );
RETURN_ERR( in.read( data.begin(), data.end() - data.begin() ) );
RETURN_ERR( check_sfm_header( data.begin() ) );
if ( file_size < 8 )
return "SFM file too small";
int metadata_size = get_le32( data.begin() + 4 );
metadata.parseDocument( (const char *)data.begin() + 8, metadata_size );
original_metadata_size = metadata_size;
return 0;
}
blargg_err_t track_info_( track_info_t* out, int ) const
{
copy_info( out, metadata );
return 0;
}
blargg_err_t set_track_info_( const track_info_t* in, int )
{
::set_track_info( in, metadata );
return 0;
}
};
static Music_Emu* new_sfm_emu () { return BLARGG_NEW Sfm_Emu ; }
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 };
extern gme_type_t const gme_sfm_type = &gme_sfm_type_;
// Setup
blargg_err_t Sfm_Emu::set_sample_rate_( long sample_rate )
{
smp.power();
if ( sample_rate != native_sample_rate )
{
RETURN_ERR( resampler.buffer_size( native_sample_rate / 20 * 2 ) );
resampler.time_ratio( (double) native_sample_rate / sample_rate, 0.9965 );
}
return 0;
}
void Sfm_Emu::mute_voices_( int m )
{
Music_Emu::mute_voices_( m );
for ( int i = 0, j = 1; i < 8; ++i, j <<= 1 )
smp.dsp.channel_enable( i, !( m & j ) );
}
blargg_err_t Sfm_Emu::load_mem_( byte const in [], long size )
{
if ( size < Sfm_Emu::sfm_min_file_size )
return gme_wrong_file_type;
file_data = in;
file_size = size;
RETURN_ERR( check_sfm_header( in ) );
const byte * ptr = file_data;
int metadata_size = get_le32(ptr + 4);
if ( file_size < metadata_size + Sfm_Emu::sfm_min_file_size )
return "SFM file too small";
metadata.parseDocument((const char *) ptr + 8, metadata_size);
return 0;
}
// Emulation
void Sfm_Emu::set_tempo_( double t )
{
smp.set_tempo( t );
}
// (n ? n : 256)
#define IF_0_THEN_256( n ) ((uint8_t) ((n) - 1) + 1)
#define META_ENUM_INT(n,d) (value = metadata.enumValue(n), value ? strtol(value, &end, 10) : (d))
static const byte ipl_rom[0x40] =
{
0xCD, 0xEF, 0xBD, 0xE8, 0x00, 0xC6, 0x1D, 0xD0,
0xFC, 0x8F, 0xAA, 0xF4, 0x8F, 0xBB, 0xF5, 0x78,
0xCC, 0xF4, 0xD0, 0xFB, 0x2F, 0x19, 0xEB, 0xF4,
0xD0, 0xFC, 0x7E, 0xF4, 0xD0, 0x0B, 0xE4, 0xF5,
0xCB, 0xF4, 0xD7, 0x00, 0xFC, 0xD0, 0xF3, 0xAB,
0x01, 0x10, 0xEF, 0x7E, 0xF4, 0x10, 0xEB, 0xBA,
0xF6, 0xDA, 0x00, 0xBA, 0xF4, 0xC4, 0xF4, 0xDD,
0x5D, 0xD0, 0xDB, 0x1F, 0x00, 0x00, 0xC0, 0xFF
};
blargg_err_t Sfm_Emu::start_track_( int track )
{
RETURN_ERR( Music_Emu::start_track_( track ) );
resampler.clear();
filter.clear();
const byte * ptr = file_data;
int metadata_size = get_le32(ptr + 4);
memcpy( smp.iplrom, ipl_rom, 64 );
smp.reset();
memcpy( smp.apuram, ptr + 8 + metadata_size, 65536 );
memcpy( smp.dsp.spc_dsp.m.regs, ptr + 8 + metadata_size + 65536, 128 );
const uint8_t* log_begin = ptr + 8 + metadata_size + 65536 + 128;
const uint8_t* log_end = ptr + file_size;
size_t loop_begin = log_end - log_begin;
char * end;
const char * value;
loop_begin = META_ENUM_INT("timing:loopstart", loop_begin);
smp.set_sfm_queue( log_begin, log_end, log_begin + loop_begin );
uint32_t test = META_ENUM_INT("smp:test", 0);
smp.status.clock_speed = (test >> 6) & 3;
smp.status.timer_speed = (test >> 4) & 3;
smp.status.timers_enable = test & 0x08;
smp.status.ram_disable = test & 0x04;
smp.status.ram_writable = test & 0x02;
smp.status.timers_disable = test & 0x01;
smp.status.iplrom_enable = META_ENUM_INT("smp:iplrom",1);
smp.status.dsp_addr = META_ENUM_INT("smp:dspaddr",0);
value = metadata.enumValue("smp:ram");
if (value)
{
smp.status.ram00f8 = strtol(value, &end, 10);
if (*end)
{
value = end + 1;
smp.status.ram00f9 = strtol(value, &end, 10);
}
}
std::string name;
std::ostringstream oss;
name = "smp:regs:";
smp.regs.pc = META_ENUM_INT(name + "pc", 0xffc0);
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);
smp.regs.p = META_ENUM_INT(name + "psw", 0x02);
value = metadata.enumValue("smp:ports");
if (value)
{
for (int i = 0; i < _countof(smp.sfm_last); i++)
{
smp.sfm_last[i] = strtol(value, &end, 10);
if (*end == ',')
value = end + 1;
else
break;
}
}
for (int i = 0; i < 3; ++i)
{
SuperFamicom::SMP::Timer<192> &t = (i == 0 ? smp.timer0 : (i == 1 ? smp.timer1 : *(SuperFamicom::SMP::Timer<192>*)&smp.timer2));
oss.str("");
oss.clear();
oss << "smp:timer[" << i << "]:";
name = oss.str();
value = metadata.enumValue(name + "enable");
if (value)
{
t.enable = !!strtol(value, &end, 10);
}
value = metadata.enumValue(name + "target");
if (value)
{
t.target = strtol(value, &end, 10);
}
value = metadata.enumValue(name + "stage");
if (value)
{
t.stage0_ticks = strtol(value, &end, 10);
if (*end != ',') break;
value = end + 1;
t.stage1_ticks = strtol(value, &end, 10);
if (*end != ',') break;
value = end + 1;
t.stage2_ticks = strtol(value, &end, 10);
if (*end != ',') break;
value = end + 1;
t.stage3_ticks = strtol(value, &end, 10);
}
value = metadata.enumValue(name + "line");
if (value)
{
t.current_line = !!strtol(value, &end, 10);
}
}
smp.dsp.clock = META_ENUM_INT("dsp:clock", 0) * 4096;
smp.dsp.spc_dsp.m.echo_hist_pos = &smp.dsp.spc_dsp.m.echo_hist[META_ENUM_INT("dsp:echohistaddr", 0)];
value = metadata.enumValue("dsp:echohistdata");
if (value)
{
for (int i = 0; i < 8; ++i)
{
smp.dsp.spc_dsp.m.echo_hist[i][0] = strtol(value, &end, 10);
value = strchr(value, ',');
if (!value) break;
++value;
smp.dsp.spc_dsp.m.echo_hist[i][1] = strtol(value, &end, 10);
value = strchr(value, ',');
if (!value) break;
++value;
}
}
smp.dsp.spc_dsp.m.phase = META_ENUM_INT("dsp:sample", 0);
smp.dsp.spc_dsp.m.kon = META_ENUM_INT("dsp:kon", 0);
smp.dsp.spc_dsp.m.noise = META_ENUM_INT("dsp:noise", 0);
smp.dsp.spc_dsp.m.counter = META_ENUM_INT("dsp:counter", 0);
smp.dsp.spc_dsp.m.echo_offset = META_ENUM_INT("dsp:echooffset", 0);
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.outx_buf = META_ENUM_INT("dsp:outx", 0);
smp.dsp.spc_dsp.m.t_pmon = META_ENUM_INT("dsp:pmon", 0);
smp.dsp.spc_dsp.m.t_non = META_ENUM_INT("dsp:non", 0);
smp.dsp.spc_dsp.m.t_eon = META_ENUM_INT("dsp:eon", 0);
smp.dsp.spc_dsp.m.t_dir = META_ENUM_INT("dsp:dir", 0);
smp.dsp.spc_dsp.m.t_koff = META_ENUM_INT("dsp:koff", 0);
smp.dsp.spc_dsp.m.t_brr_next_addr = META_ENUM_INT("dsp:brrnext", 0);
smp.dsp.spc_dsp.m.t_adsr0 = META_ENUM_INT("dsp:adsr0", 0);
smp.dsp.spc_dsp.m.t_brr_header = META_ENUM_INT("dsp:brrheader", 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);
smp.dsp.spc_dsp.m.t_echo_enabled = !META_ENUM_INT("dsp:echodisable", 0);
smp.dsp.spc_dsp.m.t_dir_addr = META_ENUM_INT("dsp:diraddr", 0);
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);
smp.dsp.spc_dsp.m.t_looped = META_ENUM_INT("dsp:looped", 0);
smp.dsp.spc_dsp.m.t_echo_ptr = META_ENUM_INT("dsp:echoaddr", 0);
#define META_ENUM_LEVELS(n, o) \
value = metadata.enumValue(n); \
if (value) \
{ \
(o)[0] = strtol(value, &end, 10); \
if (*end) \
{ \
value = end + 1; \
(o)[1] = strtol(value, &end, 10); \
} \
}
META_ENUM_LEVELS("dsp:mainout", smp.dsp.spc_dsp.m.t_main_out);
META_ENUM_LEVELS("dsp:echoout", smp.dsp.spc_dsp.m.t_echo_out);
META_ENUM_LEVELS("dsp:echoin", smp.dsp.spc_dsp.m.t_echo_in);
#undef META_ENUM_LEVELS
for (int i = 0; i < 8; ++i)
{
oss.str("");
oss.clear();
oss << "dsp:voice[" << i << "]:";
name = oss.str();
SuperFamicom::SPC_DSP::voice_t & voice = smp.dsp.spc_dsp.m.voices[i];
value = metadata.enumValue(name + "brrhistaddr");
if (value)
{
voice.buf_pos = strtol(value, &end, 10);
}
value = metadata.enumValue(name + "brrhistdata");
if (value)
{
for (int j = 0; j < SuperFamicom::SPC_DSP::brr_buf_size; ++j)
{
voice.buf[j] = voice.buf[j + SuperFamicom::SPC_DSP::brr_buf_size] = strtol(value, &end, 10);
if (!*end) break;
value = end + 1;
}
}
voice.interp_pos = META_ENUM_INT(name + "interpaddr",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);
voice.regs = &smp.dsp.spc_dsp.m.regs[META_ENUM_INT(name + "vidx",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);
voice.t_envx_out = META_ENUM_INT(name + "envxout", 0);
voice.hidden_env = META_ENUM_INT(name + "envcache", 0);
}
filter.set_gain( (int) (gain() * SPC_Filter::gain_unit) );
return 0;
}
#undef META_ENUM_INT
blargg_err_t Sfm_Emu::play_and_filter( long count, sample_t out [] )
{
smp.render( out, count );
if ( _enable_filter ) filter.run( out, count );
return 0;
}
blargg_err_t Sfm_Emu::skip_( long count )
{
if ( sample_rate() != native_sample_rate )
{
count = (long) (count * resampler.ratio()) & ~1;
count -= resampler.skip_input( count );
}
// TODO: shouldn't skip be adjusted for the 64 samples read afterwards?
if ( count > 0 )
{
smp.skip( count );
filter.clear();
}
if ( sample_rate() != native_sample_rate )
{
// eliminate pop due to resampler
const long resampler_latency = 64;
sample_t buf [resampler_latency];
return play_( resampler_latency, buf );
}
return 0;
}
blargg_err_t Sfm_Emu::play_( long count, sample_t out [] )
{
if ( sample_rate() == native_sample_rate )
return play_and_filter( count, out );
long remain = count;
while ( remain > 0 )
{
remain -= resampler.read( &out [count - remain], remain );
if ( remain > 0 )
{
int n = resampler.max_write();
RETURN_ERR( play_and_filter( n, resampler.buffer() ) );
resampler.write( n );
}
}
check( remain == 0 );
return 0;
}
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Spc_Sfm.h"
#include "blargg_endian.h"
#include <stdio.h>
/* Copyright (C) 2013-2022 Christopher Snowhill. This module is free
software; you can redistribute it and/or modify it under the terms of
the GNU Lesser General Public License as published by the Free Software
Foundation; either version 2.1 of the License, or (at your option) any
later version. This module is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
General Public License for more details. You should have received a copy
of the GNU Lesser General Public License along with this module; if not,
write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
#ifndef _countof
#define _countof(x) (sizeof((x))/sizeof((x)[0]))
#endif
// TODO: support Spc_Filter's bass
Sfm_Emu::Sfm_Emu()
{
set_type( gme_sfm_type );
set_voice_count( SuperFamicom::SPC_DSP::voice_count );
static const char* const names [SuperFamicom::SPC_DSP::voice_count] = {
"DSP 1", "DSP 2", "DSP 3", "DSP 4", "DSP 5", "DSP 6", "DSP 7", "DSP 8"
};
set_voice_names( names );
set_gain( 1.4 );
set_max_initial_silence( 30 );
set_silence_lookahead( 30 ); // Some SFMs may have a lot of initialization code
enable_echo( true );
}
Sfm_Emu::~Sfm_Emu() { }
// Track info
static void copy_field( char* out, size_t size, const Bml_Parser& in, char const* in_path )
{
const char * value = in.enumValue( in_path );
if ( value ) strncpy( out, value, size - 1 ), out[ size - 1 ] = 0;
else out[ 0 ] = 0;
}
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->game, sizeof(out->game), in, "information:game" );
copy_field( out->author, sizeof(out->author), in, "information:author" );
copy_field( out->composer, sizeof(out->composer), in, "information:composer" );
copy_field( out->copyright, sizeof(out->copyright), in, "information:copyright" );
copy_field( out->date, sizeof(out->date), in, "information:date" );
copy_field( out->track, sizeof(out->track), in, "information:track" );
copy_field( out->disc, sizeof(out->disc), in, "information:disc" );
copy_field( out->dumper, sizeof(out->dumper), in, "information:dumper" );
char * end;
const char * value = in.enumValue( "timing:length" );
if ( value )
out->length = strtoul( value, &end, 10 );
else
out->length = 0;
value = in.enumValue( "timing:fade" );
if ( value )
out->fade_length = strtoul( value, &end, 10 );
else
out->fade_length = -1;
}
blargg_err_t Sfm_Emu::track_info_( track_info_t* out, int ) const
{
copy_info( out, metadata );
return 0;
}
static void set_track_info( const track_info_t* in, Bml_Parser& out )
{
out.setValue( "information:title", in->song );
out.setValue( "information:game", in->game );
out.setValue( "information:author", in->author );
out.setValue( "information:composer", in->composer );
out.setValue( "information:copyright", in->copyright );
out.setValue( "information:date", in->date );
out.setValue( "information:track", in->track );
out.setValue( "information:disc", in->disc );
out.setValue( "information:dumper", in->dumper );
out.setValue( "timing:length", in->length );
out.setValue( "timing:fade", in->fade_length );
}
blargg_err_t Sfm_Emu::set_track_info_( const track_info_t* in, int )
{
::set_track_info(in, metadata);
return 0;
}
static blargg_err_t check_sfm_header( void const* header )
{
if ( memcmp( header, "SFM1", 4 ) )
return gme_wrong_file_type;
return 0;
}
struct Sfm_File : Gme_Info_
{
blargg_vector<byte> data;
Bml_Parser metadata;
unsigned long original_metadata_size;
Sfm_File() { set_type( gme_sfm_type ); }
blargg_err_t load_( Data_Reader& in )
{
long file_size = in.remain();
if ( file_size < Sfm_Emu::sfm_min_file_size )
return gme_wrong_file_type;
RETURN_ERR( data.resize( file_size ) );
RETURN_ERR( in.read( data.begin(), data.end() - data.begin() ) );
RETURN_ERR( check_sfm_header( data.begin() ) );
if ( file_size < 8 )
return "SFM file too small";
int metadata_size = get_le32( data.begin() + 4 );
metadata.parseDocument( (const char *)data.begin() + 8, metadata_size );
original_metadata_size = metadata_size;
return 0;
}
blargg_err_t track_info_( track_info_t* out, int ) const
{
copy_info( out, metadata );
return 0;
}
blargg_err_t set_track_info_( const track_info_t* in, int )
{
::set_track_info( in, metadata );
return 0;
}
};
static Music_Emu* new_sfm_emu () { return BLARGG_NEW Sfm_Emu ; }
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 };
extern gme_type_t const gme_sfm_type = &gme_sfm_type_;
// Setup
blargg_err_t Sfm_Emu::set_sample_rate_( long sample_rate )
{
smp.power();
if ( sample_rate != native_sample_rate )
{
RETURN_ERR( resampler.buffer_size( native_sample_rate / 20 * 2 ) );
resampler.time_ratio( (double) native_sample_rate / sample_rate, 0.9965 );
}
return 0;
}
void Sfm_Emu::enable_accuracy_( bool b )
{
Music_Emu::enable_accuracy_( b );
filter.enable( b );
if ( b ) enable_echo();
}
void Sfm_Emu::mute_voices_( int m )
{
Music_Emu::mute_voices_( m );
for ( int i = 0, j = 1; i < 8; ++i, j <<= 1 )
smp.dsp.channel_enable( i, !( m & j ) );
}
blargg_err_t Sfm_Emu::load_mem_( byte const in [], long size )
{
if ( size < Sfm_Emu::sfm_min_file_size )
return gme_wrong_file_type;
file_data = in;
file_size = size;
RETURN_ERR( check_sfm_header( in ) );
const byte * ptr = file_data;
int metadata_size = get_le32(ptr + 4);
if ( file_size < metadata_size + Sfm_Emu::sfm_min_file_size )
return "SFM file too small";
metadata.parseDocument((const char *) ptr + 8, metadata_size);
return 0;
}
// Emulation
void Sfm_Emu::set_tempo_( double t )
{
smp.set_tempo( t );
}
// (n ? n : 256)
#define IF_0_THEN_256( n ) ((uint8_t) ((n) - 1) + 1)
#define META_ENUM_INT(n,d) (value = metadata.enumValue(n), value ? strtol(value, &end, 10) : (d))
static const byte ipl_rom[0x40] =
{
0xCD, 0xEF, 0xBD, 0xE8, 0x00, 0xC6, 0x1D, 0xD0,
0xFC, 0x8F, 0xAA, 0xF4, 0x8F, 0xBB, 0xF5, 0x78,
0xCC, 0xF4, 0xD0, 0xFB, 0x2F, 0x19, 0xEB, 0xF4,
0xD0, 0xFC, 0x7E, 0xF4, 0xD0, 0x0B, 0xE4, 0xF5,
0xCB, 0xF4, 0xD7, 0x00, 0xFC, 0xD0, 0xF3, 0xAB,
0x01, 0x10, 0xEF, 0x7E, 0xF4, 0x10, 0xEB, 0xBA,
0xF6, 0xDA, 0x00, 0xBA, 0xF4, 0xC4, 0xF4, 0xDD,
0x5D, 0xD0, 0xDB, 0x1F, 0x00, 0x00, 0xC0, 0xFF
};
blargg_err_t Sfm_Emu::start_track_( int track )
{
RETURN_ERR( Music_Emu::start_track_( track ) );
resampler.clear();
filter.clear();
const byte * ptr = file_data;
int metadata_size = get_le32(ptr + 4);
memcpy( smp.iplrom, ipl_rom, 64 );
smp.reset();
memcpy( smp.apuram, ptr + 8 + metadata_size, 65536 );
memcpy( smp.dsp.spc_dsp.m.echo_ram, ptr + 8 + metadata_size, 65536 );
memcpy( smp.dsp.spc_dsp.m.regs, ptr + 8 + metadata_size + 65536, 128 );
const uint8_t* log_begin = ptr + 8 + metadata_size + 65536 + 128;
const uint8_t* log_end = ptr + file_size;
size_t loop_begin = log_end - log_begin;
char * end;
const char * value;
loop_begin = META_ENUM_INT("timing:loopstart", loop_begin);
smp.set_sfm_queue( log_begin, log_end, log_begin + loop_begin );
uint32_t test = META_ENUM_INT("smp:test", 0);
smp.status.clock_speed = (test >> 6) & 3;
smp.status.timer_speed = (test >> 4) & 3;
smp.status.timers_enable = test & 0x08;
smp.status.ram_disable = test & 0x04;
smp.status.ram_writable = test & 0x02;
smp.status.timers_disable = test & 0x01;
smp.status.iplrom_enable = META_ENUM_INT("smp:iplrom",1);
smp.status.dsp_addr = META_ENUM_INT("smp:dspaddr",0);
value = metadata.enumValue("smp:ram");
if (value)
{
smp.status.ram00f8 = strtol(value, &end, 10);
if (*end)
{
value = end + 1;
smp.status.ram00f9 = strtol(value, &end, 10);
}
}
std::string name;
std::ostringstream oss;
name = "smp:regs:";
smp.regs.pc = META_ENUM_INT(name + "pc", 0xffc0);
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);
smp.regs.p = META_ENUM_INT(name + "psw", 0x02);
value = metadata.enumValue("smp:ports");
if (value)
{
for (unsigned long i = 0; i < _countof(smp.sfm_last); i++)
{
smp.sfm_last[i] = strtol(value, &end, 10);
if (*end == ',')
value = end + 1;
else
break;
}
}
for (int i = 0; i < 3; ++i)
{
SuperFamicom::SMP::Timer<192> &t = (i == 0 ? smp.timer0 : (i == 1 ? smp.timer1 : *(SuperFamicom::SMP::Timer<192>*)&smp.timer2));
oss.str("");
oss.clear();
oss << "smp:timer[" << i << "]:";
name = oss.str();
value = metadata.enumValue(name + "enable");
if (value)
{
t.enable = !!strtol(value, &end, 10);
}
value = metadata.enumValue(name + "target");
if (value)
{
t.target = strtol(value, &end, 10);
}
value = metadata.enumValue(name + "stage");
if (value)
{
t.stage0_ticks = strtol(value, &end, 10);
if (*end != ',') break;
value = end + 1;
t.stage1_ticks = strtol(value, &end, 10);
if (*end != ',') break;
value = end + 1;
t.stage2_ticks = strtol(value, &end, 10);
if (*end != ',') break;
value = end + 1;
t.stage3_ticks = strtol(value, &end, 10);
}
value = metadata.enumValue(name + "line");
if (value)
{
t.current_line = !!strtol(value, &end, 10);
}
}
smp.dsp.clock = META_ENUM_INT("dsp:clock", 0) * 4096;
smp.dsp.spc_dsp.m.echo_hist_pos = &smp.dsp.spc_dsp.m.echo_hist[META_ENUM_INT("dsp:echohistaddr", 0)];
value = metadata.enumValue("dsp:echohistdata");
if (value)
{
for (int i = 0; i < 8; ++i)
{
smp.dsp.spc_dsp.m.echo_hist[i][0] = strtol(value, &end, 10);
value = strchr(value, ',');
if (!value) break;
++value;
smp.dsp.spc_dsp.m.echo_hist[i][1] = strtol(value, &end, 10);
value = strchr(value, ',');
if (!value) break;
++value;
}
}
smp.dsp.spc_dsp.m.phase = META_ENUM_INT("dsp:sample", 0);
smp.dsp.spc_dsp.m.kon = META_ENUM_INT("dsp:kon", 0);
smp.dsp.spc_dsp.m.noise = META_ENUM_INT("dsp:noise", 0);
smp.dsp.spc_dsp.m.counter = META_ENUM_INT("dsp:counter", 0);
smp.dsp.spc_dsp.m.echo_offset = META_ENUM_INT("dsp:echooffset", 0);
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.outx_buf = META_ENUM_INT("dsp:outx", 0);
smp.dsp.spc_dsp.m.t_pmon = META_ENUM_INT("dsp:pmon", 0);
smp.dsp.spc_dsp.m.t_non = META_ENUM_INT("dsp:non", 0);
smp.dsp.spc_dsp.m.t_eon = META_ENUM_INT("dsp:eon", 0);
smp.dsp.spc_dsp.m.t_dir = META_ENUM_INT("dsp:dir", 0);
smp.dsp.spc_dsp.m.t_koff = META_ENUM_INT("dsp:koff", 0);
smp.dsp.spc_dsp.m.t_brr_next_addr = META_ENUM_INT("dsp:brrnext", 0);
smp.dsp.spc_dsp.m.t_adsr0 = META_ENUM_INT("dsp:adsr0", 0);
smp.dsp.spc_dsp.m.t_brr_header = META_ENUM_INT("dsp:brrheader", 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);
smp.dsp.spc_dsp.m.t_echo_enabled = !META_ENUM_INT("dsp:echodisable", 0);
smp.dsp.spc_dsp.m.t_dir_addr = META_ENUM_INT("dsp:diraddr", 0);
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);
smp.dsp.spc_dsp.m.t_looped = META_ENUM_INT("dsp:looped", 0);
smp.dsp.spc_dsp.m.t_echo_ptr = META_ENUM_INT("dsp:echoaddr", 0);
#define META_ENUM_LEVELS(n, o) \
value = metadata.enumValue(n); \
if (value) \
{ \
(o)[0] = strtol(value, &end, 10); \
if (*end) \
{ \
value = end + 1; \
(o)[1] = strtol(value, &end, 10); \
} \
}
META_ENUM_LEVELS("dsp:mainout", smp.dsp.spc_dsp.m.t_main_out);
META_ENUM_LEVELS("dsp:echoout", smp.dsp.spc_dsp.m.t_echo_out);
META_ENUM_LEVELS("dsp:echoin", smp.dsp.spc_dsp.m.t_echo_in);
#undef META_ENUM_LEVELS
for (int i = 0; i < 8; ++i)
{
oss.str("");
oss.clear();
oss << "dsp:voice[" << i << "]:";
name = oss.str();
SuperFamicom::SPC_DSP::voice_t & voice = smp.dsp.spc_dsp.m.voices[i];
value = metadata.enumValue(name + "brrhistaddr");
if (value)
{
voice.buf_pos = strtol(value, &end, 10);
}
value = metadata.enumValue(name + "brrhistdata");
if (value)
{
for (int j = 0; j < SuperFamicom::SPC_DSP::brr_buf_size; ++j)
{
voice.buf[j] = voice.buf[j + SuperFamicom::SPC_DSP::brr_buf_size] = strtol(value, &end, 10);
if (!*end) break;
value = end + 1;
}
}
voice.interp_pos = META_ENUM_INT(name + "interpaddr",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);
voice.regs = &smp.dsp.spc_dsp.m.regs[META_ENUM_INT(name + "vidx",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);
voice.t_envx_out = META_ENUM_INT(name + "envxout", 0);
voice.hidden_env = META_ENUM_INT(name + "envcache", 0);
}
filter.set_gain( (int) (gain() * SPC_Filter::gain_unit) );
return 0;
}
#undef META_ENUM_INT
blargg_err_t Sfm_Emu::play_and_filter( long count, sample_t out [] )
{
smp.render( out, count );
filter.run( out, count );
return 0;
}
blargg_err_t Sfm_Emu::skip_( long count )
{
if ( sample_rate() != native_sample_rate )
{
count = long (count * resampler.ratio()) & ~1;
count -= resampler.skip_input( count );
}
// TODO: shouldn't skip be adjusted for the 64 samples read afterwards?
if ( count > 0 )
{
smp.skip( count );
filter.clear();
}
if ( sample_rate() != native_sample_rate )
{
// eliminate pop due to resampler
const long resampler_latency = 64;
sample_t buf [resampler_latency];
return play_( resampler_latency, buf );
}
return 0;
}
blargg_err_t Sfm_Emu::play_( long count, sample_t out [] )
{
if ( sample_rate() == native_sample_rate )
return play_and_filter( count, out );
long remain = count;
while ( remain > 0 )
{
remain -= resampler.read( &out [count - remain], remain );
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 $vers
#ifndef SPC_SFM_H
#define SPC_SFM_H
#include "Music_Emu.h"
#include "higan/smp/smp.hpp"
#include "Spc_Filter.h"
#include "Bml_Parser.h"
#if GME_SPC_FAST_RESAMPLER
#include "Upsampler.h"
typedef Upsampler Spc_Emu_Resampler;
#else
#include "Fir_Resampler.h"
typedef Fir_Resampler<24> Spc_Emu_Resampler;
#endif
class Sfm_Emu : public Music_Emu {
public:
// Minimum allowed file size
enum { sfm_min_file_size = 8 + 65536 + 128 };
// The Super Nintendo hardware samples at 32kHz. Other sample rates are
// handled by resampling the 32kHz output; emulation accuracy is not affected.
enum { native_sample_rate = 32000 };
// This will serialize the current state of the emulator into a new SFM file
blargg_err_t serialize( std::vector<uint8_t> & out );
// Disables annoying pseudo-surround effect some music uses
void disable_surround( bool disable = true ) { smp.dsp.disable_surround( disable ); }
// Enables gaussian, cubic or sinc interpolation
void interpolation_level( int level = 0 ) { smp.dsp.spc_dsp.interpolation_level( level ); }
// Enables an analog signal simulation filter
void enable_filter( bool enable = true ) { _enable_filter = enable; if (enable) filter.clear(); }
// Enables native echo
void enable_echo(bool enable = true) { smp.dsp.spc_dsp.enable_echo(enable); }
virtual void mute_effects(bool mute) { enable_echo(!mute); }
SuperFamicom::SMP const* get_smp() const;
SuperFamicom::SMP * get_smp();
static gme_type_t static_type() { return gme_sfm_type; }
// Implementation
public:
Sfm_Emu();
~Sfm_Emu();
protected:
blargg_err_t load_mem_( byte const [], long );
blargg_err_t track_info_( track_info_t*, int track ) const;
blargg_err_t set_track_info_( const track_info_t*, int track );
blargg_err_t set_sample_rate_( long );
blargg_err_t start_track_( int );
blargg_err_t play_( long, sample_t [] );
blargg_err_t skip_( long );
void mute_voices_( int );
void set_tempo_( double );
void enable_accuracy_( bool );
byte const* file_data;
long file_size;
private:
Spc_Emu_Resampler resampler;
SPC_Filter filter;
SuperFamicom::SMP smp;
bool _enable_filter;
Bml_Parser metadata;
blargg_err_t play_and_filter( long count, sample_t out [] );
};
inline 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
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
// Super Nintendo SFM music file emulator
#ifndef SPC_SFM_H
#define SPC_SFM_H
#include "Fir_Resampler.h"
#include "Music_Emu.h"
#include "../higan/smp/smp.hpp"
#include "Spc_Filter.h"
#include "Bml_Parser.h"
class Sfm_Emu : public Music_Emu {
public:
// Minimum allowed file size
enum { sfm_min_file_size = 8 + 65536 + 128 };
// The Super Nintendo hardware samples at 32kHz. Other sample rates are
// handled by resampling the 32kHz output; emulation accuracy is not affected.
enum { native_sample_rate = 32000 };
// This will serialize the current state of the emulator into a new SFM file
blargg_err_t serialize( std::vector<uint8_t> & out );
// Disables annoying pseudo-surround effect some music uses
void disable_surround( bool disable = true );
// Enables gaussian=0, cubic=1 or sinc=2 interpolation
// Or sets worse quality, linear=-1, nearest=-2
void interpolation_level( int level = 0 );
// Enables native echo
void enable_echo(bool enable = true);
void mute_effects(bool mute);
SuperFamicom::SMP const* get_smp() const;
SuperFamicom::SMP * get_smp();
static gme_type_t static_type() { return gme_sfm_type; }
public:
Sfm_Emu();
~Sfm_Emu();
protected:
blargg_err_t load_mem_( byte const [], long );
blargg_err_t track_info_( track_info_t*, int track ) const;
blargg_err_t set_track_info_( const track_info_t*, int track );
blargg_err_t set_sample_rate_( long );
blargg_err_t start_track_( int );
blargg_err_t play_( long, sample_t [] );
blargg_err_t skip_( long );
void mute_voices_( int );
void set_tempo_( double );
void enable_accuracy_( bool );
byte const* file_data;
long file_size;
private:
Fir_Resampler<24> resampler;
SPC_Filter filter;
SuperFamicom::SMP smp;
Bml_Parser metadata;
blargg_err_t play_and_filter( long count, sample_t out [] );
};
inline void Sfm_Emu::disable_surround( bool disable ) { smp.dsp.disable_surround( disable ); }
inline void Sfm_Emu::interpolation_level( int level ) { smp.dsp.spc_dsp.interpolation_level( level ); }
inline void Sfm_Emu::enable_echo(bool enable) { smp.dsp.spc_dsp.enable_echo(enable); }
inline void Sfm_Emu::mute_effects(bool mute) { enable_echo(!mute); }
inline SuperFamicom::SMP const* Sfm_Emu::get_smp() const { return &smp; }
inline SuperFamicom::SMP * Sfm_Emu::get_smp() { return &smp; }
#endif // SPC_SFM_H

View File

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

View File

@ -55,7 +55,8 @@ gme_type_t const* gme_type_list()
#endif
#ifdef USE_GME_SPC
gme_spc_type,
gme_sfm_type,
gme_rsn_type,
gme_sfm_type,
#endif
#ifdef USE_GME_VGM
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('S','A','P',0x0D): return "SAP";
case BLARGG_4CHAR('S','N','E','S'): return "SPC";
case BLARGG_4CHAR('R','a','r','!'): return "RSN";
case BLARGG_4CHAR('V','g','m',' '): return "VGM";
}
if (get_be16(header) == BLARGG_2CHAR(0x1F, 0x8B))
return "VGZ";

View File

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

View File

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

View File

@ -2,7 +2,7 @@
#include "SPC_DSP.h"
#include "blargg_endian.h"
#include "../../gme/blargg_endian.h"
#include <string.h>
/* Copyright (C) 2007 Shay Green. This module is free software; you
@ -16,7 +16,7 @@ details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
#include "../../gme/blargg_source.h"
namespace SuperFamicom {
@ -992,7 +992,7 @@ VOICE_CLOCK(V9_V6_V3) { voice_V9(v); voice_V6(v+1); voice_V3(v+2); }
//// Echo
// Current echo buffer pointer for left/right channel
#define ECHO_PTR( ch ) (&m.ram [m.t_echo_ptr + ch * 2])
#define ECHO_PTR( ch ) (&m.echo_ram [m.t_echo_ptr + ch * 2])
// Sample in echo history buffer, where 0 is the oldest
#define ECHO_FIR( i ) (m.echo_hist_pos [i])

View File

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

View File

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

View File

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

View File

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