Clean up new GME somewhat
parent
fc38295d02
commit
e4e6da1a94
|
@ -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 */,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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_; }
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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 );
|
||||
}
|
|
@ -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
|
|
@ -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, ®S [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 ®S [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"
|
File diff suppressed because it is too large
Load Diff
|
@ -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 ); }
|
|
@ -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
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, ...)
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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])
|
|
@ -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]
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
#include "SPC_DSP.h"
|
||||
|
||||
#include "blargg_common.h"
|
||||
#include "../../gme/blargg_common.h"
|
||||
|
||||
namespace SuperFamicom {
|
||||
|
|
@ -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;
|
|
@ -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"
|
||||
|
Loading…
Reference in New Issue