From e4e6da1a949c6694ddfc27e5783e5b7811918b71 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Tue, 4 Jan 2022 03:42:18 -0800 Subject: [PATCH] Clean up new GME somewhat --- Frameworks/GME/GME.xcodeproj/project.pbxproj | 11 +- Frameworks/GME/gme/Bml_Parser.cpp | 734 +++++----- Frameworks/GME/gme/Bml_Parser.h | 124 +- Frameworks/GME/gme/CMakeLists.txt | 12 + Frameworks/GME/gme/Gme_File.cpp | 36 +- Frameworks/GME/gme/Gme_File.h | 26 +- Frameworks/GME/gme/Hes_Cpu.cpp | 6 +- Frameworks/GME/gme/M3u_Playlist.cpp | 99 +- Frameworks/GME/gme/M3u_Playlist.h | 8 +- Frameworks/GME/gme/Nes_Fds_Apu.cpp | 564 ++++---- Frameworks/GME/gme/Nes_Fds_Apu.h | 257 ++-- Frameworks/GME/gme/Nes_Mmc5_Apu.h | 121 +- Frameworks/GME/gme/Nes_Vrc7_Apu.cpp | 436 +++--- Frameworks/GME/gme/Nes_Vrc7_Apu.h | 166 +-- Frameworks/GME/gme/Nsf_Emu.cpp | 445 +++---- Frameworks/GME/gme/Nsf_Emu.h | 10 +- Frameworks/GME/gme/Snes_Spc.cpp | 383 ++++++ Frameworks/GME/gme/Snes_Spc.h | 283 ++++ Frameworks/GME/gme/Spc_Cpu.cpp | 554 ++++++++ Frameworks/GME/gme/Spc_Cpu.h | 1182 +++++++++++++++++ Frameworks/GME/gme/Spc_Dsp.cpp | 712 ++++++++++ Frameworks/GME/gme/Spc_Dsp.h | 212 +++ Frameworks/GME/gme/Spc_Emu.cpp | 1112 +++++++++------- Frameworks/GME/gme/Spc_Emu.h | 224 ++-- Frameworks/GME/gme/Spc_Sfm.cpp | 992 +++++++------- Frameworks/GME/gme/Spc_Sfm.h | 165 ++- Frameworks/GME/gme/blargg_config.h | 4 +- Frameworks/GME/gme/gme.cpp | 5 +- Frameworks/GME/gme/gme.h | 3 +- Frameworks/GME/gme/nes_cpu_io.h | 26 +- .../GME/{gme => }/higan/dsp/SPC_DSP.cpp | 6 +- Frameworks/GME/{gme => }/higan/dsp/SPC_DSP.h | 5 +- Frameworks/GME/{gme => }/higan/dsp/dsp.cpp | 0 Frameworks/GME/{gme => }/higan/dsp/dsp.hpp | 2 +- .../higan/processor/spc700/algorithms.cpp | 3 + .../higan/processor/spc700/disassembler.cpp | 0 .../higan/processor/spc700/instructions.cpp | 0 .../higan/processor/spc700/memory.hpp | 0 .../higan/processor/spc700/registers.hpp | 0 .../higan/processor/spc700/spc700.cpp | 0 .../higan/processor/spc700/spc700.hpp | 0 Frameworks/GME/{gme => }/higan/smp/memory.cpp | 0 Frameworks/GME/{gme => }/higan/smp/smp.cpp | 0 Frameworks/GME/{gme => }/higan/smp/smp.hpp | 2 +- Frameworks/GME/{gme => }/higan/smp/timing.cpp | 0 45 files changed, 6237 insertions(+), 2693 deletions(-) create mode 100644 Frameworks/GME/gme/Snes_Spc.cpp create mode 100644 Frameworks/GME/gme/Snes_Spc.h create mode 100644 Frameworks/GME/gme/Spc_Cpu.cpp create mode 100644 Frameworks/GME/gme/Spc_Cpu.h create mode 100644 Frameworks/GME/gme/Spc_Dsp.cpp create mode 100644 Frameworks/GME/gme/Spc_Dsp.h rename Frameworks/GME/{gme => }/higan/dsp/SPC_DSP.cpp (99%) rename Frameworks/GME/{gme => }/higan/dsp/SPC_DSP.h (98%) rename Frameworks/GME/{gme => }/higan/dsp/dsp.cpp (100%) rename Frameworks/GME/{gme => }/higan/dsp/dsp.hpp (93%) rename Frameworks/GME/{gme => }/higan/processor/spc700/algorithms.cpp (98%) rename Frameworks/GME/{gme => }/higan/processor/spc700/disassembler.cpp (100%) rename Frameworks/GME/{gme => }/higan/processor/spc700/instructions.cpp (100%) rename Frameworks/GME/{gme => }/higan/processor/spc700/memory.hpp (100%) rename Frameworks/GME/{gme => }/higan/processor/spc700/registers.hpp (100%) rename Frameworks/GME/{gme => }/higan/processor/spc700/spc700.cpp (100%) rename Frameworks/GME/{gme => }/higan/processor/spc700/spc700.hpp (100%) rename Frameworks/GME/{gme => }/higan/smp/memory.cpp (100%) rename Frameworks/GME/{gme => }/higan/smp/smp.cpp (100%) rename Frameworks/GME/{gme => }/higan/smp/smp.hpp (98%) rename Frameworks/GME/{gme => }/higan/smp/timing.cpp (100%) diff --git a/Frameworks/GME/GME.xcodeproj/project.pbxproj b/Frameworks/GME/GME.xcodeproj/project.pbxproj index 29dd6ac7d..4b9b91dd4 100644 --- a/Frameworks/GME/GME.xcodeproj/project.pbxproj +++ b/Frameworks/GME/GME.xcodeproj/project.pbxproj @@ -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 = ""; }; 17C8F1E60CBED286008D969D /* Spc_Emu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Spc_Emu.cpp; path = gme/Spc_Emu.cpp; sourceTree = ""; }; 17C8F1E70CBED286008D969D /* Spc_Emu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Spc_Emu.h; path = gme/Spc_Emu.h; sourceTree = ""; }; + 8302AF4E2784668C0066143E /* vrc7tone.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vrc7tone.h; sourceTree = ""; }; 833F68361CDBCAB200AFB9F0 /* es */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; 83489CB12783015200BDCEA2 /* gme_types.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gme_types.h; path = gme/gme_types.h; sourceTree = ""; }; 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 = ""; }; @@ -226,7 +227,6 @@ 83489CDC2783CAC100BDCEA2 /* emu2413_NESpatches.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = emu2413_NESpatches.txt; sourceTree = ""; }; 83489CDD2783CAC100BDCEA2 /* emu2413.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = emu2413.h; sourceTree = ""; }; 83489CDF2783CAC100BDCEA2 /* emu2413.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = emu2413.c; sourceTree = ""; }; - 83489CE62783CADC00BDCEA2 /* vrc7tone.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vrc7tone.h; sourceTree = ""; }; 83489CE72783CADC00BDCEA2 /* panning.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = panning.c; sourceTree = ""; }; 83489CE82783CADC00BDCEA2 /* panning.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = panning.h; sourceTree = ""; }; 83489CEC2783D86700BDCEA2 /* mamedef.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mamedef.h; sourceTree = ""; }; @@ -432,7 +432,6 @@ 8370B70B17F615FE001A4D7A /* Spc_Filter.h */, 8370B70C17F615FE001A4D7A /* Spc_Sfm.cpp */, 8370B70D17F615FE001A4D7A /* Spc_Sfm.h */, - 83489CE62783CADC00BDCEA2 /* vrc7tone.h */, ); name = Source; sourceTree = ""; @@ -456,6 +455,7 @@ 83489CEC2783D86700BDCEA2 /* mamedef.h */, 83489CE72783CADC00BDCEA2 /* panning.c */, 83489CE82783CADC00BDCEA2 /* panning.h */, + 8302AF4E2784668C0066143E /* vrc7tone.h */, ); path = ext; sourceTree = ""; @@ -467,8 +467,7 @@ 83FC5D36181B47FB00B917E5 /* dsp */, 83FC5D40181B47FB00B917E5 /* smp */, ); - name = higan; - path = gme/higan; + path = higan; sourceTree = ""; }; 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 */, diff --git a/Frameworks/GME/gme/Bml_Parser.cpp b/Frameworks/GME/gme/Bml_Parser.cpp index 1bde07be1..761e4a779 100644 --- a/Frameworks/GME/gme/Bml_Parser.cpp +++ b/Frameworks/GME/gme/Bml_Parser.cpp @@ -1,357 +1,377 @@ -#include -#include -#include - -#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::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::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::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 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 +#include +#include + +#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::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::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::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 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; + } +} diff --git a/Frameworks/GME/gme/Bml_Parser.h b/Frameworks/GME/gme/Bml_Parser.h index f8bcbd511..3f65c015c 100644 --- a/Frameworks/GME/gme/Bml_Parser.h +++ b/Frameworks/GME/gme/Bml_Parser.h @@ -1,61 +1,63 @@ -#ifndef BML_PARSER_H -#define BML_PARSER_H - -#include -#include -#include - -class Bml_Node -{ - char * name; - char * value; - - std::vector 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 +#include +#include + +class Bml_Node +{ + char * name; + char * value; + + std::vector 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 diff --git a/Frameworks/GME/gme/CMakeLists.txt b/Frameworks/GME/gme/CMakeLists.txt index 62f2de65f..5b4c644e6 100644 --- a/Frameworks/GME/gme/CMakeLists.txt +++ b/Frameworks/GME/gme/CMakeLists.txt @@ -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) diff --git a/Frameworks/GME/gme/Gme_File.cpp b/Frameworks/GME/gme/Gme_File.cpp index 37c78d50d..24bcada6f 100644 --- a/Frameworks/GME/gme/Gme_File.cpp +++ b/Frameworks/GME/gme/Gme_File.cpp @@ -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; } diff --git a/Frameworks/GME/gme/Gme_File.h b/Frameworks/GME/gme/Gme_File.h index 8724b1313..b1ff0ba05 100644 --- a/Frameworks/GME/gme/Gme_File.h +++ b/Frameworks/GME/gme/Gme_File.h @@ -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 }; diff --git a/Frameworks/GME/gme/Hes_Cpu.cpp b/Frameworks/GME/gme/Hes_Cpu.cpp index 8bf186e68..0c5808413 100644 --- a/Frameworks/GME/gme/Hes_Cpu.cpp +++ b/Frameworks/GME/gme/Hes_Cpu.cpp @@ -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 diff --git a/Frameworks/GME/gme/M3u_Playlist.cpp b/Frameworks/GME/gme/M3u_Playlist.cpp index 498deee78..8759c6e95 100644 --- a/Frameworks/GME/gme/M3u_Playlist.cpp +++ b/Frameworks/GME/gme/M3u_Playlist.cpp @@ -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"; diff --git a/Frameworks/GME/gme/M3u_Playlist.h b/Frameworks/GME/gme/M3u_Playlist.h index f6ab2600c..63e199a16 100644 --- a/Frameworks/GME/gme/M3u_Playlist.h +++ b/Frameworks/GME/gme/M3u_Playlist.h @@ -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_; } diff --git a/Frameworks/GME/gme/Nes_Fds_Apu.cpp b/Frameworks/GME/gme/Nes_Fds_Apu.cpp index 17fa21151..30ffedded 100644 --- a/Frameworks/GME/gme/Nes_Fds_Apu.cpp +++ b/Frameworks/GME/gme/Nes_Fds_Apu.cpp @@ -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 - -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 + +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; +} diff --git a/Frameworks/GME/gme/Nes_Fds_Apu.h b/Frameworks/GME/gme/Nes_Fds_Apu.h index 16676795c..0dc7cc71b 100644 --- a/Frameworks/GME/gme/Nes_Fds_Apu.h +++ b/Frameworks/GME/gme/Nes_Fds_Apu.h @@ -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 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 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 diff --git a/Frameworks/GME/gme/Nes_Mmc5_Apu.h b/Frameworks/GME/gme/Nes_Mmc5_Apu.h index 749587a57..a5c7cf55e 100644 --- a/Frameworks/GME/gme/Nes_Mmc5_Apu.h +++ b/Frameworks/GME/gme/Nes_Mmc5_Apu.h @@ -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 diff --git a/Frameworks/GME/gme/Nes_Vrc7_Apu.cpp b/Frameworks/GME/gme/Nes_Vrc7_Apu.cpp index c6c64d515..179827136 100644 --- a/Frameworks/GME/gme/Nes_Vrc7_Apu.cpp +++ b/Frameworks/GME/gme/Nes_Vrc7_Apu.cpp @@ -1,218 +1,218 @@ -#include "Nes_Vrc7_Apu.h" - -extern "C" { -#include "../ext/emu2413.h" -} - -#include - -#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 + +#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; +} diff --git a/Frameworks/GME/gme/Nes_Vrc7_Apu.h b/Frameworks/GME/gme/Nes_Vrc7_Apu.h index d61472569..5e44e6b2a 100644 --- a/Frameworks/GME/gme/Nes_Vrc7_Apu.h +++ b/Frameworks/GME/gme/Nes_Vrc7_Apu.h @@ -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 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 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 diff --git a/Frameworks/GME/gme/Nsf_Emu.cpp b/Frameworks/GME/gme/Nsf_Emu.cpp index 89ebf3207..6d19ee1a2 100644 --- a/Frameworks/GME/gme/Nsf_Emu.cpp +++ b/Frameworks/GME/gme/Nsf_Emu.cpp @@ -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 diff --git a/Frameworks/GME/gme/Nsf_Emu.h b/Frameworks/GME/gme/Nsf_Emu.h index 8786a225c..0feec1fa6 100644 --- a/Frameworks/GME/gme/Nsf_Emu.h +++ b/Frameworks/GME/gme/Nsf_Emu.h @@ -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 apu_names; static int pcm_read( void*, nes_addr_t ); blargg_err_t init_sound(); diff --git a/Frameworks/GME/gme/Snes_Spc.cpp b/Frameworks/GME/gme/Snes_Spc.cpp new file mode 100644 index 000000000..064a0fde7 --- /dev/null +++ b/Frameworks/GME/gme/Snes_Spc.cpp @@ -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 + +/* 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 ); +} diff --git a/Frameworks/GME/gme/Snes_Spc.h b/Frameworks/GME/gme/Snes_Spc.h new file mode 100644 index 000000000..68c780ab7 --- /dev/null +++ b/Frameworks/GME/gme/Snes_Spc.h @@ -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 + +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 + +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 diff --git a/Frameworks/GME/gme/Spc_Cpu.cpp b/Frameworks/GME/gme/Spc_Cpu.cpp new file mode 100644 index 000000000..5944ff516 --- /dev/null +++ b/Frameworks/GME/gme/Spc_Cpu.cpp @@ -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 + +/* 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" diff --git a/Frameworks/GME/gme/Spc_Cpu.h b/Frameworks/GME/gme/Spc_Cpu.h new file mode 100644 index 000000000..ec56146e7 --- /dev/null +++ b/Frameworks/GME/gme/Spc_Cpu.h @@ -0,0 +1,1182 @@ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ + +/* 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 */ + +//// Memory access + +#if SPC_MORE_ACCURACY + #define SUSPICIOUS_OPCODE( name ) ((void) 0) +#else + #define SUSPICIOUS_OPCODE( name ) debug_printf( "SPC: suspicious opcode: " name "\n" ) +#endif + +#define CPU_READ( time, offset, addr )\ + cpu_read( addr, time + offset ) + +#define CPU_WRITE( time, offset, addr, data )\ + cpu_write( data, addr, time + offset ) + +#if SPC_MORE_ACCURACY + #define CPU_READ_TIMER( time, offset, addr, out )\ + { out = CPU_READ( time, offset, addr ); } + +#else + // timers are by far the most common thing read from dp + #define CPU_READ_TIMER( time, offset, addr_, out )\ + {\ + rel_time_t adj_time = time + offset;\ + int dp_addr = addr_;\ + int ti = dp_addr - (r_t0out + 0xF0);\ + if ( (unsigned) ti < timer_count )\ + {\ + Timer* t = &m.timers [ti];\ + if ( adj_time >= t->next_time )\ + t = run_timer_( t, adj_time );\ + out = t->counter;\ + t->counter = 0;\ + }\ + else\ + {\ + out = ram [dp_addr];\ + int i = dp_addr - 0xF0;\ + if ( (unsigned) i < 0x10 )\ + out = cpu_read_smp_reg( i, adj_time );\ + }\ + } +#endif + +#define TIME_ADJ( n ) (n) + +#define READ_TIMER( time, addr, out ) CPU_READ_TIMER( rel_time, TIME_ADJ(time), (addr), out ) +#define READ( time, addr ) CPU_READ ( rel_time, TIME_ADJ(time), (addr) ) +#define WRITE( time, addr, data ) CPU_WRITE( rel_time, TIME_ADJ(time), (addr), (data) ) + +#define DP_ADDR( addr ) (dp + (addr)) + +#define READ_DP_TIMER( time, addr, out ) CPU_READ_TIMER( rel_time, TIME_ADJ(time), DP_ADDR( addr ), out ) +#define READ_DP( time, addr ) READ ( time, DP_ADDR( addr ) ) +#define WRITE_DP( time, addr, data ) WRITE( time, DP_ADDR( addr ), data ) + +#define READ_PROG16( addr ) (RAM [(addr) & 0xffff] | (RAM [((addr) + 1) & 0xffff] << 8)) + +#define SET_PC( n ) (pc = n) +#define GET_PC() (pc) +#define READ_PC( pc ) (ram [pc]) +#define READ_PC16( pc ) READ_PROG16( pc ) + +#define SET_SP( v ) (sp = v) +#define GET_SP() ((uint8_t) (sp)) + +#define PUSH16( data )\ +{\ + PUSH( (data & 0xff00) >> 8 );\ + PUSH( data & 0xff );\ +} + +#define PUSH( data )\ +{\ + ram [0x100 + sp] = (uint8_t) (data);\ + --sp;\ +} + +#define POP( out )\ +{\ + ++sp;\ + out = ram [0x100 + sp];\ +} + +#define MEM_BIT( rel ) CPU_mem_bit( pc, rel_time + rel ) + +unsigned Snes_Spc::CPU_mem_bit( uint16_t pc, rel_time_t rel_time ) +{ + unsigned addr = READ_PC16( pc ); + unsigned t = READ( 0, addr & 0x1FFF ) >> (addr >> 13); + return t << 8 & 0x100; +} + +//// Status flag handling + +// Hex value in name to clarify code and bit shifting. +// Flag stored in indicated variable during emulation +int const n80 = 0x80; // nz +int const v40 = 0x40; // psw +int const p20 = 0x20; // dp +int const b10 = 0x10; // psw +int const h08 = 0x08; // psw +int const i04 = 0x04; // psw +int const z02 = 0x02; // nz +int const c01 = 0x01; // c + +int const nz_neg_mask = 0x880; // either bit set indicates N flag set + +#define GET_PSW( out )\ +{\ + out = psw & ~(n80 | p20 | z02 | c01);\ + out |= c >> 8 & c01;\ + out |= dp >> 3 & p20;\ + out |= ((nz >> 4) | nz) & n80;\ + if ( !(uint8_t) nz ) out |= z02;\ +} + +#define SET_PSW( in )\ +{\ + psw = in;\ + c = in << 8;\ + dp = in << 3 & 0x100;\ + nz = (in << 4 & 0x800) | (~in & z02);\ +} + +SPC_CPU_RUN_FUNC +{ + uint8_t* const ram = RAM; + uint8_t a = m.cpu_regs.a; + uint8_t x = m.cpu_regs.x; + uint8_t y = m.cpu_regs.y; + uint16_t pc; + uint8_t sp; + int psw; + int c; + int nz; + int dp; + + SET_PC( m.cpu_regs.pc ); + SET_SP( m.cpu_regs.sp ); + SET_PSW( m.cpu_regs.psw ); + + goto loop; + + + // Main loop + +cbranch_taken_loop: + pc += (int8_t) ram [pc]; +inc_pc_loop: + pc++; +loop: +{ + unsigned opcode; + unsigned data; + + check( (unsigned) a < 0x100 ); + check( (unsigned) x < 0x100 ); + check( (unsigned) y < 0x100 ); + + opcode = ram [pc]; + if ( (rel_time += m.cycle_table [opcode]) > 0 ) + goto out_of_time; + + #ifdef SPC_CPU_OPCODE_HOOK + SPC_CPU_OPCODE_HOOK( GET_PC(), opcode ); + #endif + /* + //SUB_CASE_COUNTER( 1 ); + #define PROFILE_TIMER_LOOP( op, addr, len )\ + if ( opcode == op )\ + {\ + int cond = (unsigned) ((addr) - 0xFD) < 3 &&\ + pc [len] == 0xF0 && pc [len+1] == 0xFE - len;\ + SUB_CASE_COUNTER( op && cond );\ + } + + PROFILE_TIMER_LOOP( 0xEC, GET_LE16( pc + 1 ), 3 ); + PROFILE_TIMER_LOOP( 0xEB, pc [1], 2 ); + PROFILE_TIMER_LOOP( 0xE4, pc [1], 2 ); + */ + + // TODO: if PC is at end of memory, this will get wrong operand (very obscure) + pc++; + data = ram [pc]; + switch ( opcode ) + { + +// Common instructions + +#define BRANCH( cond )\ +{\ + pc++;\ + pc += (int8_t) data;\ + if ( cond )\ + goto loop;\ + pc -= (int8_t) data;\ + rel_time -= 2;\ + goto loop;\ +} + + case 0xF0: // BEQ + BRANCH( !(uint8_t) nz ) // 89% taken + + case 0xD0: // BNE + BRANCH( (uint8_t) nz ) + + case 0x3F:{// CALL + int old_addr = GET_PC() + 2; + SET_PC( READ_PC16( pc ) ); + PUSH16( old_addr ); + goto loop; + } + + case 0x6F:// RET + { + uint8_t l, h; + POP( l ); + POP( h ); + SET_PC( l | (h << 8) ); + } + goto loop; + + case 0xE4: // MOV a,dp + ++pc; + // 80% from timer + READ_DP_TIMER( 0, data, a = nz ); + goto loop; + + case 0xFA:{// MOV dp,dp + int temp; + READ_DP_TIMER( -2, data, temp ); + data = temp + no_read_before_write ; + } + // fall through + case 0x8F:{// MOV dp,#imm + int temp = READ_PC( pc + 1 ); + pc += 2; + + #if !SPC_MORE_ACCURACY + { + int i = dp + temp; + ram [i] = (uint8_t) data; + i -= 0xF0; + if ( (unsigned) i < 0x10 ) // 76% + { + REGS [i] = (uint8_t) data; + + // Registers other than $F2 and $F4-$F7 + if ( i != 2 && (i < 4 || i > 7)) // 12% + cpu_write_smp_reg( data, rel_time, i ); + } + } + #else + WRITE_DP( 0, temp, data ); + #endif + goto loop; + } + + case 0xC4: // MOV dp,a + ++pc; + #if !SPC_MORE_ACCURACY + { + int i = dp + data; + ram [i] = (uint8_t) a; + i -= 0xF0; + if ( (unsigned) i < 0x10 ) // 39% + { + unsigned sel = i - 2; + REGS [i] = (uint8_t) a; + + if ( sel == 1 ) // 51% $F3 + dsp_write( a, rel_time ); + else if ( sel > 1 ) // 1% not $F2 or $F3 + cpu_write_smp_reg_( a, rel_time, i ); + } + } + #else + WRITE_DP( 0, data, a ); + #endif + goto loop; + +#define CASE( n ) /*FALLTHRU*/case n: + +// Define common address modes based on opcode for immediate mode. Execution +// ends with data set to the address of the operand. +#define ADDR_MODES_( op )\ + CASE( op - 0x02 ) /* (X) */\ + data = x + dp;\ + pc--;\ + goto end_##op;\ + CASE( op + 0x0F ) /* (dp)+Y */\ + data = READ_PROG16( data + dp ) + y;\ + goto end_##op;\ + CASE( op - 0x01 ) /* (dp+X) */\ + data = READ_PROG16( ((uint8_t) (data + x)) + dp );\ + goto end_##op;\ + CASE( op + 0x0E ) /* abs+Y */\ + data += y;\ + goto abs_##op;\ + CASE( op + 0x0D ) /* abs+X */\ + data += x;/*FALLTHRU*/\ + CASE( op - 0x03 ) /* abs */\ + abs_##op:\ + data += 0x100 * READ_PC( ++pc );\ + goto end_##op;\ + CASE( op + 0x0C ) /* dp+X */\ + data = (uint8_t) (data + x);/*FALLTHRU*/ + +#define ADDR_MODES_NO_DP( op )\ + ADDR_MODES_( op )\ + data += dp;\ + end_##op: + +#define ADDR_MODES( op )\ + ADDR_MODES_( op )\ + CASE( op - 0x04 ) /* dp */\ + data += dp;\ + end_##op: + +// 1. 8-bit Data Transmission Commands. Group I + + ADDR_MODES_NO_DP( 0xE8 ) // MOV A,addr + a = nz = READ( 0, data ); + goto inc_pc_loop; + + case 0xBF:{// MOV A,(X)+ + int temp = x + dp; + x = (uint8_t) (x + 1); + a = nz = READ( -1, temp ); + goto loop; + } + + case 0xE8: // MOV A,imm + a = data; + nz = data; + goto inc_pc_loop; + + case 0xF9: // MOV X,dp+Y + data = (uint8_t) (data + y);/*FALLTHRU*/ + case 0xF8: // MOV X,dp + READ_DP_TIMER( 0, data, x = nz ); + goto inc_pc_loop; + + case 0xE9: // MOV X,abs + data = READ_PC16( pc ); + ++pc; + data = READ( 0, data );/*FALLTHRU*/ + case 0xCD: // MOV X,imm + x = data; + nz = data; + goto inc_pc_loop; + + case 0xFB: // MOV Y,dp+X + data = (uint8_t) (data + x);/*FALLTHRU*/ + case 0xEB: // MOV Y,dp + // 70% from timer + pc++; + READ_DP_TIMER( 0, data, y = nz ); + goto loop; + + case 0xEC:{// MOV Y,abs + int temp = READ_PC16( pc ); + pc += 2; + READ_TIMER( 0, temp, y = nz ); + //y = nz = READ( 0, temp ); + goto loop; + } + + case 0x8D: // MOV Y,imm + y = data; + nz = data; + goto inc_pc_loop; + +// 2. 8-BIT DATA TRANSMISSION COMMANDS, GROUP 2 + + ADDR_MODES_NO_DP( 0xC8 ) // MOV addr,A + WRITE( 0, data, a ); + goto inc_pc_loop; + + { + int temp; + case 0xCC: // MOV abs,Y + temp = y; + goto mov_abs_temp; + case 0xC9: // MOV abs,X + temp = x; + mov_abs_temp: + WRITE( 0, READ_PC16( pc ), temp ); + pc += 2; + goto loop; + } + + case 0xD9: // MOV dp+Y,X + data = (uint8_t) (data + y);/*FALLTHRU*/ + case 0xD8: // MOV dp,X + WRITE( 0, data + dp, x ); + goto inc_pc_loop; + + case 0xDB: // MOV dp+X,Y + data = (uint8_t) (data + x);/*FALLTHRU*/ + case 0xCB: // MOV dp,Y + WRITE( 0, data + dp, y ); + goto inc_pc_loop; + +// 3. 8-BIT DATA TRANSMISSIN COMMANDS, GROUP 3. + + case 0x7D: // MOV A,X + a = x; + nz = x; + goto loop; + + case 0xDD: // MOV A,Y + a = y; + nz = y; + goto loop; + + case 0x5D: // MOV X,A + x = a; + nz = a; + goto loop; + + case 0xFD: // MOV Y,A + y = a; + nz = a; + goto loop; + + case 0x9D: // MOV X,SP + x = nz = GET_SP(); + goto loop; + + case 0xBD: // MOV SP,X + SET_SP( x ); + goto loop; + + //case 0xC6: // MOV (X),A (handled by MOV addr,A in group 2) + + case 0xAF: // MOV (X)+,A + WRITE_DP( 0, x, a + no_read_before_write ); + x = (uint8_t) (x + 1); + goto loop; + +// 5. 8-BIT LOGIC OPERATION COMMANDS + +#define LOGICAL_OP( op, func )\ + ADDR_MODES( op ) /* addr */\ + data = READ( 0, data );/*FALLTHRU*/\ + case op: /* imm */\ + nz = a func##= data;\ + goto inc_pc_loop;\ + { unsigned addr;\ + case op + 0x11: /* X,Y */\ + data = READ_DP( -2, y );\ + addr = x + dp;\ + goto addr_##op;\ + case op + 0x01: /* dp,dp */\ + data = READ_DP( -3, data );\ + case op + 0x10:{/*dp,imm*/\ + uint16_t addr2 = pc + 1;\ + pc += 2;\ + addr = READ_PC( addr2 ) + dp;\ + }\ + addr_##op:\ + nz = data func READ( -1, addr );\ + WRITE( 0, addr, nz );\ + goto loop;\ + } + + LOGICAL_OP( 0x28, & ); // AND + + LOGICAL_OP( 0x08, | ); // OR + + LOGICAL_OP( 0x48, ^ ); // EOR + +// 4. 8-BIT ARITHMETIC OPERATION COMMANDS + + ADDR_MODES( 0x68 ) // CMP addr + data = READ( 0, data );/*FALLTHRU*/ + case 0x68: // CMP imm + nz = a - data; + c = ~nz; + nz &= 0xFF; + goto inc_pc_loop; + + case 0x79: // CMP (X),(Y) + data = READ_DP( -2, y ); + nz = READ_DP( -1, x ) - data; + c = ~nz; + nz &= 0xFF; + goto loop; + + case 0x69: // CMP dp,dp + data = READ_DP( -3, data );/*FALLTHRU*/ + case 0x78: // CMP dp,imm + nz = READ_DP( -1, READ_PC( ++pc ) ) - data; + c = ~nz; + nz &= 0xFF; + goto inc_pc_loop; + + case 0x3E: // CMP X,dp + data += dp; + goto cmp_x_addr; + case 0x1E: // CMP X,abs + data = READ_PC16( pc ); + pc++; + cmp_x_addr: + data = READ( 0, data );/*FALLTHRU*/ + case 0xC8: // CMP X,imm + nz = x - data; + c = ~nz; + nz &= 0xFF; + goto inc_pc_loop; + + case 0x7E: // CMP Y,dp + data += dp; + goto cmp_y_addr; + case 0x5E: // CMP Y,abs + data = READ_PC16( pc ); + pc++; + cmp_y_addr: + data = READ( 0, data );/*FALLTHRU*/ + case 0xAD: // CMP Y,imm + nz = y - data; + c = ~nz; + nz &= 0xFF; + goto inc_pc_loop; + + { + int addr; + case 0xB9: // SBC (x),(y) + case 0x99: // ADC (x),(y) + pc--; // compensate for inc later + data = READ_DP( -2, y ); + addr = x + dp; + goto adc_addr; + case 0xA9: // SBC dp,dp + case 0x89: // ADC dp,dp + data = READ_DP( -3, data ); + case 0xB8: // SBC dp,imm + case 0x98: // ADC dp,imm + addr = READ_PC( ++pc ) + dp; + adc_addr: + nz = READ( -1, addr ); + goto adc_data; + +// catch ADC and SBC together, then decode later based on operand +#undef CASE +#define CASE( n ) case n: case (n) + 0x20: + ADDR_MODES( 0x88 ) // ADC/SBC addr + data = READ( 0, data ); + case 0xA8: // SBC imm + case 0x88: // ADC imm + addr = -1; // A + nz = a; + adc_data: { + int flags; + if ( opcode >= 0xA0 ) // SBC + data ^= 0xFF; + + flags = data ^ nz; + nz += data + (c >> 8 & 1); + flags ^= nz; + + psw = (psw & ~(v40 | h08)) | + (flags >> 1 & h08) | + ((flags + 0x80) >> 2 & v40); + c = nz; + if ( addr < 0 ) + { + a = (uint8_t) nz; + goto inc_pc_loop; + } + WRITE( 0, addr, /*(uint8_t)*/ nz ); + goto inc_pc_loop; + } + + } + +// 6. ADDITION & SUBTRACTION COMMANDS + +#define INC_DEC_REG( reg, op )\ + nz = reg op;\ + reg = (uint8_t) nz;\ + goto loop; + + case 0xBC: INC_DEC_REG( a, + 1 ) // INC A + case 0x3D: INC_DEC_REG( x, + 1 ) // INC X + case 0xFC: INC_DEC_REG( y, + 1 ) // INC Y + + case 0x9C: INC_DEC_REG( a, - 1 ) // DEC A + case 0x1D: INC_DEC_REG( x, - 1 ) // DEC X + case 0xDC: INC_DEC_REG( y, - 1 ) // DEC Y + + case 0x9B: // DEC dp+X + case 0xBB: // INC dp+X + data = (uint8_t) (data + x); /* fallthrough */ + case 0x8B: // DEC dp + case 0xAB: // INC dp + data += dp; + goto inc_abs; + case 0x8C: // DEC abs + case 0xAC: // INC abs + data = READ_PC16( pc ); + pc++; + inc_abs: + nz = (opcode >> 4 & 2) - 1; + nz += READ( -1, data ); + WRITE( 0, data, /*(uint8_t)*/ nz ); + goto inc_pc_loop; + +// 7. SHIFT, ROTATION COMMANDS + + case 0x5C: // LSR A + c = 0; /*fallthrough*/ + case 0x7C:{// ROR A + nz = (c >> 1 & 0x80) | (a >> 1); + c = a << 8; + a = nz; + goto loop; + } + + case 0x1C: // ASL A + c = 0; /*fallthrough*/ + case 0x3C:{// ROL A + int temp = c >> 8 & 1; + c = a << 1; + nz = c | temp; + a = (uint8_t) nz; + goto loop; + } + + case 0x0B: // ASL dp + c = 0; + data += dp; + goto rol_mem; + case 0x1B: // ASL dp+X + c = 0; /*fallthrough*/ + case 0x3B: // ROL dp+X + data = (uint8_t) (data + x); /*fallthrough*/ + case 0x2B: // ROL dp + data += dp; + goto rol_mem; + case 0x0C: // ASL abs + c = 0; /*fallthrough*/ + case 0x2C: // ROL abs + data = READ_PC16( pc ); + pc++; + rol_mem: + nz = c >> 8 & 1; + nz |= (c = READ( -1, data ) << 1); + WRITE( 0, data, /*(uint8_t)*/ nz ); + goto inc_pc_loop; + + case 0x4B: // LSR dp + c = 0; + data += dp; + goto ror_mem; + case 0x5B: // LSR dp+X + c = 0; /*fallthrough*/ + case 0x7B: // ROR dp+X + data = (uint8_t) (data + x); /*fallthrough*/ + case 0x6B: // ROR dp + data += dp; + goto ror_mem; + case 0x4C: // LSR abs + c = 0; /*fallthrough*/ + case 0x6C: // ROR abs + data = READ_PC16( pc ); + pc++; + ror_mem: { + int temp = READ( -1, data ); + nz = (c >> 1 & 0x80) | (temp >> 1); + c = temp << 8; + WRITE( 0, data, nz ); + goto inc_pc_loop; + } + + case 0x9F: // XCN + nz = a = (a >> 4) | (uint8_t) (a << 4); + goto loop; + +// 8. 16-BIT TRANSMISION COMMANDS + + case 0xBA: // MOVW YA,dp + a = READ_DP( -2, data ); + nz = (a & 0x7F) | (a >> 1); + y = READ_DP( 0, (uint8_t) (data + 1) ); + nz |= y; + goto inc_pc_loop; + + case 0xDA: // MOVW dp,YA + WRITE_DP( -1, data, a ); + WRITE_DP( 0, (uint8_t) (data + 1), y + no_read_before_write ); + goto inc_pc_loop; + +// 9. 16-BIT OPERATION COMMANDS + + case 0x3A: // INCW dp + case 0x1A:{// DECW dp + int temp; + // low byte + data += dp; + temp = READ( -3, data ); + temp += (opcode >> 4 & 2) - 1; // +1 for INCW, -1 for DECW + nz = ((temp >> 1) | temp) & 0x7F; + WRITE( -2, data, /*(uint8_t)*/ temp ); + + // high byte + data = (uint8_t) (data + 1) + dp; + temp = (uint8_t) ((temp >> 8) + READ( -1, data )); + nz |= temp; + WRITE( 0, data, temp ); + + goto inc_pc_loop; + } + + case 0x7A: // ADDW YA,dp + case 0x9A:{// SUBW YA,dp + int lo = READ_DP( -2, data ); + int hi = READ_DP( 0, (uint8_t) (data + 1) ); + int result; + int flags; + + if ( opcode == 0x9A ) // SUBW + { + lo = (lo ^ 0xFF) + 1; + hi ^= 0xFF; + } + + lo += a; + result = y + hi + (lo >> 8); + flags = hi ^ y ^ result; + + psw = (psw & ~(v40 | h08)) | + (flags >> 1 & h08) | + ((flags + 0x80) >> 2 & v40); + c = result; + a = (uint8_t) lo; + result = (uint8_t) result; + y = result; + nz = (((lo >> 1) | lo) & 0x7F) | result; + + goto inc_pc_loop; + } + + case 0x5A: { // CMPW YA,dp + int temp = a - READ_DP( -1, data ); + nz = ((temp >> 1) | temp) & 0x7F; + temp = y + (temp >> 8); + temp -= READ_DP( 0, (uint8_t) (data + 1) ); + nz |= temp; + c = ~temp; + nz &= 0xFF; + goto inc_pc_loop; + } + +// 10. MULTIPLICATION & DIVISON COMMANDS + + case 0xCF: { // MUL YA + unsigned temp = y * a; + a = (uint8_t) temp; + nz = ((temp >> 1) | temp) & 0x7F; + y = (uint8_t) (temp >> 8); + nz |= y; + goto loop; + } + + case 0x9E: // DIV YA,X + { + unsigned ya = y * 0x100 + a; + + psw &= ~(h08 | v40); + + if ( y >= x ) + psw |= v40; + + if ( (y & 15) >= (x & 15) ) + psw |= h08; + + if ( y < x * 2 ) + { + a = ya / x; + y = ya - a * x; + } + else + { + a = 255 - (ya - x * 0x200) / (256 - x); + y = x + (ya - x * 0x200) % (256 - x); + } + + nz = (uint8_t) a; + a = (uint8_t) a; + y = (uint8_t) y; + + goto loop; + } + +// 11. DECIMAL COMPENSATION COMMANDS + + case 0xDF: // DAA + SUSPICIOUS_OPCODE( "DAA" ); + if ( a > 0x99 || c & 0x100 ) + { + a += 0x60; + c = 0x100; + } + + if ( (a & 0x0F) > 9 || psw & h08 ) + a += 0x06; + + nz = a; + a = (uint8_t) a; + goto loop; + + case 0xBE: // DAS + SUSPICIOUS_OPCODE( "DAS" ); + if ( a > 0x99 || !(c & 0x100) ) + { + a -= 0x60; + c = 0; + } + + if ( (a & 0x0F) > 9 || !(psw & h08) ) + a -= 0x06; + + nz = a; + a = (uint8_t) a; + goto loop; + +// 12. BRANCHING COMMANDS + + case 0x2F: // BRA rel + pc += (int8_t) data; + goto inc_pc_loop; + + case 0x30: // BMI + BRANCH( (nz & nz_neg_mask) ) + + case 0x10: // BPL + BRANCH( !(nz & nz_neg_mask) ) + + case 0xB0: // BCS + BRANCH( c & 0x100 ) + + case 0x90: // BCC + BRANCH( !(c & 0x100) ) + + case 0x70: // BVS + BRANCH( psw & v40 ) + + case 0x50: // BVC + BRANCH( !(psw & v40) ) + + #define CBRANCH( cond )\ + {\ + pc++;\ + if ( cond )\ + goto cbranch_taken_loop;\ + rel_time -= 2;\ + goto inc_pc_loop;\ + } + + case 0x03: // BBS dp.bit,rel + case 0x23: + case 0x43: + case 0x63: + case 0x83: + case 0xA3: + case 0xC3: + case 0xE3: + CBRANCH( READ_DP( -4, data ) >> (opcode >> 5) & 1 ) + + case 0x13: // BBC dp.bit,rel + case 0x33: + case 0x53: + case 0x73: + case 0x93: + case 0xB3: + case 0xD3: + case 0xF3: + CBRANCH( !(READ_DP( -4, data ) >> (opcode >> 5) & 1) ) + + case 0xDE: // CBNE dp+X,rel + data = (uint8_t) (data + x); + // fall through + case 0x2E:{// CBNE dp,rel + int temp; + // 61% from timer + READ_DP_TIMER( -4, data, temp ); + CBRANCH( temp != a ) + } + + case 0x6E: { // DBNZ dp,rel + unsigned temp = READ_DP( -4, data ) - 1; + WRITE_DP( -3, (uint8_t) data, /*(uint8_t)*/ temp + no_read_before_write ); + CBRANCH( temp ) + } + + case 0xFE: // DBNZ Y,rel + y = (uint8_t) (y - 1); + BRANCH( y ) + + case 0x1F: // JMP [abs+X] + SET_PC( READ_PC16( pc ) + x ); + // fall through + case 0x5F: // JMP abs + SET_PC( READ_PC16( pc ) ); + goto loop; + +// 13. SUB-ROUTINE CALL RETURN COMMANDS + + case 0x0F:{// BRK + int temp; + int ret_addr = GET_PC(); + SUSPICIOUS_OPCODE( "BRK" ); + SET_PC( READ_PROG16( 0xFFDE ) ); // vector address verified + PUSH16( ret_addr ); + GET_PSW( temp ); + psw = (psw | b10) & ~i04; + PUSH( temp ); + goto loop; + } + + case 0x4F:{// PCALL offset + int ret_addr = GET_PC() + 1; + SET_PC( 0xFF00 | data ); + PUSH16( ret_addr ); + goto loop; + } + + case 0x01: // TCALL n + case 0x11: + case 0x21: + case 0x31: + case 0x41: + case 0x51: + case 0x61: + case 0x71: + case 0x81: + case 0x91: + case 0xA1: + case 0xB1: + case 0xC1: + case 0xD1: + case 0xE1: + case 0xF1: { + int ret_addr = GET_PC(); + SET_PC( READ_PROG16( 0xFFDE - (opcode >> 3) ) ); + PUSH16( ret_addr ); + goto loop; + } + +// 14. STACK OPERATION COMMANDS + + { + int temp; + uint8_t l, h; + case 0x7F: // RET1 + POP (temp); + POP (l); + POP (h); + SET_PC( l | (h << 8) ); + goto set_psw; + case 0x8E: // POP PSW + POP( temp ); + set_psw: + SET_PSW( temp ); + goto loop; + } + + case 0x0D: { // PUSH PSW + int temp; + GET_PSW( temp ); + PUSH( temp ); + goto loop; + } + + case 0x2D: // PUSH A + PUSH( a ); + goto loop; + + case 0x4D: // PUSH X + PUSH( x ); + goto loop; + + case 0x6D: // PUSH Y + PUSH( y ); + goto loop; + + case 0xAE: // POP A + POP( a ); + goto loop; + + case 0xCE: // POP X + POP( x ); + goto loop; + + case 0xEE: // POP Y + POP( y ); + goto loop; + +// 15. BIT OPERATION COMMANDS + + case 0x02: // SET1 + case 0x22: + case 0x42: + case 0x62: + case 0x82: + case 0xA2: + case 0xC2: + case 0xE2: + case 0x12: // CLR1 + case 0x32: + case 0x52: + case 0x72: + case 0x92: + case 0xB2: + case 0xD2: + case 0xF2: { + int bit = 1 << (opcode >> 5); + int mask = ~bit; + if ( opcode & 0x10 ) + bit = 0; + data += dp; + WRITE( 0, data, (READ( -1, data ) & mask) | bit ); + goto inc_pc_loop; + } + + case 0x0E: // TSET1 abs + case 0x4E: // TCLR1 abs + data = READ_PC16( pc ); + pc += 2; + { + unsigned temp = READ( -2, data ); + nz = (uint8_t) (a - temp); + temp &= ~a; + if ( opcode == 0x0E ) + temp |= a; + WRITE( 0, data, temp ); + } + goto loop; + + case 0x4A: // AND1 C,mem.bit + c &= MEM_BIT( 0 ); + pc += 2; + goto loop; + + case 0x6A: // AND1 C,/mem.bit + c &= ~MEM_BIT( 0 ); + pc += 2; + goto loop; + + case 0x0A: // OR1 C,mem.bit + c |= MEM_BIT( -1 ); + pc += 2; + goto loop; + + case 0x2A: // OR1 C,/mem.bit + c |= ~MEM_BIT( -1 ); + pc += 2; + goto loop; + + case 0x8A: // EOR1 C,mem.bit + c ^= MEM_BIT( -1 ); + pc += 2; + goto loop; + + case 0xEA: // NOT1 mem.bit + data = READ_PC16( pc ); + pc += 2; + { + unsigned temp = READ( -1, data & 0x1FFF ); + temp ^= 1 << (data >> 13); + WRITE( 0, data & 0x1FFF, temp ); + } + goto loop; + + case 0xCA: // MOV1 mem.bit,C + data = READ_PC16( pc ); + pc += 2; + { + unsigned temp = READ( -2, data & 0x1FFF ); + unsigned bit = data >> 13; + temp = (temp & ~(1 << bit)) | ((c >> 8 & 1) << bit); + WRITE( 0, data & 0x1FFF, temp + no_read_before_write ); + } + goto loop; + + case 0xAA: // MOV1 C,mem.bit + c = MEM_BIT( 0 ); + pc += 2; + goto loop; + +// 16. PROGRAM PSW FLAG OPERATION COMMANDS + + case 0x60: // CLRC + c = 0; + goto loop; + + case 0x80: // SETC + c = ~0; + goto loop; + + case 0xED: // NOTC + c ^= 0x100; + goto loop; + + case 0xE0: // CLRV + psw &= ~(v40 | h08); + goto loop; + + case 0x20: // CLRP + dp = 0; + goto loop; + + case 0x40: // SETP + dp = 0x100; + goto loop; + + case 0xA0: // EI + SUSPICIOUS_OPCODE( "EI" ); + psw |= i04; + goto loop; + + case 0xC0: // DI + SUSPICIOUS_OPCODE( "DI" ); + psw &= ~i04; + goto loop; + +// 17. OTHER COMMANDS + + case 0x00: // NOP + goto loop; + + case 0xFF:{// STOP + // handle PC wrap-around + if ( pc == 0x0000 ) + { + debug_printf( "SPC: PC wrapped around\n" ); + goto loop; + } + } + // fall through + case 0xEF: // SLEEP + SUSPICIOUS_OPCODE( "STOP/SLEEP" ); + --pc; + rel_time = 0; + m.cpu_error = "SPC emulation error"; + goto stop; + } // switch + + assert( 0 ); // catch any unhandled instructions +} +out_of_time: + rel_time -= m.cycle_table [ ram [pc] ]; // undo partial execution of opcode +stop: + + // Uncache registers + m.cpu_regs.pc = (uint16_t) GET_PC(); + m.cpu_regs.sp = ( uint8_t) GET_SP(); + m.cpu_regs.a = ( uint8_t) a; + m.cpu_regs.x = ( uint8_t) x; + m.cpu_regs.y = ( uint8_t) y; + { + int temp; + GET_PSW( temp ); + m.cpu_regs.psw = (uint8_t) temp; + } +} +SPC_CPU_RUN_FUNC_END diff --git a/Frameworks/GME/gme/Spc_Dsp.cpp b/Frameworks/GME/gme/Spc_Dsp.cpp new file mode 100644 index 000000000..b4f85c0bd --- /dev/null +++ b/Frameworks/GME/gme/Spc_Dsp.cpp @@ -0,0 +1,712 @@ +// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ + +#include "Spc_Dsp.h" + +#include "blargg_endian.h" +#include + +/* 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 ); } diff --git a/Frameworks/GME/gme/Spc_Dsp.h b/Frameworks/GME/gme/Spc_Dsp.h new file mode 100644 index 000000000..b4379ca09 --- /dev/null +++ b/Frameworks/GME/gme/Spc_Dsp.h @@ -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 + +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 diff --git a/Frameworks/GME/gme/Spc_Emu.cpp b/Frameworks/GME/gme/Spc_Emu.cpp index e7d97e76c..3a9039181 100644 --- a/Frameworks/GME/gme/Spc_Emu.cpp +++ b/Frameworks/GME/gme/Spc_Emu.cpp @@ -1,451 +1,661 @@ -// Game_Music_Emu $vers. http://www.slack.net/~ant/ - -#include "Spc_Emu.h" - -#include "blargg_endian.h" - -/* Copyright (C) 2004-2009 Shay Green. This module is free software; you -can redistribute it and/or modify it under the terms of the GNU Lesser -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" - -using std::min; -using std::max; - -// TODO: support Spc_Filter's bass - -Spc_Emu::Spc_Emu( gme_type_t type ) -{ - set_type( type ); - - set_voice_count( SuperFamicom::SPC_DSP::voice_count ); - static const char* const names [SuperFamicom::SPC_DSP::voice_count] = { - "DSP 1", "DSP 2", "DSP 3", "DSP 4", "DSP 5", "DSP 6", "DSP 7", "DSP 8" - }; - set_voice_names( names ); - - set_gain( 1.4 ); - - enable_filter( false ); - enable_echo( true ); -} - -Spc_Emu::~Spc_Emu() { } - -// Track info - -long const spc_size = 0x10200; -long const head_size = Spc_Emu::header_size; - -byte const* Spc_Emu::trailer() const { return &file_data [min( file_size, spc_size )]; } - -long Spc_Emu::trailer_size() const { return max( 0L, file_size - spc_size ); } - -static void get_spc_xid6( byte const begin [], long size, track_info_t* out ) -{ - // header - byte const* end = begin + size; - if ( size < 8 || memcmp( begin, "xid6", 4 ) ) - { - check( false ); - return; - } - long info_size = get_le32( begin + 4 ); - byte const* in = begin + 8; - if ( end - in > info_size ) - { - debug_printf( "SPC: Extra data after xid6\n" ); - end = in + info_size; - } - - int year = 0; - char copyright [256 + 5]; - long copyright_len = 0; - int const year_len = 5; - int disc = 0, track = 0; - - while ( end - in >= 4 ) - { - // header - int id = in [0]; - int data = in [3] * 0x100 + in [2]; - int type = in [1]; - int len = type ? data : 0; - in += 4; - if ( len > end - in ) - { - debug_printf( "SPC: xid6 goes past end" ); - break; // block goes past end of data - } - - // handle specific block types - char* field = NULL; - switch ( id ) - { - case 0x01: field = out->song; break; - case 0x02: field = out->game; break; - case 0x03: field = out->author; break; - case 0x04: field = out->dumper; break; - case 0x07: field = out->comment; break; - case 0x10: field = out->ost; break; - case 0x11: disc = data; break; - case 0x12: track = data; break; - case 0x14: year = data; break; - - //case 0x30: // intro length - // Many SPCs have intro length set wrong for looped tracks, making it useless - /* - case 0x30: - check( len == 4 ); - if ( len >= 4 ) - { - out->intro_length = get_le32( in ) / 64; - if ( out->length > 0 ) - { - int loop = out->length - out->intro_length; - if ( loop >= 2000 ) - out->loop_length = loop; - } - } - break; - */ - - case 0x33: - check( len == 4 ); - if ( len >= 4 ) - { - out->fade_length = get_le32( in ) / 64; - } - break; - - case 0x13: - copyright_len = min( len, (int) sizeof copyright - year_len ); - memcpy( ©right [year_len], in, copyright_len ); - break; - - default: - if ( id < 0x01 || (id > 0x07 && id < 0x10) || - (id > 0x14 && id < 0x30) || id > 0x36 ) - debug_printf( "SPC: Unknown xid6 block: %X\n", (int) id ); - break; - } - if ( field ) - { - check( type == 1 ); - Gme_File::copy_field_( field, (char const*) in, len ); - } - - // skip to next block - in += len; - - // blocks are supposed to be 4-byte aligned with zero-padding... - byte const* unaligned = in; - while ( (in - begin) & 3 && in < end ) - { - if ( *in++ != 0 ) - { - // ...but some files have no padding - in = unaligned; - //dprintf( "SPC: xid6 info tag wasn't properly padded to align\n" ); - break; - } - } - } - - char* p = ©right [year_len]; - if ( year ) - { - *--p = ' '; - // avoid using bloated printf - for ( int n = 4; n--; ) - { - *--p = char (year % 10 + '0'); - year /= 10; - } - copyright_len += year_len; - } - if ( copyright_len ) - Gme_File::copy_field_( out->copyright, p, copyright_len ); - - if ( disc > 0 && disc <= 9 ) - { - out->disc [0] = disc + '0'; - out->disc [1] = 0; - } - - if ( track > 255 && track < ( ( 100 << 8 ) - 1 ) ) - { - char* p = ©right [3]; - *p = 0; - if ( track & 255 ) *--p = char (track & 255); - track >>= 8; - for ( int n = 2; n-- && track; ) - { - *--p = char (track % 10 + '0'); - track /= 10; - } - memcpy( out->track, p, ©right [4] - p ); - } - - check( in == end ); -} - -static void get_spc_info( Spc_Emu::header_t const& h, byte const xid6 [], long xid6_size, - track_info_t* out ) -{ - // decode length (can be in text or binary format, sometimes ambiguous ugh) - int len_secs = 0; - int i; - for ( i = 0; i < 3; i++ ) - { - unsigned n = h.len_secs [i] - '0'; - if ( n > 9 ) - { - // ignore single-digit text lengths - // (except if author field is present and begins at offset 1, ugh) - if ( i == 1 && (h.author [0] || !h.author [1]) ) - len_secs = 0; - break; - } - len_secs *= 10; - len_secs += n; - } - if ( !len_secs || len_secs > 0x1FFF ) - len_secs = get_le16( h.len_secs ); - if ( len_secs < 0x1FFF ) - out->length = len_secs * 1000; - - long fade_msec = 0; - for ( i = 0; i < 4; i++ ) - { - unsigned n = h.fade_msec [i] - '0'; - if ( n > 9 ) - { - if ( i == 1 && (h.author [0] || !h.author [1]) ) - fade_msec = -1; - break; - } - fade_msec *= 10; - fade_msec += n; - } - if ( i == 4 && unsigned( h.author [0] - '0' ) <= 9 ) - fade_msec = fade_msec * 10 + h.author [0] - '0'; - if ( fade_msec < 0 || fade_msec > 0x7FFF ) - fade_msec = get_le32( h.fade_msec ); - if ( fade_msec < 0x7FFF ) - out->fade_length = fade_msec; - - int offset = (h.author [0] < ' ' || unsigned (h.author [0] - '0') <= 9); - Gme_File::copy_field_( out->author, &h.author [offset], sizeof h.author - offset ); - - GME_COPY_FIELD( h, out, song ); - GME_COPY_FIELD( h, out, game ); - GME_COPY_FIELD( h, out, dumper ); - GME_COPY_FIELD( h, out, comment ); - - if ( xid6_size ) - get_spc_xid6( xid6, xid6_size, out ); -} - -blargg_err_t Spc_Emu::track_info_( track_info_t* out, int ) const -{ - get_spc_info( header(), trailer(), trailer_size(), out ); - return 0; -} - -void Spc_Emu::enable_accuracy_(bool enable) -{ - (void)enable; -} - -static blargg_err_t check_spc_header( void const* header ) -{ - if ( memcmp( header, "SNES-SPC700 Sound File Data", 27 ) ) - return gme_wrong_file_type; - return 0; -} - -struct Spc_File : Gme_Info_ -{ - Spc_Emu::header_t header; - blargg_vector data; - blargg_vector xid6; - - Spc_File() { set_type( gme_spc_type ); } - - blargg_err_t load_( Data_Reader& in ) - { - long file_size = in.remain(); - if ( file_size < 0x10180 ) - return gme_wrong_file_type; - RETURN_ERR( in.read( &header, Spc_Emu::header_size ) ); - RETURN_ERR( check_spc_header( header.tag ) ); - long const xid6_offset = 0x10200; - RETURN_ERR( data.resize( min( xid6_offset - Spc_Emu::header_size, file_size - Spc_Emu::header_size ) ) ); - RETURN_ERR( in.read( data.begin(), data.end() - data.begin() ) ); - int xid6_size = file_size - xid6_offset; - if ( xid6_size > 0 ) - { - RETURN_ERR( xid6.resize( xid6_size ) ); - RETURN_ERR( in.read( xid6.begin(), xid6.size() ) ); - } - return 0; - } - - blargg_err_t track_info_( track_info_t* out, int ) const - { - get_spc_info( header, xid6.begin(), xid6.size(), out ); - return 0; - } -}; - -static Music_Emu* new_spc_emu () { return BLARGG_NEW Spc_Emu ; } -static Music_Emu* new_spc_file() { return BLARGG_NEW Spc_File; } - -static gme_type_t_ const gme_spc_type_ = { "Super Nintendo", 1, &new_spc_emu, &new_spc_file, "SPC", 0 }; -extern gme_type_t const gme_spc_type = &gme_spc_type_; - -// Setup - -blargg_err_t Spc_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 Spc_Emu::mute_voices_( int m ) -{ - Music_Emu::mute_voices_( m ); - for ( int i = 0, j = 1; i < SuperFamicom::SPC_DSP::voice_count; ++i, j <<= 1 ) - smp.dsp.channel_enable( i, !( m & j ) ); -} - -blargg_err_t Spc_Emu::load_mem_( byte const* in, long size ) -{ - assert( offsetof (header_t,unused2 [46]) == header_size ); - if ( size < 0x10180 ) - return gme_wrong_file_type; - - file_data = in; - file_size = size; - - return check_spc_header( in ); -} - -// Emulation - -void Spc_Emu::set_tempo_( double t ) -{ - smp.set_tempo( t ); -} - -blargg_err_t Spc_Emu::start_track_( int track ) -{ - RETURN_ERR( Music_Emu::start_track_( track ) ); - resampler.clear(); - filter.clear(); - smp.reset(); - const byte * ptr = file_data; - - Spc_Emu::header_t & header = *(Spc_Emu::header_t*)ptr; - ptr += sizeof(header); - - smp.regs.pc = header.pc[0] + header.pc[1] * 0x100; - smp.regs.a = header.a; - smp.regs.x = header.x; - smp.regs.y = header.y; - smp.regs.p = header.psw; - smp.regs.s = header.sp; - - memcpy( smp.apuram, ptr, 0x10000 ); - memset( smp.apuram + 0xF4, 0, 4 ); - memcpy( smp.sfm_last, ptr + 0xF4, 4 ); - - static const uint8_t regs_to_copy[][2] = { {0xFC,0xFF}, {0xFB,0xFF}, {0xFA,0xFF}, {0xF9,0xFF}, {0xF8,0xFF}, {0xF2,0xFF}, {0xF1,0x87} }; - for (auto n : regs_to_copy) smp.op_buswrite( n[0], ptr[ n[0] ] & n[1] ); - smp.timer0.stage3_ticks = ptr[ 0xFD ] & 0x0F; - smp.timer1.stage3_ticks = ptr[ 0xFE ] & 0x0F; - smp.timer2.stage3_ticks = ptr[ 0xFF ] & 0x0F; - ptr += 0x10000; - - smp.dsp.spc_dsp.load( ptr ); - - if ( !(smp.dsp.read( SuperFamicom::SPC_DSP::r_flg ) & 0x20) ) - { - int addr = 0x100 * smp.dsp.read( SuperFamicom::SPC_DSP::r_esa ); - int end = addr + 0x800 * (smp.dsp.read( SuperFamicom::SPC_DSP::r_edl ) & 0x0F); - if ( end > 0x10000 ) - end = 0x10000; - memset( &smp.apuram [addr], 0xFF, end - addr ); - } - - filter.set_gain( (int) (gain() * SPC_Filter::gain_unit) ); - return 0; -} - -blargg_err_t Spc_Emu::play_and_filter( long count, sample_t out [] ) -{ - smp.render( out, count ); - filter.run( out, count ); - return 0; -} - -blargg_err_t Spc_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(); - } - - // eliminate pop due to resampler - if ( sample_rate() != native_sample_rate ) - { - const long resampler_latency = 64; - sample_t buf [resampler_latency]; - return play_( resampler_latency, buf ); - } - - return 0; -} - -blargg_err_t Spc_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 ) - { - long 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_Emu.h" + +#include "blargg_endian.h" +#include +#include +#include + +#ifdef RARDLL +#define PASCAL +#define CALLBACK +#define LONG long +#define HANDLE void * +#define LPARAM intptr_t +#define UINT __attribute__((unused)) unsigned int +#include +#endif + +/* Copyright (C) 2004-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +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" + +using std::min; +using std::max; + +// TODO: support Spc_Filter's bass + +Spc_Emu::Spc_Emu( gme_type_t type ) +{ + set_type( type ); + + 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 ); + + enable_echo( true ); +} + +Spc_Emu::~Spc_Emu() { } + +// Track info + +long const spc_size = 0x10200; +long const head_size = Spc_Emu::header_size; + +byte const* Spc_Emu::trailer() const { return &file_data [min( file_size, spc_size )]; } + +long Spc_Emu::trailer_size() const { return max( 0L, file_size - spc_size ); } + +byte const* Rsn_Emu::trailer( int track ) const +{ + const byte *track_data = spc[track]; + long track_size = spc[track + 1] - spc[track]; + return &track_data [min( track_size, spc_size )]; +} + +long Rsn_Emu::trailer_size( int track ) const +{ + long track_size = spc[track + 1] - spc[track]; + return max( 0L, track_size - spc_size ); +} + +static void get_spc_xid6( byte const* begin, long size, track_info_t* out ) +{ + // header + byte const* end = begin + size; + if ( size < 8 || memcmp( begin, "xid6", 4 ) ) + { + check( false ); + return; + } + long info_size = get_le32( begin + 4 ); + byte const* in = begin + 8; + if ( end - in > info_size ) + { + debug_printf( "Extra data after SPC xid6 info\n" ); + end = in + info_size; + } + + int year = 0; + char copyright [256 + 5]; + int copyright_len = 0; + int const year_len = 5; + int disc = 0, track = 0; + + while ( end - in >= 4 ) + { + // header + int id = in [0]; + int data = in [3] * 0x100 + in [2]; + int type = in [1]; + int len = type ? data : 0; + in += 4; + if ( len > end - in ) + { + check( false ); + break; // block goes past end of data + } + + // handle specific block types + char* field = 0; + switch ( id ) + { + case 0x01: field = out->song; break; + case 0x02: field = out->game; break; + case 0x03: field = out->author; break; + case 0x04: field = out->dumper; break; + case 0x07: field = out->comment; break; + case 0x10: field = out->ost; break; + case 0x11: disc = data; break; + case 0x12: track = data; break; + case 0x14: year = data; break; + + //case 0x30: // intro length + // Many SPCs have intro length set wrong for looped tracks, making it useless + /* + case 0x30: + check( len == 4 ); + if ( len >= 4 ) + { + out->intro_length = get_le32( in ) / 64; + if ( out->length > 0 ) + { + long loop = out->length - out->intro_length; + if ( loop >= 2000 ) + out->loop_length = loop; + } + } + break; + */ + + case 0x33: + check( len == 4 ); + if ( len >= 4 ) + { + out->fade_length = get_le32( in ) / 64; + } + break; + + case 0x13: + copyright_len = min( len, (int) sizeof copyright - year_len ); + memcpy( ©right [year_len], in, copyright_len ); + break; + + default: + if ( id < 0x01 || (id > 0x07 && id < 0x10) || + (id > 0x14 && id < 0x30) || id > 0x36 ) + debug_printf( "Unknown SPC xid6 block: %X\n", (int) id ); + break; + } + if ( field ) + { + check( type == 1 ); + Gme_File::copy_field_( field, (char const*) in, len ); + } + + // skip to next block + in += len; + + // blocks are supposed to be 4-byte aligned with zero-padding... + byte const* unaligned = in; + while ( (in - begin) & 3 && in < end ) + { + if ( *in++ != 0 ) + { + // ...but some files have no padding + in = unaligned; + debug_printf( "SPC info tag wasn't properly padded to align\n" ); + break; + } + } + } + + char* p = ©right [year_len]; + if ( year ) + { + *--p = ' '; + for ( int n = 4; n--; ) + { + *--p = char (year % 10 + '0'); + year /= 10; + } + copyright_len += year_len; + } + if ( copyright_len ) + Gme_File::copy_field_( out->copyright, p, copyright_len ); + + if ( disc > 0 && disc <= 9 ) + { + out->disc [0] = disc + '0'; + out->disc [1] = 0; + } + + if ( track > 255 && track < ( ( 100 << 8 ) - 1 ) ) + { + char* p = ©right [3]; + *p = 0; + if ( track & 255 ) *--p = char (track & 255); + track >>= 8; + for ( int n = 2; n-- && track; ) + { + *--p = char (track % 10 + '0'); + track /= 10; + } + memcpy( out->track, p, ©right [4] - p ); + } + + check( in == end ); +} + +static void get_spc_info( Spc_Emu::header_t const& h, byte const* xid6, long xid6_size, + track_info_t* out ) +{ + // decode length (can be in text or binary format, sometimes ambiguous ugh) + long len_secs = 0; + int i; + for ( i = 0; i < 3; i++ ) + { + unsigned n = h.len_secs [i] - '0'; + if ( n > 9 ) + { + // ignore single-digit text lengths + // (except if author field is present and begins at offset 1, ugh) + if ( i == 1 && (h.author [0] || !h.author [1]) ) + len_secs = 0; + break; + } + len_secs *= 10; + len_secs += n; + } + if ( !len_secs || len_secs > 0x1FFF ) + len_secs = get_le16( h.len_secs ); + if ( len_secs < 0x1FFF ) + out->length = len_secs * 1000; + + long fade_msec = 0; + for ( i = 0; i < 4; i++ ) + { + unsigned n = h.fade_msec [i] - '0'; + if ( n > 9 ) + { + if ( i == 1 && (h.author [0] || !h.author [1]) ) + fade_msec = -1; + break; + } + fade_msec *= 10; + fade_msec += n; + } + if ( i == 4 && unsigned( h.author [0] - '0' ) <= 9 ) + fade_msec = fade_msec * 10 + h.author [0] - '0'; + if ( fade_msec < 0 || fade_msec > 0x7FFF ) + fade_msec = get_le32( h.fade_msec ); + if ( fade_msec < 0x7FFF ) + out->fade_length = fade_msec; + + int offset = (h.author [0] < ' ' || unsigned (h.author [0] - '0') <= 9); + Gme_File::copy_field_( out->author, &h.author [offset], sizeof h.author - offset ); + + GME_COPY_FIELD( h, out, song ); + GME_COPY_FIELD( h, out, game ); + GME_COPY_FIELD( h, out, dumper ); + GME_COPY_FIELD( h, out, comment ); + + if ( xid6_size ) + get_spc_xid6( xid6, xid6_size, out ); +} + +blargg_err_t Spc_Emu::track_info_( track_info_t* out, int ) const +{ + get_spc_info( header(), trailer(), trailer_size(), out ); + return 0; +} + +blargg_err_t Rsn_Emu::track_info_( track_info_t* out, int track ) const +{ + get_spc_info( header( track ), trailer( track ), trailer_size( track ), out ); + return 0; +} + +static blargg_err_t check_spc_header( void const* header ) +{ + if ( memcmp( header, "SNES-SPC700 Sound File Data", 27 ) ) + return gme_wrong_file_type; + return 0; +} + +struct Spc_File : Gme_Info_ +{ + Spc_Emu::header_t header; + blargg_vector xid6; + + Spc_File( gme_type_t type ) { set_type( type ); } + Spc_File() : Spc_File( gme_spc_type ) {} + + blargg_err_t load_( Data_Reader& in ) + { + long file_size = in.remain(); + if ( is_archive ) + return 0; + if ( file_size < 0x10180 ) + return gme_wrong_file_type; + RETURN_ERR( in.read( &header, head_size ) ); + RETURN_ERR( check_spc_header( header.tag ) ); + long xid6_size = file_size - spc_size; + if ( xid6_size > 0 ) + { + RETURN_ERR( xid6.resize( xid6_size ) ); + RETURN_ERR( in.skip( spc_size - head_size ) ); + RETURN_ERR( in.read( xid6.begin(), xid6.size() ) ); + } + return 0; + } + + blargg_err_t track_info_( track_info_t* out, int ) const + { + get_spc_info( header, xid6.begin(), xid6.size(), out ); + return 0; + } +}; + +static Music_Emu* new_spc_emu () { return BLARGG_NEW Spc_Emu ; } +static Music_Emu* new_spc_file() { return BLARGG_NEW Spc_File; } + +static gme_type_t_ const gme_spc_type_ = { "Super Nintendo", 1, &new_spc_emu, &new_spc_file, "SPC", 0 }; +extern gme_type_t const gme_spc_type = &gme_spc_type_; + + +#ifdef RARDLL +static int CALLBACK call_rsn(UINT msg, LPARAM UserData, LPARAM P1, LPARAM P2) +{ + byte **bp = (byte **)UserData; + unsigned char *addr = (unsigned char *)P1; + memcpy( *bp, addr, P2 ); + *bp += P2; + return 0; +} +#endif + +struct Rsn_File : Spc_File +{ + blargg_vector spc; + + Rsn_File() : Spc_File( gme_rsn_type ) { is_archive = true; } + + blargg_err_t load_archive( const char* path ) + { + #ifdef RARDLL + struct RAROpenArchiveData data = { + .ArcName = (char *)path, + .OpenMode = RAR_OM_LIST, .OpenResult = 0, + .CmtBuf = 0, .CmtBufSize = 0, .CmtSize = 0, .CmtState = 0 + }; + + // get the size of all unpacked headers combined + long pos = 0; + int count = 0; + unsigned biggest = 0; + blargg_vector temp; + HANDLE PASCAL rar = RAROpenArchive( &data ); + struct RARHeaderData head; + for ( ; RARReadHeader( rar, &head ) == ERAR_SUCCESS; count++ ) + { + RARProcessFile( rar, RAR_SKIP, 0, 0 ); + long xid6_size = head.UnpSize - spc_size; + if ( xid6_size > 0 ) + pos += xid6_size; + pos += head_size; + biggest = max( biggest, head.UnpSize ); + } + xid6.resize( pos ); + spc.resize( count + 1 ); + temp.resize( biggest ); + RARCloseArchive( rar ); + + // copy the headers/xid6 and index them + byte *bp; + data.OpenMode = RAR_OM_EXTRACT; + rar = RAROpenArchive( &data ); + RARSetCallback( rar, call_rsn, (intptr_t)&bp ); + for ( count = 0, pos = 0; RARReadHeader( rar, &head ) == ERAR_SUCCESS; ) + { + bp = &temp[0]; + RARProcessFile( rar, RAR_TEST, 0, 0 ); + if ( !check_spc_header( bp - head.UnpSize ) ) + { + spc[count++] = &xid6[pos]; + memcpy( &xid6[pos], &temp[0], head_size ); + pos += head_size; + long xid6_size = head.UnpSize - spc_size; + if ( xid6_size > 0 ) + { + memcpy( &xid6[pos], &temp[spc_size], xid6_size ); + pos += xid6_size; + } + } + } + spc[count] = &xid6[pos]; + set_track_count( count ); + RARCloseArchive( rar ); + + return 0; + #else + (void) path; + return gme_wrong_file_type; + #endif + } + + blargg_err_t track_info_( track_info_t* out, int track ) const + { + if ( static_cast(track) >= spc.size() ) + return "Invalid track"; + long xid6_size = spc[track + 1] - ( spc[track] + head_size ); + get_spc_info( + *(Spc_Emu::header_t const*) spc[track], + spc[track] + head_size, xid6_size, out + ); + return 0; + } +}; + +static Music_Emu* new_rsn_emu () { return BLARGG_NEW Rsn_Emu ; } +static Music_Emu* new_rsn_file() { return BLARGG_NEW Rsn_File; } + +static gme_type_t_ const gme_rsn_type_ = { "Super Nintendo", 0, &new_rsn_emu, &new_rsn_file, "RSN", 0 }; +extern gme_type_t const gme_rsn_type = &gme_rsn_type_; + + +// Setup + +blargg_err_t Spc_Emu::set_sample_rate_( long sample_rate ) +{ + smp.power(); + enable_accuracy( false ); + if ( sample_rate != native_sample_rate ) + { + RETURN_ERR( resampler.buffer_size( native_sample_rate / 20 * 2 ) ); + resampler.time_ratio( (double) native_sample_rate / sample_rate, 0.9965 ); + } + return 0; +} + +void Spc_Emu::enable_accuracy_( bool b ) +{ + Music_Emu::enable_accuracy_( b ); + filter.enable( b ); + if ( b ) enable_echo(); +} + +void Spc_Emu::mute_voices_( int m ) +{ + Music_Emu::mute_voices_( m ); + for ( int i = 0, j = 1; i < SuperFamicom::SPC_DSP::voice_count; ++i, j <<= 1 ) + smp.dsp.channel_enable( i, !( m & j ) ); +} + +blargg_err_t Spc_Emu::load_mem_( byte const* in, long size ) +{ + assert( offsetof (header_t,unused2 [46]) == header_size ); + file_data = in; + file_size = size; + set_voice_count( SuperFamicom::SPC_DSP::voice_count ); + if ( is_archive ) + return 0; + if ( size < 0x10180 ) + return gme_wrong_file_type; + return check_spc_header( in ); +} + +// Emulation + +void Spc_Emu::set_tempo_( double t ) +{ + smp.set_tempo( t ); +} + +blargg_err_t Spc_Emu::start_track_( int track ) +{ + RETURN_ERR( Music_Emu::start_track_( track ) ); + resampler.clear(); + filter.clear(); + smp.reset(); + const byte * ptr = file_data; + + Spc_Emu::header_t & header = *(Spc_Emu::header_t*)ptr; + ptr += sizeof(header); + + smp.regs.pc = header.pc[0] + header.pc[1] * 0x100; + smp.regs.a = header.a; + smp.regs.x = header.x; + smp.regs.y = header.y; + smp.regs.p = header.psw; + smp.regs.s = header.sp; + + memcpy( smp.apuram, ptr, sizeof smp.apuram ); + + // clear input ports that contain out port data from dump + memset( smp.apuram + 0xF4, 0, 4 ); + memcpy( smp.sfm_last, ptr + 0xF4, 4 ); + + static const uint8_t regs_to_copy[][2] = { + {0xFC,0xFF}, {0xFB,0xFF}, {0xFA,0xFF}, {0xF9,0xFF}, + {0xF8,0xFF}, {0xF2,0xFF}, {0xF1,0x87} + }; + + for (auto n : regs_to_copy) + smp.op_buswrite( n[0], ptr[ n[0] ] & n[1] ); + + smp.timer0.stage3_ticks = ptr[ 0xFD ] & 0x0F; + smp.timer1.stage3_ticks = ptr[ 0xFE ] & 0x0F; + smp.timer2.stage3_ticks = ptr[ 0xFF ] & 0x0F; + ptr += sizeof smp.apuram; + + smp.dsp.spc_dsp.load( ptr ); + +#if 1 + // clear fake echo ram + memset( &smp.dsp.spc_dsp.m.echo_ram, 0xFF, sizeof smp.dsp.spc_dsp.m.echo_ram ); +#else + if ( !(smp.dsp.read( SuperFamicom::SPC_DSP::r_flg ) & 0x20) ) + { + // if echo enabled, clear echo buffer + int addr = 0x100 * smp.dsp.read( SuperFamicom::SPC_DSP::r_esa ); + int end = addr + 0x800 * (smp.dsp.read( SuperFamicom::SPC_DSP::r_edl ) & 0x0F); + if ( end > 0x10000 ) + end = 0x10000; + memset( &smp.apuram [addr], 0xFF, end - addr ); + } +#endif + filter.set_gain( (int) (gain() * SPC_Filter::gain_unit) ); + track_info_t spc_info; + RETURN_ERR( track_info_( &spc_info, track ) ); + + // Set a default track length, need a non-zero fadeout + if ( autoload_playback_limit() && ( spc_info.length > 0 ) ) + set_fade ( spc_info.length, 50 ); + return 0; +} + +blargg_err_t Spc_Emu::play_and_filter( long count, sample_t out [] ) +{ + smp.render( out, count ); + filter.run( out, count ); + return 0; +} + +blargg_err_t Spc_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(); + } + + // eliminate pop due to resampler + if ( sample_rate() != native_sample_rate ) + { + const int resampler_latency = 64; + sample_t buf [resampler_latency]; + return play_( resampler_latency, buf ); + } + + return 0; +} + +blargg_err_t Spc_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 ) + { + long n = resampler.max_write(); + RETURN_ERR( play_and_filter( n, resampler.buffer() ) ); + resampler.write( n ); + } + } + check( remain == 0 ); + return 0; +} + +blargg_err_t Rsn_Emu::load_archive( const char* path ) +{ +#ifdef RARDLL + struct RAROpenArchiveData data = { + .ArcName = (char *)path, + .OpenMode = RAR_OM_LIST, .OpenResult = 0, + .CmtBuf = 0, .CmtBufSize = 0, .CmtSize = 0, .CmtState = 0 + }; + + // get the file count and unpacked size + long pos = 0; + int count = 0; + HANDLE PASCAL rar = RAROpenArchive( &data ); + struct RARHeaderData head; + for ( ; RARReadHeader( rar, &head ) == ERAR_SUCCESS; count++ ) + { + RARProcessFile( rar, RAR_SKIP, 0, 0 ); + pos += head.UnpSize; + } + rsn.resize( pos ); + spc.resize( count + 1 ); + RARCloseArchive( rar ); + + // copy the stream and index the tracks + byte *bp = &rsn[0]; + data.OpenMode = RAR_OM_EXTRACT; + rar = RAROpenArchive( &data ); + RARSetCallback( rar, call_rsn, (intptr_t)&bp ); + for ( count = 0, pos = 0; RARReadHeader( rar, &head ) == ERAR_SUCCESS; ) + { + RARProcessFile( rar, RAR_TEST, 0, 0 ); + if ( !check_spc_header( bp - head.UnpSize ) ) + spc[count++] = &rsn[pos]; + pos += head.UnpSize; + } + spc[count] = &rsn[pos]; + set_track_count( count ); + RARCloseArchive( rar ); + + return 0; +#else + (void) path; + return gme_wrong_file_type; +#endif +} + +blargg_err_t Rsn_Emu::start_track_( int track ) +{ + if ( static_cast(track) >= spc.size() ) + return "Invalid track requested"; + file_data = spc[track]; + file_size = spc[track + 1] - spc[track]; + return Spc_Emu::start_track_( track ); +} + +Rsn_Emu::~Rsn_Emu() { } diff --git a/Frameworks/GME/gme/Spc_Emu.h b/Frameworks/GME/gme/Spc_Emu.h index e0fb8d161..c8fcda082 100644 --- a/Frameworks/GME/gme/Spc_Emu.h +++ b/Frameworks/GME/gme/Spc_Emu.h @@ -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 rsn; + blargg_vector spc; +}; + +#endif diff --git a/Frameworks/GME/gme/Spc_Sfm.cpp b/Frameworks/GME/gme/Spc_Sfm.cpp index d156c81ac..abefb0f4e 100644 --- a/Frameworks/GME/gme/Spc_Sfm.cpp +++ b/Frameworks/GME/gme/Spc_Sfm.cpp @@ -1,492 +1,500 @@ -// Game_Music_Emu $vers. http://www.slack.net/~ant/ - -#include "Spc_Sfm.h" - -#include "blargg_endian.h" - -#include - -/* 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 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 + +/* 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 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; +} diff --git a/Frameworks/GME/gme/Spc_Sfm.h b/Frameworks/GME/gme/Spc_Sfm.h index 94bd1f6d4..a0d4ef36e 100644 --- a/Frameworks/GME/gme/Spc_Sfm.h +++ b/Frameworks/GME/gme/Spc_Sfm.h @@ -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 & 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 & 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 diff --git a/Frameworks/GME/gme/blargg_config.h b/Frameworks/GME/gme/blargg_config.h index 40c546d15..8487d3600 100644 --- a/Frameworks/GME/gme/blargg_config.h +++ b/Frameworks/GME/gme/blargg_config.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, ...) diff --git a/Frameworks/GME/gme/gme.cpp b/Frameworks/GME/gme/gme.cpp index d605adb38..9ba7c1e3b 100644 --- a/Frameworks/GME/gme/gme.cpp +++ b/Frameworks/GME/gme/gme.cpp @@ -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"; diff --git a/Frameworks/GME/gme/gme.h b/Frameworks/GME/gme/gme.h index 76f771cda..80bc82cca 100644 --- a/Frameworks/GME/gme/gme.h +++ b/Frameworks/GME/gme/gme.h @@ -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; diff --git a/Frameworks/GME/gme/nes_cpu_io.h b/Frameworks/GME/gme/nes_cpu_io.h index 2c85db0b3..3d1b8e2e8 100644 --- a/Frameworks/GME/gme/nes_cpu_io.h +++ b/Frameworks/GME/gme/nes_cpu_io.h @@ -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 diff --git a/Frameworks/GME/gme/higan/dsp/SPC_DSP.cpp b/Frameworks/GME/higan/dsp/SPC_DSP.cpp similarity index 99% rename from Frameworks/GME/gme/higan/dsp/SPC_DSP.cpp rename to Frameworks/GME/higan/dsp/SPC_DSP.cpp index 111503843..383afa7a9 100755 --- a/Frameworks/GME/gme/higan/dsp/SPC_DSP.cpp +++ b/Frameworks/GME/higan/dsp/SPC_DSP.cpp @@ -2,7 +2,7 @@ #include "SPC_DSP.h" -#include "blargg_endian.h" +#include "../../gme/blargg_endian.h" #include /* 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]) diff --git a/Frameworks/GME/gme/higan/dsp/SPC_DSP.h b/Frameworks/GME/higan/dsp/SPC_DSP.h similarity index 98% rename from Frameworks/GME/gme/higan/dsp/SPC_DSP.h rename to Frameworks/GME/higan/dsp/SPC_DSP.h index c27c6931b..94c1eaf4d 100755 --- a/Frameworks/GME/gme/higan/dsp/SPC_DSP.h +++ b/Frameworks/GME/higan/dsp/SPC_DSP.h @@ -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] diff --git a/Frameworks/GME/gme/higan/dsp/dsp.cpp b/Frameworks/GME/higan/dsp/dsp.cpp similarity index 100% rename from Frameworks/GME/gme/higan/dsp/dsp.cpp rename to Frameworks/GME/higan/dsp/dsp.cpp diff --git a/Frameworks/GME/gme/higan/dsp/dsp.hpp b/Frameworks/GME/higan/dsp/dsp.hpp similarity index 93% rename from Frameworks/GME/gme/higan/dsp/dsp.hpp rename to Frameworks/GME/higan/dsp/dsp.hpp index dce3cdc02..a8465d613 100755 --- a/Frameworks/GME/gme/higan/dsp/dsp.hpp +++ b/Frameworks/GME/higan/dsp/dsp.hpp @@ -3,7 +3,7 @@ #include "SPC_DSP.h" -#include "blargg_common.h" +#include "../../gme/blargg_common.h" namespace SuperFamicom { diff --git a/Frameworks/GME/gme/higan/processor/spc700/algorithms.cpp b/Frameworks/GME/higan/processor/spc700/algorithms.cpp similarity index 98% rename from Frameworks/GME/gme/higan/processor/spc700/algorithms.cpp rename to Frameworks/GME/higan/processor/spc700/algorithms.cpp index d3d554689..21bff32ad 100755 --- a/Frameworks/GME/gme/higan/processor/spc700/algorithms.cpp +++ b/Frameworks/GME/higan/processor/spc700/algorithms.cpp @@ -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; diff --git a/Frameworks/GME/gme/higan/processor/spc700/disassembler.cpp b/Frameworks/GME/higan/processor/spc700/disassembler.cpp similarity index 100% rename from Frameworks/GME/gme/higan/processor/spc700/disassembler.cpp rename to Frameworks/GME/higan/processor/spc700/disassembler.cpp diff --git a/Frameworks/GME/gme/higan/processor/spc700/instructions.cpp b/Frameworks/GME/higan/processor/spc700/instructions.cpp similarity index 100% rename from Frameworks/GME/gme/higan/processor/spc700/instructions.cpp rename to Frameworks/GME/higan/processor/spc700/instructions.cpp diff --git a/Frameworks/GME/gme/higan/processor/spc700/memory.hpp b/Frameworks/GME/higan/processor/spc700/memory.hpp similarity index 100% rename from Frameworks/GME/gme/higan/processor/spc700/memory.hpp rename to Frameworks/GME/higan/processor/spc700/memory.hpp diff --git a/Frameworks/GME/gme/higan/processor/spc700/registers.hpp b/Frameworks/GME/higan/processor/spc700/registers.hpp similarity index 100% rename from Frameworks/GME/gme/higan/processor/spc700/registers.hpp rename to Frameworks/GME/higan/processor/spc700/registers.hpp diff --git a/Frameworks/GME/gme/higan/processor/spc700/spc700.cpp b/Frameworks/GME/higan/processor/spc700/spc700.cpp similarity index 100% rename from Frameworks/GME/gme/higan/processor/spc700/spc700.cpp rename to Frameworks/GME/higan/processor/spc700/spc700.cpp diff --git a/Frameworks/GME/gme/higan/processor/spc700/spc700.hpp b/Frameworks/GME/higan/processor/spc700/spc700.hpp similarity index 100% rename from Frameworks/GME/gme/higan/processor/spc700/spc700.hpp rename to Frameworks/GME/higan/processor/spc700/spc700.hpp diff --git a/Frameworks/GME/gme/higan/smp/memory.cpp b/Frameworks/GME/higan/smp/memory.cpp similarity index 100% rename from Frameworks/GME/gme/higan/smp/memory.cpp rename to Frameworks/GME/higan/smp/memory.cpp diff --git a/Frameworks/GME/gme/higan/smp/smp.cpp b/Frameworks/GME/higan/smp/smp.cpp similarity index 100% rename from Frameworks/GME/gme/higan/smp/smp.cpp rename to Frameworks/GME/higan/smp/smp.cpp diff --git a/Frameworks/GME/gme/higan/smp/smp.hpp b/Frameworks/GME/higan/smp/smp.hpp similarity index 98% rename from Frameworks/GME/gme/higan/smp/smp.hpp rename to Frameworks/GME/higan/smp/smp.hpp index 4fa6fe83e..49bf4e65b 100755 --- a/Frameworks/GME/gme/higan/smp/smp.hpp +++ b/Frameworks/GME/higan/smp/smp.hpp @@ -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" diff --git a/Frameworks/GME/gme/higan/smp/timing.cpp b/Frameworks/GME/higan/smp/timing.cpp similarity index 100% rename from Frameworks/GME/gme/higan/smp/timing.cpp rename to Frameworks/GME/higan/smp/timing.cpp