# HG changeset patch # User nenolod # Date 1138162741 28800 # Node ID c04dff121e1df6d4cc8124b18cc00a3d29e45c61 # Parent ccb68bad47b2ed08f0ec85c1058d477bf4ca124a [svn] hostile merge, phase 2: reimport based on new plugin code diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Audacious_Driver.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Audacious_Driver.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,703 @@ +/* + * Audacious: Cross platform multimedia player + * Copyright (c) 2005 Audacious Team + * + * Driver for Game_Music_Emu library. See details at: + * http://www.slack.net/~ant/libs/ + */ + +#include +#include +#include +#include "libaudacious/configdb.h" +#include "libaudacious/util.h" +#include "libaudacious/titlestring.h" +extern "C" { +#include "audacious/output.h" +} +#include +#include +#include +#include + +// Game_Music_Emu +#include "Nsf_Emu.h" +#include "Nsfe_Emu.h" +#include "Gbs_Emu.h" +#include "Vgm_Emu.h" +#include "Gym_Emu.h" +#include "Spc_Emu.h" + +#include "Track_Emu.h" +#include "Vfs_File.h" +#include "Gzip_File.h" +#include "blargg_endian.h" + +//typedef Vfs_File_Reader Audacious_Reader; // will use VFS once it handles gzip transparently +typedef Gzip_File_Reader Audacious_Reader; + +struct AudaciousConsoleConfig { + gint loop_length; // length of tracks that lack timing information + gboolean resample; // whether or not to resample + gint resample_rate; // rate to resample at + gboolean nsfe_playlist; // if true, use optional NSFE playlist +}; +static AudaciousConsoleConfig audcfg = { 180, FALSE, 32000, TRUE }; +static GThread* decode_thread; +static GStaticMutex playback_mutex = G_STATIC_MUTEX_INIT; +static volatile gboolean console_ip_is_going; +static volatile long pending_seek; +extern InputPlugin console_ip; +static Music_Emu* emu = 0; +static Track_Emu track_emu; + +static void unload_file() +{ + delete emu; + emu = NULL; +} + +// Information + +typedef unsigned char byte; + +#define DUPE_FIELD( field ) g_strndup( field, sizeof (field) ); + +struct track_info_t +{ + int track; // track to get info for + int length; // in msec, -1 = unknown + int loop; // in msec, -1 = unknown, 0 = not looped + int intro; // in msec, -1 = unknown + + TitleInput* ti; +}; + +// NSFE + +void get_nsfe_info( Nsfe_Info const& nsfe, track_info_t* out ) +{ + Nsfe_Info::info_t const& h = nsfe.info(); + out->ti->performer = DUPE_FIELD( h.author ); + out->ti->album_name = DUPE_FIELD( h.game ); + out->ti->comment = DUPE_FIELD( h.copyright ); + out->ti->track_name = g_strdup( nsfe.track_name( out->track ) ); + int time = nsfe.track_time( out->track ); + if ( time > 0 ) + out->length = time; + if ( nsfe.info().track_count > 1 ) + out->ti->track_number = out->track + 1; +} + +inline void get_info_emu( Nsfe_Emu& emu, track_info_t* out ) +{ + emu.enable_playlist( audcfg.nsfe_playlist ); // to do: kind of hacky + get_nsfe_info( emu, out ); +} + +inline void get_file_info( Nsfe_Emu::header_t const& h, Data_Reader& in, track_info_t* out ) +{ + Nsfe_Info nsfe; + if ( !nsfe.load( h, in ) ) + { + nsfe.enable_playlist( audcfg.nsfe_playlist ); + get_nsfe_info( nsfe, out ); + } +} + +// NSF + +static void get_nsf_info_( Nsf_Emu::header_t const& h, track_info_t* out ) +{ + out->ti->performer = DUPE_FIELD( h.author ); + out->ti->album_name = DUPE_FIELD( h.game ); + out->ti->comment = DUPE_FIELD( h.copyright ); + if ( h.track_count > 1 ) + out->ti->track_number = out->track + 1; +} + +inline void get_info_emu( Nsf_Emu& emu, track_info_t* out ) +{ + get_nsf_info_( emu.header(), out ); +} + +inline void get_file_info( Nsf_Emu::header_t const& h, Data_Reader& in, track_info_t* out ) +{ + get_nsf_info_( h, out ); +} + +// GBS + +static void get_gbs_info_( Gbs_Emu::header_t const& h, track_info_t* out ) +{ + out->ti->performer = DUPE_FIELD( h.author ); + out->ti->album_name = DUPE_FIELD( h.game ); + out->ti->comment = DUPE_FIELD( h.copyright ); + if ( h.track_count > 1 ) + out->ti->track_number = out->track + 1; +} + +inline void get_info_emu( Gbs_Emu& emu, track_info_t* out ) +{ + get_gbs_info_( emu.header(), out ); +} + +inline void get_file_info( Gbs_Emu::header_t const& h, Data_Reader& in, track_info_t* out ) +{ + get_gbs_info_( h, out ); +} + +// GYM + +static void get_gym_info_( Gym_Emu::header_t const& h, track_info_t* out ) +{ + if ( !memcmp( h.tag, "GYMX", 4 ) ) + { + out->ti->performer = DUPE_FIELD( h.copyright ); + out->ti->album_name = DUPE_FIELD( h.game ); + out->ti->track_name = DUPE_FIELD( h.song ); + out->ti->comment = DUPE_FIELD( h.comment ); + } +} + +static void get_gym_timing_( Gym_Emu const& emu, track_info_t* out ) +{ + out->length = emu.track_length() * 50 / 3; // 1000 / 60 + out->loop = 0; + + long loop = get_le32( emu.header().loop_start ); + if ( loop ) + { + out->intro = loop * 50 / 3; + out->loop = out->length - out->intro; + out->length = -1; + } +} + +inline void get_info_emu( Gym_Emu& emu, track_info_t* out ) +{ + get_gym_info_( emu.header(), out ); + get_gym_timing_( emu, out ); +} + +inline void get_file_info( Gym_Emu::header_t const& h, Data_Reader& in, track_info_t* out ) +{ + get_gym_info_( h, out ); + + // have to load and parse entire GYM file to determine length + // to do: could make more efficient by manually parsing data (format is simple) + // rather than loading into emulator with its FM chips and resampler + Gym_Emu* emu = new Gym_Emu; + if ( emu && !emu->set_sample_rate( 44100 ) && !emu->load( h, in ) ) + get_gym_timing_( *emu, out ); + delete emu; +} + +// SPC + +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 ) ) + return; + long info_size = get_le32( begin + 4 ); + byte const* in = begin + 8; + if ( end - in > info_size ) + end = in + info_size; + + 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 ) + break; // block goes past end of data + + // handle specific block types + switch ( id ) + { + case 0x01: out->ti->track_name = g_strndup( (char*) in, len ); break; + case 0x02: out->ti->album_name = g_strndup( (char*) in, len ); break; + case 0x03: out->ti->performer = g_strndup( (char*) in, len ); break; + case 0x07: out->ti->comment = g_strndup( (char*) in, len ); break; + //case 0x31: // loop length, but I haven't found any SPC files that use this + } + + // 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; + break; + } + } + } +} + +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) + char s [4] = { h.len_secs [0], h.len_secs [1], h.len_secs [2], 0 }; + int len_secs = (unsigned char) s [1] * 0x100 + s [0]; + if ( s [1] >= ' ' || (!s [1] && isdigit( s [0] )) ) + len_secs = atoi( s ); + if ( len_secs ) + out->length = len_secs * 1000; + + if ( xid6_size ) + get_spc_xid6( xid6, xid6_size, out ); + + // use header to fill any remaining fields + if ( !out->ti->performer ) out->ti->performer = DUPE_FIELD( h.author ); + if ( !out->ti->album_name ) out->ti->album_name = DUPE_FIELD( h.game ); + if ( !out->ti->track_name ) out->ti->track_name = DUPE_FIELD( h.song ); +} + +inline void get_info_emu( Spc_Emu& emu, track_info_t* out ) +{ + get_spc_info_( emu.header(), emu.trailer(), emu.trailer_size(), out ); +} + +inline void get_file_info( Spc_Emu::header_t const& h, Data_Reader& in, track_info_t* out ) +{ + // handle xid6 data at end of file + long const xid6_skip = 0x10200 - sizeof (Spc_Emu::header_t); + long xid6_size = in.remain() - xid6_skip; + blargg_vector xid6; + if ( xid6_size <= 0 || xid6.resize( xid6_size ) || in.skip( xid6_skip ) || + in.read( xid6.begin(), xid6.size() ) ) + xid6_size = 0; + + get_spc_info_( h, xid6.begin(), xid6_size, out ); +} + +// VGM + +static void get_gd3_str( byte const* in, byte const* end, gchar** out ) +{ + int len = (end - in) / 2 - 1; + if ( len > 0 ) + { + *out = g_strndup( "", len ); + if ( !*out ) + return; + for ( int i = 0; i < len; i++ ) + (*out) [i] = in [i * 2]; // to do: convert to utf-8 + } +} + +static byte const* skip_gd3_str( byte const* in, byte const* end ) +{ + while ( end - in >= 2 ) + { + in += 2; + if ( !(in [-2] | in [-1]) ) + break; + } + return in; +} + +static byte const* get_gd3_pair( byte const* in, byte const* end, gchar** out ) +{ + byte const* mid = skip_gd3_str( in, end ); + if ( out ) + get_gd3_str( in, mid, out ); + return skip_gd3_str( mid, end ); +} + +static void get_vgm_gd3( byte const* in, long size, track_info_t* out ) +{ + byte const* end = in + size; + in = get_gd3_pair( in, end, &out->ti->track_name ); + in = get_gd3_pair( in, end, &out->ti->album_name ); + in = get_gd3_pair( in, end, 0 ); // system + in = get_gd3_pair( in, end, &out->ti->performer ); + in = get_gd3_pair( in, end, 0 ); // copyright + // ... other fields (release date, dumper, notes) +} + +static void get_vgm_length( Vgm_Emu::header_t const& h, track_info_t* out ) +{ + long length = get_le32( h.track_duration ); + if ( length > 0 ) + { + out->length = length * 10 / 441; // 1000 / 44100 (VGM files used 44100 as timebase) + out->loop = 0; + + long loop = get_le32( h.loop_duration ); + if ( loop > 0 && get_le32( h.loop_offset ) ) + { + out->loop = loop * 10 / 441; + out->intro = out->length - out->loop; + out->length = -1; + } + } +} + +inline void get_info_emu( Vgm_Emu& emu, track_info_t* out ) +{ + get_vgm_length( emu.header(), out ); + + int size; + byte const* data = emu.gd3_data( &size ); + if ( data ) + get_vgm_gd3( data + 12, size, out ); +} + +inline void get_file_info( Vgm_Emu::header_t const& vgm_h, Data_Reader& in, track_info_t* out ) +{ + get_vgm_length( vgm_h, out ); + + // find gd3 header + long gd3_offset = get_le32( vgm_h.gd3_offset ) + offsetof(Vgm_Emu::header_t,gd3_offset) - + sizeof vgm_h; + long gd3_max_size = in.remain() - gd3_offset; + byte gd3_h [12]; + if ( gd3_offset <= 0 || gd3_max_size < (int) sizeof gd3_h ) + return; + + // read gd3 header + if ( in.skip( gd3_offset ) || in.read( gd3_h, sizeof gd3_h ) ) + return; + + // check header signature and version + if ( memcmp( gd3_h, "Gd3 ", 4 ) || get_le32( gd3_h + 4 ) >= 0x200 ) + return; + + // get and check size + long gd3_size = get_le32( gd3_h + 8 ); + if ( gd3_size > gd3_max_size - 12 ) + return; + + // read and parse gd3 data + blargg_vector gd3; + if ( gd3.resize( gd3_size ) || in.read( gd3.begin(), gd3.size() ) ) + return; + + get_vgm_gd3( gd3.begin(), gd3.size(), out ); +} + +// File identification + +enum { type_none = 0, type_spc, type_nsf, type_nsfe, type_vgm, type_gbs, type_gym }; + +int const tag_size = 4; +typedef char tag_t [tag_size]; + +static int identify_file( gchar* path, tag_t tag ) +{ + // GYM file format doesn't require *any* header, just the ".gym" extension + if ( g_str_has_suffix( path, ".gym" ) ) // to do: is pathname in unicode? + return type_gym; + // to do: trust suffix for all file types, avoiding having to look inside files? + + int result = type_none; + if ( !memcmp( tag, "SNES", 4 ) ) result = type_spc; + if ( !memcmp( tag, "NESM", 4 ) ) result = type_nsf; + if ( !memcmp( tag, "NSFE", 4 ) ) result = type_nsfe; + if ( !memcmp( tag, "GYMX", 4 ) ) result = type_gym; + if ( !memcmp( tag, "GBS" , 3 ) ) result = type_gbs; + if ( !memcmp( tag, "Vgm ", 4 ) ) result = type_vgm; + return result; +} + +static gint is_our_file( gchar* path ) +{ + Audacious_Reader in; + tag_t tag; + return !in.open( path ) && !in.read( tag, sizeof tag ) && identify_file( path, tag ); +} + +// Get info + +static int begin_get_info( const char* path, track_info_t* out ) +{ + out->track = 0; + out->length = -1; + out->loop = -1; + out->intro = -1; + TitleInput* fields = bmp_title_input_new(); + out->ti = fields; + if ( !fields ) + return true; + + fields->file_name = g_path_get_basename( path ); + fields->file_path = g_path_get_dirname( path ); + return false; +} + +static char* end_get_info( track_info_t const& info, int* length, bool* has_length ) +{ + *length = info.length; + if ( has_length ) + *has_length = (*length > 0); + + if ( *length <= 0 ) + *length = audcfg.loop_length * 1000; + + // use filename for formats that don't have field for name of game + // to do: strip off file extension + if ( !info.ti->track_name ) + info.ti->track_name = g_strdup( info.ti->file_name ); + + char* result = xmms_get_titlestring( xmms_get_gentitle_format(), info.ti ); + g_free( info.ti ); + return result; +} + +template +inline void get_info_t( tag_t tag, Data_Reader& in, track_info_t* out, Header* ) +{ + Header h; + memcpy( &h, tag, tag_size ); + if ( !in.read( (char*) &h + tag_size, sizeof h - tag_size ) ) + get_file_info( h, in, out ); +} + +static void get_song_info( char* path, char** title, int* length ) +{ + int track = 0; // to do: way to select other tracks + + *length = -1; + *title = NULL; + Audacious_Reader in; + tag_t tag; + if ( in.open( path ) || in.read( tag, sizeof tag ) ) + return; + + int type = identify_file( path, tag ); + if ( !type ) + return; + + track_info_t info; + if ( begin_get_info( path, &info ) ) + return; + info.track = track; + + switch ( type ) + { + case type_nsf: get_info_t( tag, in, &info, (Nsf_Emu::header_t*) 0 ); break; + case type_gbs: get_info_t( tag, in, &info, (Gbs_Emu::header_t*) 0 ); break; + case type_gym: get_info_t( tag, in, &info, (Gym_Emu::header_t*) 0 ); break; + case type_vgm: get_info_t( tag, in, &info, (Vgm_Emu::header_t*) 0 ); break; + case type_spc: get_info_t( tag, in, &info, (Spc_Emu::header_t*) 0 ); break; + case type_nsfe:get_info_t( tag, in, &info, (Nsfe_Emu::header_t*)0 ); break; + } + *title = end_get_info( info, length, 0 ); +} + +// Playback + +static int silence_pending; + +static void* play_loop_track( gpointer ) +{ + g_static_mutex_lock( &playback_mutex ); + + while ( console_ip_is_going ) + { + int const buf_size = 1024; + Music_Emu::sample_t buf [buf_size]; + + // wait for free space + while ( console_ip.output->buffer_free() < (int) sizeof buf ) + xmms_usleep( 10000 ); + + // handle pending seek + long s = pending_seek; + pending_seek = -1; // to do: use atomic swap + if ( s >= 0 ) + track_emu.seek( s ); + + // fill buffer + if ( track_emu.play( buf_size, buf ) ) + console_ip_is_going = false; + produce_audio( console_ip.output->written_time(), FMT_S16_NE, 1, sizeof buf, buf, NULL ); + } + + // stop playing + unload_file(); + console_ip.output->close_audio(); + console_ip_is_going = FALSE; + g_static_mutex_unlock( &playback_mutex ); + // to do: should decode_thread be cleared here? + g_thread_exit( NULL ); + return NULL; +} + +template +void load_file( tag_t tag, Data_Reader& in, long rate, track_info_t* out, Emu* dummy ) +{ + typename Emu::header_t h; + memcpy( &h, tag, tag_size ); + if ( in.read( (char*) &h + tag_size, sizeof h - tag_size ) ) + return; + + Emu* local_emu = new Emu; + if ( !local_emu || local_emu->set_sample_rate( rate ) || local_emu->load( h, in ) ) + { + delete local_emu; // delete NULL is safe + return; + } + + emu = local_emu; + get_info_emu( *local_emu, out ); +} + +static void play_file( char* path ) +{ + int track = 0; // to do: some way to select other tracks + + // open and identify file + unload_file(); + Audacious_Reader in; + tag_t tag; + if ( in.open( path ) || in.read( tag, sizeof tag ) ) + return; + int type = identify_file( path, tag ); + + // setup info + long sample_rate = 44100; + if ( type == type_spc ) + sample_rate = Spc_Emu::native_sample_rate; + if ( audcfg.resample ) + sample_rate = audcfg.resample_rate; + track_info_t info; + info.track = track; + if ( begin_get_info( path, &info ) ) + return; + + // load in emulator and get info + switch ( type ) + { + case type_nsf: load_file( tag, in, sample_rate, &info, (Nsf_Emu*) 0 ); break; + case type_nsfe:load_file( tag, in, sample_rate, &info, (Nsfe_Emu*)0 ); break; + case type_gbs: load_file( tag, in, sample_rate, &info, (Gbs_Emu*) 0 ); break; + case type_gym: load_file( tag, in, sample_rate, &info, (Gym_Emu*) 0 ); break; + case type_vgm: load_file( tag, in, sample_rate, &info, (Vgm_Emu*) 0 ); break; + case type_spc: load_file( tag, in, sample_rate, &info, (Spc_Emu*) 0 ); break; + } + in.close(); + if ( !emu ) + return; + + // set info + int length = -1; + bool has_length = false; + char* title = end_get_info( info, &length, &has_length ); + if ( title ) + { + console_ip.set_info( title, length, emu->voice_count() * 1000, sample_rate, 2 ); + g_free( title ); + } + + // start + if ( !console_ip.output->open_audio( FMT_S16_NE, sample_rate, 2 ) ) + return; + pending_seek = -1; + track_emu.start_track( emu, track, length, !has_length ); + console_ip_is_going = TRUE; + decode_thread = g_thread_create( play_loop_track, NULL, TRUE, NULL ); +} + +static void seek( gint time ) +{ + // to do: be sure seek works at all + // to do: disallow seek on slow formats (SPC, GYM, VGM using FM)? + pending_seek = time; +} + +static void console_stop(void) +{ + console_ip_is_going = FALSE; + if ( decode_thread ) + { + g_thread_join( decode_thread ); + decode_thread = NULL; + } + console_ip.output->close_audio(); + unload_file(); +} + +static void console_pause(gshort p) +{ + console_ip.output->pause(p); +} + +static int get_time(void) +{ + return console_ip_is_going ? console_ip.output->output_time() : -1; +} + +// Setup + +static void console_init(void) +{ + ConfigDb *db; + + db = bmp_cfg_db_open(); + + bmp_cfg_db_get_int(db, "console", "loop_length", &audcfg.loop_length); + bmp_cfg_db_get_bool(db, "console", "resample", &audcfg.resample); + bmp_cfg_db_get_int(db, "console", "resample_rate", &audcfg.resample_rate); + bmp_cfg_db_get_bool(db, "console", "nsfe_playlist", &audcfg.nsfe_playlist); + + bmp_cfg_db_close(db); +} + +extern "C" void console_aboutbox(void) +{ + xmms_show_message(_("About the Console Music Decoder"), + _("Console music decoder engine based on Game_Music_Emu 0.3.0.\n" + "Audacious implementation by: William Pitcock , " + // Please do not put my hotpop.com address in the clear (I hate spam) + "Shay Green "), + _("Ok"), + FALSE, NULL, NULL); +} + +InputPlugin console_ip = +{ + NULL, + NULL, + NULL, + console_init, + console_aboutbox, + NULL, + is_our_file, + NULL, + play_file, + console_stop, + console_pause, + seek, + NULL, + get_time, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + get_song_info, + NULL, + NULL +}; + +extern "C" InputPlugin *get_iplugin_info(void) +{ + console_ip.description = g_strdup_printf(_("SPC, VGM, NSF/NSFE, GBS, and GYM module decoder")); + return &console_ip; +} + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Blip_Buffer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Blip_Buffer.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,406 @@ + +// Blip_Buffer 0.4.0. http://www.slack.net/~ant/ + +#include "Blip_Buffer.h" + +#include +#include +#include +#include +#include + +/* Copyright (C) 2003-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +int const buffer_extra = blip_widest_impulse_ + 2; + +Blip_Buffer::Blip_Buffer() +{ + factor_ = LONG_MAX; + offset_ = 0; + buffer_ = 0; + buffer_size_ = 0; + sample_rate_ = 0; + reader_accum = 0; + bass_shift = 0; + clock_rate_ = 0; + bass_freq_ = 16; + length_ = 0; + + // assumptions code makes about implementation-defined features + #ifndef NDEBUG + // right shift of negative value preserves sign + buf_t_ i = -0x7FFFFFFE; + assert( (i >> 1) == -0x3FFFFFFF ); + + // casting to short truncates to 16 bits and sign-extends + i = 0x18000; + assert( (short) i == -0x8000 ); + #endif +} + +Blip_Buffer::~Blip_Buffer() +{ + free( buffer_ ); +} + +void Blip_Buffer::clear( int entire_buffer ) +{ + offset_ = 0; + reader_accum = 0; + if ( buffer_ ) + { + long count = (entire_buffer ? buffer_size_ : samples_avail()); + memset( buffer_, 0, (count + buffer_extra) * sizeof (buf_t_) ); + } +} + +Blip_Buffer::blargg_err_t Blip_Buffer::set_sample_rate( long new_rate, int msec ) +{ + // start with maximum length that resampled time can represent + long new_size = (ULONG_MAX >> BLIP_BUFFER_ACCURACY) - buffer_extra - 64; + if ( msec != blip_max_length ) + { + long s = (new_rate * (msec + 1) + 999) / 1000; + if ( s < new_size ) + new_size = s; + else + assert( 0 ); // fails if requested buffer length exceeds limit + } + + if ( buffer_size_ != new_size ) + { + void* p = realloc( buffer_, (new_size + buffer_extra) * sizeof *buffer_ ); + if ( !p ) + return "Out of memory"; + buffer_ = (buf_t_*) p; + } + + buffer_size_ = new_size; + + // update things based on the sample rate + sample_rate_ = new_rate; + length_ = new_size * 1000 / new_rate - 1; + if ( msec ) + assert( length_ == msec ); // ensure length is same as that passed in + if ( clock_rate_ ) + clock_rate( clock_rate_ ); + bass_freq( bass_freq_ ); + + clear(); + + return 0; // success +} + +blip_resampled_time_t Blip_Buffer::clock_rate_factor( long clock_rate ) const +{ + double ratio = (double) sample_rate_ / clock_rate; + long factor = (long) floor( ratio * (1L << BLIP_BUFFER_ACCURACY) + 0.5 ); + assert( factor > 0 || !sample_rate_ ); // fails if clock/output ratio is too large + return (blip_resampled_time_t) factor; +} + +void Blip_Buffer::bass_freq( int freq ) +{ + bass_freq_ = freq; + int shift = 31; + if ( freq > 0 ) + { + shift = 13; + long f = (freq << 16) / sample_rate_; + while ( (f >>= 1) && --shift ) { } + } + bass_shift = shift; +} + +void Blip_Buffer::end_frame( blip_time_t t ) +{ + offset_ += t * factor_; + assert( samples_avail() <= (long) buffer_size_ ); // time outside buffer length +} + +void Blip_Buffer::remove_silence( long count ) +{ + assert( count <= samples_avail() ); // tried to remove more samples than available + offset_ -= (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY; +} + +long Blip_Buffer::count_samples( blip_time_t t ) const +{ + unsigned long last_sample = resampled_time( t ) >> BLIP_BUFFER_ACCURACY; + unsigned long first_sample = offset_ >> BLIP_BUFFER_ACCURACY; + return (long) (last_sample - first_sample); +} + +blip_time_t Blip_Buffer::count_clocks( long count ) const +{ + if ( count > buffer_size_ ) + count = buffer_size_; + blip_resampled_time_t time = (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY; + return (blip_time_t) ((time - offset_ + factor_ - 1) / factor_); +} + +void Blip_Buffer::remove_samples( long count ) +{ + if ( count ) + { + remove_silence( count ); + + // copy remaining samples to beginning and clear old samples + long remain = samples_avail() + buffer_extra; + memmove( buffer_, buffer_ + count, remain * sizeof *buffer_ ); + memset( buffer_ + remain, 0, count * sizeof *buffer_ ); + } +} + +// Blip_Synth_ + +Blip_Synth_::Blip_Synth_( short* p, int w ) : + impulses( p ), + width( w ) +{ + volume_unit_ = 0.0; + kernel_unit = 0; + buf = 0; + last_amp = 0; + delta_factor = 0; +} + +static double const pi = 3.1415926535897932384626433832795029; + +static void gen_sinc( float* out, int count, double oversample, double treble, double cutoff ) +{ + if ( cutoff >= 0.999 ) + cutoff = 0.999; + + if ( treble < -300.0 ) + treble = -300.0; + if ( treble > 5.0 ) + treble = 5.0; + + double const maxh = 4096.0; + double const rolloff = pow( 10.0, 1.0 / (maxh * 20.0) * treble / (1.0 - cutoff) ); + double const pow_a_n = pow( rolloff, maxh - maxh * cutoff ); + double const to_angle = pi / 2 / maxh / oversample; + for ( int i = 0; i < count; i++ ) + { + double angle = ((i - count) * 2 + 1) * to_angle; + double c = rolloff * cos( (maxh - 1.0) * angle ) - cos( maxh * angle ); + double cos_nc_angle = cos( maxh * cutoff * angle ); + double cos_nc1_angle = cos( (maxh * cutoff - 1.0) * angle ); + double cos_angle = cos( angle ); + + c = c * pow_a_n - rolloff * cos_nc1_angle + cos_nc_angle; + double d = 1.0 + rolloff * (rolloff - cos_angle - cos_angle); + double b = 2.0 - cos_angle - cos_angle; + double a = 1.0 - cos_angle - cos_nc_angle + cos_nc1_angle; + + out [i] = (float) ((a * d + c * b) / (b * d)); // a / b + c / d + } +} + +void blip_eq_t::generate( float* out, int count ) const +{ + // lower cutoff freq for narrow kernels with their wider transition band + // (8 points->1.49, 16 points->1.15) + double oversample = blip_res * 2.25 / count + 0.85; + double half_rate = sample_rate * 0.5; + if ( cutoff_freq ) + oversample = half_rate / cutoff_freq; + double cutoff = rolloff_freq * oversample / half_rate; + + gen_sinc( out, count, blip_res * oversample, treble, cutoff ); + + // apply (half of) hamming window + double to_fraction = pi / (count - 1); + for ( int i = count; i--; ) + out [i] *= 0.54 - 0.46 * cos( i * to_fraction ); +} + +void Blip_Synth_::adjust_impulse() +{ + // sum pairs for each phase and add error correction to end of first half + int const size = impulses_size(); + for ( int p = blip_res; p-- >= blip_res / 2; ) + { + int p2 = blip_res - 2 - p; + long error = kernel_unit; + for ( int i = 1; i < size; i += blip_res ) + { + error -= impulses [i + p ]; + error -= impulses [i + p2]; + } + if ( p == p2 ) + error /= 2; // phase = 0.5 impulse uses same half for both sides + impulses [size - blip_res + p] += error; + //printf( "error: %ld\n", error ); + } + + //for ( int i = blip_res; i--; printf( "\n" ) ) + // for ( int j = 0; j < width / 2; j++ ) + // printf( "%5ld,", impulses [j * blip_res + i + 1] ); +} + +void Blip_Synth_::treble_eq( blip_eq_t const& eq ) +{ + float fimpulse [blip_res / 2 * (blip_widest_impulse_ - 1) + blip_res * 2]; + + int const half_size = blip_res / 2 * (width - 1); + eq.generate( &fimpulse [blip_res], half_size ); + + int i; + + // need mirror slightly past center for calculation + for ( i = blip_res; i--; ) + fimpulse [blip_res + half_size + i] = fimpulse [blip_res + half_size - 1 - i]; + + // starts at 0 + for ( i = 0; i < blip_res; i++ ) + fimpulse [i] = 0.0f; + + // find rescale factor + double total = 0.0; + for ( i = 0; i < half_size; i++ ) + total += fimpulse [blip_res + i]; + + //double const base_unit = 44800.0 - 128 * 18; // allows treble up to +0 dB + //double const base_unit = 37888.0; // allows treble to +5 dB + double const base_unit = 32768.0; // necessary for blip_unscaled to work + double rescale = base_unit / 2 / total; + kernel_unit = (long) base_unit; + + // integrate, first difference, rescale, convert to int + double sum = 0.0; + double next = 0.0; + int const impulses_size = this->impulses_size(); + for ( i = 0; i < impulses_size; i++ ) + { + impulses [i] = (short) floor( (next - sum) * rescale + 0.5 ); + sum += fimpulse [i]; + next += fimpulse [i + blip_res]; + } + adjust_impulse(); + + // volume might require rescaling + double vol = volume_unit_; + if ( vol ) + { + volume_unit_ = 0.0; + volume_unit( vol ); + } +} + +void Blip_Synth_::volume_unit( double new_unit ) +{ + if ( new_unit != volume_unit_ ) + { + // use default eq if it hasn't been set yet + if ( !kernel_unit ) + treble_eq( -8.0 ); + + volume_unit_ = new_unit; + double factor = new_unit * (1L << blip_sample_bits) / kernel_unit; + + if ( factor > 0.0 ) + { + int shift = 0; + + // if unit is really small, might need to attenuate kernel + while ( factor < 2.0 ) + { + shift++; + factor *= 2.0; + } + + if ( shift ) + { + kernel_unit >>= shift; + assert( kernel_unit > 0 ); // fails if volume unit is too low + + // keep values positive to avoid round-towards-zero of sign-preserving + // right shift for negative values + long offset = 0x8000 + (1 << (shift - 1)); + long offset2 = 0x8000 >> shift; + for ( int i = impulses_size(); i--; ) + impulses [i] = (short) (((impulses [i] + offset) >> shift) - offset2); + adjust_impulse(); + } + } + delta_factor = (int) floor( factor + 0.5 ); + //printf( "delta_factor: %d, kernel_unit: %d\n", delta_factor, kernel_unit ); + } +} + +long Blip_Buffer::read_samples( blip_sample_t* out, long max_samples, int stereo ) +{ + long count = samples_avail(); + if ( count > max_samples ) + count = max_samples; + + if ( count ) + { + int const sample_shift = blip_sample_bits - 16; + int const bass_shift = this->bass_shift; + long accum = reader_accum; + buf_t_* in = buffer_; + + if ( !stereo ) + { + for ( long n = count; n--; ) + { + long s = accum >> sample_shift; + accum -= accum >> bass_shift; + accum += *in++; + *out++ = (blip_sample_t) s; + + // clamp sample + if ( (blip_sample_t) s != s ) + out [-1] = (blip_sample_t) (0x7FFF - (s >> 24)); + } + } + else + { + for ( long n = count; n--; ) + { + long s = accum >> sample_shift; + accum -= accum >> bass_shift; + accum += *in++; + *out = (blip_sample_t) s; + out += 2; + + // clamp sample + if ( (blip_sample_t) s != s ) + out [-2] = (blip_sample_t) (0x7FFF - (s >> 24)); + } + } + + reader_accum = accum; + remove_samples( count ); + } + return count; +} + +void Blip_Buffer::mix_samples( blip_sample_t const* in, long count ) +{ + buf_t_* out = buffer_ + (offset_ >> BLIP_BUFFER_ACCURACY) + blip_widest_impulse_ / 2; + + int const sample_shift = blip_sample_bits - 16; + int prev = 0; + while ( count-- ) + { + long s = (long) *in++ << sample_shift; + *out += s - prev; + prev = s; + ++out; + } + *out -= prev; +} + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Blip_Buffer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Blip_Buffer.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,354 @@ + +// Band-limited sound synthesis and buffering + +// Blip_Buffer 0.4.0 + +#ifndef BLIP_BUFFER_H +#define BLIP_BUFFER_H + +// Time unit at source clock rate +typedef long blip_time_t; + +// Output samples are 16-bit signed, with a range of -32768 to 32767 +typedef short blip_sample_t; +enum { blip_sample_max = 32767 }; + +class Blip_Buffer { +public: + typedef const char* blargg_err_t; + + // Set output sample rate and buffer length in milliseconds (1/1000 sec, defaults + // to 1/4 second), then clear buffer. Returns NULL on success, otherwise if there + // isn't enough memory, returns error without affecting current buffer setup. + blargg_err_t set_sample_rate( long samples_per_sec, int msec_length = 1000 / 4 ); + + // Set number of source time units per second + void clock_rate( long ); + + // End current time frame of specified duration and make its samples available + // (along with any still-unread samples) for reading with read_samples(). Begins + // a new time frame at the end of the current frame. + void end_frame( blip_time_t time ); + + // Read at most 'max_samples' out of buffer into 'dest', removing them from from + // the buffer. Returns number of samples actually read and removed. If stereo is + // true, increments 'dest' one extra time after writing each sample, to allow + // easy interleving of two channels into a stereo output buffer. + long read_samples( blip_sample_t* dest, long max_samples, int stereo = 0 ); + +// Additional optional features + + // Current output sample rate + long sample_rate() const; + + // Length of buffer, in milliseconds + int length() const; + + // Number of source time units per second + long clock_rate() const; + + // Set frequency high-pass filter frequency, where higher values reduce bass more + void bass_freq( int frequency ); + + // Number of samples delay from synthesis to samples read out + int output_latency() const; + + // Remove all available samples and clear buffer to silence. If 'entire_buffer' is + // false, just clears out any samples waiting rather than the entire buffer. + void clear( int entire_buffer = 1 ); + + // Number of samples available for reading with read_samples() + long samples_avail() const; + + // Remove 'count' samples from those waiting to be read + void remove_samples( long count ); + +// Experimental features + + // Number of raw samples that can be mixed within frame of specified duration. + long count_samples( blip_time_t duration ) const; + + // Mix 'count' samples from 'buf' into buffer. + void mix_samples( blip_sample_t const* buf, long count ); + + // Count number of clocks needed until 'count' samples will be available. + // If buffer can't even hold 'count' samples, returns number of clocks until + // buffer becomes full. + blip_time_t count_clocks( long count ) const; + + // not documented yet + typedef unsigned long blip_resampled_time_t; + void remove_silence( long count ); + blip_resampled_time_t resampled_duration( int t ) const { return t * factor_; } + blip_resampled_time_t resampled_time( blip_time_t t ) const { return t * factor_ + offset_; } + blip_resampled_time_t clock_rate_factor( long clock_rate ) const; +public: + Blip_Buffer(); + ~Blip_Buffer(); + + // Deprecated + typedef blip_resampled_time_t resampled_time_t; + blargg_err_t sample_rate( long r ) { return set_sample_rate( r ); } + blargg_err_t sample_rate( long r, int msec ) { return set_sample_rate( r, msec ); } +private: + // noncopyable + Blip_Buffer( const Blip_Buffer& ); + Blip_Buffer& operator = ( const Blip_Buffer& ); +public: + typedef long buf_t_; + unsigned long factor_; + blip_resampled_time_t offset_; + buf_t_* buffer_; + long buffer_size_; +private: + long reader_accum; + int bass_shift; + long sample_rate_; + long clock_rate_; + int bass_freq_; + int length_; + friend class Blip_Reader; +}; + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +// Number of bits in resample ratio fraction. Higher values give a more accurate ratio +// but reduce maximum buffer size. +#ifndef BLIP_BUFFER_ACCURACY + #define BLIP_BUFFER_ACCURACY 16 +#endif + +// Number bits in phase offset. Fewer than 6 bits (64 phase offsets) results in +// noticeable broadband noise when synthesizing high frequency square waves. +// Affects size of Blip_Synth objects since they store the waveform directly. +#ifndef BLIP_PHASE_BITS + #define BLIP_PHASE_BITS 6 +#endif + + // Internal + typedef unsigned long blip_resampled_time_t; + int const blip_widest_impulse_ = 16; + int const blip_res = 1 << BLIP_PHASE_BITS; + class blip_eq_t; + + class Blip_Synth_ { + double volume_unit_; + short* const impulses; + int const width; + long kernel_unit; + int impulses_size() const { return blip_res / 2 * width + 1; } + void adjust_impulse(); + public: + Blip_Buffer* buf; + int last_amp; + int delta_factor; + + Blip_Synth_( short* impulses, int width ); + void treble_eq( blip_eq_t const& ); + void volume_unit( double ); + }; + +// Quality level. Start with blip_good_quality. +const int blip_med_quality = 8; +const int blip_good_quality = 12; +const int blip_high_quality = 16; + +// Range specifies the greatest expected change in amplitude. Calculate it +// by finding the difference between the maximum and minimum expected +// amplitudes (max - min). +template +class Blip_Synth { +public: + // Set overall volume of waveform + void volume( double v ) { impl.volume_unit( v * (1.0 / (range < 0 ? -range : range)) ); } + + // Configure low-pass filter (see notes.txt) + void treble_eq( blip_eq_t const& eq ) { impl.treble_eq( eq ); } + + // Get/set Blip_Buffer used for output + Blip_Buffer* output() const { return impl.buf; } + void output( Blip_Buffer* b ) { impl.buf = b; impl.last_amp = 0; } + + // Update amplitude of waveform at given time. Using this requires a separate + // Blip_Synth for each waveform. + void update( blip_time_t time, int amplitude ); + +// Low-level interface + + // Add an amplitude transition of specified delta, optionally into specified buffer + // rather than the one set with output(). Delta can be positive or negative. + // The actual change in amplitude is delta * (volume / range) + void offset( blip_time_t, int delta, Blip_Buffer* ) const; + void offset( blip_time_t t, int delta ) const { offset( t, delta, impl.buf ); } + + // Works directly in terms of fractional output samples. Contact author for more. + void offset_resampled( blip_resampled_time_t, int delta, Blip_Buffer* ) const; + + // Same as offset(), except code is inlined for higher performance + void offset_inline( blip_time_t t, int delta, Blip_Buffer* buf ) const { + offset_resampled( t * buf->factor_ + buf->offset_, delta, buf ); + } + void offset_inline( blip_time_t t, int delta ) const { + offset_resampled( t * impl.buf->factor_ + impl.buf->offset_, delta, impl.buf ); + } + +public: + Blip_Synth() : impl( impulses, quality ) { } +private: + typedef short imp_t; + imp_t impulses [blip_res * (quality / 2) + 1]; + Blip_Synth_ impl; +}; + +// Low-pass equalization parameters +class blip_eq_t { +public: + // Logarithmic rolloff to treble dB at half sampling rate. Negative values reduce + // treble, small positive values (0 to 5.0) increase treble. + blip_eq_t( double treble_db = 0 ); + + // See notes.txt + blip_eq_t( double treble, long rolloff_freq, long sample_rate, long cutoff_freq = 0 ); + +private: + double treble; + long rolloff_freq; + long sample_rate; + long cutoff_freq; + void generate( float* out, int count ) const; + friend class Blip_Synth_; +}; + +int const blip_sample_bits = 30; + +// Optimized inline sample reader for custom sample formats and mixing of Blip_Buffer samples +class Blip_Reader { +public: + // Begin reading samples from buffer. Returns value to pass to next() (can + // be ignored if default bass_freq is acceptable). + int begin( Blip_Buffer& ); + + // Current sample + long read() const { return accum >> (blip_sample_bits - 16); } + + // Current raw sample in full internal resolution + long read_raw() const { return accum; } + + // Advance to next sample + void next( int bass_shift = 9 ) { accum += *buf++ - (accum >> bass_shift); } + + // End reading samples from buffer. The number of samples read must now be removed + // using Blip_Buffer::remove_samples(). + void end( Blip_Buffer& b ) { b.reader_accum = accum; } + +private: + const Blip_Buffer::buf_t_* buf; + long accum; +}; + + +// End of public interface + + +#include + +// Compatibility with older version +const long blip_unscaled = 65535; +const int blip_low_quality = blip_med_quality; +const int blip_best_quality = blip_high_quality; + +#define BLIP_FWD( i ) { \ + long t0 = i0 * delta + buf [fwd + i]; \ + long t1 = imp [blip_res * (i + 1)] * delta + buf [fwd + 1 + i]; \ + i0 = imp [blip_res * (i + 2)]; \ + buf [fwd + i] = t0; \ + buf [fwd + 1 + i] = t1; } + +#define BLIP_REV( r ) { \ + long t0 = i0 * delta + buf [rev - r]; \ + long t1 = imp [blip_res * r] * delta + buf [rev + 1 - r]; \ + i0 = imp [blip_res * (r - 1)]; \ + buf [rev - r] = t0; \ + buf [rev + 1 - r] = t1; } + +template +inline void Blip_Synth::offset_resampled( blip_resampled_time_t time, + int delta, Blip_Buffer* blip_buf ) const +{ + // Fails if time is beyond end of Blip_Buffer, due to a bug in caller code or the + // need for a longer buffer as set by set_sample_rate(). + assert( (long) (time >> BLIP_BUFFER_ACCURACY) < blip_buf->buffer_size_ ); + delta *= impl.delta_factor; + int phase = (int) (time >> (BLIP_BUFFER_ACCURACY - BLIP_PHASE_BITS) & (blip_res - 1)); + imp_t const* imp = impulses + blip_res - phase; + long* buf = blip_buf->buffer_ + (time >> BLIP_BUFFER_ACCURACY); + long i0 = *imp; + + int const fwd = (blip_widest_impulse_ - quality) / 2; + int const rev = fwd + quality - 2; + + BLIP_FWD( 0 ) + if ( quality > 8 ) BLIP_FWD( 2 ) + if ( quality > 12 ) BLIP_FWD( 4 ) + { + int const mid = quality / 2 - 1; + long t0 = i0 * delta + buf [fwd + mid - 1]; + long t1 = imp [blip_res * mid] * delta + buf [fwd + mid]; + imp = impulses + phase; + i0 = imp [blip_res * mid]; + buf [fwd + mid - 1] = t0; + buf [fwd + mid] = t1; + } + if ( quality > 12 ) BLIP_REV( 6 ) + if ( quality > 8 ) BLIP_REV( 4 ) + BLIP_REV( 2 ) + + long t0 = i0 * delta + buf [rev]; + long t1 = *imp * delta + buf [rev + 1]; + buf [rev] = t0; + buf [rev + 1] = t1; +} + +#undef BLIP_FWD +#undef BLIP_REV + +template +void Blip_Synth::offset( blip_time_t t, int delta, Blip_Buffer* buf ) const +{ + offset_resampled( t * buf->factor_ + buf->offset_, delta, buf ); +} + +template +void Blip_Synth::update( blip_time_t t, int amp ) +{ + int delta = amp - impl.last_amp; + impl.last_amp = amp; + offset_resampled( t * impl.buf->factor_ + impl.buf->offset_, delta, impl.buf ); +} + +inline blip_eq_t::blip_eq_t( double t ) : + treble( t ), rolloff_freq( 0 ), sample_rate( 44100 ), cutoff_freq( 0 ) { } +inline blip_eq_t::blip_eq_t( double t, long rf, long sr, long cf ) : + treble( t ), rolloff_freq( rf ), sample_rate( sr ), cutoff_freq( cf ) { } + +inline int Blip_Buffer::length() const { return length_; } +inline long Blip_Buffer::samples_avail() const { return (long) (offset_ >> BLIP_BUFFER_ACCURACY); } +inline long Blip_Buffer::sample_rate() const { return sample_rate_; } +inline int Blip_Buffer::output_latency() const { return blip_widest_impulse_ / 2; } +inline long Blip_Buffer::clock_rate() const { return clock_rate_; } +inline void Blip_Buffer::clock_rate( long cps ) { factor_ = clock_rate_factor( clock_rate_ = cps ); } + +inline int Blip_Reader::begin( Blip_Buffer& blip_buf ) +{ + buf = blip_buf.buffer_; + accum = blip_buf.reader_accum; + return blip_buf.bass_shift; +} + +int const blip_max_length = 0; +int const blip_default_length = 250; + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Classic_Emu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Classic_Emu.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,117 @@ + +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ + +#include "Classic_Emu.h" + +#include "Multi_Buffer.h" + +/* Copyright (C) 2003-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +Classic_Emu::Classic_Emu() +{ + buf = NULL; + stereo_buffer = NULL; +} + +Classic_Emu::~Classic_Emu() +{ + delete stereo_buffer; +} + +void Classic_Emu::set_equalizer( equalizer_t const& eq ) +{ + Music_Emu::set_equalizer( eq ); + update_eq( eq.treble ); + if ( buf ) + buf->bass_freq( equalizer().bass ); +} + +blargg_err_t Classic_Emu::set_sample_rate( long sample_rate ) +{ + if ( !buf ) + { + if ( !stereo_buffer ) + BLARGG_CHECK_ALLOC( stereo_buffer = BLARGG_NEW Stereo_Buffer ); + buf = stereo_buffer; + } + + BLARGG_RETURN_ERR( buf->set_sample_rate( sample_rate, 1000 / 20 ) ); + return Music_Emu::set_sample_rate( sample_rate ); +} + +void Classic_Emu::mute_voices( int mask ) +{ + require( buf ); // set_sample_rate() must have been called + + Music_Emu::mute_voices( mask ); + for ( int i = voice_count(); i--; ) + { + if ( mask & (1 << i) ) + { + set_voice( i, NULL, NULL, NULL ); + } + else + { + Multi_Buffer::channel_t ch = buf->channel( i ); + set_voice( i, ch.center, ch.left, ch.right ); + } + } +} + +blargg_err_t Classic_Emu::setup_buffer( long new_clock_rate ) +{ + require( sample_rate() ); // fails if set_sample_rate() hasn't been called yet + + clock_rate = new_clock_rate; + buf->clock_rate( clock_rate ); + BLARGG_RETURN_ERR( buf->set_channel_count( voice_count() ) ); + set_equalizer( equalizer() ); + remute_voices(); + return blargg_success; +} + +void Classic_Emu::start_track( int track ) +{ + Music_Emu::start_track( track ); + buf->clear(); +} + +blip_time_t Classic_Emu::run_clocks( blip_time_t t, bool* ) +{ + assert( false ); + return t; +} + +blip_time_t Classic_Emu::run( int msec, bool* added_stereo ) +{ + return run_clocks( (long) msec * clock_rate / 1000, added_stereo ); +} + +void Classic_Emu::play( long count, sample_t* out ) +{ + require( sample_rate() ); // fails if set_sample_rate() hasn't been called yet + + long remain = count; + while ( remain ) + { + remain -= buf->read_samples( &out [count - remain], remain ); + if ( remain ) + { + bool added_stereo = false; + blip_time_t clocks_emulated = run( buf->length(), &added_stereo ); + buf->end_frame( clocks_emulated, added_stereo ); + } + } +} + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Classic_Emu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Classic_Emu.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,48 @@ + +// Classic game music emulator interface base class for emulators which use Blip_Buffer +// for sound output. + +// Game_Music_Emu 0.3.0 + +#ifndef CLASSIC_EMU_H +#define CLASSIC_EMU_H + +#include "Music_Emu.h" +class Blip_Buffer; +class blip_eq_t; +typedef long blip_time_t; + +class Classic_Emu : public Music_Emu { +public: + Classic_Emu(); + ~Classic_Emu(); + blargg_err_t set_sample_rate( long sample_rate ); + void set_buffer( Multi_Buffer* ); + void mute_voices( int ); + void play( long, sample_t* ); + void start_track( int track ); + void set_equalizer( equalizer_t const& ); +public: + // deprecated + blargg_err_t init( long rate ) { return set_sample_rate( rate ); } +protected: + virtual blargg_err_t setup_buffer( long clock_rate ); + virtual void set_voice( int index, Blip_Buffer* center, + Blip_Buffer* left, Blip_Buffer* right ) = 0; + virtual blip_time_t run( int msec, bool* added_stereo ); + virtual blip_time_t run_clocks( blip_time_t, bool* added_stereo ); + virtual void update_eq( blip_eq_t const& ) = 0; +private: + Multi_Buffer* buf; + Multi_Buffer* stereo_buffer; + long clock_rate; +}; + +inline void Classic_Emu::set_buffer( Multi_Buffer* new_buf ) +{ + assert( !buf && new_buf ); + buf = new_buf; +} + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Dual_Resampler.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Dual_Resampler.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,120 @@ + +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ + +#include "Dual_Resampler.h" + +#include +#include + +/* Copyright (C) 2003-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +int const resampler_extra = 256; + +Dual_Resampler::Dual_Resampler() +{ +} + +Dual_Resampler::~Dual_Resampler() +{ +} + +blargg_err_t Dual_Resampler::resize( int pairs ) +{ + BLARGG_RETURN_ERR( sample_buf.resize( pairs * 2 ) ); + buf_pos = sample_buf.size(); + oversamples_per_frame = int (pairs * resampler.ratio()) * 2 + 2; + return resampler.buffer_size( oversamples_per_frame + resampler_extra ); +} + +void Dual_Resampler::play_frame_( Blip_Buffer& blip_buf, sample_t* out ) +{ + long pair_count = sample_buf.size() >> 1; + blip_time_t blip_time = blip_buf.count_clocks( pair_count ); + int sample_count = oversamples_per_frame - resampler.written(); + + int new_count = play_frame( blip_time, sample_count, resampler.buffer() ); + assert( unsigned (new_count - sample_count) < resampler_extra ); + + blip_buf.end_frame( blip_time ); + assert( blip_buf.samples_avail() == pair_count ); + + resampler.write( new_count ); + + long count = resampler.read( sample_buf.begin(), sample_buf.size() ); + assert( count == (long) sample_buf.size() ); + + mix_samples( blip_buf, out ); + blip_buf.remove_samples( pair_count ); +} + +void Dual_Resampler::play( long count, sample_t* out, Blip_Buffer& blip_buf ) +{ + // empty extra buffer + long remain = sample_buf.size() - buf_pos; + if ( remain ) + { + if ( remain > count ) + remain = count; + count -= remain; + memcpy( out, &sample_buf [buf_pos], remain * sizeof *out ); + out += remain; + buf_pos += remain; + } + + // entire frames + while ( count >= (long) sample_buf.size() ) + { + play_frame_( blip_buf, out ); + out += sample_buf.size(); + count -= sample_buf.size(); + } + + // extra + if ( count ) + { + play_frame_( blip_buf, sample_buf.begin() ); + buf_pos = count; + memcpy( out, sample_buf.begin(), count * sizeof *out ); + out += count; + } +} + +#include BLARGG_ENABLE_OPTIMIZER + +void Dual_Resampler::mix_samples( Blip_Buffer& blip_buf, sample_t* out ) +{ + Blip_Reader sn; + int bass = sn.begin( blip_buf ); + const sample_t* in = sample_buf.begin(); + + for ( int n = sample_buf.size() >> 1; n--; ) + { + int s = sn.read(); + long l = (long) in [0] * 2 + s; + sn.next( bass ); + long r = in [1]; + if ( (BOOST::int16_t) l != l ) + l = 0x7FFF - (l >> 24); + r = r * 2 + s; + in += 2; + out [0] = l; + out [1] = r; + out += 2; + if ( (BOOST::int16_t) r != r ) + out [-1] = 0x7FFF - (r >> 24); + } + + sn.end( blip_buf ); +} + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Dual_Resampler.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Dual_Resampler.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,49 @@ + +// Combination of Fir_Resampler and Blip_Buffer mixing. Used by Sega FM emulators. + +// Game_Music_Emu 0.3.0 + +#ifndef DUAL_RESAMPLER_H +#define DUAL_RESAMPLER_H + +#include "Fir_Resampler.h" +#include "Blip_Buffer.h" + +class Dual_Resampler { +public: + Dual_Resampler(); + virtual ~Dual_Resampler(); + + typedef short sample_t; + + double setup( double oversample, double rolloff, double gain ); + blargg_err_t resize( int pairs ); + void clear(); + + void play( long count, sample_t* out, Blip_Buffer& ); + +protected: + virtual int play_frame( blip_time_t, int pcm_count, sample_t* pcm_out ) = 0; +private: + + blargg_vector sample_buf; + int oversamples_per_frame; + int buf_pos; + Fir_Resampler<12> resampler; + void mix_samples( Blip_Buffer&, sample_t* ); + void play_frame_( Blip_Buffer&, sample_t* ); +}; + +inline double Dual_Resampler::setup( double oversample, double rolloff, double gain ) +{ + return resampler.time_ratio( oversample, rolloff, gain * 0.5 ); +} + +inline void Dual_Resampler::clear() +{ + buf_pos = sample_buf.size(); + resampler.clear(); +} + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Fir_Resampler.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Fir_Resampler.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,254 @@ + +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ + +#include "Fir_Resampler.h" + +#include +#include +#include +#include + +/* Copyright (C) 2004-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +// to do: fix problems with rolloff < 0.99 or so, and rolloff == 1.0, and related problems + +// Sinc impulse genertor + +const bool show_impulse = 0; + +static const double pi = 3.1415926535897932384626433832795029L; + +class Dsf { + double rolloff; + double factor; +public: + Dsf( double r ) : rolloff( r ) + { + factor = 1.0; + //if ( rolloff < 1.0 ) + // factor = 1.0 / (*this)( 0 ); + } + + double operator () ( double angle ) const + { + double const n_harm = 256; + angle /= n_harm; + double pow_a_n = pow( rolloff, n_harm ); + //double rescale = 1.0 / n_harm; + + double num = 1.0 - rolloff * cos( angle ) - + pow_a_n * cos( n_harm * angle ) + + pow_a_n * rolloff * cos( (n_harm - 1) * angle ); + double den = 1 + rolloff * (rolloff - 2 * cos( angle )); + + return (num / den - 1) / n_harm * factor; + } +}; + +template +void gen_sinc( int width, double offset, double spacing, int count, double scale, short* p, + const Sinc& sinc ) +{ + double range = pi * (width / 2); + double step = pi * spacing; + double a = -step * (count / 2 - 1); + a -= offset * step; + + while ( count-- ) + { + double w = a / range; + double y = 0.0; + if ( fabs( w ) < 1.0 ) + { + double window = cos( pi * w ) * 0.5 + 0.5; + y = sinc( a ) * window; + } + + *p++ = (short) (y * scale); + a += step; + } +} + +static double plain_sinc( double a ) +{ + return fabs( a ) < 0.00001 ? 1.0 : sin( a ) / a; +} + +// Fir_Resampler + +Fir_Resampler_::Fir_Resampler_( int width, sample_t* impulses_ ) : + width_( width ), + write_offset( width * stereo - stereo ), + impulses( impulses_ ) +{ + write_pos = NULL; + res = 1; + imp = 0; + skip_bits = 0; + step = stereo; + ratio_ = 1.0; +} + +Fir_Resampler_::~Fir_Resampler_() +{ +} + +void Fir_Resampler_::clear() +{ + imp = 0; + if ( buf.size() ) + { + write_pos = &buf [write_offset]; + memset( buf.begin(), 0, write_offset * sizeof buf [0] ); + } +} + +blargg_err_t Fir_Resampler_::buffer_size( int new_size ) +{ + BLARGG_RETURN_ERR( buf.resize( new_size + write_offset ) ); + clear(); + return blargg_success; +} + +double Fir_Resampler_::time_ratio( double new_factor, double rolloff, double gain ) +{ + ratio_ = new_factor; + + double fstep = 0.0; + { + double least_error = 2; + double pos = 0; + res = -1; + for ( int r = 1; r <= max_res; r++ ) + { + pos += ratio_; + double nearest = floor( pos + 0.5 ); + double error = fabs( pos - nearest ); + if ( error < least_error ) + { + res = r; + fstep = nearest / res; + least_error = error; + } + } + } + + skip_bits = 0; + + step = stereo * (int) floor( fstep ); + + ratio_ = fstep; + fstep = fmod( fstep, 1.0 ); + + double filter = (ratio_ < 1.0) ? 1.0 : 1.0 / ratio_; + double pos = 0.0; + input_per_cycle = 0; + Dsf dsf( rolloff ); + for ( int i = 0; i < res; i++ ) + { + if ( show_impulse ) + printf( "pos = %f\n", pos ); + + gen_sinc( int (width_ * filter + 1) & ~1, pos, filter, (int) width_, + double (0x7fff * gain * filter), impulses + i * width_, dsf ); + + if ( show_impulse ) + { + for ( int j = 0; j < width_; j++ ) + printf( "%d ", (int) impulses [i * width_ + j] ); + printf( "\n" ); + } + + pos += fstep; + input_per_cycle += step; + if ( pos >= 0.9999999 ) + { + pos -= 1.0; + skip_bits |= 1 << i; + input_per_cycle++; + } + } + + if ( show_impulse ) + { + printf( "skip = %8lX\n", (long) skip_bits ); + printf( "step = %d\n", step ); + } + + clear(); + + return ratio_; +} + +int Fir_Resampler_::input_needed( long output_count ) const +{ + long input_count = 0; + + unsigned long skip = skip_bits >> imp; + int remain = res - imp; + while ( (output_count -= 2) > 0 ) + { + input_count += step + (skip & 1) * stereo; + skip >>= 1; + if ( !--remain ) + { + skip = skip_bits; + remain = res; + } + output_count -= 2; + } + + long input_extra = input_count - (write_pos - &buf [(width_ - 1) * stereo]); + if ( input_extra < 0 ) + input_extra = 0; + return input_extra; +} + +int Fir_Resampler_::avail_( long input_count ) const +{ + int cycle_count = input_count / input_per_cycle; + int output_count = cycle_count * res * stereo; + input_count -= cycle_count * input_per_cycle; + + unsigned long skip = skip_bits >> imp; + int remain = res - imp; + while ( input_count >= 0 ) + { + input_count -= step + (skip & 1) * stereo; + skip >>= 1; + if ( !--remain ) + { + skip = skip_bits; + remain = res; + } + output_count += 2; + } + return output_count; +} + +int Fir_Resampler_::skip_input( long count ) +{ + int remain = write_pos - buf.begin(); + int avail = remain - width_ * stereo; + if ( avail < 0 ) avail = 0; // inserted + if ( count > avail ) + count = avail; + + remain -= count; + write_pos = &buf [remain]; + memmove( buf.begin(), &buf [count], remain * sizeof buf [0] ); + + return count; +} + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Fir_Resampler.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Fir_Resampler.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,174 @@ + +// Finite impulse response (FIR) resampler with adjustable FIR size + +// Game_Music_Emu 0.3.0 + +#ifndef FIR_RESAMPLER_H +#define FIR_RESAMPLER_H + +#include "blargg_common.h" +#include + +class Fir_Resampler_ { +public: + + // Use Fir_Resampler (below) + + // Set input/output resampling ratio and optionally low-pass rolloff and gain. + // Returns actual ratio used (rounded to internal precision). + double time_ratio( double factor, double rolloff = 0.999, double gain = 1.0 ); + + // Current input/output ratio + double ratio() const { return ratio_; } + +// Input + + typedef short sample_t; + + // Resize and clear input buffer + blargg_err_t buffer_size( int ); + + // Clear input buffer. At least two output samples will be available after + // two input samples are written. + void clear(); + + // Number of input samples that can be written + int max_write() const { return buf.end() - write_pos; } + + // Pointer to place to write input samples + sample_t* buffer() { return write_pos; } + + // Notify resampler that 'count' input samples have been written + void write( long count ); + + // Number of input samples in buffer + int written() const { return write_pos - &buf [write_offset]; } + + // Skip 'count' input samples. Returns number of samples actually skipped. + int skip_input( long count ); + +// Output + + // Number of extra input samples needed until 'count' output samples are available + int input_needed( long count ) const; + + // Number of output samples available + int avail() const { return avail_( write_pos - &buf [width_ * stereo] ); } + +public: + ~Fir_Resampler_(); +protected: + enum { stereo = 2 }; + enum { max_res = 32 }; + blargg_vector buf; + sample_t* write_pos; + int res; + int imp; + int const width_; + int const write_offset; + unsigned long skip_bits; + int step; + int input_per_cycle; + double ratio_; + sample_t* impulses; + + Fir_Resampler_( int width, sample_t* ); + int avail_( long input_count ) const; +}; + +// Width is number of points in FIR. Must be even and 4 or more. More points give +// better quality and rolloff effectiveness, and take longer to calculate. +template +class Fir_Resampler : public Fir_Resampler_ { + BOOST_STATIC_ASSERT( width >= 4 && width % 2 == 0 ); + short impulses [max_res] [width]; +public: + Fir_Resampler() : Fir_Resampler_( width, impulses [0] ) { } + + // Read at most 'count' samples. Returns number of samples actually read. + typedef short sample_t; + int read( sample_t* out, long count ); +}; + +// End of public interface + +inline void Fir_Resampler_::write( long count ) +{ + write_pos += count; + assert( write_pos <= buf.end() ); +} + +template +int Fir_Resampler::read( sample_t* out_begin, long count ) +{ + sample_t* out = out_begin; + const sample_t* in = buf.begin(); + sample_t* end_pos = write_pos; + unsigned long skip = skip_bits >> this->imp; + sample_t const* imp = impulses [this->imp]; + int remain = res - this->imp; + int const step = this->step; + + count >>= 1; + + if ( end_pos - in >= width * stereo ) + { + end_pos -= width * stereo; + do + { + count--; + + // accumulate in extended precision + long l = 0; + long r = 0; + + const sample_t* i = in; + if ( count < 0 ) + break; + + for ( int n = width / 2; n; --n ) + { + int pt0 = imp [0]; + l += pt0 * i [0]; + r += pt0 * i [1]; + int pt1 = imp [1]; + imp += 2; + l += pt1 * i [2]; + r += pt1 * i [3]; + i += 4; + } + + remain--; + + l >>= 15; + r >>= 15; + + in += (skip * stereo) & stereo; + skip >>= 1; + in += step; + + if ( !remain ) + { + imp = impulses [0]; + skip = skip_bits; + remain = res; + } + + out [0] = l; + out [1] = r; + out += 2; + } + while ( in <= end_pos ); + } + + this->imp = res - remain; + + int left = write_pos - in; + write_pos = &buf [left]; + memmove( buf.begin(), in, left * sizeof *in ); + + return out - out_begin; +} + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Gb_Apu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Gb_Apu.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,311 @@ + +// Gb_Snd_Emu 0.1.4. http://www.slack.net/~ant/ + +#include "Gb_Apu.h" + +#include + +/* Copyright (C) 2003-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +int const vol_reg = 0xFF24; +int const status_reg = 0xFF26; + +Gb_Apu::Gb_Apu() +{ + square1.synth = &square_synth; + square2.synth = &square_synth; + wave.synth = &other_synth; + noise.synth = &other_synth; + + oscs [0] = &square1; + oscs [1] = &square2; + oscs [2] = &wave; + oscs [3] = &noise; + + for ( int i = 0; i < osc_count; i++ ) + { + Gb_Osc& osc = *oscs [i]; + osc.regs = ®s [i * 5]; + osc.output = NULL; + osc.outputs [0] = NULL; + osc.outputs [1] = NULL; + osc.outputs [2] = NULL; + osc.outputs [3] = NULL; + } + + volume( 1.0 ); + reset(); +} + +Gb_Apu::~Gb_Apu() +{ +} + +void Gb_Apu::treble_eq( const blip_eq_t& eq ) +{ + square_synth.treble_eq( eq ); + other_synth.treble_eq( eq ); +} + +void Gb_Apu::osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +{ + require( (unsigned) index < osc_count ); + require( (center && left && right) || (!center && !left && !right) ); + Gb_Osc& osc = *oscs [index]; + osc.outputs [1] = right; + osc.outputs [2] = left; + osc.outputs [3] = center; + osc.output = osc.outputs [osc.output_select]; +} + +void Gb_Apu::output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, center, left, right ); +} + +void Gb_Apu::update_volume() +{ + // to do: doesn't handle differing left/right global volume + int data = regs [vol_reg - start_addr]; + double vol = (max( data & 7, data >> 4 & 7 ) + 1) * volume_unit; + square_synth.volume( vol ); + other_synth.volume( vol ); +} + +static unsigned char const powerup_regs [0x30] = { + 0x80,0x3F,0x00,0xFF,0xBF, // square 1 + 0xFF,0x3F,0x00,0xFF,0xBF, // square 2 + 0x7F,0xFF,0x9F,0xFF,0xBF, // wave + 0xFF,0xFF,0x00,0x00,0xBF, // noise + 0x00, // left/right enables + 0x77, // master volume + 0x80, // power + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0x84,0x40,0x43,0xAA,0x2D,0x78,0x92,0x3C, // wave table + 0x60,0x59,0x59,0xB0,0x34,0xB8,0x2E,0xDA +}; + +void Gb_Apu::reset() +{ + next_frame_time = 0; + last_time = 0; + frame_count = 0; + stereo_found = false; + + square1.reset(); + square2.reset(); + wave.reset(); + noise.reset(); + noise.bits = 1; + wave.wave_pos = 0; + + // avoid click at beginning + regs [vol_reg - start_addr] = 0x77; + update_volume(); + + regs [status_reg - start_addr] = 0x01; // force power + write_register( 0, status_reg, 0x00 ); +} + +// to do: remove +static unsigned long abs_time; + +void Gb_Apu::run_until( gb_time_t end_time ) +{ + require( end_time >= last_time ); // end_time must not be before previous time + if ( end_time == last_time ) + return; + + while ( true ) + { + gb_time_t time = next_frame_time; + if ( time > end_time ) + time = end_time; + + // run oscillators + for ( int i = 0; i < osc_count; ++i ) + { + Gb_Osc& osc = *oscs [i]; + if ( osc.output ) + { + int playing = false; + if ( osc.enabled && osc.volume && + (!(osc.regs [4] & osc.len_enabled_mask) || osc.length) ) + playing = -1; + if ( osc.output != osc.outputs [3] ) + stereo_found = true; + switch ( i ) + { + case 0: square1.run( last_time, time, playing ); break; + case 1: square2.run( last_time, time, playing ); break; + case 2: wave .run( last_time, time, playing ); break; + case 3: noise .run( last_time, time, playing ); break; + } + } + } + last_time = time; + + if ( time == end_time ) + break; + + next_frame_time += 4194304 / 256; // 256 Hz + + // 256 Hz actions + square1.clock_length(); + square2.clock_length(); + wave.clock_length(); + noise.clock_length(); + + frame_count = (frame_count + 1) & 3; + if ( frame_count == 0 ) + { + // 64 Hz actions + square1.clock_envelope(); + square2.clock_envelope(); + noise.clock_envelope(); + } + + if ( frame_count & 1 ) + square1.clock_sweep(); // 128 Hz action + } +} + +bool Gb_Apu::end_frame( gb_time_t end_time ) +{ + if ( end_time > last_time ) + run_until( end_time ); + + abs_time += end_time; + + assert( next_frame_time >= end_time ); + next_frame_time -= end_time; + + assert( last_time >= end_time ); + last_time -= end_time; + + bool result = stereo_found; + stereo_found = false; + return result; +} + +void Gb_Apu::write_register( gb_time_t time, gb_addr_t addr, int data ) +{ + require( (unsigned) data < 0x100 ); + + int reg = addr - start_addr; + if ( (unsigned) reg >= register_count ) + return; + + run_until( time ); + + int old_reg = regs [reg]; + regs [reg] = data; + + if ( addr < vol_reg ) + { + write_osc( reg / 5, reg, data ); + } + else if ( addr == vol_reg && data != old_reg ) // global volume + { + // return all oscs to 0 + for ( int i = 0; i < osc_count; i++ ) + { + Gb_Osc& osc = *oscs [i]; + int amp = osc.last_amp; + osc.last_amp = 0; + if ( amp && osc.enabled && osc.output ) + other_synth.offset( time, -amp, osc.output ); + } + + if ( wave.outputs [3] ) + other_synth.offset( time, 30, wave.outputs [3] ); + + update_volume(); + + if ( wave.outputs [3] ) + other_synth.offset( time, -30, wave.outputs [3] ); + + // oscs will update with new amplitude when next run + } + else if ( addr == 0xFF25 || addr == status_reg ) + { + int mask = (regs [status_reg - start_addr] & 0x80) ? ~0 : 0; + int flags = regs [0xFF25 - start_addr] & mask; + + // left/right assignments + for ( int i = 0; i < osc_count; i++ ) + { + Gb_Osc& osc = *oscs [i]; + osc.enabled &= mask; + int bits = flags >> i; + Blip_Buffer* old_output = osc.output; + osc.output_select = (bits >> 3 & 2) | (bits & 1); + osc.output = osc.outputs [osc.output_select]; + if ( osc.output != old_output ) + { + int amp = osc.last_amp; + osc.last_amp = 0; + if ( amp && old_output ) + other_synth.offset( time, -amp, old_output ); + } + } + + if ( addr == status_reg && data != old_reg ) + { + if ( !(data & 0x80) ) + { + for ( int i = 0; i < (int) sizeof powerup_regs; i++ ) + { + if ( i != status_reg - start_addr ) + write_register( time, i + start_addr, powerup_regs [i] ); + } + } + else + { + //dprintf( "APU powered on\n" ); + } + } + } + else if ( addr >= 0xFF30 ) + { + + int index = (addr & 0x0F) * 2; + wave.wave [index] = data >> 4; + wave.wave [index + 1] = data & 0x0F; + } +} + +int Gb_Apu::read_register( gb_time_t time, gb_addr_t addr ) +{ + run_until( time ); + + int index = addr - start_addr; + require( (unsigned) index < register_count ); + int data = regs [index]; + + if ( addr == status_reg ) + { + data = (data & 0x80) | 0x70; + for ( int i = 0; i < osc_count; i++ ) + { + const Gb_Osc& osc = *oscs [i]; + if ( osc.enabled && (osc.length || !(osc.regs [4] & osc.len_enabled_mask)) ) + data |= 1 << i; + } + } + + return data; +} + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Gb_Apu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Gb_Apu.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,97 @@ + +// Nintendo Game Boy PAPU sound chip emulator + +// Gb_Snd_Emu 0.1.4 + +#ifndef GB_APU_H +#define GB_APU_H + +typedef long gb_time_t; // clock cycle count +typedef unsigned gb_addr_t; // 16-bit address + +#include "Gb_Oscs.h" + +class Gb_Apu { +public: + + // Set overall volume of all oscillators, where 1.0 is full volume + void volume( double ); + + // Set treble equalization + void treble_eq( const blip_eq_t& ); + + // Outputs can be assigned to a single buffer for mono output, or to three + // buffers for stereo output (using Stereo_Buffer to do the mixing). + + // Assign all oscillator outputs to specified buffer(s). If buffer + // is NULL, silences all oscillators. + void output( Blip_Buffer* mono ); + void output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ); + + // Assign single oscillator output to buffer(s). Valid indicies are 0 to 3, + // which refer to Square 1, Square 2, Wave, and Noise. If buffer is NULL, + // silences oscillator. + enum { osc_count = 4 }; + void osc_output( int index, Blip_Buffer* mono ); + void osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ); + + // Reset oscillators and internal state + void reset(); + + // Reads and writes at addr must satisfy start_addr <= addr <= end_addr + enum { start_addr = 0xFF10 }; + enum { end_addr = 0xFF3f }; + enum { register_count = end_addr - start_addr + 1 }; + + // Write 'data' to address at specified time + void write_register( gb_time_t, gb_addr_t, int data ); + + // Read from address at specified time + int read_register( gb_time_t, gb_addr_t ); + + // Run all oscillators up to specified time, end current time frame, then + // start a new frame at time 0. Returns true if any oscillators added + // sound to one of the left/right buffers, false if they only added + // to the center buffer. + bool end_frame( gb_time_t ); + +public: + Gb_Apu(); + ~Gb_Apu(); +private: + // noncopyable + Gb_Apu( const Gb_Apu& ); + Gb_Apu& operator = ( const Gb_Apu& ); + + Gb_Osc* oscs [osc_count]; + gb_time_t next_frame_time; + gb_time_t last_time; + double volume_unit; + int frame_count; + bool stereo_found; + + Gb_Square square1; + Gb_Square square2; + Gb_Wave wave; + Gb_Noise noise; + BOOST::uint8_t regs [register_count]; + Gb_Square::Synth square_synth; // used by squares + Gb_Wave::Synth other_synth; // used by wave and noise + + void update_volume(); + void run_until( gb_time_t ); + void write_osc( int index, int reg, int data ); +}; + +inline void Gb_Apu::output( Blip_Buffer* b ) { output( b, b, b ); } + +inline void Gb_Apu::osc_output( int i, Blip_Buffer* b ) { osc_output( i, b, b, b ); } + +inline void Gb_Apu::volume( double vol ) +{ + volume_unit = 0.60 / osc_count / 15 /*steps*/ / 2 /*?*/ / 8 /*master vol range*/ * vol; + update_volume(); +} + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Gb_Cpu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Gb_Cpu.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,1113 @@ + +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ + +#include "Gb_Cpu.h" + +#include +#include + +#include "blargg_endian.h" + +/* Copyright (C) 2003-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +// Common instructions: +// +// 365880 FA LD A,IND16 +// 355863 20 JR NZ +// 313655 21 LD HL,IMM +// 274580 28 JR Z +// 252878 FE CMP IMM +// 230541 7E LD A,(HL) +// 226209 2A LD A,(HL+) +// 217467 CD CALL +// 212034 C9 RET +// 208376 CB CB prefix +// +// 27486 CB 7E BIT 7,(HL) +// 15925 CB 76 BIT 6,(HL) +// 13035 CB 19 RR C +// 11557 CB 7F BIT 7,A +// 10898 CB 37 SWAP A +// 10208 CB 66 BIT 4,(HL) + +#if BLARGG_NONPORTABLE + #define PAGE_OFFSET( addr ) (addr) +#else + #define PAGE_OFFSET( addr ) ((addr) & (page_size - 1)) +#endif + +Gb_Cpu::Gb_Cpu( Gbs_Emu* gbs_emu ) +{ + callback_data = gbs_emu; + rst_base = 0; + reset(); +} + +inline void Gb_Cpu::set_code_page( int i, uint8_t const* p ) +{ + code_map [i] = p - PAGE_OFFSET( i * page_size ); +} + +void Gb_Cpu::reset( const void* unmapped_code_page, reader_t read, writer_t write ) +{ + interrupts_enabled = false; + remain_ = 0; + + r.pc = 0; + r.sp = 0; + r.flags = 0; + r.a = 0; + r.b = 0; + r.c = 0; + r.d = 0; + r.e = 0; + r.h = 0; + r.l = 0; + + for ( int i = 0; i < page_count + 1; i++ ) + { + set_code_page( i, (uint8_t*) unmapped_code_page ); + data_reader [i] = read; + data_writer [i] = write; + } +} + +void Gb_Cpu::map_code( gb_addr_t start, unsigned long size, const void* data ) +{ + // address range must begin and end on page boundaries + require( start % page_size == 0 ); + require( size % page_size == 0 ); + + unsigned first_page = start / page_size; + for ( unsigned i = size / page_size; i--; ) + set_code_page( first_page + i, (uint8_t*) data + i * page_size ); +} + +void Gb_Cpu::map_memory( gb_addr_t start, unsigned long size, reader_t read, writer_t write ) +{ + // address range must begin and end on page boundaries + require( start % page_size == 0 ); + require( size % page_size == 0 ); + + unsigned first_page = start / page_size; + for ( unsigned i = size / page_size; i--; ) + { + data_reader [first_page + i] = read; + data_writer [first_page + i] = write; + } +} + +// Note: 'addr' is evaulated more than once in the following macros, so it +// must not contain side-effects. + +#define READ( addr ) (data_reader [(addr) >> page_bits]( callback_data, addr )) +#define WRITE( addr, data ) (data_writer [(addr) >> page_bits]( callback_data, addr, data )) + +#define READ_PROG( addr ) (code_map [(addr) >> page_bits] [PAGE_OFFSET( addr )]) +#define READ_PROG16( addr ) GET_LE16( &READ_PROG( addr ) ) + +int Gb_Cpu::read( gb_addr_t addr ) +{ + return READ( addr ); +} + +void Gb_Cpu::write( gb_addr_t addr, int data ) +{ + WRITE( addr, data ); +} + +BOOST::uint8_t* Gb_Cpu::get_code( gb_addr_t addr ) +{ + return (uint8_t*) &READ_PROG( addr ); +} + +#ifndef GB_CPU_GLUE_ONLY + +const unsigned z_flag = 0x80; +const unsigned n_flag = 0x40; +const unsigned h_flag = 0x20; +const unsigned c_flag = 0x10; + +#include BLARGG_ENABLE_OPTIMIZER + +Gb_Cpu::result_t Gb_Cpu::run( long cycle_count ) +{ + const int cycles_per_instruction = 4; + + // to do: use cycle table + remain_ = cycle_count + cycles_per_instruction; + + Gb_Cpu::result_t result = result_cycles; + +#if BLARGG_CPU_POWERPC + const reader_t* data_reader = this->data_reader; // cache + const writer_t* data_writer = this->data_writer; // cache +#endif + + union { + struct { +#if BLARGG_BIG_ENDIAN + uint8_t b, c, d, e, h, l, unused, a; +# define R8( n ) (r8_ [n]) +#elif BLARGG_LITTLE_ENDIAN + uint8_t c, b, e, d, l, h, a, unused; +# define R8( n ) (r8_ [(n) ^ 1]) +#else +# error "Byte order of CPU must be known" +#endif + } rg; // registers + + struct { + BOOST::uint16_t bc, de, hl, unused; // pairs + } rp; + + uint8_t r8_ [8]; // indexed registers (use R8 macro due to endian dependence) + BOOST::uint16_t r16 [4]; // indexed pairs + }; + BOOST_STATIC_ASSERT( sizeof rg == 8 && sizeof rp == 8 ); + + rg.a = r.a; + rg.b = r.b; + rg.c = r.c; + rg.d = r.d; + rg.e = r.e; + rg.h = r.h; + rg.l = r.l; + unsigned pc = r.pc; + unsigned sp = r.sp; + unsigned flags = r.flags; + +loop: + + int new_remain = remain_ - cycles_per_instruction; + + check( (unsigned) pc < 0x10000 ); + check( (unsigned) sp < 0x10000 ); + check( (flags & ~0xf0) == 0 ); + + uint8_t const* page = code_map [pc >> page_bits]; + unsigned op = page [PAGE_OFFSET( pc )]; + unsigned data = page [PAGE_OFFSET( pc ) + 1]; + check( op == 0xC9 || &READ_PROG( pc + 1 ) == &page [PAGE_OFFSET( pc ) + 1] ); + + pc++; + + remain_ = new_remain; + if ( new_remain <= 0 ) + goto stop; + + switch ( op ) + { + +#define BRANCH( cond ) \ +{ \ + pc++; \ + int offset = (BOOST::int8_t) data; \ + if ( !(cond) ) goto loop; \ + pc += offset; \ + goto loop; \ +} + +// Most Common + + case 0x20: // JR NZ + BRANCH( !(flags & z_flag) ) + + case 0x21: // LD HL,IMM (common) + rp.hl = READ_PROG16( pc ); + pc += 2; + goto loop; + + case 0x28: // JR Z + BRANCH( flags & z_flag ) + + { + unsigned temp; + + case 0xF0: // LD A,(0xff00+imm) + temp = data + 0xff00; + pc++; + goto ld_a_ind_comm; + + case 0xF2: // LD A,(0xff00+C) + temp = rg.c + 0xff00; + goto ld_a_ind_comm; + + case 0x0A: // LD A,(BC) + temp = rp.bc; + goto ld_a_ind_comm; + + case 0x3A: // LD A,(HL-) + temp = rp.hl; + rp.hl = temp - 1; + goto ld_a_ind_comm; + + case 0x1A: // LD A,(DE) + temp = rp.de; + goto ld_a_ind_comm; + + case 0x2A: // LD A,(HL+) (common) + temp = rp.hl; + rp.hl = temp + 1; + goto ld_a_ind_comm; + + case 0xFA: // LD A,IND16 (common) + temp = READ_PROG16( pc ); + pc += 2; + ld_a_ind_comm: + rg.a = READ( temp ); + goto loop; + } + + case 0xBE: // CMP (HL) + data = READ( rp.hl ); + goto cmp_comm; + + case 0xB8: // CMP B + case 0xB9: // CMP C + case 0xBA: // CMP D + case 0xBB: // CMP E + case 0xBC: // CMP H + case 0xBD: // CMP L + data = R8( op & 7 ); + goto cmp_comm; + + case 0xFE: // CMP IMM + pc++; + cmp_comm: + op = rg.a; + data = op - data; + sub_set_flags: + flags = ((op & 15) - (data & 15)) & h_flag; + flags |= (data >> 4) & c_flag; + flags |= n_flag; + if ( data & 0xff ) + goto loop; + flags |= z_flag; + goto loop; + + case 0x46: // LD B,(HL) + case 0x4E: // LD C,(HL) + case 0x56: // LD D,(HL) + case 0x5E: // LD E,(HL) + case 0x66: // LD H,(HL) + case 0x6E: // LD L,(HL) + case 0x7E: // LD A,(HL) + R8( (op >> 3) & 7 ) = READ( rp.hl ); + goto loop; + + case 0xC4: // CNZ (next-most-common) + pc += 2; + if ( flags & z_flag ) + goto loop; + call: + pc -= 2; + case 0xCD: // CALL (most-common) + data = pc + 2; + pc = READ_PROG16( pc ); + push: + sp = (sp - 1) & 0xFFFF; + WRITE( sp, data >> 8 ); + sp = (sp - 1) & 0xFFFF; + WRITE( sp, data & 0xff ); + goto loop; + + case 0xC8: // RNZ (next-most-common) + if ( !(flags & z_flag) ) + goto loop; + case 0xC9: // RET (most common) + ret: + pc = READ( sp ); + pc += 0x100 * READ( (sp + 1) & 0xFFFF ); + sp = (sp + 2) & 0xFFFF; + goto loop; + + case 0x00: // NOP + case 0x40: // LD B,B + case 0x49: // LD C,C + case 0x52: // LD D,D + case 0x5B: // LD E,E + case 0x64: // LD H,H + case 0x6D: // LD L,L + case 0x7F: // LD A,A + goto loop; + +// CB Instructions + + case 0xCB: + pc++; + // now data is the opcode + switch ( data ) { + + { + int temp; + + case 0x46: // BIT b,(HL) + case 0x4E: + case 0x56: + case 0x5E: + case 0x66: + case 0x6E: + case 0x76: + case 0x7E: + temp = READ( rp.hl ); + goto bit_comm; + + case 0x40: case 0x41: case 0x42: case 0x43: // BIT b,r + case 0x44: case 0x45: case 0x47: case 0x48: + case 0x49: case 0x4A: case 0x4B: case 0x4C: + case 0x4D: case 0x4F: case 0x50: case 0x51: + case 0x52: case 0x53: case 0x54: case 0x55: + case 0x57: case 0x58: case 0x59: case 0x5A: + case 0x5B: case 0x5C: case 0x5D: case 0x5F: + case 0x60: case 0x61: case 0x62: case 0x63: + case 0x64: case 0x65: case 0x67: case 0x68: + case 0x69: case 0x6A: case 0x6B: case 0x6C: + case 0x6D: case 0x6F: case 0x70: case 0x71: + case 0x72: case 0x73: case 0x74: case 0x75: + case 0x77: case 0x78: case 0x79: case 0x7A: + case 0x7B: case 0x7C: case 0x7D: case 0x7F: + temp = R8( data & 7 ); + bit_comm: + int bit = (~data >> 3) & 7; + flags &= ~n_flag; + flags |= h_flag | z_flag; + flags ^= (temp << bit) & z_flag; + goto loop; + } + + case 0x86: // RES b,(HL) + case 0x8E: + case 0x96: + case 0x9E: + case 0xA6: + case 0xAE: + case 0xB6: + case 0xBE: + case 0xC6: // SET b,(HL) + case 0xCE: + case 0xD6: + case 0xDE: + case 0xE6: + case 0xEE: + case 0xF6: + case 0xFE: { + int temp = READ( rp.hl ); + int bit = 1 << ((data >> 3) & 7); + temp &= ~bit; + if ( !(data & 0x40) ) + bit = 0; + WRITE( rp.hl, temp | bit ); + goto loop; + } + + case 0xC0: case 0xC1: case 0xC2: case 0xC3: // SET b,r + case 0xC4: case 0xC5: case 0xC7: case 0xC8: + case 0xC9: case 0xCA: case 0xCB: case 0xCC: + case 0xCD: case 0xCF: case 0xD0: case 0xD1: + case 0xD2: case 0xD3: case 0xD4: case 0xD5: + case 0xD7: case 0xD8: case 0xD9: case 0xDA: + case 0xDB: case 0xDC: case 0xDD: case 0xDF: + case 0xE0: case 0xE1: case 0xE2: case 0xE3: + case 0xE4: case 0xE5: case 0xE7: case 0xE8: + case 0xE9: case 0xEA: case 0xEB: case 0xEC: + case 0xED: case 0xEF: case 0xF0: case 0xF1: + case 0xF2: case 0xF3: case 0xF4: case 0xF5: + case 0xF7: case 0xF8: case 0xF9: case 0xFA: + case 0xFB: case 0xFC: case 0xFD: case 0xFF: + R8( data & 7 ) |= 1 << ((data >> 3) & 7); + goto loop; + + case 0x80: case 0x81: case 0x82: case 0x83: // RES b,r + case 0x84: case 0x85: case 0x87: case 0x88: + case 0x89: case 0x8A: case 0x8B: case 0x8C: + case 0x8D: case 0x8F: case 0x90: case 0x91: + case 0x92: case 0x93: case 0x94: case 0x95: + case 0x97: case 0x98: case 0x99: case 0x9A: + case 0x9B: case 0x9C: case 0x9D: case 0x9F: + case 0xA0: case 0xA1: case 0xA2: case 0xA3: + case 0xA4: case 0xA5: case 0xA7: case 0xA8: + case 0xA9: case 0xAA: case 0xAB: case 0xAC: + case 0xAD: case 0xAF: case 0xB0: case 0xB1: + case 0xB2: case 0xB3: case 0xB4: case 0xB5: + case 0xB7: case 0xB8: case 0xB9: case 0xBA: + case 0xBB: case 0xBC: case 0xBD: case 0xBF: + R8( data & 7 ) &= ~(1 << ((data >> 3) & 7)); + goto loop; + + { + int temp; + case 0x36: // SWAP (HL) + temp = READ( rp.hl ); + goto swap_comm; + + case 0x30: // SWAP B + case 0x31: // SWAP C + case 0x32: // SWAP D + case 0x33: // SWAP E + case 0x34: // SWAP H + case 0x35: // SWAP L + case 0x37: // SWAP A + temp = R8( data & 7 ); + swap_comm: + op = (temp >> 4) | (temp << 4); + flags = 0; + goto shift_comm; + } + +// Shift/Rotate + + case 0x06: // RLC (HL) + case 0x16: // RL (HL) + case 0x26: // SLA (HL) + op = READ( rp.hl ); + goto rl_comm; + + case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x27: // SLA A + case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x07: // RLC A + case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x17: // RL A + op = R8( data & 7 ); + goto rl_comm; + + case 0x3E: // SRL (HL) + data += 0x10; // bump up to 0x4n to avoid preserving sign bit + case 0x1E: // RR (HL) + case 0x0E: // RRC (HL) + case 0x2E: // SRA (HL) + op = READ( rp.hl ); + goto rr_comm; + + case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3F: // SRL A + data += 0x10; // bump up to 0x4n + case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1F: // RR A + case 0x08: case 0x09: case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0F: // RRC A + case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2F: // SRA A + op = R8( data & 7 ); + goto rr_comm; + + } // CB op + assert( false ); // unhandled CB op + + case 0x07: // RLCA + case 0x17: // RLA + data = op; + op = rg.a; + rl_comm: + op <<= 1; + op |= ((data & flags) >> 4) & 1; // RL and carry is set + flags = (op >> 4) & c_flag; // C = bit shifted out + if ( data < 0x10 ) // RLC + op |= op >> 8; + // SLA doesn't fill lower bit + goto shift_comm; + + case 0x0F: // RRCA + case 0x1F: // RRA + data = op; + op = rg.a; + rr_comm: + op |= (data & flags) << 4; // RR and carry is set + flags = (op << 4) & c_flag; // C = bit shifted out + if ( data < 0x10 ) // RRC + op |= op << 8; + op >>= 1; + if ( data & 0x20 ) // SRA propagates sign bit + op |= (op << 1) & 0x80; + shift_comm: + data &= 7; + if ( !(op & 0xff) ) + flags |= z_flag; + if ( data == 6 ) + goto write_hl_op_ff; + R8( data ) = op; + goto loop; + +// Load + + case 0x70: // LD (HL),B + case 0x71: // LD (HL),C + case 0x72: // LD (HL),D + case 0x73: // LD (HL),E + case 0x74: // LD (HL),H + case 0x75: // LD (HL),L + case 0x77: // LD (HL),A + op = R8( op & 7 ); + write_hl_op_ff: + WRITE( rp.hl, op & 0xff ); + goto loop; + + case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x47: // LD r,r + case 0x48: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4F: + case 0x50: case 0x51: case 0x53: case 0x54: case 0x55: case 0x57: + case 0x58: case 0x59: case 0x5A: case 0x5C: case 0x5D: case 0x5F: + case 0x60: case 0x61: case 0x62: case 0x63: case 0x65: case 0x67: + case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6F: + case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: + R8( (op >> 3) & 7 ) = R8( op & 7 ); + goto loop; + + case 0x08: // LD IND16,SP + data = READ_PROG16( pc ); + pc += 2; + WRITE( data, sp&0xff ); + data++; + WRITE( data, sp >> 8 ); + goto loop; + + case 0xF9: // LD SP,HL + sp = rp.hl; + goto loop; + + case 0x31: // LD SP,IMM + sp = READ_PROG16( pc ); + pc += 2; + goto loop; + + case 0x01: // LD BC,IMM + case 0x11: // LD DE,IMM + r16 [op >> 4] = READ_PROG16( pc ); + pc += 2; + goto loop; + + { + unsigned temp; + case 0xE0: // LD (0xff00+imm),A + temp = data + 0xff00; + pc++; + goto write_data_rg_a; + + case 0xE2: // LD (0xff00+C),A + temp = rg.c + 0xff00; + goto write_data_rg_a; + + case 0x32: // LD (HL-),A + temp = rp.hl; + rp.hl = temp - 1; + goto write_data_rg_a; + + case 0x02: // LD (BC),A + temp = rp.bc; + goto write_data_rg_a; + + case 0x12: // LD (DE),A + temp = rp.de; + goto write_data_rg_a; + + case 0x22: // LD (HL+),A + temp = rp.hl; + rp.hl = temp + 1; + goto write_data_rg_a; + + case 0xEA: // LD IND16,A (common) + temp = READ_PROG16( pc ); + pc += 2; + write_data_rg_a: + WRITE( temp, rg.a ); + goto loop; + } + + case 0x06: // LD B,IMM + rg.b = data; + pc++; + goto loop; + + case 0x0E: // LD C,IMM + rg.c = data; + pc++; + goto loop; + + case 0x16: // LD D,IMM + rg.d = data; + pc++; + goto loop; + + case 0x1E: // LD E,IMM + rg.e = data; + pc++; + goto loop; + + case 0x26: // LD H,IMM + rg.h = data; + pc++; + goto loop; + + case 0x2E: // LD L,IMM + rg.l = data; + pc++; + goto loop; + + case 0x36: // LD (HL),IMM + WRITE( rp.hl, data ); + pc++; + goto loop; + + case 0x3E: // LD A,IMM + rg.a = data; + pc++; + goto loop; + +// Increment/Decrement + + case 0x03: // INC BC + case 0x13: // INC DE + case 0x23: // INC HL + r16 [op >> 4]++; + goto loop; + + case 0x33: // INC SP + sp = (sp + 1) & 0xFFFF; + goto loop; + + case 0x0B: // DEC BC + case 0x1B: // DEC DE + case 0x2B: // DEC HL + r16 [op >> 4]--; + goto loop; + + case 0x3B: // DEC SP + sp = (sp - 1) & 0xFFFF; + goto loop; + + case 0x34: // INC (HL) + op = rp.hl; + data = READ( op ); + data++; + WRITE( op, data & 0xff ); + goto inc_comm; + + case 0x04: // INC B + case 0x0C: // INC C (common) + case 0x14: // INC D + case 0x1C: // INC E + case 0x24: // INC H + case 0x2C: // INC L + case 0x3C: // INC A + op = (op >> 3) & 7; + R8( op ) = data = R8( op ) + 1; + inc_comm: + flags = (flags & c_flag) | (((data & 15) - 1) & h_flag) | ((data >> 1) & z_flag); + goto loop; + + case 0x35: // DEC (HL) + op = rp.hl; + data = READ( op ); + data--; + WRITE( op, data & 0xff ); + goto dec_comm; + + case 0x05: // DEC B + case 0x0D: // DEC C + case 0x15: // DEC D + case 0x1D: // DEC E + case 0x25: // DEC H + case 0x2D: // DEC L + case 0x3D: // DEC A + op = (op >> 3) & 7; + data = R8( op ) - 1; + R8( op ) = data; + dec_comm: + flags = (flags & c_flag) | n_flag | (((data & 15) + 0x31) & h_flag); + if ( data & 0xff ) + goto loop; + flags |= z_flag; + goto loop; + +// Add 16-bit + + { + unsigned long temp; // need more than 16 bits for carry + unsigned prev; + + case 0xF8: // LD HL,SP+imm + temp = BOOST::int8_t (data); // sign-extend to 16 bits + pc++; + flags = 0; + temp += sp; + prev = sp; + goto add_16_hl; + + case 0xE8: // ADD SP,IMM + temp = BOOST::int8_t (data); // sign-extend to 16 bits + pc++; + flags = 0; + temp += sp; + prev = sp; + sp = temp & 0xffff; + goto add_16_comm; + + case 0x39: // ADD HL,SP + temp = sp; + goto add_hl_comm; + + case 0x09: // ADD HL,BC + case 0x19: // ADD HL,DE + case 0x29: // ADD HL,HL + temp = r16 [op >> 4]; + add_hl_comm: + prev = rp.hl; + temp += prev; + flags &= z_flag; + add_16_hl: + rp.hl = temp; + add_16_comm: + flags |= (temp >> 12) & c_flag; + flags |= (((temp & 0x0fff) - (prev & 0x0fff)) >> 7) & h_flag; + goto loop; + } + + case 0x86: // ADD (HL) + data = READ( rp.hl ); + goto add_comm; + + case 0x80: // ADD B + case 0x81: // ADD C + case 0x82: // ADD D + case 0x83: // ADD E + case 0x84: // ADD H + case 0x85: // ADD L + case 0x87: // ADD A + data = R8( op & 7 ); + goto add_comm; + + case 0xC6: // ADD IMM + pc++; + add_comm: + flags = rg.a; + data += flags; + flags = ((data & 15) - (flags & 15)) & h_flag; + flags |= (data >> 4) & c_flag; + rg.a = data; + if ( data & 0xff ) + goto loop; + flags |= z_flag; + goto loop; + +// Add/Subtract + + case 0x8E: // ADC (HL) + data = READ( rp.hl ); + goto adc_comm; + + case 0x88: // ADC B + case 0x89: // ADC C + case 0x8A: // ADC D + case 0x8B: // ADC E + case 0x8C: // ADC H + case 0x8D: // ADC L + case 0x8F: // ADC A + data = R8( op & 7 ); + goto adc_comm; + + case 0xCE: // ADC IMM + pc++; + adc_comm: + data += (flags >> 4) & 1; + data &= 0xff; // to do: does carry get set when sum + carry = 0x100? + goto add_comm; + + case 0x96: // SUB (HL) + data = READ( rp.hl ); + goto sub_comm; + + case 0x90: // SUB B + case 0x91: // SUB C + case 0x92: // SUB D + case 0x93: // SUB E + case 0x94: // SUB H + case 0x95: // SUB L + case 0x97: // SUB A + data = R8( op & 7 ); + goto sub_comm; + + case 0xD6: // SUB IMM + pc++; + sub_comm: + op = rg.a; + data = op - data; + rg.a = data; + goto sub_set_flags; + + case 0x9E: // SBC (HL) + data = READ( rp.hl ); + goto sbc_comm; + + case 0x98: // SBC B + case 0x99: // SBC C + case 0x9A: // SBC D + case 0x9B: // SBC E + case 0x9C: // SBC H + case 0x9D: // SBC L + case 0x9F: // SBC A + data = R8( op & 7 ); + goto sbc_comm; + + case 0xDE: // SBC IMM + pc++; + sbc_comm: + data += (flags >> 4) & 1; + data &= 0xff; // to do: does carry get set when sum + carry = 0x100? + goto sub_comm; + +// Logical + + case 0xA0: // AND B + case 0xA1: // AND C + case 0xA2: // AND D + case 0xA3: // AND E + case 0xA4: // AND H + case 0xA5: // AND L + data = R8( op & 7 ); + goto and_comm; + + case 0xA6: // AND (HL) + data = READ( rp.hl ); + pc--; + case 0xE6: // AND IMM + pc++; + and_comm: + rg.a &= data; + case 0xA7: // AND A + flags = h_flag | (((rg.a - 1) >> 1) & z_flag); + goto loop; + + case 0xB0: // OR B + case 0xB1: // OR C + case 0xB2: // OR D + case 0xB3: // OR E + case 0xB4: // OR H + case 0xB5: // OR L + data = R8( op & 7 ); + goto or_comm; + + case 0xB6: // OR (HL) + data = READ( rp.hl ); + pc--; + case 0xF6: // OR IMM + pc++; + or_comm: + rg.a |= data; + case 0xB7: // OR A + flags = ((rg.a - 1) >> 1) & z_flag; + goto loop; + + case 0xA8: // XOR B + case 0xA9: // XOR C + case 0xAA: // XOR D + case 0xAB: // XOR E + case 0xAC: // XOR H + case 0xAD: // XOR L + data = R8( op & 7 ); + goto xor_comm; + + case 0xAE: // XOR (HL) + data = READ( rp.hl ); + pc--; + case 0xEE: // XOR IMM + pc++; + xor_comm: + data ^= rg.a; + rg.a = data; + data--; + flags = (data >> 1) & z_flag; + goto loop; + + case 0xAF: // XOR A + rg.a = 0; + flags = z_flag; + goto loop; + +// Stack + + case 0xC1: // POP BC + case 0xD1: // POP DE + case 0xE1:{// POP HL (common) + int temp = READ( sp ); + r16 [(op >> 4) & 3] = temp + 0x100 * READ( (sp + 1) & 0xFFFF ); + sp = (sp + 2) & 0xFFFF; + goto loop; + } + + case 0xF1: // POP FA + rg.a = READ( sp ); + flags = READ( (sp + 1) & 0xFFFF ) & 0xf0; + sp = (sp + 2) & 0xFFFF; + goto loop; + + case 0xC5: // PUSH BC + data = rp.bc; + goto push; + + case 0xD5: // PUSH DE + data = rp.de; + goto push; + + case 0xE5: // PUSH HL + data = rp.hl; + goto push; + + case 0xF5: // PUSH FA + data = (flags << 8) | rg.a; + goto push; + +// Flow control + + case 0xC7: case 0xCF: case 0xD7: case 0xDF: // RST + case 0xE7: case 0xEF: case 0xF7: case 0xFF: + data = pc; + pc = (op & 0x38) + rst_base; + goto push; + + case 0xCC: // CZ + pc += 2; + if ( flags & z_flag ) + goto call; + goto loop; + + case 0xD4: // CNC + pc += 2; + if ( !(flags & c_flag) ) + goto call; + goto loop; + + case 0xDC: // CC + pc += 2; + if ( flags & c_flag ) + goto call; + goto loop; + + case 0xD9: // RETI + Gb_Cpu::interrupts_enabled = 1; + goto ret; + + case 0xC0: // RZ + if ( !(flags & z_flag) ) + goto ret; + goto loop; + + case 0xD0: // RNC + if ( !(flags & c_flag) ) + goto ret; + goto loop; + + case 0xD8: // RC + if ( flags & c_flag ) + goto ret; + goto loop; + + case 0x18: // JR + BRANCH( true ) + + case 0x30: // JR NC + BRANCH( !(flags & c_flag) ) + + case 0x38: // JR C + BRANCH( flags & c_flag ) + + case 0xE9: // JP_HL + pc = rp.hl; + goto loop; + + case 0xC3: // JP (next-most-common) + pc = READ_PROG16( pc ); + goto loop; + + case 0xC2: // JP NZ + pc += 2; + if ( !(flags & z_flag) ) + goto jp_taken; + goto loop; + + case 0xCA: // JP Z (most common) + pc += 2; + if ( !(flags & z_flag) ) + goto loop; + jp_taken: + pc -= 2; + pc = READ_PROG16( pc ); + goto loop; + + case 0xD2: // JP NC + pc += 2; + if ( !(flags & c_flag) ) + goto jp_taken; + goto loop; + + case 0xDA: // JP C + pc += 2; + if ( flags & c_flag ) + goto jp_taken; + goto loop; + +// Flags + + case 0x2F: // CPL + rg.a = ~rg.a; + flags |= n_flag | h_flag; + goto loop; + + case 0x3F: // CCF + flags = (flags ^ c_flag) & ~(n_flag | h_flag); + goto loop; + + case 0x37: // SCF + flags = (flags | c_flag) & ~(n_flag | h_flag); + goto loop; + + case 0xF3: // DI + interrupts_enabled = 0; + goto loop; + + case 0xFB: // EI + interrupts_enabled = 1; + goto loop; + +// Special + + case 0xDD: case 0xD3: case 0xDB: case 0xE3: case 0xE4: // ? + case 0xEB: case 0xEC: case 0xF4: case 0xFD: case 0xFC: + case 0x10: // STOP + case 0x27: // DAA (I'll have to implement this eventually...) + case 0xBF: + case 0xED: // Z80 prefix + result = Gb_Cpu::result_badop; + goto stop; + + case 0x76: // HALT + result = Gb_Cpu::result_halt; + goto stop; + } + + // If this fails then the case above is missing an opcode + assert( false ); + +stop: + pc--; + + // copy state back + r.pc = pc; + r.sp = sp; + r.flags = flags; + r.a = rg.a; + r.b = rg.b; + r.c = rg.c; + r.d = rg.d; + r.e = rg.e; + r.h = rg.h; + r.l = rg.l; + + return result; +} + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Gb_Cpu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Gb_Cpu.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,103 @@ + +// Nintendo Game Boy CPU emulator + +// Game_Music_Emu 0.3.0 + +#ifndef GB_CPU_H +#define GB_CPU_H + +#include "blargg_common.h" + +typedef unsigned gb_addr_t; // 16-bit CPU address + +class Gbs_Emu; + +// Game Boy CPU emulator. Currently treats every instruction as taking 4 cycles. +class Gb_Cpu { + typedef BOOST::uint8_t uint8_t; + enum { page_bits = 8 }; + enum { page_count = 0x10000 >> page_bits }; + uint8_t const* code_map [page_count + 1]; + long remain_; + Gbs_Emu* callback_data; +public: + + Gb_Cpu( Gbs_Emu* ); + + // Memory read/write function types. Reader must return value from 0 to 255. + typedef int (*reader_t)( Gbs_Emu*, gb_addr_t ); + typedef void (*writer_t)( Gbs_Emu*, gb_addr_t, int data ); + + // Clear registers, unmap memory, and map code pages to unmapped_page. + void reset( const void* unmapped_page = NULL, reader_t read = NULL, writer_t write = NULL ); + + // Memory mapping functions take a block of memory of specified 'start' address + // and 'size' in bytes. Both start address and size must be a multiple of page_size. + enum { page_size = 1L << page_bits }; + + // Map code memory to 'code' (memory accessed via the program counter) + void map_code( gb_addr_t start, unsigned long size, const void* code ); + + // Map data memory to read and write functions + void map_memory( gb_addr_t start, unsigned long size, reader_t, writer_t ); + + // Access memory as the emulated CPU does. + int read( gb_addr_t ); + void write( gb_addr_t, int data ); + uint8_t* get_code( gb_addr_t ); // non-const to allow debugger to modify code + + // Push a byte on the stack + void push_byte( int ); + + // Game Boy Z80 registers. *Not* kept updated during a call to run(). + struct registers_t { + long pc; // more than 16 bits to allow overflow detection + BOOST::uint16_t sp; + uint8_t flags; + uint8_t a; + uint8_t b; + uint8_t c; + uint8_t d; + uint8_t e; + uint8_t h; + uint8_t l; + }; + registers_t r; + + // Interrupt enable flag set by EI and cleared by DI + bool interrupts_enabled; + + // Base address for RST vectors (normally 0) + gb_addr_t rst_base; + + // Reasons that run() returns + enum result_t { + result_cycles, // Requested number of cycles (or more) were executed + result_halt, // PC is at HALT instruction + result_badop // PC is at bad (unimplemented) instruction + }; + + // Run CPU for at least 'count' cycles, or until one of the above conditions + // arises. Returns reason for stopping. + result_t run( long count ); + + // Number of clock cycles remaining for most recent run() call + long remain() const; + +private: + // noncopyable + Gb_Cpu( const Gb_Cpu& ); + Gb_Cpu& operator = ( const Gb_Cpu& ); + + reader_t data_reader [page_count + 1]; // extra entry catches address overflow + writer_t data_writer [page_count + 1]; + void set_code_page( int, uint8_t const* ); +}; + +inline long Gb_Cpu::remain() const +{ + return remain_; +} + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Gb_Oscs.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Gb_Oscs.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,333 @@ + +// Gb_Snd_Emu 0.1.4. http://www.slack.net/~ant/ + +#include "Gb_Apu.h" + +#include + +/* Copyright (C) 2003-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +// Gb_Osc + +void Gb_Osc::reset() +{ + delay = 0; + last_amp = 0; + length = 0; + output_select = 3; + output = outputs [output_select]; +} + +void Gb_Osc::clock_length() +{ + if ( (regs [4] & len_enabled_mask) && length ) + length--; +} + +// Gb_Env + +void Gb_Env::clock_envelope() +{ + if ( env_delay && !--env_delay ) + { + env_delay = regs [2] & 7; + int v = volume - 1 + (regs [2] >> 2 & 2); + if ( (unsigned) v < 15 ) + volume = v; + } +} + +bool Gb_Env::write_register( int reg, int data ) +{ + switch ( reg ) + { + case 1: + length = 64 - (regs [1] & 0x3f); + break; + + case 2: + if ( !(data >> 4) ) + enabled = false; + break; + + case 4: + if ( data & trigger ) + { + env_delay = regs [2] & 7; + volume = regs [2] >> 4; + enabled = true; + if ( length == 0 ) + length = 64; + return true; + } + } + return false; +} + +// Gb_Square + +void Gb_Square::reset() +{ + phase = 0; + sweep_freq = 0; + sweep_delay = 0; + Gb_Env::reset(); +} + +void Gb_Square::clock_sweep() +{ + int sweep_period = (regs [0] & period_mask) >> 4; + if ( sweep_period && sweep_delay && !--sweep_delay ) + { + sweep_delay = sweep_period; + regs [3] = sweep_freq & 0xFF; + regs [4] = (regs [4] & ~0x07) | (sweep_freq >> 8 & 0x07); + + int offset = sweep_freq >> (regs [0] & shift_mask); + if ( regs [0] & 0x08 ) + offset = -offset; + sweep_freq += offset; + + if ( sweep_freq < 0 ) + { + sweep_freq = 0; + } + else if ( sweep_freq >= 2048 ) + { + sweep_delay = 0; // don't modify channel frequency any further + sweep_freq = 2048; // silence sound immediately + } + } +} + +void Gb_Square::run( gb_time_t time, gb_time_t end_time, int playing ) +{ + if ( sweep_freq == 2048 ) + playing = false; + + static unsigned char const table [4] = { 1, 2, 4, 6 }; + int const duty = table [regs [1] >> 6]; + int amp = volume & playing; + if ( phase >= duty ) + amp = -amp; + + int frequency = this->frequency(); + if ( unsigned (frequency - 1) > 2040 ) // frequency < 1 || frequency > 2041 + { + // really high frequency results in DC at half volume + amp = volume >> 1; + playing = false; + } + + int delta = amp - last_amp; + if ( delta ) + { + last_amp = amp; + synth->offset( time, delta, output ); + } + + time += delay; + if ( !playing ) + time = end_time; + + if ( time < end_time ) + { + int const period = (2048 - frequency) * 4; + Blip_Buffer* const output = this->output; + int phase = this->phase; + int delta = amp * 2; + do + { + phase = (phase + 1) & 7; + if ( phase == 0 || phase == duty ) + { + delta = -delta; + synth->offset_inline( time, delta, output ); + } + time += period; + } + while ( time < end_time ); + + this->phase = phase; + last_amp = delta >> 1; + } + delay = time - end_time; +} + +// Gb_Noise + +#include BLARGG_ENABLE_OPTIMIZER + +void Gb_Noise::run( gb_time_t time, gb_time_t end_time, int playing ) +{ + int amp = volume & playing; + int tap = 13 - (regs [3] & 8); + if ( bits >> tap & 2 ) + amp = -amp; + + int delta = amp - last_amp; + if ( delta ) + { + last_amp = amp; + synth->offset( time, delta, output ); + } + + time += delay; + if ( !playing ) + time = end_time; + + if ( time < end_time ) + { + static unsigned char const table [8] = { 8, 16, 32, 48, 64, 80, 96, 112 }; + int period = table [regs [3] & 7] << (regs [3] >> 4); + + // keep parallel resampled time to eliminate time conversion in the loop + Blip_Buffer* const output = this->output; + const blip_resampled_time_t resampled_period = + output->resampled_duration( period ); + blip_resampled_time_t resampled_time = output->resampled_time( time ); + unsigned bits = this->bits; + int delta = amp * 2; + + do + { + unsigned changed = (bits >> tap) + 1; + time += period; + bits <<= 1; + if ( changed & 2 ) + { + delta = -delta; + bits |= 1; + synth->offset_resampled( resampled_time, delta, output ); + } + resampled_time += resampled_period; + } + while ( time < end_time ); + + this->bits = bits; + last_amp = delta >> 1; + } + delay = time - end_time; +} + +// Gb_Wave + +inline void Gb_Wave::write_register( int reg, int data ) +{ + switch ( reg ) + { + case 0: + if ( !(data & 0x80) ) + enabled = false; + break; + + case 1: + length = 256 - regs [1]; + break; + + case 2: + volume = data >> 5 & 3; + break; + + case 4: + if ( data & trigger & regs [0] ) + { + wave_pos = 0; + enabled = true; + if ( length == 0 ) + length = 256; + } + } +} + +void Gb_Wave::run( gb_time_t time, gb_time_t end_time, int playing ) +{ + int volume_shift = (volume - 1) & 7; // volume = 0 causes shift = 7 + int amp = (wave [wave_pos] >> volume_shift & playing) * 2; + + int frequency = this->frequency(); + if ( unsigned (frequency - 1) > 2044 ) // frequency < 1 || frequency > 2045 + { + amp = 30 >> volume_shift & playing; + playing = false; + } + + int delta = amp - last_amp; + if ( delta ) + { + last_amp = amp; + synth->offset( time, delta, output ); + } + + time += delay; + if ( !playing ) + time = end_time; + + if ( time < end_time ) + { + Blip_Buffer* const output = this->output; + int const period = (2048 - frequency) * 2; + int wave_pos = (this->wave_pos + 1) & (wave_size - 1); + + do + { + int amp = (wave [wave_pos] >> volume_shift) * 2; + wave_pos = (wave_pos + 1) & (wave_size - 1); + int delta = amp - last_amp; + if ( delta ) + { + last_amp = amp; + synth->offset_inline( time, delta, output ); + } + time += period; + } + while ( time < end_time ); + + this->wave_pos = (wave_pos - 1) & (wave_size - 1); + } + delay = time - end_time; +} + +// Gb_Apu::write_osc + +void Gb_Apu::write_osc( int index, int reg, int data ) +{ + reg -= index * 5; + Gb_Square* sq = &square2; + switch ( index ) + { + case 0: + sq = &square1; + case 1: + if ( sq->write_register( reg, data ) && index == 0 ) + { + square1.sweep_freq = square1.frequency(); + if ( (regs [0] & sq->period_mask) && (regs [0] & sq->shift_mask) ) + { + square1.sweep_delay = 1; // cause sweep to recalculate now + square1.clock_sweep(); + } + } + break; + + case 2: + wave.write_register( reg, data ); + break; + + case 3: + if ( noise.write_register( reg, data ) ) + noise.bits = 0x7FFF; + } +} + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Gb_Oscs.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Gb_Oscs.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,86 @@ + +// Private oscillators used by Gb_Apu + +// Gb_Snd_Emu 0.1.4 + +#ifndef GB_OSCS_H +#define GB_OSCS_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +struct Gb_Osc +{ + enum { trigger = 0x80 }; + enum { len_enabled_mask = 0x40 }; + + Blip_Buffer* outputs [4]; // NULL, right, left, center + Blip_Buffer* output; + int output_select; + BOOST::uint8_t* regs; // osc's 5 registers + + int delay; + int last_amp; + int volume; + int length; + bool enabled; + + void reset(); + void clock_length(); + int frequency() const { return (regs [4] & 7) * 0x100 + regs [3]; } +}; + +struct Gb_Env : Gb_Osc +{ + int env_delay; + + void reset(); + void clock_envelope(); + bool write_register( int, int ); +}; + +struct Gb_Square : Gb_Env +{ + enum { period_mask = 0x70 }; + enum { shift_mask = 0x07 }; + + typedef Blip_Synth Synth; + Synth const* synth; + int sweep_delay; + int sweep_freq; + int phase; + + void reset(); + void clock_sweep(); + void run( gb_time_t, gb_time_t, int playing ); +}; + +struct Gb_Noise : Gb_Env +{ + typedef Blip_Synth Synth; + Synth const* synth; + unsigned bits; + + void run( gb_time_t, gb_time_t, int playing ); +}; + +struct Gb_Wave : Gb_Osc +{ + typedef Blip_Synth Synth; + Synth const* synth; + int wave_pos; + enum { wave_size = 32 }; + BOOST::uint8_t wave [wave_size]; + + void write_register( int, int ); + void run( gb_time_t, gb_time_t, int playing ); +}; + +inline void Gb_Env::reset() +{ + env_delay = 0; + Gb_Osc::reset(); +} + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Gbs_Emu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Gbs_Emu.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,366 @@ + +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ + +#include "Gbs_Emu.h" + +#include + +#include "blargg_endian.h" + +/* Copyright (C) 2003-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +#ifndef RUN_GB_CPU + #define RUN_GB_CPU( cpu, n ) cpu.run( n ) +#endif + +const long bank_size = 0x4000; +const gb_addr_t ram_addr = 0xa000; +const gb_addr_t halt_addr = 0x9EFE; +static BOOST::uint8_t unmapped_code [Gb_Cpu::page_size]; + +Gbs_Emu::equalizer_t const Gbs_Emu::handheld_eq = { -47.0, 2000 }; +Gbs_Emu::equalizer_t const Gbs_Emu::headphones_eq = { 0.0, 300 }; + +// RAM + +int Gbs_Emu::read_ram( Gbs_Emu* emu, gb_addr_t addr ) +{ + return emu->ram [addr - ram_addr]; +} + +void Gbs_Emu::write_ram( Gbs_Emu* emu, gb_addr_t addr, int data ) +{ + emu->ram [addr - ram_addr] = data; +} + +// Unmapped + +int Gbs_Emu::read_unmapped( Gbs_Emu*, gb_addr_t addr ) +{ + dprintf( "Read from unmapped memory $%.4x\n", (unsigned) addr ); + return 0xFF; // open bus value +} + +void Gbs_Emu::write_unmapped( Gbs_Emu*, gb_addr_t addr, int ) +{ + dprintf( "Wrote to unmapped memory $%.4x\n", (unsigned) addr ); +} + +// ROM + +int Gbs_Emu::read_rom( Gbs_Emu* emu, gb_addr_t addr ) +{ + return emu->rom [addr]; +} + +int Gbs_Emu::read_bank( Gbs_Emu* emu, gb_addr_t addr ) +{ + return emu->rom_bank [addr & (bank_size - 1)]; +} + +void Gbs_Emu::set_bank( int n ) +{ + if ( n >= bank_count ) + { + n = 0; + dprintf( "Set to non-existent bank %d\n", (int) n ); + } + if ( n == 0 && bank_count > 1 ) + { + // to do: what is the correct behavior? Current Wario Land 3 and + // Tetris DX GBS rips require that this have no effect or set to bank 1. + //return; + //dprintf( "Selected ROM bank 0\n" ); + } + rom_bank = &rom [n * bank_size]; + cpu.map_code( bank_size, bank_size, rom_bank ); +} + +void Gbs_Emu::write_rom( Gbs_Emu* emu, gb_addr_t addr, int data ) +{ + if ( unsigned (addr - 0x2000) < 0x2000 ) + emu->set_bank( data & 0x1F ); +} + +// I/O: Timer, APU + +void Gbs_Emu::set_timer( int modulo, int rate ) +{ + if ( timer_mode ) + { + static byte const rates [4] = { 10, 4, 6, 8 }; + play_period = (gb_time_t) (256 - modulo) << (rates [rate & 3] - double_speed); + } +} + +inline gb_time_t Gbs_Emu::clock() const +{ + return cpu_time - cpu.remain(); +} + +int Gbs_Emu::read_io( Gbs_Emu* emu, gb_addr_t addr ) +{ + // hi_page is accessed most + if ( addr >= 0xFF80 ) + return emu->hi_page [addr & 0xFF]; + + if ( unsigned (addr - Gb_Apu::start_addr) <= Gb_Apu::register_count ) + return emu->apu.read_register( emu->clock(), addr ); + + if ( addr == 0xFF00 ) + return 0; // joypad + + dprintf( "Unhandled I/O read 0x%4X\n", (unsigned) addr ); + + return 0xFF; +} + +void Gbs_Emu::write_io( Gbs_Emu* emu, gb_addr_t addr, int data ) +{ + // apu is accessed most + if ( unsigned (addr - Gb_Apu::start_addr) < Gb_Apu::register_count ) + { + emu->apu.write_register( emu->clock(), addr, data ); + } + else + { + emu->hi_page [addr & 0xFF] = data; + + if ( addr == 0xFF06 || addr == 0xFF07 ) + emu->set_timer( emu->hi_page [6], emu->hi_page [7] ); + + //if ( addr == 0xFFFF ) + // dprintf( "Wrote interrupt mask\n" ); + } +} + +Gbs_Emu::Gbs_Emu( double gain ) : cpu( this ) +{ + apu.volume( gain ); + + static equalizer_t const eq = { -1.0, 120 }; + set_equalizer( eq ); + + // unmapped code is all HALT instructions + memset( unmapped_code, 0x76, sizeof unmapped_code ); + + // cpu + cpu.reset( unmapped_code, read_unmapped, write_unmapped ); + cpu.map_memory( 0x0000, 0x4000, read_rom, write_rom ); + cpu.map_memory( 0x4000, 0x4000, read_bank, write_rom ); + cpu.map_memory( ram_addr, 0x4000, read_ram, write_ram ); + cpu.map_code( ram_addr, 0x4000, ram ); + cpu.map_code( 0xFF00, 0x0100, hi_page ); + cpu.map_memory( 0xFF00, 0x0100, read_io, write_io ); +} + +Gbs_Emu::~Gbs_Emu() +{ +} + +void Gbs_Emu::unload() +{ + cpu.r.pc = halt_addr; + rom.clear(); +} + +void Gbs_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) +{ + apu.osc_output( i, c, l, r ); +} + +void Gbs_Emu::update_eq( blip_eq_t const& eq ) +{ + apu.treble_eq( eq ); +} + +blargg_err_t Gbs_Emu::load( Data_Reader& in ) +{ + header_t h; + BLARGG_RETURN_ERR( in.read( &h, sizeof h ) ); + return load( h, in ); +} + +blargg_err_t Gbs_Emu::load( const header_t& h, Data_Reader& in ) +{ + header_ = h; + unload(); + + // check compatibility + if ( 0 != memcmp( header_.tag, "GBS", 3 ) ) + return "Not a GBS file"; + if ( header_.vers != 1 ) + return "Unsupported GBS format"; + + // gather relevant fields + load_addr = get_le16( header_.load_addr ); + init_addr = get_le16( header_.init_addr ); + play_addr = get_le16( header_.play_addr ); + stack_ptr = get_le16( header_.stack_ptr ); + double_speed = (header_.timer_mode & 0x80) != 0; + timer_modulo_init = header_.timer_modulo; + timer_mode = header_.timer_mode; + if ( !(timer_mode & 0x04) ) + timer_mode = 0; // using vbl + + #ifndef NDEBUG + { + if ( header_.timer_mode & 0x78 ) + dprintf( "TAC field has extra bits set: 0x%02x\n", (unsigned) header_.timer_mode ); + + if ( load_addr < 0x400 || load_addr >= 0x8000 || + init_addr < 0x400 || init_addr >= 0x8000 || + play_addr < 0x400 || play_addr >= 0x8000 ) + dprintf( "Load/init/play address violates GBS spec.\n" ); + } + #endif + + // rom + bank_count = (load_addr + in.remain() + bank_size - 1) / bank_size; + BLARGG_RETURN_ERR( rom.resize( bank_count * bank_size ) ); + memset( rom.begin(), 0, rom.size() ); + blargg_err_t err = in.read( &rom [load_addr], in.remain() ); + if ( err ) + { + unload(); + return err; + } + + // cpu + cpu.rst_base = load_addr; + cpu.map_code( 0x0000, 0x4000, rom.begin() ); + + set_voice_count( Gb_Apu::osc_count ); + set_track_count( header_.track_count ); + + return setup_buffer( 4194304 ); +} + +const char** Gbs_Emu::voice_names() const +{ + static const char* names [] = { "Square 1", "Square 2", "Wave", "Noise" }; + return names; +} + +// Emulation + +static const BOOST::uint8_t sound_data [Gb_Apu::register_count] = { + 0x80, 0xBF, 0x00, 0x00, 0xBF, // square 1 + 0x00, 0x3F, 0x00, 0x00, 0xBF, // square 2 + 0x7F, 0xFF, 0x9F, 0x00, 0xBF, // wave + 0x00, 0xFF, 0x00, 0x00, 0xBF, // noise + 0x77, 0xF3, 0xF1, // vin/volume, status, power mode + 0, 0, 0, 0, 0, 0, 0, 0, 0, // unused + 0xAC, 0xDD, 0xDA, 0x48, 0x36, 0x02, 0xCF, 0x16, // waveform data + 0x2C, 0x04, 0xE5, 0x2C, 0xAC, 0xDD, 0xDA, 0x48 +}; + +void Gbs_Emu::cpu_jsr( gb_addr_t addr ) +{ + cpu.write( --cpu.r.sp, cpu.r.pc >> 8 ); + cpu.write( --cpu.r.sp, cpu.r.pc&0xFF ); + cpu.r.pc = addr; +} + +void Gbs_Emu::start_track( int track_index ) +{ + require( rom.size() ); // file must be loaded + + Classic_Emu::start_track( track_index ); + + apu.reset(); + + memset( ram, 0, sizeof ram ); + memset( hi_page, 0, sizeof hi_page ); + + // configure hardware + set_bank( bank_count > 1 ); + for ( int i = 0; i < (int) sizeof sound_data; i++ ) + apu.write_register( 0, i + apu.start_addr, sound_data [i] ); + play_period = 70224; // 59.73 Hz + set_timer( timer_modulo_init, timer_mode ); // ignored if using vbl + next_play = play_period; + + // set up init call + cpu.r.a = track_index; + cpu.r.b = 0; + cpu.r.c = 0; + cpu.r.d = 0; + cpu.r.e = 0; + cpu.r.h = 0; + cpu.r.l = 0; + cpu.r.flags = 0; + cpu.r.pc = halt_addr; + cpu.r.sp = stack_ptr; + cpu_jsr( init_addr ); +} + +blip_time_t Gbs_Emu::run_clocks( blip_time_t duration, bool* added_stereo ) +{ + require( rom.size() ); // file must be loaded + + cpu_time = 0; + while ( cpu_time < duration ) + { + // check for idle cpu + if ( cpu.r.pc == halt_addr ) + { + if ( next_play > duration ) + { + cpu_time = duration; + break; + } + + if ( cpu_time < next_play ) + cpu_time = next_play; + next_play += play_period; + cpu_jsr( play_addr ); + } + + long count = duration - cpu_time; + cpu_time = duration; + Gb_Cpu::result_t result = RUN_GB_CPU( cpu, count ); + cpu_time -= cpu.remain(); + + if ( (result == Gb_Cpu::result_halt && cpu.r.pc != halt_addr) || + result == Gb_Cpu::result_badop ) + { + if ( cpu.r.pc > 0xFFFF ) + { + dprintf( "PC wrapped around\n" ); + cpu.r.pc &= 0xFFFF; + } + else + { + log_error(); + dprintf( "Bad opcode $%.2x at $%.4x\n", + (int) cpu.read( cpu.r.pc ), (int) cpu.r.pc ); + cpu.r.pc = (cpu.r.pc + 1) & 0xFFFF; + cpu_time += 6; + } + } + } + + // end time frame + + next_play -= cpu_time; + if ( next_play < 0 ) // could go negative if routine is taking too long to return + next_play = 0; + + if ( apu.end_frame( cpu_time ) && added_stereo ) + *added_stereo = true; + + return cpu_time; +} + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Gbs_Emu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Gbs_Emu.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,108 @@ + +// Nintendo Game Boy GBS music file emulator + +// Game_Music_Emu 0.3.0 + +#ifndef GBS_EMU_H +#define GBS_EMU_H + +#include "Classic_Emu.h" +#include "Gb_Apu.h" +#include "Gb_Cpu.h" + +class Gbs_Emu : public Classic_Emu { +public: + + // Sets internal gain, where 1.0 results in almost no clamping. Default gain + // roughly matches volume of other emulators. + Gbs_Emu( double gain = 1.2 ); + + // GBS file header + struct header_t + { + char tag [3]; + byte vers; + byte track_count; + byte first_track; + byte load_addr [2]; + byte init_addr [2]; + byte play_addr [2]; + byte stack_ptr [2]; + byte timer_modulo; + byte timer_mode; + char game [32]; + char author [32]; + char copyright [32]; + + enum { song = 0 }; // no song titles + }; + BOOST_STATIC_ASSERT( sizeof (header_t) == 112 ); + + // Load GBS data + blargg_err_t load( Data_Reader& ); + + // Load GBS using already-loaded header and remaining data + blargg_err_t load( header_t const&, Data_Reader& ); + + // Header for currently loaded GBS + header_t const& header() const { return header_; } + + // Equalizer profiles for Game Boy Color speaker and headphones + static equalizer_t const handheld_eq; + static equalizer_t const headphones_eq; + +public: + ~Gbs_Emu(); + const char** voice_names() const; + void start_track( int ); +protected: + void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); + void update_eq( blip_eq_t const& ); + blip_time_t run_clocks( blip_time_t, bool* ); +private: + // rom + const byte* rom_bank; + blargg_vector rom; + void unload(); + int bank_count; + void set_bank( int ); + static void write_rom( Gbs_Emu*, gb_addr_t, int ); + static int read_rom( Gbs_Emu*, gb_addr_t ); + static int read_bank( Gbs_Emu*, gb_addr_t ); + + // state + gb_addr_t load_addr; + gb_addr_t init_addr; + gb_addr_t play_addr; + gb_addr_t stack_ptr; + int timer_modulo_init; + int timer_mode; + + // timer + gb_time_t cpu_time; + gb_time_t play_period; + gb_time_t next_play; + int double_speed; + + // hardware + Gb_Apu apu; + void set_timer( int tma, int tmc ); + static int read_io( Gbs_Emu*, gb_addr_t ); + static void write_io( Gbs_Emu*, gb_addr_t, int ); + static int read_unmapped( Gbs_Emu*, gb_addr_t ); + static void write_unmapped( Gbs_Emu*, gb_addr_t, int ); + + // large objects + + header_t header_; + byte hi_page [0x100]; + Gb_Cpu cpu; + void cpu_jsr( gb_addr_t ); + gb_time_t clock() const; + byte ram [0x4000]; + static int read_ram( Gbs_Emu*, gb_addr_t ); + static void write_ram( Gbs_Emu*, gb_addr_t, int ); +}; + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Gym_Emu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Gym_Emu.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,350 @@ + +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ + +#include "Gym_Emu.h" + +#include +#include "blargg_endian.h" + +/* Copyright (C) 2003-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +double const gain = 3.0; +double const oversample_factor = 5 / 3.0; + +const long base_clock = 53700300; +const long clock_rate = base_clock / 15; + +Gym_Emu::Gym_Emu() +{ + data = NULL; + pos = NULL; +} + +Gym_Emu::~Gym_Emu() +{ + unload(); +} + +void Gym_Emu::unload() +{ + data = NULL; + pos = NULL; + set_track_ended( false ); + mem.clear(); +} + +blargg_err_t Gym_Emu::set_sample_rate( long sample_rate ) +{ + blip_eq_t eq( -32, 8000, sample_rate ); + apu.treble_eq( eq ); + apu.volume( 0.135 * gain ); + dac_synth.treble_eq( eq ); + dac_synth.volume( 0.125 / 256 * gain ); + + BLARGG_RETURN_ERR( blip_buf.set_sample_rate( sample_rate, 1000 / 60 ) ); + blip_buf.clock_rate( clock_rate ); + + double factor = Dual_Resampler::setup( oversample_factor, 0.990, gain ); + double fm_sample_rate = sample_rate * factor; + BLARGG_RETURN_ERR( fm.set_rate( fm_sample_rate, base_clock / 7.0 ) ); + BLARGG_RETURN_ERR( Dual_Resampler::resize( sample_rate / 60 ) ); + + return Music_Emu::set_sample_rate( sample_rate ); +} + +void Gym_Emu::mute_voices( int mask ) +{ + Music_Emu::mute_voices( mask ); + fm.mute_voices( mask ); + dac_muted = mask & 0x40; + apu.output( (mask & 0x80) ? NULL : &blip_buf ); +} + +const char** Gym_Emu::voice_names() const +{ + static const char* names [] = { + "FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PCM", "PSG" + }; + return names; +} + +static blargg_err_t check_header( const Gym_Emu::header_t& h, int* data_offset = NULL ) +{ + if ( memcmp( h.tag, "GYMX", 4 ) == 0 ) + { + if ( memcmp( h.packed, "\0\0\0\0", 4 ) != 0 ) + return "Packed GYM file not supported"; + + if ( data_offset ) + *data_offset = sizeof h; + } + else if ( h.tag [0] != 0 && h.tag [0] != 1 ) + { + // not a headerless GYM + // to do: more thorough check, or just require a damn header + return "Not a GYM file"; + } + + return blargg_success; +} + +blargg_err_t Gym_Emu::load_( const void* file, long data_offset, long file_size ) +{ + require( blip_buf.length() ); + + data = (const byte*) file + data_offset; + data_end = (const byte*) file + file_size; + + loop_begin = NULL; + if ( data_offset ) + header_ = *(header_t*) file; + else + memset( &header_, 0, sizeof header_ ); + + set_voice_count( 8 ); + set_track_count( 1 ); + remute_voices(); + + return blargg_success; +} + +blargg_err_t Gym_Emu::load( const void* file, long file_size ) +{ + unload(); + + if ( file_size < (int) sizeof (header_t) ) + return "Not a GYM file"; + + int data_offset = 0; + BLARGG_RETURN_ERR( check_header( *(header_t*) file, &data_offset ) ); + + return load_( file, data_offset, file_size ); +} + +blargg_err_t Gym_Emu::load( Data_Reader& in ) +{ + header_t h; + BLARGG_RETURN_ERR( in.read( &h, sizeof h ) ); + return load( h, in ); +} + +blargg_err_t Gym_Emu::load( const header_t& h, Data_Reader& in ) +{ + unload(); + + int data_offset = 0; + BLARGG_RETURN_ERR( check_header( h, &data_offset ) ); + + BLARGG_RETURN_ERR( mem.resize( sizeof h + in.remain() ) ); + memcpy( mem.begin(), &h, sizeof h ); + BLARGG_RETURN_ERR( in.read( &mem [sizeof h], mem.size() - sizeof h ) ); + + return load_( mem.begin(), data_offset, mem.size() ); +} + +long Gym_Emu::track_length() const +{ + long time = 0; + const byte* p = data; + while ( p < data_end ) + { + switch ( *p++ ) + { + case 0: + time++; + break; + + case 1: + case 2: + p += 2; + break; + + case 3: + p += 1; + break; + } + } + return time; +} + +void Gym_Emu::start_track( int track ) +{ + require( data ); + + Music_Emu::start_track( track ); + + pos = &data [0]; + loop_remain = get_le32( header_.loop_start ); + + prev_dac_count = 0; + dac_enabled = false; + dac_amp = -1; + + fm.reset(); + apu.reset(); + blip_buf.clear(); + Dual_Resampler::clear(); +} + +void Gym_Emu::run_dac( int dac_count ) +{ + // Guess beginning and end of sample and adjust rate and buffer position accordingly. + + // count dac samples in next frame + int next_dac_count = 0; + const byte* p = this->pos; + int cmd; + while ( (cmd = *p++) != 0 ) + { + int data = *p++; + if ( cmd <= 2 ) + ++p; + if ( cmd == 1 && data == 0x2A ) + next_dac_count++; + } + + // detect beginning and end of sample + int rate_count = dac_count; + int start = 0; + if ( !prev_dac_count && next_dac_count && dac_count < next_dac_count ) + { + rate_count = next_dac_count; + start = next_dac_count - dac_count; + } + else if ( prev_dac_count && !next_dac_count && dac_count < prev_dac_count ) + { + rate_count = prev_dac_count; + } + + // Evenly space samples within buffer section being used + blip_resampled_time_t period = + blip_buf.resampled_duration( clock_rate / 60 ) / rate_count; + + blip_resampled_time_t time = blip_buf.resampled_time( 0 ) + + period * start + (period >> 1); + + int dac_amp = this->dac_amp; + if ( dac_amp < 0 ) + dac_amp = dac_buf [0]; + + for ( int i = 0; i < dac_count; i++ ) + { + int delta = dac_buf [i] - dac_amp; + dac_amp += delta; + dac_synth.offset_resampled( time, delta, &blip_buf ); + time += period; + } + this->dac_amp = dac_amp; +} + +void Gym_Emu::parse_frame() +{ + int dac_count = 0; + const byte* pos = this->pos; + + if ( loop_remain && !--loop_remain ) + loop_begin = pos; // find loop on first time through sequence + + int cmd; + while ( (cmd = *pos++) != 0 ) + { + int data = *pos++; + if ( cmd == 1 ) + { + int data2 = *pos++; + if ( data != 0x2A ) + { + if ( data == 0x2B ) + dac_enabled = (data2 & 0x80) != 0; + + fm.write0( data, data2 ); + } + else if ( dac_count < (int) sizeof dac_buf ) + { + dac_buf [dac_count] = data2; + dac_count += dac_enabled; + } + } + else if ( cmd == 2 ) + { + fm.write1( data, *pos++ ); + } + else if ( cmd == 3 ) + { + apu.write_data( 0, data ); + } + else + { + // to do: many GYM streams are full of errors, and error count should + // reflect cases where music is really having problems + //log_error(); + --pos; // put data back + } + } + + // loop + if ( pos >= data_end ) + { + if ( pos > data_end ) + log_error(); + + if ( loop_begin ) + pos = loop_begin; + else + set_track_ended(); + } + this->pos = pos; + + // dac + if ( dac_count && !dac_muted ) + run_dac( dac_count ); + prev_dac_count = dac_count; +} + +int Gym_Emu::play_frame( blip_time_t blip_time, int sample_count, sample_t* buf ) +{ + if ( !track_ended() ) + parse_frame(); + + apu.end_frame( blip_time ); + + memset( buf, 0, sample_count * sizeof *buf ); + fm.run( sample_count >> 1, buf ); + + return sample_count; +} + +void Gym_Emu::play( long count, sample_t* out ) +{ + require( pos ); + + Dual_Resampler::play( count, out, blip_buf ); +} + +void Gym_Emu::skip( long count ) +{ + // to do: figure out why total muting generated access violation on MorphOS + const int buf_size = 1024; + sample_t buf [buf_size]; + + while ( count ) + { + int n = buf_size; + if ( n > count ) + n = count; + count -= n; + play( n, buf ); + } +} + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Gym_Emu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Gym_Emu.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,96 @@ + +// Sega Genesis GYM music file emulator + +// Game_Music_Emu 0.3.0 + +#ifndef GYM_EMU_H +#define GYM_EMU_H + +#include "Dual_Resampler.h" +#include "Ym2612_Emu.h" +#include "Music_Emu.h" +#include "Sms_Apu.h" + +class Gym_Emu : public Music_Emu, private Dual_Resampler { +public: + + // GYM file header + struct header_t + { + char tag [4]; + char song [32]; + char game [32]; + char copyright [32]; + char emulator [32]; + char dumper [32]; + char comment [256]; + byte loop_start [4]; // in 1/60 seconds, 0 if not looped + byte packed [4]; + + enum { track_count = 1 }; // one track per file + enum { author = 0 }; // no author field + }; + BOOST_STATIC_ASSERT( sizeof (header_t) == 428 ); + + // Load GYM data + blargg_err_t load( Data_Reader& ); + + // Load GYM file using already-loaded header and remaining data + blargg_err_t load( header_t const&, Data_Reader& ); + blargg_err_t load( void const* data, long size ); // keeps pointer to data + + // Header for currently loaded GYM (cleared to zero if GYM lacks header) + header_t const& header() const { return header_; } + + // Length of track in 1/60 seconds + enum { gym_rate = 60 }; // GYM time units (frames) per second + long track_length() const; + +public: + typedef Music_Emu::sample_t sample_t; + Gym_Emu(); + ~Gym_Emu(); + blargg_err_t set_sample_rate( long sample_rate ); + void mute_voices( int ); + void start_track( int ); + void play( long count, sample_t* ); + const char** voice_names() const; + void skip( long count ); +public: + // deprecated + blargg_err_t init( long r, double gain = 1.5, double oversample = 5 / 3.0 ) + { + return set_sample_rate( r ); + } +protected: + int play_frame( blip_time_t blip_time, int sample_count, sample_t* buf ); +private: + // sequence data begin, loop begin, current position, end + const byte* data; + const byte* loop_begin; + const byte* pos; + const byte* data_end; + long loop_remain; // frames remaining until loop beginning has been located + blargg_vector mem; + header_t header_; + blargg_err_t load_( const void* file, long data_offset, long file_size ); + void unload(); + void parse_frame(); + + // dac (pcm) + int dac_amp; + int prev_dac_count; + bool dac_enabled; + bool dac_muted; + void run_dac( int ); + + // sound + Blip_Buffer blip_buf; + Ym2612_Emu fm; + Blip_Synth dac_synth; + Sms_Apu apu; + byte dac_buf [1024]; +}; + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Gzip_File.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Gzip_File.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,148 @@ + +#include "Gzip_File.h" + +#include "zlib.h" + +/* Copyright (C) 2005 Shay Green. Permission is hereby granted, free of +charge, to any person obtaining a copy of this software module and associated +documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and +to permit persons to whom the Software is furnished to do so, subject to the +following conditions: The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. THE +SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + +static const char* get_gzip_eof( FILE* file, long* eof ) +{ + unsigned char buf [4]; + if ( !fread( buf, 2, 1, file ) ) + return "Couldn't read from file"; + + if ( buf [0] == 0x1F && buf [1] == 0x8B ) + { + if ( fseek( file, -4, SEEK_END ) ) + return "Couldn't seek in file"; + + if ( !fread( buf, 4, 1, file ) ) + return "Couldn't read from file"; + + *eof = buf [3] * 0x1000000L + buf [2] * 0x10000L + buf [1] * 0x100L + buf [0]; + } + else + { + if ( fseek( file, 0, SEEK_END ) ) + return "Couldn't seek in file"; + + *eof = ftell( file ); + } + + return NULL; +} + +const char* get_gzip_eof( const char* path, long* eof ) +{ + FILE* file = fopen( path, "rb" ); + if ( !file ) + return "Couldn't open file"; + const char* error = get_gzip_eof( file, eof ); + fclose( file ); + return error; +} + +// Gzip_File_Reader + +Gzip_File_Reader::Gzip_File_Reader() : file_( NULL ) +{ +} + +Gzip_File_Reader::~Gzip_File_Reader() +{ + close(); +} + +Gzip_File_Reader::error_t Gzip_File_Reader::open( const char* path ) +{ + error_t error = get_gzip_eof( path, &size_ ); + if ( error ) + return error; + + file_ = gzopen( path, "rb" ); + if ( !file_ ) + return "Couldn't open file"; + + return NULL; +} + +long Gzip_File_Reader::size() const +{ + return size_; +} + +long Gzip_File_Reader::read_avail( void* p, long s ) +{ + return (long) gzread( file_, p, s ); +} + +long Gzip_File_Reader::tell() const +{ + return gztell( file_ ); +} + +Gzip_File_Reader::error_t Gzip_File_Reader::seek( long n ) +{ + if ( gzseek( file_, n, SEEK_SET ) < 0 ) + return "Error seeking in file"; + return NULL; +} + +void Gzip_File_Reader::close() +{ + if ( file_ ) + { + gzclose( file_ ); + file_ = NULL; + } +} + +// Gzip_File_Writer + +Gzip_File_Writer::Gzip_File_Writer() : file_( NULL ) +{ +} + +Gzip_File_Writer::~Gzip_File_Writer() +{ + close(); +} + +Gzip_File_Writer::error_t Gzip_File_Writer::open( const char* path ) +{ + file_ = gzopen( path, "wb9" ); + if ( !file_ ) + return "Couldn't open file for writing"; + + return NULL; +} + +Gzip_File_Writer::error_t Gzip_File_Writer::write( const void* p, long s ) +{ + long result = (long) gzwrite( file_ , (void*) p, s ); + if ( result != s ) + return "Couldn't write to file"; + return NULL; +} + +void Gzip_File_Writer::close() +{ + if ( file_ ) + { + gzclose( file_ ); + file_ = NULL; + } +} diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Gzip_File.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Gzip_File.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,43 @@ + +// Gzip file access + +#ifndef GZIP_FILE_H +#define GZIP_FILE_H + +#include "abstract_file.h" + +// Get size of gzipped file data (or size of file if not gzipped). NULL +// on success, otherwise error string. +const char* get_gzip_eof( const char* path, long* eof_out ); + +class Gzip_File_Reader : public File_Reader { + void* file_; + long size_; +public: + Gzip_File_Reader(); + ~Gzip_File_Reader(); + + error_t open( const char* ); + + long size() const; + long read_avail( void*, long ); + + long tell() const; + error_t seek( long ); + + void close(); +}; + +class Gzip_File_Writer : public Data_Writer { + void* file_; +public: + Gzip_File_Writer(); + ~Gzip_File_Writer(); + + error_t open( const char* ); + error_t write( const void*, long ); + void close(); +}; + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Makefile.am Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,43 @@ +lib_LTLIBRARIES = libconsole.la + +libdir = $(plugindir)/$(INPUT_PLUGIN_DIR) + +libconsole_la_SOURCES = \ + Blip_Buffer.cpp \ + Classic_Emu.cpp \ + Dual_Resampler.cpp \ + Fir_Resampler.cpp \ + Gb_Apu.cpp \ + Gb_Cpu.cpp \ + Gb_Oscs.cpp \ + Gbs_Emu.cpp \ + Gym_Emu.cpp \ + Multi_Buffer.cpp \ + Music_Emu.cpp \ + Nes_Apu.cpp \ + Nes_Cpu.cpp \ + Nes_Fme7_Apu.cpp \ + Nes_Namco_Apu.cpp \ + Nes_Oscs.cpp \ + Nes_Vrc6_Apu.cpp \ + Nsfe_Emu.cpp \ + Nsf_Emu.cpp \ + Sms_Apu.cpp \ + Snes_Spc.cpp \ + Spc_Cpu.cpp \ + Spc_Dsp.cpp \ + Spc_Emu.cpp \ + Vgm_Emu.cpp \ + abstract_file.cpp \ + Vfs_File.cpp \ + Gzip_File.cpp \ + Vgm_Emu_Impl.cpp \ + Ym2413_Emu.cpp \ + Ym2612_Emu.cpp \ + Track_Emu.cpp \ + Audacious_Driver.cpp + +libconsole_la_LDFLAGS = $(PLUGIN_LDFLAGS) +libconsole_la_LIBADD = -lz $(GTK_LIBS) $(top_builddir)/libaudacious/libaudacious.la +INCLUDES = $(GTK_CFLAGS) $(ARCH_DEFINES) -I$(top_srcdir)/intl -I$(top_srcdir) + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Multi_Buffer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Multi_Buffer.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,215 @@ + +// Blip_Buffer 0.4.0. http://www.slack.net/~ant/ + +#include "Multi_Buffer.h" + +/* Copyright (C) 2003-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +Multi_Buffer::Multi_Buffer( int spf ) : samples_per_frame_( spf ) +{ + length_ = 0; + sample_rate_ = 0; + channels_changed_count_ = 1; +} + +blargg_err_t Multi_Buffer::set_channel_count( int ) +{ + return blargg_success; +} + +Mono_Buffer::Mono_Buffer() : Multi_Buffer( 1 ) +{ +} + +Mono_Buffer::~Mono_Buffer() +{ +} + +blargg_err_t Mono_Buffer::set_sample_rate( long rate, int msec ) +{ + BLARGG_RETURN_ERR( buf.set_sample_rate( rate, msec ) ); + return Multi_Buffer::set_sample_rate( buf.sample_rate(), buf.length() ); +} + +// Silent_Buffer + +Silent_Buffer::Silent_Buffer() : Multi_Buffer( 1 ) // 0 channels would probably confuse +{ + chan.left = NULL; + chan.center = NULL; + chan.right = NULL; +} + +// Mono_Buffer + +Mono_Buffer::channel_t Mono_Buffer::channel( int ) +{ + channel_t ch; + ch.center = &buf; + ch.left = &buf; + ch.right = &buf; + return ch; +} + +void Mono_Buffer::end_frame( blip_time_t t, bool ) +{ + buf.end_frame( t ); +} + +// Stereo_Buffer + +Stereo_Buffer::Stereo_Buffer() : Multi_Buffer( 2 ) +{ + chan.center = &bufs [0]; + chan.left = &bufs [1]; + chan.right = &bufs [2]; +} + +Stereo_Buffer::~Stereo_Buffer() +{ +} + +blargg_err_t Stereo_Buffer::set_sample_rate( long rate, int msec ) +{ + for ( int i = 0; i < buf_count; i++ ) + BLARGG_RETURN_ERR( bufs [i].set_sample_rate( rate, msec ) ); + return Multi_Buffer::set_sample_rate( bufs [0].sample_rate(), bufs [0].length() ); +} + +void Stereo_Buffer::clock_rate( long rate ) +{ + for ( int i = 0; i < buf_count; i++ ) + bufs [i].clock_rate( rate ); +} + +void Stereo_Buffer::bass_freq( int bass ) +{ + for ( unsigned i = 0; i < buf_count; i++ ) + bufs [i].bass_freq( bass ); +} + +void Stereo_Buffer::clear() +{ + stereo_added = false; + was_stereo = false; + for ( int i = 0; i < buf_count; i++ ) + bufs [i].clear(); +} + +void Stereo_Buffer::end_frame( blip_time_t clock_count, bool stereo ) +{ + for ( unsigned i = 0; i < buf_count; i++ ) + bufs [i].end_frame( clock_count ); + + stereo_added |= stereo; +} + +long Stereo_Buffer::read_samples( blip_sample_t* out, long count ) +{ + require( !(count & 1) ); // count must be even + count = (unsigned) count / 2; + + long avail = bufs [0].samples_avail(); + if ( count > avail ) + count = avail; + if ( count ) + { + if ( stereo_added || was_stereo ) + { + mix_stereo( out, count ); + + bufs [0].remove_samples( count ); + bufs [1].remove_samples( count ); + bufs [2].remove_samples( count ); + } + else + { + mix_mono( out, count ); + + bufs [0].remove_samples( count ); + + bufs [1].remove_silence( count ); + bufs [2].remove_silence( count ); + } + + // to do: this might miss opportunities for optimization + if ( !bufs [0].samples_avail() ) { + was_stereo = stereo_added; + stereo_added = false; + } + } + + return count * 2; +} + +#include BLARGG_ENABLE_OPTIMIZER + +void Stereo_Buffer::mix_stereo( blip_sample_t* out, long count ) +{ + Blip_Reader left; + Blip_Reader right; + Blip_Reader center; + + left.begin( bufs [1] ); + right.begin( bufs [2] ); + int bass = center.begin( bufs [0] ); + + while ( count-- ) + { + int c = center.read(); + long l = c + left.read(); + long r = c + right.read(); + center.next( bass ); + out [0] = l; + out [1] = r; + out += 2; + + if ( (BOOST::int16_t) l != l ) + out [-2] = 0x7FFF - (l >> 24); + + left.next( bass ); + right.next( bass ); + + if ( (BOOST::int16_t) r != r ) + out [-1] = 0x7FFF - (r >> 24); + } + + center.end( bufs [0] ); + right.end( bufs [2] ); + left.end( bufs [1] ); +} + +void Stereo_Buffer::mix_mono( blip_sample_t* out, long count ) +{ + Blip_Reader in; + int bass = in.begin( bufs [0] ); + + while ( count-- ) + { + long s = in.read(); + in.next( bass ); + out [0] = s; + out [1] = s; + out += 2; + + if ( (BOOST::int16_t) s != s ) { + s = 0x7FFF - (s >> 24); + out [-2] = s; + out [-1] = s; + } + } + + in.end( bufs [0] ); +} + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Multi_Buffer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Multi_Buffer.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,175 @@ + +// Multi-channel sound buffer interface, and basic mono and stereo buffers + +// Blip_Buffer 0.4.0 + +#ifndef MULTI_BUFFER_H +#define MULTI_BUFFER_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +// Interface to one or more Blip_Buffers mapped to one or more channels +// consisting of left, center, and right buffers. +class Multi_Buffer { +public: + Multi_Buffer( int samples_per_frame ); + virtual ~Multi_Buffer() { } + + // Set the number of channels available + virtual blargg_err_t set_channel_count( int ); + + // Get indexed channel, from 0 to channel count - 1 + struct channel_t { + Blip_Buffer* center; + Blip_Buffer* left; + Blip_Buffer* right; + }; + virtual channel_t channel( int index ) = 0; + + // See Blip_Buffer.h + virtual blargg_err_t set_sample_rate( long rate, int msec = blip_default_length ) = 0; + virtual void clock_rate( long ) = 0; + virtual void bass_freq( int ) = 0; + virtual void clear() = 0; + long sample_rate() const; + + // Length of buffer, in milliseconds + int length() const; + + // See Blip_Buffer.h. For optimal operation, pass false for 'added_stereo' + // if nothing was added to the left and right buffers of any channel for + // this time frame. + virtual void end_frame( blip_time_t, bool added_stereo = true ) = 0; + + // Number of samples per output frame (1 = mono, 2 = stereo) + int samples_per_frame() const; + + // Count of changes to channel configuration. Incremented whenever + // a change is made to any of the Blip_Buffers for any channel. + unsigned channels_changed_count() { return channels_changed_count_; } + + // See Blip_Buffer.h + virtual long read_samples( blip_sample_t*, long ) = 0; + virtual long samples_avail() const = 0; + +protected: + void channels_changed() { channels_changed_count_++; } +private: + // noncopyable + Multi_Buffer( const Multi_Buffer& ); + Multi_Buffer& operator = ( const Multi_Buffer& ); + + unsigned channels_changed_count_; + long sample_rate_; + int length_; + int const samples_per_frame_; +}; + +// Uses a single buffer and outputs mono samples. +class Mono_Buffer : public Multi_Buffer { + Blip_Buffer buf; +public: + Mono_Buffer(); + ~Mono_Buffer(); + + // Buffer used for all channels + Blip_Buffer* center() { return &buf; } + + // See Multi_Buffer + blargg_err_t set_sample_rate( long rate, int msec = blip_default_length ); + void clock_rate( long ); + void bass_freq( int ); + void clear(); + channel_t channel( int ); + void end_frame( blip_time_t, bool unused = true ); + long samples_avail() const; + long read_samples( blip_sample_t*, long ); +}; + +// Uses three buffers (one for center) and outputs stereo sample pairs. +class Stereo_Buffer : public Multi_Buffer { +public: + Stereo_Buffer(); + ~Stereo_Buffer(); + + // Buffers used for all channels + Blip_Buffer* center() { return &bufs [0]; } + Blip_Buffer* left() { return &bufs [1]; } + Blip_Buffer* right() { return &bufs [2]; } + + // See Multi_Buffer + blargg_err_t set_sample_rate( long, int msec = blip_default_length ); + void clock_rate( long ); + void bass_freq( int ); + void clear(); + channel_t channel( int index ); + void end_frame( blip_time_t, bool added_stereo = true ); + + long samples_avail() const; + long read_samples( blip_sample_t*, long ); + +private: + enum { buf_count = 3 }; + Blip_Buffer bufs [buf_count]; + channel_t chan; + bool stereo_added; + bool was_stereo; + + void mix_stereo( blip_sample_t*, long ); + void mix_mono( blip_sample_t*, long ); +}; + +// Silent_Buffer generates no samples, useful where no sound is wanted +class Silent_Buffer : public Multi_Buffer { + channel_t chan; +public: + Silent_Buffer(); + + blargg_err_t set_sample_rate( long rate, int msec = blip_default_length ); + void clock_rate( long ) { } + void bass_freq( int ) { } + void clear() { } + channel_t channel( int ) { return chan; } + void end_frame( blip_time_t, bool unused = true ) { } + long samples_avail() const { return 0; } + long read_samples( blip_sample_t*, long ) { return 0; } +}; + + +// End of public interface + +inline blargg_err_t Multi_Buffer::set_sample_rate( long rate, int msec ) +{ + sample_rate_ = rate; + length_ = msec; + return blargg_success; +} + +inline blargg_err_t Silent_Buffer::set_sample_rate( long rate, int msec ) +{ + return Multi_Buffer::set_sample_rate( rate, msec ); +} + +inline int Multi_Buffer::samples_per_frame() const { return samples_per_frame_; } + +inline long Stereo_Buffer::samples_avail() const { return bufs [0].samples_avail() * 2; } + +inline Stereo_Buffer::channel_t Stereo_Buffer::channel( int ) { return chan; } + +inline long Multi_Buffer::sample_rate() const { return sample_rate_; } + +inline int Multi_Buffer::length() const { return length_; } + +inline void Mono_Buffer::clock_rate( long rate ) { buf.clock_rate( rate ); } + +inline void Mono_Buffer::clear() { buf.clear(); } + +inline void Mono_Buffer::bass_freq( int freq ) { buf.bass_freq( freq ); } + +inline long Mono_Buffer::read_samples( blip_sample_t* p, long s ) { return buf.read_samples( p, s ); } + +inline long Mono_Buffer::samples_avail() const { return buf.samples_avail(); } + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Music_Emu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Music_Emu.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,84 @@ + +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ + +#include "Music_Emu.h" + +#include + +/* Copyright (C) 2003-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +Music_Emu::equalizer_t const Music_Emu::tv_eq = { -8.0, 180 }; + +Music_Emu::Music_Emu() +{ + equalizer_.treble = -1.0; + equalizer_.bass = 60; + sample_rate_ = 0; + voice_count_ = 0; + mute_mask_ = 0; + track_count_ = 0; + error_count_ = 0; + track_ended_ = false; +} + +Music_Emu::~Music_Emu() +{ +} + +blargg_err_t Music_Emu::load_file( const char* path ) +{ + Std_File_Reader in; + BLARGG_RETURN_ERR( in.open( path ) ); + return load( in ); +} + +void Music_Emu::skip( long count ) +{ + const int buf_size = 1024; + sample_t buf [buf_size]; + + const long threshold = 30000; + if ( count > threshold ) + { + int saved_mute = mute_mask_; + mute_voices( ~0 ); + + while ( count > threshold / 2 ) + { + play( buf_size, buf ); + count -= buf_size; + } + + mute_voices( saved_mute ); + } + + while ( count ) + { + int n = buf_size; + if ( n > count ) + n = count; + count -= n; + play( n, buf ); + } +} + +const char** Music_Emu::voice_names() const +{ + static const char* names [] = { + "Voice 1", "Voice 2", "Voice 3", "Voice 4", + "Voice 5", "Voice 6", "Voice 7", "Voice 8" + }; + return names; +} + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Music_Emu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Music_Emu.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,137 @@ + +// Game music emulator interface base class + +// Game_Music_Emu 0.3.0 + +#ifndef MUSIC_EMU_H +#define MUSIC_EMU_H + +#include "blargg_common.h" +#include "abstract_file.h" +class Multi_Buffer; + +class Music_Emu { +public: + + // Initialize emulator with specified sample rate. Currently should only be + // called once. + virtual blargg_err_t set_sample_rate( long sample_rate ) = 0; + + // Load music file + blargg_err_t load_file( const char* path ); + + // Start a track, where 0 is the first track. Might un-mute any muted voices. + virtual void start_track( int ) = 0; + + // Generate 'count' samples info 'buf'. Output is in stereo unless using custom + // buffer that generates mono output. + typedef short sample_t; + virtual void play( long count, sample_t* buf ) = 0; + +// Additional optional features + + // Request use of custom multichannel buffer. Only supported by "classic" emulators; + // on others this has no effect. Should be called only once *before* set_sample_rate(). + virtual void set_buffer( Multi_Buffer* ) { } + + // Load music file data from custom source + virtual blargg_err_t load( Data_Reader& ) = 0; + + // Sample rate sound is generated at + long sample_rate() const; + + // Number of voices used by currently loaded file + int voice_count() const; + + // Names of voices + virtual const char** voice_names() const; + + // Mute voice n if bit n (1 << n) of mask is set + virtual void mute_voices( int mask ); + + // Frequency equalizer parameters (see notes.txt) + struct equalizer_t { + double treble; // -50.0 = muffled, 0 = flat, +5.0 = extra-crisp + long bass; // 1 = full bass, 90 = average, 16000 = almost no bass + }; + + // Current frequency equalizater parameters + const equalizer_t& equalizer() const; + + // Set frequency equalizer parameters + virtual void set_equalizer( equalizer_t const& ); + + // Equalizer settings for TV speaker + static equalizer_t const tv_eq; + + // Number of tracks. Zero if file hasn't been loaded yet. + int track_count() const; + + // Skip 'count' samples + virtual void skip( long count ); + + // True if a track was started and has since ended. Currently only logged + // format tracks (VGM, GYM) without loop points have an ending. + bool track_ended() const; + + // Number of errors encountered while playing track due to undefined CPU + // instructions in emulated formats and undefined stream events in + // logged formats. + int error_count() const; + + Music_Emu(); + virtual ~Music_Emu(); + +protected: + typedef BOOST::uint8_t byte; + void set_voice_count( int n ) { voice_count_ = n; } + void set_track_count( int n ) { track_count_ = n; } + void set_track_ended( bool b = true ) { track_ended_ = b; } + void log_error() { error_count_++; } + void remute_voices(); +private: + // noncopyable + Music_Emu( const Music_Emu& ); + Music_Emu& operator = ( const Music_Emu& ); + + equalizer_t equalizer_; + long sample_rate_; + int voice_count_; + int mute_mask_; + int track_count_; + int error_count_; + bool track_ended_; +}; + +// Deprecated +typedef Data_Reader Emu_Reader; +typedef Std_File_Reader Emu_Std_Reader; +typedef Mem_File_Reader Emu_Mem_Reader; + +inline int Music_Emu::error_count() const { return error_count_; } +inline int Music_Emu::voice_count() const { return voice_count_; } +inline int Music_Emu::track_count() const { return track_count_; } +inline bool Music_Emu::track_ended() const { return track_ended_; } +inline void Music_Emu::mute_voices( int mask ) { mute_mask_ = mask; } +inline void Music_Emu::remute_voices() { mute_voices( mute_mask_ ); } +inline const Music_Emu::equalizer_t& Music_Emu::equalizer() const { return equalizer_; } +inline void Music_Emu::set_equalizer( const equalizer_t& eq ) { equalizer_ = eq; } +inline long Music_Emu::sample_rate() const { return sample_rate_; } + +inline blargg_err_t Music_Emu::set_sample_rate( long r ) +{ + assert( !sample_rate_ ); // sample rate can't be changed once set + sample_rate_ = r; + return blargg_success; +} + +inline void Music_Emu::start_track( int track ) +{ + assert( (unsigned) track <= (unsigned) track_count() ); + assert( sample_rate_ ); // set_sample_rate() must have been called first + track_ended_ = false; + error_count_ = 0; +} + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Nes_Apu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nes_Apu.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,383 @@ + +// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/ + +#include "Nes_Apu.h" + +/* Copyright (C) 2003-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +int const amp_range = 15; + +Nes_Apu::Nes_Apu() : + square1( &square_synth ), + square2( &square_synth ) +{ + dmc.apu = this; + dmc.rom_reader = NULL; + irq_notifier_ = NULL; + + oscs [0] = &square1; + oscs [1] = &square2; + oscs [2] = ▵ + oscs [3] = &noise; + oscs [4] = &dmc; + + output( NULL ); + volume( 1.0 ); + reset( false ); +} + +Nes_Apu::~Nes_Apu() +{ +} + +void Nes_Apu::treble_eq( const blip_eq_t& eq ) +{ + square_synth.treble_eq( eq ); + triangle.synth.treble_eq( eq ); + noise.synth.treble_eq( eq ); + dmc.synth.treble_eq( eq ); +} + +void Nes_Apu::enable_nonlinear( double v ) +{ + dmc.nonlinear = true; + square_synth.volume( 1.3 * 0.25751258 / 0.742467605 * 0.25 / amp_range * v ); + + const double tnd = 0.48 / 202 * nonlinear_tnd_gain(); + triangle.synth.volume( 3.0 * tnd ); + noise.synth.volume( 2.0 * tnd ); + dmc.synth.volume( tnd ); + + square1 .last_amp = 0; + square2 .last_amp = 0; + triangle.last_amp = 0; + noise .last_amp = 0; + dmc .last_amp = 0; +} + +void Nes_Apu::volume( double v ) +{ + dmc.nonlinear = false; + square_synth.volume( 0.1128 / amp_range * v ); + triangle.synth.volume( 0.12765 / amp_range * v ); + noise.synth.volume( 0.0741 / amp_range * v ); + dmc.synth.volume( 0.42545 / 127 * v ); +} + +void Nes_Apu::output( Blip_Buffer* buffer ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, buffer ); +} + +void Nes_Apu::reset( bool pal_mode, int initial_dmc_dac ) +{ + // to do: time pal frame periods exactly + frame_period = pal_mode ? 8314 : 7458; + dmc.pal_mode = pal_mode; + + square1.reset(); + square2.reset(); + triangle.reset(); + noise.reset(); + dmc.reset(); + + last_time = 0; + last_dmc_time = 0; + osc_enables = 0; + irq_flag = false; + earliest_irq_ = no_irq; + frame_delay = 1; + write_register( 0, 0x4017, 0x00 ); + write_register( 0, 0x4015, 0x00 ); + + for ( nes_addr_t addr = start_addr; addr <= 0x4013; addr++ ) + write_register( 0, addr, (addr & 3) ? 0x00 : 0x10 ); + + dmc.dac = initial_dmc_dac; + //if ( !dmc.nonlinear ) // to do: remove? + // dmc.last_amp = initial_dmc_dac; // prevent output transition +} + +void Nes_Apu::irq_changed() +{ + nes_time_t new_irq = dmc.next_irq; + if ( dmc.irq_flag | irq_flag ) { + new_irq = 0; + } + else if ( new_irq > next_irq ) { + new_irq = next_irq; + } + + if ( new_irq != earliest_irq_ ) { + earliest_irq_ = new_irq; + if ( irq_notifier_ ) + irq_notifier_( irq_data ); + } +} + +// frames + +void Nes_Apu::run_until( nes_time_t end_time ) +{ + require( end_time >= last_dmc_time ); + if ( end_time > next_dmc_read_time() ) + { + nes_time_t start = last_dmc_time; + last_dmc_time = end_time; + dmc.run( start, end_time ); + } +} + +void Nes_Apu::run_until_( nes_time_t end_time ) +{ + require( end_time >= last_time ); + + if ( end_time == last_time ) + return; + + if ( last_dmc_time < end_time ) + { + nes_time_t start = last_dmc_time; + last_dmc_time = end_time; + dmc.run( start, end_time ); + } + + while ( true ) + { + // earlier of next frame time or end time + nes_time_t time = last_time + frame_delay; + if ( time > end_time ) + time = end_time; + frame_delay -= time - last_time; + + // run oscs to present + square1.run( last_time, time ); + square2.run( last_time, time ); + triangle.run( last_time, time ); + noise.run( last_time, time ); + last_time = time; + + if ( time == end_time ) + break; // no more frames to run + + // take frame-specific actions + frame_delay = frame_period; + switch ( frame++ ) + { + case 0: + if ( !(frame_mode & 0xc0) ) { + next_irq = time + frame_period * 4 + 1; + irq_flag = true; + } + // fall through + case 2: + // clock length and sweep on frames 0 and 2 + square1.clock_length( 0x20 ); + square2.clock_length( 0x20 ); + noise.clock_length( 0x20 ); + triangle.clock_length( 0x80 ); // different bit for halt flag on triangle + + square1.clock_sweep( -1 ); + square2.clock_sweep( 0 ); + break; + + case 1: + // frame 1 is slightly shorter + frame_delay -= 2; + break; + + case 3: + frame = 0; + + // frame 3 is almost twice as long in mode 1 + if ( frame_mode & 0x80 ) + frame_delay += frame_period - 6; + break; + } + + // clock envelopes and linear counter every frame + triangle.clock_linear_counter(); + square1.clock_envelope(); + square2.clock_envelope(); + noise.clock_envelope(); + } +} + +// to do: remove +static long abs_time; + +template +inline void zero_apu_osc( T* osc, nes_time_t time ) +{ + Blip_Buffer* output = osc->output; + int last_amp = osc->last_amp; + osc->last_amp = 0; + if ( output && last_amp ) + osc->synth.offset( time, -last_amp, output ); +} + +void Nes_Apu::end_frame( nes_time_t end_time ) +{ + if ( end_time > last_time ) + run_until_( end_time ); + + abs_time += end_time; + + if ( dmc.nonlinear ) + { + zero_apu_osc( &square1, last_time ); + zero_apu_osc( &square2, last_time ); + zero_apu_osc( &triangle, last_time ); + zero_apu_osc( &noise, last_time ); + zero_apu_osc( &dmc, last_time ); + } + + // make times relative to new frame + last_time -= end_time; + require( last_time >= 0 ); + + last_dmc_time -= end_time; + require( last_dmc_time >= 0 ); + + if ( next_irq != no_irq ) { + next_irq -= end_time; + assert( next_irq >= 0 ); + } + if ( dmc.next_irq != no_irq ) { + dmc.next_irq -= end_time; + assert( dmc.next_irq >= 0 ); + } + if ( earliest_irq_ != no_irq ) { + earliest_irq_ -= end_time; + if ( earliest_irq_ < 0 ) + earliest_irq_ = 0; + } +} + +// registers + +static const unsigned char length_table [0x20] = { + 0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, + 0xA0, 0x08, 0x3C, 0x0A, 0x0E, 0x0C, 0x1A, 0x0E, + 0x0C, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16, + 0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E +}; + +void Nes_Apu::write_register( nes_time_t time, nes_addr_t addr, int data ) +{ + require( addr > 0x20 ); // addr must be actual address (i.e. 0x40xx) + require( (unsigned) data <= 0xff ); + + // Ignore addresses outside range + if ( addr < start_addr || end_addr < addr ) + return; + + run_until_( time ); + + if ( addr < 0x4014 ) + { + // Write to channel + int osc_index = (addr - start_addr) >> 2; + Nes_Osc* osc = oscs [osc_index]; + + int reg = addr & 3; + osc->regs [reg] = data; + osc->reg_written [reg] = true; + + if ( osc_index == 4 ) + { + // handle DMC specially + dmc.write_register( reg, data ); + } + else if ( reg == 3 ) + { + // load length counter + if ( (osc_enables >> osc_index) & 1 ) + osc->length_counter = length_table [(data >> 3) & 0x1f]; + + // reset square phase + if ( osc_index < 2 ) + ((Nes_Square*) osc)->phase = Nes_Square::phase_range - 1; + } + } + else if ( addr == 0x4015 ) + { + // Channel enables + for ( int i = osc_count; i--; ) + if ( !((data >> i) & 1) ) + oscs [i]->length_counter = 0; + + bool recalc_irq = dmc.irq_flag; + dmc.irq_flag = false; + + int old_enables = osc_enables; + osc_enables = data; + if ( !(data & 0x10) ) { + dmc.next_irq = no_irq; + recalc_irq = true; + } + else if ( !(old_enables & 0x10) ) { + dmc.start(); // dmc just enabled + } + + if ( recalc_irq ) + irq_changed(); + } + else if ( addr == 0x4017 ) + { + // Frame mode + frame_mode = data; + + bool irq_enabled = !(data & 0x40); + irq_flag &= irq_enabled; + next_irq = no_irq; + + // mode 1 + frame_delay = (frame_delay & 1); + frame = 0; + + if ( !(data & 0x80) ) + { + // mode 0 + frame = 1; + frame_delay += frame_period; + if ( irq_enabled ) + next_irq = time + frame_delay + frame_period * 3; + } + + irq_changed(); + } +} + +int Nes_Apu::read_status( nes_time_t time ) +{ + run_until_( time - 1 ); + + int result = (dmc.irq_flag << 7) | (irq_flag << 6); + + for ( int i = 0; i < osc_count; i++ ) + if ( oscs [i]->length_counter ) + result |= 1 << i; + + run_until_( time ); + + if ( irq_flag ) { + irq_flag = false; + irq_changed(); + } + + return result; +} + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Nes_Apu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nes_Apu.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,174 @@ + +// NES 2A03 APU sound chip emulator + +// Nes_Snd_Emu 0.1.7 + +#ifndef NES_APU_H +#define NES_APU_H + +typedef long nes_time_t; // CPU clock cycle count +typedef unsigned nes_addr_t; // 16-bit memory address + +#include "Nes_Oscs.h" + +struct apu_snapshot_t; +class Nonlinear_Buffer; + +class Nes_Apu { +public: + Nes_Apu(); + ~Nes_Apu(); + + // Set buffer to generate all sound into, or disable sound if NULL + void output( Blip_Buffer* ); + + // Set memory reader callback used by DMC oscillator to fetch samples. + // When callback is invoked, 'user_data' is passed unchanged as the + // first parameter. + void dmc_reader( int (*callback)( void* user_data, nes_addr_t ), void* user_data = NULL ); + + // All time values are the number of CPU clock cycles relative to the + // beginning of the current time frame. Before resetting the CPU clock + // count, call end_frame( last_cpu_time ). + + // Write to register (0x4000-0x4017, except 0x4014 and 0x4016) + enum { start_addr = 0x4000 }; + enum { end_addr = 0x4017 }; + void write_register( nes_time_t, nes_addr_t, int data ); + + // Read from status register at 0x4015 + enum { status_addr = 0x4015 }; + int read_status( nes_time_t ); + + // Run all oscillators up to specified time, end current time frame, then + // start a new time frame at time 0. Time frames have no effect on emulation + // and each can be whatever length is convenient. + void end_frame( nes_time_t ); + +// Additional optional features (can be ignored without any problem) + + // Reset internal frame counter, registers, and all oscillators. + // Use PAL timing if pal_timing is true, otherwise use NTSC timing. + // Set the DMC oscillator's initial DAC value to initial_dmc_dac without + // any audible click. + void reset( bool pal_timing = false, int initial_dmc_dac = 0 ); + + // Save/load snapshot of exact emulation state + void save_snapshot( apu_snapshot_t* out ) const; + void load_snapshot( apu_snapshot_t const& ); + + // Set overall volume (default is 1.0) + void volume( double ); + + // Set treble equalization (see notes.txt) + void treble_eq( const blip_eq_t& ); + + // Set sound output of specific oscillator to buffer. If buffer is NULL, + // the specified oscillator is muted and emulation accuracy is reduced. + // The oscillators are indexed as follows: 0) Square 1, 1) Square 2, + // 2) Triangle, 3) Noise, 4) DMC. + enum { osc_count = 5 }; + void osc_output( int index, Blip_Buffer* buffer ); + + // Set IRQ time callback that is invoked when the time of earliest IRQ + // may have changed, or NULL to disable. When callback is invoked, + // 'user_data' is passed unchanged as the first parameter. + void irq_notifier( void (*callback)( void* user_data ), void* user_data = NULL ); + + // Get time that APU-generated IRQ will occur if no further register reads + // or writes occur. If IRQ is already pending, returns irq_waiting. If no + // IRQ will occur, returns no_irq. + enum { no_irq = LONG_MAX / 2 + 1 }; + enum { irq_waiting = 0 }; + nes_time_t earliest_irq( nes_time_t ) const; + + // Count number of DMC reads that would occur if 'run_until( t )' were executed. + // If last_read is not NULL, set *last_read to the earliest time that + // 'count_dmc_reads( time )' would result in the same result. + int count_dmc_reads( nes_time_t t, nes_time_t* last_read = NULL ) const; + + // Time when next DMC memory read will occur + nes_time_t next_dmc_read_time() const; + + // Run DMC until specified time, so that any DMC memory reads can be + // accounted for (i.e. inserting CPU wait states). + void run_until( nes_time_t ); + +// End of public interface. +private: + friend class Nes_Nonlinearizer; + void enable_nonlinear( double volume ); + static double nonlinear_tnd_gain() { return 0.75; } +private: + friend struct Nes_Dmc; + + // noncopyable + Nes_Apu( const Nes_Apu& ); + Nes_Apu& operator = ( const Nes_Apu& ); + + Nes_Osc* oscs [osc_count]; + Nes_Square square1; + Nes_Square square2; + Nes_Noise noise; + Nes_Triangle triangle; + Nes_Dmc dmc; + + nes_time_t last_time; // has been run until this time in current frame + nes_time_t last_dmc_time; + nes_time_t earliest_irq_; + nes_time_t next_irq; + int frame_period; + int frame_delay; // cycles until frame counter runs next + int frame; // current frame (0-3) + int osc_enables; + int frame_mode; + bool irq_flag; + void (*irq_notifier_)( void* user_data ); + void* irq_data; + Nes_Square::Synth square_synth; // shared by squares + + void irq_changed(); + void state_restored(); + void run_until_( nes_time_t ); +}; + +inline void Nes_Apu::osc_output( int osc, Blip_Buffer* buf ) +{ + assert( (unsigned) osc < osc_count ); + oscs [osc]->output = buf; +} + +inline nes_time_t Nes_Apu::earliest_irq( nes_time_t ) const +{ + return earliest_irq_; +} + +inline void Nes_Apu::dmc_reader( int (*func)( void*, nes_addr_t ), void* user_data ) +{ + dmc.rom_reader_data = user_data; + dmc.rom_reader = func; +} + +inline void Nes_Apu::irq_notifier( void (*func)( void* user_data ), void* user_data ) +{ + irq_notifier_ = func; + irq_data = user_data; +} + +inline int Nes_Apu::count_dmc_reads( nes_time_t time, nes_time_t* last_read ) const +{ + return dmc.count_reads( time, last_read ); +} + +inline nes_time_t Nes_Dmc::next_read_time() const +{ + if ( length_counter == 0 ) + return Nes_Apu::no_irq; // not reading + + return apu->last_dmc_time + delay + long (bits_remain - 1) * period; +} + +inline nes_time_t Nes_Apu::next_dmc_read_time() const { return dmc.next_read_time(); } + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Nes_Cpu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nes_Cpu.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,950 @@ + +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/nes-emu/ + +#include "Nes_Cpu.h" + +#include +#include + +#include "blargg_endian.h" + +/* Copyright (C) 2003-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +#if BLARGG_NONPORTABLE + #define PAGE_OFFSET( addr ) (addr) +#else + #define PAGE_OFFSET( addr ) ((addr) & (page_size - 1)) +#endif + +Nes_Cpu::Nes_Cpu() +{ + callback_data = NULL; + reset(); +} + +inline void Nes_Cpu::set_code_page( int i, uint8_t const* p ) +{ + code_map [i] = p - PAGE_OFFSET( i * page_size ); +} + +void Nes_Cpu::reset( const void* unmapped_code_page, reader_t read, writer_t write ) +{ + r.status = 0; + r.sp = 0; + r.pc = 0; + r.a = 0; + r.x = 0; + r.y = 0; + + clock_count = 0; + base_time = 0; + clock_limit = 0; + irq_time_ = LONG_MAX / 2 + 1; + end_time_ = LONG_MAX / 2 + 1; + + for ( int i = 0; i < page_count + 1; i++ ) + { + set_code_page( i, (uint8_t*) unmapped_code_page ); + data_reader [i] = read; + data_writer [i] = write; + } +} + +void Nes_Cpu::map_code( nes_addr_t start, unsigned long size, const void* data ) +{ + // address range must begin and end on page boundaries + require( start % page_size == 0 ); + require( size % page_size == 0 ); + require( start + size <= 0x10000 ); + + unsigned first_page = start / page_size; + for ( unsigned i = size / page_size; i--; ) + set_code_page( first_page + i, (uint8_t*) data + i * page_size ); +} + +void Nes_Cpu::set_reader( nes_addr_t start, unsigned long size, reader_t func ) +{ + // address range must begin and end on page boundaries + require( start % page_size == 0 ); + require( size % page_size == 0 ); + require( start + size <= 0x10000 + page_size ); + + unsigned first_page = start / page_size; + for ( unsigned i = size / page_size; i--; ) + data_reader [first_page + i] = func; +} + +void Nes_Cpu::set_writer( nes_addr_t start, unsigned long size, writer_t func ) +{ + // address range must begin and end on page boundaries + require( start % page_size == 0 ); + require( size % page_size == 0 ); + require( start + size <= 0x10000 + page_size ); + + unsigned first_page = start / page_size; + for ( unsigned i = size / page_size; i--; ) + data_writer [first_page + i] = func; +} + +// Note: 'addr' is evaulated more than once in the following macros, so it +// must not contain side-effects. + +#define READ( addr ) (data_reader [(addr) >> page_bits]( callback_data, addr )) +#define WRITE( addr, data ) (data_writer [(addr) >> page_bits]( callback_data, addr, data )) + +#define READ_LOW( addr ) (low_mem [int (addr)]) +#define WRITE_LOW( addr, data ) (void) (READ_LOW( addr ) = (data)) + +#define READ_PROG( addr ) (code_map [(addr) >> page_bits] [PAGE_OFFSET( addr )]) +#define READ_PROG16( addr ) GET_LE16( &READ_PROG( addr ) ) + +#define SET_SP( v ) (sp = ((v) + 1) | 0x100) +#define GET_SP() ((sp - 1) & 0xff) + +#define PUSH( v ) ((sp = (sp - 1) | 0x100), WRITE_LOW( sp, v )) + +int Nes_Cpu::read( nes_addr_t addr ) +{ + return READ( addr ); +} + +void Nes_Cpu::write( nes_addr_t addr, int value ) +{ + WRITE( addr, value ); +} + +#ifndef NES_CPU_GLUE_ONLY + +static const unsigned char clock_table [256] = { +// 0 1 2 3 4 5 6 7 8 9 A B C D E F + 7,6,2,8,3,3,5,5,3,2,2,2,4,4,6,6,// 0 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 1 + 6,6,2,8,3,3,5,5,4,2,2,2,4,4,6,6,// 2 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 3 + 6,6,2,8,3,3,5,5,3,2,2,2,3,4,6,6,// 4 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 5 + 6,6,2,8,3,3,5,5,4,2,2,2,5,4,6,6,// 6 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 7 + 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,// 8 + 3,6,2,6,4,4,4,4,2,5,2,5,5,5,5,5,// 9 + 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,// A + 3,5,2,5,4,4,4,4,2,4,2,4,4,4,4,4,// B + 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,// C + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// D + 2,6,3,8,3,3,5,5,2,2,2,2,4,4,6,6,// E + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7 // F +}; + +#include BLARGG_ENABLE_OPTIMIZER + +Nes_Cpu::result_t Nes_Cpu::run( nes_time_t end ) +{ + set_end_time( end ); + + volatile result_t result = result_cycles; + +#if BLARGG_CPU_POWERPC + // cache commonly-used values in registers + long clock_count = this->clock_count; + writer_t* const data_writer = this->data_writer; + reader_t* const data_reader = this->data_reader; + uint8_t* const low_mem = this->low_mem; +#endif + + // registers + unsigned pc = r.pc; + int sp; + SET_SP( r.sp ); + int a = r.a; + int x = r.x; + int y = r.y; + + // status flags + + const int st_n = 0x80; + const int st_v = 0x40; + const int st_r = 0x20; + const int st_b = 0x10; + const int st_d = 0x08; + const int st_i = 0x04; + const int st_z = 0x02; + const int st_c = 0x01; + + #define IS_NEG (nz & 0x880) + + #define CALC_STATUS( out ) do { \ + out = status & (st_v | st_d | st_i); \ + out |= (c >> 8) & st_c; \ + if ( IS_NEG ) out |= st_n; \ + if ( !(nz & 0xFF) ) out |= st_z; \ + } while ( false ) + + #define SET_STATUS( in ) do { \ + status = in & (st_v | st_d | st_i); \ + c = in << 8; \ + nz = (in << 4) & 0x800; \ + nz |= ~in & st_z; \ + } while ( false ) + + int status; + int c; // carry set if (c & 0x100) != 0 + int nz; // Z set if (nz & 0xff) == 0, N set if (nz & 0x880) != 0 + { + int temp = r.status; + SET_STATUS( temp ); + } + + goto loop; +dec_clock_loop: + clock_count--; +loop: + + assert( unsigned (GET_SP()) < 0x100 ); + assert( unsigned (a) < 0x100 ); + assert( unsigned (x) < 0x100 ); + assert( unsigned (y) < 0x100 ); + + uint8_t const* page = code_map [pc >> page_bits]; + unsigned opcode = page [PAGE_OFFSET( pc )]; + unsigned data = page [PAGE_OFFSET( pc ) + 1]; + pc++; + + // page crossing + //check( opcode == 0x60 || &READ_PROG( pc ) == &page [PAGE_OFFSET( pc )] ); + + if ( clock_count >= clock_limit ) + goto stop; + + clock_count += clock_table [opcode]; + + #if BLARGG_CPU_POWERPC + this->clock_count = clock_count; + #endif + + switch ( opcode ) + { + +// Macros + +#define ADD_PAGE (pc++, data += 0x100 * READ_PROG( pc )); +#define GET_ADDR() READ_PROG16( pc ) + +#define HANDLE_PAGE_CROSSING( lsb ) clock_count += (lsb) >> 8; + +#define INC_DEC_XY( reg, n ) reg = uint8_t (nz = reg + n); goto loop; + +#define IND_Y { \ + int temp = READ_LOW( data ) + y; \ + data = temp + 0x100 * READ_LOW( uint8_t (data + 1) ); \ + HANDLE_PAGE_CROSSING( temp ); \ + } + +#define IND_X { \ + int temp = data + x; \ + data = 0x100 * READ_LOW( uint8_t (temp + 1) ) + READ_LOW( uint8_t (temp) ); \ + } + +#define ARITH_ADDR_MODES( op ) \ +case op - 0x04: /* (ind,x) */ \ + IND_X \ + goto ptr##op; \ +case op + 0x0C: /* (ind),y */ \ + IND_Y \ + goto ptr##op; \ +case op + 0x10: /* zp,X */ \ + data = uint8_t (data + x); \ +case op + 0x00: /* zp */ \ + data = READ_LOW( data ); \ + goto imm##op; \ +case op + 0x14: /* abs,Y */ \ + data += y; \ + goto ind##op; \ +case op + 0x18: /* abs,X */ \ + data += x; \ +ind##op: \ + HANDLE_PAGE_CROSSING( data ); \ +case op + 0x08: /* abs */ \ + ADD_PAGE \ +ptr##op: \ + data = READ( data ); \ +case op + 0x04: /* imm */ \ +imm##op: \ + +#define BRANCH( cond ) \ +{ \ + pc++; \ + int offset = (BOOST::int8_t) data; \ + int extra_clock = (pc & 0xff) + offset; \ + if ( !(cond) ) goto dec_clock_loop; \ + pc += offset; \ + clock_count += (extra_clock >> 8) & 1; \ + goto loop; \ +} + +// Often-Used + + case 0xB5: // LDA zp,x + data = uint8_t (data + x); + case 0xA5: // LDA zp + a = nz = READ_LOW( data ); + pc++; + goto loop; + + case 0xD0: // BNE + BRANCH( (uint8_t) nz ); + + case 0x20: { // JSR + int temp = pc + 1; + pc = READ_PROG16( pc ); + WRITE_LOW( 0x100 | (sp - 1), temp >> 8 ); + sp = (sp - 2) | 0x100; + WRITE_LOW( sp, temp ); + goto loop; + } + + case 0x4C: // JMP abs + pc = READ_PROG16( pc ); + goto loop; + + case 0xE8: INC_DEC_XY( x, 1 ) // INX + + case 0x10: // BPL + BRANCH( !IS_NEG ) + + ARITH_ADDR_MODES( 0xC5 ) // CMP + nz = a - data; + pc++; + c = ~nz; + nz &= 0xff; + goto loop; + + case 0x30: // BMI + BRANCH( IS_NEG ) + + case 0xF0: // BEQ + BRANCH( !(uint8_t) nz ); + + case 0x95: // STA zp,x + data = uint8_t (data + x); + case 0x85: // STA zp + pc++; + WRITE_LOW( data, a ); + goto loop; + + case 0xC8: INC_DEC_XY( y, 1 ) // INY + + case 0xA8: // TAY + y = a; + case 0x98: // TYA + a = nz = y; + goto loop; + + case 0xB9: // LDA abs,Y + data += y; + goto lda_ind_common; + + case 0xBD: // LDA abs,X + data += x; + lda_ind_common: + HANDLE_PAGE_CROSSING( data ); + case 0xAD: // LDA abs + ADD_PAGE + lda_ptr: + a = nz = READ( data ); + pc++; + goto loop; + + case 0x60: // RTS + pc = 1 + READ_LOW( sp ); + pc += READ_LOW( 0x100 | (sp - 0xff) ) * 0x100; + sp = (sp - 0xfe) | 0x100; + goto loop; + + case 0x99: // STA abs,Y + data += y; + goto sta_ind_common; + + case 0x9D: // STA abs,X + data += x; + sta_ind_common: + HANDLE_PAGE_CROSSING( data ); + case 0x8D: // STA abs + ADD_PAGE + sta_ptr: + pc++; + WRITE( data, a ); + goto loop; + + case 0xA9: // LDA #imm + pc++; + a = data; + nz = data; + goto loop; + +// Branch + + case 0x50: // BVC + BRANCH( !(status & st_v) ) + + case 0x70: // BVS + BRANCH( status & st_v ) + + case 0xB0: // BCS + BRANCH( c & 0x100 ) + + case 0x90: // BCC + BRANCH( !(c & 0x100) ) + +// Load/store + + case 0x94: // STY zp,x + data = uint8_t (data + x); + case 0x84: // STY zp + pc++; + WRITE_LOW( data, y ); + goto loop; + + case 0x96: // STX zp,y + data = uint8_t (data + y); + case 0x86: // STX zp + pc++; + WRITE_LOW( data, x ); + goto loop; + + case 0xB6: // LDX zp,y + data = uint8_t (data + y); + case 0xA6: // LDX zp + data = READ_LOW( data ); + case 0xA2: // LDX #imm + pc++; + x = data; + nz = data; + goto loop; + + case 0xB4: // LDY zp,x + data = uint8_t (data + x); + case 0xA4: // LDY zp + data = READ_LOW( data ); + case 0xA0: // LDY #imm + pc++; + y = data; + nz = data; + goto loop; + + case 0xB1: // LDA (ind),Y + IND_Y + goto lda_ptr; + + case 0xA1: // LDA (ind,X) + IND_X + goto lda_ptr; + + case 0x91: // STA (ind),Y + IND_Y + goto sta_ptr; + + case 0x81: // STA (ind,X) + IND_X + goto sta_ptr; + + case 0xBC: // LDY abs,X + data += x; + HANDLE_PAGE_CROSSING( data ); + case 0xAC:{// LDY abs + pc++; + unsigned addr = data + 0x100 * READ_PROG( pc ); + pc++; + y = nz = READ( addr ); + goto loop; + } + + case 0xBE: // LDX abs,y + data += y; + HANDLE_PAGE_CROSSING( data ); + case 0xAE:{// LDX abs + pc++; + unsigned addr = data + 0x100 * READ_PROG( pc ); + pc++; + x = nz = READ( addr ); + goto loop; + } + + { + int temp; + case 0x8C: // STY abs + temp = y; + goto store_abs; + + case 0x8E: // STX abs + temp = x; + store_abs: + unsigned addr = GET_ADDR(); + WRITE( addr, temp ); + pc += 2; + goto loop; + } + +// Compare + + case 0xEC:{// CPX abs + unsigned addr = GET_ADDR(); + pc++; + data = READ( addr ); + goto cpx_data; + } + + case 0xE4: // CPX zp + data = READ_LOW( data ); + case 0xE0: // CPX #imm + cpx_data: + nz = x - data; + pc++; + c = ~nz; + nz &= 0xff; + goto loop; + + case 0xCC:{// CPY abs + unsigned addr = GET_ADDR(); + pc++; + data = READ( addr ); + goto cpy_data; + } + + case 0xC4: // CPY zp + data = READ_LOW( data ); + case 0xC0: // CPY #imm + cpy_data: + nz = y - data; + pc++; + c = ~nz; + nz &= 0xff; + goto loop; + +// Logical + + ARITH_ADDR_MODES( 0x25 ) // AND + nz = (a &= data); + pc++; + goto loop; + + ARITH_ADDR_MODES( 0x45 ) // EOR + nz = (a ^= data); + pc++; + goto loop; + + ARITH_ADDR_MODES( 0x05 ) // ORA + nz = (a |= data); + pc++; + goto loop; + + case 0x2C:{// BIT abs + unsigned addr = GET_ADDR(); + pc++; + nz = READ( addr ); + goto bit_common; + } + + case 0x24: // BIT zp + nz = READ_LOW( data ); + bit_common: + pc++; + status &= ~st_v; + status |= nz & st_v; + // if result is zero, might also be negative, so use secondary N bit + if ( !(a & nz) ) + nz = (nz << 4) & 0x800; + goto loop; + +// Add/subtract + + ARITH_ADDR_MODES( 0xE5 ) // SBC + case 0xEB: // unofficial equivalent + data ^= 0xFF; + goto adc_imm; + + ARITH_ADDR_MODES( 0x65 ) // ADC + adc_imm: { + int carry = (c >> 8) & 1; + int ov = (a ^ 0x80) + carry + (BOOST::int8_t) data; // sign-extend + status &= ~st_v; + status |= (ov >> 2) & 0x40; + c = nz = a + data + carry; + pc++; + a = (uint8_t) nz; + goto loop; + } + +// Shift/rotate + + case 0x4A: // LSR A + c = 0; + case 0x6A: // ROR A + nz = (c >> 1) & 0x80; // could use bit insert macro here + c = a << 8; + nz |= a >> 1; + a = nz; + goto loop; + + case 0x0A: // ASL A + nz = a << 1; + c = nz; + a = (uint8_t) nz; + goto loop; + + case 0x2A: { // ROL A + nz = a << 1; + int temp = (c >> 8) & 1; + c = nz; + nz |= temp; + a = (uint8_t) nz; + goto loop; + } + + case 0x3E: // ROL abs,X + data += x; + goto rol_abs; + + case 0x1E: // ASL abs,X + data += x; + case 0x0E: // ASL abs + c = 0; + case 0x2E: // ROL abs + rol_abs: + HANDLE_PAGE_CROSSING( data ); + ADD_PAGE + nz = (c >> 8) & 1; + nz |= (c = READ( data ) << 1); + rotate_common: + pc++; + WRITE( data, (uint8_t) nz ); + goto loop; + + case 0x7E: // ROR abs,X + data += x; + goto ror_abs; + + case 0x5E: // LSR abs,X + data += x; + case 0x4E: // LSR abs + c = 0; + case 0x6E: // ROR abs + ror_abs: { + HANDLE_PAGE_CROSSING( data ); + ADD_PAGE + int temp = READ( data ); + nz = ((c >> 1) & 0x80) | (temp >> 1); + c = temp << 8; + goto rotate_common; + } + + case 0x76: // ROR zp,x + data = uint8_t (data + x); + goto ror_zp; + + case 0x56: // LSR zp,x + data = uint8_t (data + x); + case 0x46: // LSR zp + c = 0; + case 0x66: // ROR zp + ror_zp: { + int temp = READ_LOW( data ); + nz = ((c >> 1) & 0x80) | (temp >> 1); + c = temp << 8; + goto write_nz_zp; + } + + case 0x36: // ROL zp,x + data = uint8_t (data + x); + goto rol_zp; + + case 0x16: // ASL zp,x + data = uint8_t (data + x); + case 0x06: // ASL zp + c = 0; + case 0x26: // ROL zp + rol_zp: + nz = (c >> 8) & 1; + nz |= (c = READ_LOW( data ) << 1); + goto write_nz_zp; + +// Increment/decrement + + case 0xCA: INC_DEC_XY( x, -1 ) // DEX + + case 0x88: INC_DEC_XY( y, -1 ) // DEY + + case 0xF6: // INC zp,x + data = uint8_t (data + x); + case 0xE6: // INC zp + nz = 1; + goto add_nz_zp; + + case 0xD6: // DEC zp,x + data = uint8_t (data + x); + case 0xC6: // DEC zp + nz = -1; + add_nz_zp: + nz += READ_LOW( data ); + write_nz_zp: + pc++; + WRITE_LOW( data, nz ); + goto loop; + + case 0xFE: // INC abs,x + HANDLE_PAGE_CROSSING( data + x ); + data = x + GET_ADDR(); + goto inc_ptr; + + case 0xEE: // INC abs + data = GET_ADDR(); + inc_ptr: + nz = 1; + goto inc_common; + + case 0xDE: // DEC abs,x + HANDLE_PAGE_CROSSING( data + x ); + data = x + GET_ADDR(); + goto dec_ptr; + + case 0xCE: // DEC abs + data = GET_ADDR(); + dec_ptr: + nz = -1; + inc_common: + nz += READ( data ); + pc += 2; + WRITE( data, (uint8_t) nz ); + goto loop; + +// Transfer + + case 0xAA: // TAX + x = a; + case 0x8A: // TXA + a = nz = x; + goto loop; + + case 0x9A: // TXS + SET_SP( x ); // verified (no flag change) + goto loop; + + case 0xBA: // TSX + x = nz = GET_SP(); + goto loop; + +// Stack + + case 0x48: // PHA + PUSH( a ); // verified + goto loop; + + case 0x68: // PLA + a = nz = READ_LOW( sp ); + sp = (sp - 0xff) | 0x100; + goto loop; + + case 0x40: // RTI + { + int temp = READ_LOW( sp ); + pc = READ_LOW( 0x100 | (sp - 0xff) ); + pc |= READ_LOW( 0x100 | (sp - 0xfe) ) * 0x100; + sp = (sp - 0xfd) | 0x100; + data = status; + SET_STATUS( temp ); + } + if ( !((data ^ status) & st_i) ) + goto loop; // I flag didn't change + i_flag_changed: + //dprintf( "%6d %s\n", time(), (status & st_i ? "SEI" : "CLI") ); + this->r.status = status; // update externally-visible I flag + // update clock_limit based on modified I flag + clock_limit = end_time_; + if ( end_time_ <= irq_time_ ) + goto loop; + if ( status & st_i ) + goto loop; + clock_limit = irq_time_; + goto loop; + + case 0x28:{// PLP + int temp = READ_LOW( sp ); + sp = (sp - 0xff) | 0x100; + data = status; + SET_STATUS( temp ); + if ( !((data ^ status) & st_i) ) + goto loop; // I flag didn't change + if ( !(status & st_i) ) + goto handle_cli; + goto handle_sei; + } + + case 0x08: { // PHP + int temp; + CALC_STATUS( temp ); + PUSH( temp | st_b | st_r ); + goto loop; + } + + case 0x6C: // JMP (ind) + data = GET_ADDR(); + pc = READ( data ); + pc |= READ( (data & 0xff00) | ((data + 1) & 0xff) ) << 8; + goto loop; + + case 0x00: { // BRK + pc++; + WRITE_LOW( 0x100 | (sp - 1), pc >> 8 ); + WRITE_LOW( 0x100 | (sp - 2), pc ); + int temp; + CALC_STATUS( temp ); + sp = (sp - 3) | 0x100; + WRITE_LOW( sp, temp | st_b | st_r ); + pc = READ_PROG16( 0xFFFE ); + status |= st_i; + goto i_flag_changed; + } + +// Flags + + case 0x38: // SEC + c = ~0; + goto loop; + + case 0x18: // CLC + c = 0; + goto loop; + + case 0xB8: // CLV + status &= ~st_v; + goto loop; + + case 0xD8: // CLD + status &= ~st_d; + goto loop; + + case 0xF8: // SED + status |= st_d; + goto loop; + + case 0x58: // CLI + if ( !(status & st_i) ) + goto loop; + status &= ~st_i; + handle_cli: + //dprintf( "%6d CLI\n", time() ); + this->r.status = status; // update externally-visible I flag + if ( clock_count < end_time_ ) + { + assert( clock_limit == end_time_ ); + if ( end_time_ <= irq_time_ ) + goto loop; // irq is later + if ( clock_count >= irq_time_ ) + irq_time_ = clock_count + 1; // delay IRQ until after next instruction + clock_limit = irq_time_; + goto loop; + } + // execution is stopping now, so delayed CLI must be handled by caller + result = result_cli; + goto end; + + case 0x78: // SEI + if ( status & st_i ) + goto loop; + status |= st_i; + handle_sei: + //dprintf( "%6d SEI\n", time() ); + this->r.status = status; // update externally-visible I flag + clock_limit = end_time_; + if ( clock_count < irq_time_ ) + goto loop; + result = result_sei; // IRQ will occur now, even though I flag is set + goto end; + +// Undocumented + + case 0x0C: case 0x1C: case 0x3C: case 0x5C: case 0x7C: case 0xDC: case 0xFC: // SKW + pc++; + case 0x74: case 0x04: case 0x14: case 0x34: case 0x44: case 0x54: case 0x64: // SKB + case 0x80: case 0x82: case 0x89: case 0xC2: case 0xD4: case 0xE2: case 0xF4: + pc++; + case 0xEA: case 0x1A: case 0x3A: case 0x5A: case 0x7A: case 0xDA: case 0xFA: // NOP + goto loop; + +// Unimplemented + + case page_wrap_opcode: // HLT + if ( pc > 0x10000 ) + { + // handle wrap-around (assumes caller has put page of HLT at 0x10000) + pc = (pc - 1) & 0xffff; + clock_count -= 2; + goto loop; + } + // fall through + case 0x02: case 0x12: case 0x22: case 0x32: // HLT + case 0x42: case 0x52: case 0x62: case 0x72: + case 0x92: case 0xB2: case 0xD2: + case 0x9B: // TAS + case 0x9C: // SAY + case 0x9E: // XAS + case 0x93: // AXA + case 0x9F: // AXA + case 0x0B: // ANC + case 0x2B: // ANC + case 0xBB: // LAS + case 0x4B: // ALR + case 0x6B: // AAR + case 0x8B: // XAA + case 0xAB: // OAL + case 0xCB: // SAX + case 0x83: case 0x87: case 0x8F: case 0x97: // AXS + case 0xA3: case 0xA7: case 0xAF: case 0xB3: case 0xB7: case 0xBF: // LAX + case 0xE3: case 0xE7: case 0xEF: case 0xF3: case 0xF7: case 0xFB: case 0xFF: // INS + case 0xC3: case 0xC7: case 0xCF: case 0xD3: case 0xD7: case 0xDB: case 0xDF: // DCM + case 0x63: case 0x67: case 0x6F: case 0x73: case 0x77: case 0x7B: case 0x7F: // RRA + case 0x43: case 0x47: case 0x4F: case 0x53: case 0x57: case 0x5B: case 0x5F: // LSE + case 0x23: case 0x27: case 0x2F: case 0x33: case 0x37: case 0x3B: case 0x3F: // RLA + case 0x03: case 0x07: case 0x0F: case 0x13: case 0x17: case 0x1B: case 0x1F: // ASO + result = result_badop; + goto stop; + } + + // If this fails then the case above is missing an opcode + assert( false ); + +stop: + pc--; +end: + + { + int temp; + CALC_STATUS( temp ); + r.status = temp; + } + + base_time += clock_count; + clock_limit -= clock_count; + this->clock_count = 0; + r.pc = pc; + r.sp = GET_SP(); + r.a = a; + r.x = x; + r.y = y; + irq_time_ = LONG_MAX / 2 + 1; + + return result; +} + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Nes_Cpu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nes_Cpu.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,174 @@ + +// Nintendo Entertainment System (NES) 6502 CPU emulator + +// Game_Music_Emu 0.3.0 + +#ifndef NES_CPU_H +#define NES_CPU_H + +#include "blargg_common.h" + +typedef long nes_time_t; // clock cycle count +typedef unsigned nes_addr_t; // 16-bit address + +class Nes_Emu; + +class Nes_Cpu { + typedef BOOST::uint8_t uint8_t; + enum { page_bits = 11 }; + enum { page_count = 0x10000 >> page_bits }; + uint8_t const* code_map [page_count + 1]; +public: + Nes_Cpu(); + + // Memory read/write function types. Reader must return value from 0 to 255. + typedef int (*reader_t)( Nes_Emu*, nes_addr_t ); + typedef void (*writer_t)( Nes_Emu*, nes_addr_t, int data ); + void set_emu( Nes_Emu* emu ) { callback_data = emu; } + + // Clear registers, unmap memory, and map code pages to unmapped_page. + void reset( const void* unmapped_page = NULL, reader_t read = NULL, writer_t write = NULL ); + + // Memory mapping functions take a block of memory of specified 'start' address + // and 'size' in bytes. Both start address and size must be a multiple of page_size. + enum { page_size = 1L << page_bits }; + + // Map code memory (memory accessed via the program counter) + void map_code( nes_addr_t start, unsigned long size, const void* code ); + + // Set read function for address range + void set_reader( nes_addr_t start, unsigned long size, reader_t ); + + // Set write function for address range + void set_writer( nes_addr_t start, unsigned long size, writer_t ); + + // Set read and write functions for address range + void map_memory( nes_addr_t start, unsigned long size, reader_t, writer_t ); + + // Access memory as the emulated CPU does. + int read( nes_addr_t ); + void write( nes_addr_t, int data ); + uint8_t* get_code( nes_addr_t ); // non-const to allow debugger to modify code + + // Push a byte on the stack + void push_byte( int ); + + // NES 6502 registers. *Not* kept updated during a call to run(). + struct registers_t { + long pc; // more than 16 bits to allow overflow detection + BOOST::uint8_t a; + BOOST::uint8_t x; + BOOST::uint8_t y; + BOOST::uint8_t status; + BOOST::uint8_t sp; + }; + registers_t r; + + // Reasons that run() returns + enum result_t { + result_cycles, // Requested number of cycles (or more) were executed + result_sei, // I flag just set and IRQ time would generate IRQ now + result_cli, // I flag just cleared but IRQ should occur *after* next instr + result_badop // unimplemented/illegal instruction + }; + + result_t run( nes_time_t end_time_ ); + + nes_time_t time() const { return base_time + clock_count; } + void set_time( nes_time_t t ); + void end_frame( nes_time_t ); + nes_time_t end_time() const { return base_time + end_time_; } + nes_time_t irq_time() const { return base_time + irq_time_; } + void set_end_time( nes_time_t t ); + void set_irq_time( nes_time_t t ); + + // If PC exceeds 0xFFFF and encounters page_wrap_opcode, it will be silently wrapped. + enum { page_wrap_opcode = 0xF2 }; + + // One of the many opcodes that are undefined and stop CPU emulation. + enum { bad_opcode = 0xD2 }; + + // End of public interface +private: + // noncopyable + Nes_Cpu( const Nes_Cpu& ); + Nes_Cpu& operator = ( const Nes_Cpu& ); + + nes_time_t clock_limit; + nes_time_t base_time; + nes_time_t clock_count; + nes_time_t irq_time_; + nes_time_t end_time_; + + Nes_Emu* callback_data; + + enum { irq_inhibit = 0x04 }; + reader_t data_reader [page_count + 1]; // extra entry catches address overflow + writer_t data_writer [page_count + 1]; + void set_code_page( int, uint8_t const* ); + void update_clock_limit(); + +public: + // low_mem is a full page size so it can be mapped with code_map + uint8_t low_mem [page_size > 0x800 ? page_size : 0x800]; +}; + +inline BOOST::uint8_t* Nes_Cpu::get_code( nes_addr_t addr ) +{ + #if BLARGG_NONPORTABLE + return (uint8_t*) code_map [addr >> page_bits] + addr; + #else + return (uint8_t*) code_map [addr >> page_bits] + (addr & (page_size - 1)); + #endif +} + +inline void Nes_Cpu::update_clock_limit() +{ + nes_time_t t = end_time_; + if ( t > irq_time_ && !(r.status & irq_inhibit) ) + t = irq_time_; + clock_limit = t; +} + +inline void Nes_Cpu::set_end_time( nes_time_t t ) +{ + end_time_ = t - base_time; + update_clock_limit(); +} + +inline void Nes_Cpu::set_irq_time( nes_time_t t ) +{ + irq_time_ = t - base_time; + update_clock_limit(); +} + +inline void Nes_Cpu::end_frame( nes_time_t end_time_ ) +{ + base_time -= end_time_; + assert( time() >= 0 ); +} + +inline void Nes_Cpu::set_time( nes_time_t t ) +{ + t -= time(); + clock_limit -= t; + end_time_ -= t; + irq_time_ -= t; + base_time += t; +} + +inline void Nes_Cpu::push_byte( int data ) +{ + int sp = r.sp; + r.sp = (sp - 1) & 0xff; + low_mem [0x100 + sp] = data; +} + +inline void Nes_Cpu::map_memory( nes_addr_t addr, unsigned long s, reader_t r, writer_t w ) +{ + set_reader( addr, s, r ); + set_writer( addr, s, w ); +} + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Nes_Fme7_Apu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nes_Fme7_Apu.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,122 @@ + +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ + +#include "Nes_Fme7_Apu.h" + +#include + +/* Copyright (C) 2003-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +void Nes_Fme7_Apu::reset() +{ + last_time = 0; + + for ( int i = 0; i < osc_count; i++ ) + oscs [i].last_amp = 0; + + fme7_snapshot_t* state = this; + memset( state, 0, sizeof *state ); +} + +#include BLARGG_ENABLE_OPTIMIZER + +unsigned char Nes_Fme7_Apu::amp_table [16] = +{ + #define ENTRY( n ) (unsigned char) (n * amp_range + 0.5) + ENTRY(0.0000), ENTRY(0.0078), ENTRY(0.0110), ENTRY(0.0156), + ENTRY(0.0221), ENTRY(0.0312), ENTRY(0.0441), ENTRY(0.0624), + ENTRY(0.0883), ENTRY(0.1249), ENTRY(0.1766), ENTRY(0.2498), + ENTRY(0.3534), ENTRY(0.4998), ENTRY(0.7070), ENTRY(1.0000) + #undef ENTRY +}; + +void Nes_Fme7_Apu::run_until( blip_time_t end_time ) +{ + require( end_time >= last_time ); + + for ( int index = 0; index < osc_count; index++ ) + { + int mode = regs [7] >> index; + int vol_mode = regs [010 + index]; + int volume = amp_table [vol_mode & 0x0f]; + + if ( !oscs [index].output ) + continue; + + // check for unsupported mode + #ifndef NDEBUG + if ( (mode & 011) <= 001 && vol_mode & 0x1f ) + dprintf( "FME7 used unimplemented sound mode: %02X, vol_mode: %02X\n", + mode, vol_mode & 0x1f ); + #endif + + if ( (mode & 001) | (vol_mode & 0x10) ) + volume = 0; // noise and envelope aren't supported + + // period + int const period_factor = 16; + unsigned period = (regs [index * 2 + 1] & 0x0f) * 0x100 * period_factor + + regs [index * 2] * period_factor; + if ( period < 50 ) // around 22 kHz + { + volume = 0; + if ( !period ) // on my AY-3-8910A, period doesn't have extra one added + period = period_factor; + } + + // current amplitude + int amp = volume; + if ( !phases [index] ) + amp = 0; + int delta = amp - oscs [index].last_amp; + if ( delta ) + { + oscs [index].last_amp = amp; + synth.offset( last_time, delta, oscs [index].output ); + } + + blip_time_t time = last_time + delays [index]; + if ( time < end_time ) + { + Blip_Buffer* const osc_output = oscs [index].output; + int delta = amp * 2 - volume; + + if ( volume ) + { + do + { + delta = -delta; + synth.offset_inline( time, delta, osc_output ); + time += period; + } + while ( time < end_time ); + + oscs [index].last_amp = (delta + volume) >> 1; + phases [index] = (delta > 0); + } + else + { + // maintain phase when silent + int count = (end_time - time + period - 1) / period; + phases [index] ^= count & 1; + time += (long) count * period; + } + } + + delays [index] = time - end_time; + } + + last_time = end_time; +} + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Nes_Fme7_Apu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nes_Fme7_Apu.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,135 @@ + +// Sunsoft FME-7 sound emulator + +// Game_Music_Emu 0.3.0 + +#ifndef NES_FME7_APU_H +#define NES_FME7_APU_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +struct fme7_snapshot_t +{ + enum { reg_count = 14 }; + BOOST::uint8_t regs [reg_count]; + BOOST::uint8_t phases [3]; // 0 or 1 + BOOST::uint8_t latch; + BOOST::uint16_t delays [3]; // a, b, c +}; +BOOST_STATIC_ASSERT( sizeof (fme7_snapshot_t) == 24 ); + +class Nes_Fme7_Apu : private fme7_snapshot_t { +public: + Nes_Fme7_Apu(); + + // See Nes_Apu.h for reference + void reset(); + void volume( double ); + void treble_eq( blip_eq_t const& ); + void output( Blip_Buffer* ); + enum { osc_count = 3 }; + void osc_output( int index, Blip_Buffer* ); + void end_frame( blip_time_t ); + void save_snapshot( fme7_snapshot_t* ) const; + void load_snapshot( fme7_snapshot_t const& ); + + // Mask and addresses of registers + enum { addr_mask = 0xe000 }; + enum { data_addr = 0xe000 }; + enum { latch_addr = 0xc000 }; + + // (addr & addr_mask) == latch_addr + void write_latch( int ); + + // (addr & addr_mask) == data_addr + void write_data( blip_time_t, int data ); + + // End of public interface +private: + // noncopyable + Nes_Fme7_Apu( const Nes_Fme7_Apu& ); + Nes_Fme7_Apu& operator = ( const Nes_Fme7_Apu& ); + + static unsigned char amp_table [16]; + + struct { + Blip_Buffer* output; + int last_amp; + } oscs [osc_count]; + blip_time_t last_time; + + enum { amp_range = 192 }; // can be any value; this gives best error/quality tradeoff + Blip_Synth synth; + + void run_until( blip_time_t ); +}; + +inline void Nes_Fme7_Apu::volume( double v ) +{ + synth.volume( 0.38 / amp_range * v ); // to do: fine-tune +} + +inline void Nes_Fme7_Apu::treble_eq( blip_eq_t const& eq ) +{ + synth.treble_eq( eq ); +} + +inline void Nes_Fme7_Apu::osc_output( int i, Blip_Buffer* buf ) +{ + assert( (unsigned) i < osc_count ); + oscs [i].output = buf; +} + +inline void Nes_Fme7_Apu::output( Blip_Buffer* buf ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, buf ); +} + +inline Nes_Fme7_Apu::Nes_Fme7_Apu() +{ + output( NULL ); + volume( 1.0 ); + reset(); +} + +inline void Nes_Fme7_Apu::write_latch( int data ) { latch = data; } + +inline void Nes_Fme7_Apu::write_data( blip_time_t time, int data ) +{ + if ( (unsigned) latch >= reg_count ) + { + #ifdef dprintf + dprintf( "FME7 write to %02X (past end of sound registers)\n", (int) latch ); + #endif + return; + } + + run_until( time ); + regs [latch] = data; +} + +inline void Nes_Fme7_Apu::end_frame( blip_time_t time ) +{ + if ( time > last_time ) + run_until( time ); + + assert( last_time >= time ); + last_time -= time; +} + +inline void Nes_Fme7_Apu::save_snapshot( fme7_snapshot_t* out ) const +{ + *out = *this; +} + +inline void Nes_Fme7_Apu::load_snapshot( fme7_snapshot_t const& in ) +{ + reset(); + fme7_snapshot_t* state = this; + *state = in; +} + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Nes_Namco_Apu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nes_Namco_Apu.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,151 @@ + +// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/ + +#include "Nes_Namco_Apu.h" + +/* Copyright (C) 2003-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +Nes_Namco_Apu::Nes_Namco_Apu() +{ + output( NULL ); + volume( 1.0 ); + reset(); +} + +Nes_Namco_Apu::~Nes_Namco_Apu() +{ +} + +void Nes_Namco_Apu::reset() +{ + last_time = 0; + addr_reg = 0; + + int i; + for ( i = 0; i < reg_count; i++ ) + reg [i] = 0; + + for ( i = 0; i < osc_count; i++ ) + { + Namco_Osc& osc = oscs [i]; + osc.delay = 0; + osc.last_amp = 0; + osc.wave_pos = 0; + } +} + +void Nes_Namco_Apu::output( Blip_Buffer* buf ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, buf ); +} + +/* +void Nes_Namco_Apu::reflect_state( Tagged_Data& data ) +{ + reflect_int16( data, 'ADDR', &addr_reg ); + + static const char hex [17] = "0123456789ABCDEF"; + int i; + for ( i = 0; i < reg_count; i++ ) + reflect_int16( data, 'RG\0\0' + hex [i >> 4] * 0x100 + hex [i & 15], ® [i] ); + + for ( i = 0; i < osc_count; i++ ) + { + reflect_int32( data, 'DLY0' + i, &oscs [i].delay ); + reflect_int16( data, 'POS0' + i, &oscs [i].wave_pos ); + } +} +*/ + +void Nes_Namco_Apu::end_frame( nes_time_t time ) +{ + if ( time > last_time ) + run_until( time ); + + assert( last_time >= time ); + last_time -= time; +} + +#include BLARGG_ENABLE_OPTIMIZER + +void Nes_Namco_Apu::run_until( nes_time_t nes_end_time ) +{ + int active_oscs = (reg [0x7F] >> 4 & 7) + 1; + for ( int i = osc_count - active_oscs; i < osc_count; i++ ) + { + Namco_Osc& osc = oscs [i]; + Blip_Buffer* output = osc.output; + if ( !output ) + continue; + + blip_resampled_time_t time = + output->resampled_time( last_time ) + osc.delay; + blip_resampled_time_t end_time = output->resampled_time( nes_end_time ); + osc.delay = 0; + if ( time < end_time ) + { + const BOOST::uint8_t* osc_reg = ® [i * 8 + 0x40]; + if ( !(osc_reg [4] & 0xE0) ) + continue; + + int volume = osc_reg [7] & 15; + if ( !volume ) + continue; + + long freq = (osc_reg [4] & 3) * 0x10000 + osc_reg [2] * 0x100L + osc_reg [0]; + if ( freq < 64 * active_oscs ) + continue; // prevent low frequencies from excessively delaying freq changes + blip_resampled_time_t period = + output->resampled_duration( 983040 ) / freq * active_oscs; + + int wave_size = 32 - (osc_reg [4] >> 2 & 7) * 4; + if ( !wave_size ) + continue; + + int last_amp = osc.last_amp; + int wave_pos = osc.wave_pos; + + do + { + // read wave sample + int addr = wave_pos + osc_reg [6]; + int sample = reg [addr >> 1] >> (addr << 2 & 4); + wave_pos++; + sample = (sample & 15) * volume; + + // output impulse if amplitude changed + int delta = sample - last_amp; + if ( delta ) + { + last_amp = sample; + synth.offset_resampled( time, delta, output ); + } + + // next sample + time += period; + if ( wave_pos >= wave_size ) + wave_pos = 0; + } + while ( time < end_time ); + + osc.wave_pos = wave_pos; + osc.last_amp = last_amp; + } + osc.delay = time - end_time; + } + + last_time = nes_end_time; +} + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Nes_Namco_Apu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nes_Namco_Apu.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,104 @@ + +// Namco 106 sound chip emulator + +// Nes_Snd_Emu 0.1.7 + +#ifndef NES_NAMCO_APU_H +#define NES_NAMCO_APU_H + +#include "Nes_Apu.h" + +struct namco_snapshot_t; + +class Nes_Namco_Apu { +public: + Nes_Namco_Apu(); + ~Nes_Namco_Apu(); + + // See Nes_Apu.h for reference. + void volume( double ); + void treble_eq( const blip_eq_t& ); + void output( Blip_Buffer* ); + enum { osc_count = 8 }; + void osc_output( int index, Blip_Buffer* ); + void reset(); + void end_frame( nes_time_t ); + + // Read/write data register is at 0x4800 + enum { data_reg_addr = 0x4800 }; + void write_data( nes_time_t, int ); + int read_data(); + + // Write-only address register is at 0xF800 + enum { addr_reg_addr = 0xF800 }; + void write_addr( int ); + + // to do: implement save/restore + void save_snapshot( namco_snapshot_t* out ) const; + void load_snapshot( namco_snapshot_t const& ); + +private: + // noncopyable + Nes_Namco_Apu( const Nes_Namco_Apu& ); + Nes_Namco_Apu& operator = ( const Nes_Namco_Apu& ); + + struct Namco_Osc { + long delay; + Blip_Buffer* output; + short last_amp; + short wave_pos; + }; + + Namco_Osc oscs [osc_count]; + + nes_time_t last_time; + int addr_reg; + + enum { reg_count = 0x80 }; + BOOST::uint8_t reg [reg_count]; + Blip_Synth synth; + + BOOST::uint8_t& access(); + void run_until( nes_time_t ); +}; +/* +struct namco_snapshot_t +{ + BOOST::uint8_t regs [0x80]; + BOOST::uint8_t addr; + BOOST::uint8_t unused; + BOOST::uint8_t positions [8]; + BOOST::uint32_t delays [8]; +}; +*/ + +inline BOOST::uint8_t& Nes_Namco_Apu::access() +{ + int addr = addr_reg & 0x7f; + if ( addr_reg & 0x80 ) + addr_reg = (addr + 1) | 0x80; + return reg [addr]; +} + +inline void Nes_Namco_Apu::volume( double v ) { synth.volume( 0.10 / osc_count * v ); } + +inline void Nes_Namco_Apu::treble_eq( const blip_eq_t& eq ) { synth.treble_eq( eq ); } + +inline void Nes_Namco_Apu::write_addr( int v ) { addr_reg = v; } + +inline int Nes_Namco_Apu::read_data() { return access(); } + +inline void Nes_Namco_Apu::osc_output( int i, Blip_Buffer* buf ) +{ + assert( (unsigned) i < osc_count ); + oscs [i].output = buf; +} + +inline void Nes_Namco_Apu::write_data( nes_time_t time, int data ) +{ + run_until( time ); + access() = data; +} + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Nes_Oscs.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nes_Oscs.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,498 @@ + +// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/ + +#include "Nes_Apu.h" + +/* Copyright (C) 2003-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +// Nes_Osc + +void Nes_Osc::clock_length( int halt_mask ) +{ + if ( length_counter && !(regs [0] & halt_mask) ) + length_counter--; +} + +void Nes_Envelope::clock_envelope() +{ + int period = regs [0] & 15; + if ( reg_written [3] ) { + reg_written [3] = false; + env_delay = period; + envelope = 15; + } + else if ( --env_delay < 0 ) { + env_delay = period; + if ( envelope | (regs [0] & 0x20) ) + envelope = (envelope - 1) & 15; + } +} + +int Nes_Envelope::volume() const +{ + return length_counter == 0 ? 0 : (regs [0] & 0x10) ? (regs [0] & 15) : envelope; +} + +// Nes_Square + +void Nes_Square::clock_sweep( int negative_adjust ) +{ + int sweep = regs [1]; + + if ( --sweep_delay < 0 ) + { + reg_written [1] = true; + + int period = this->period(); + int shift = sweep & shift_mask; + if ( shift && (sweep & 0x80) && period >= 8 ) + { + int offset = period >> shift; + + if ( sweep & negate_flag ) + offset = negative_adjust - offset; + + if ( period + offset < 0x800 ) + { + period += offset; + // rewrite period + regs [2] = period & 0xff; + regs [3] = (regs [3] & ~7) | ((period >> 8) & 7); + } + } + } + + if ( reg_written [1] ) { + reg_written [1] = false; + sweep_delay = (sweep >> 4) & 7; + } +} + +void Nes_Square::run( nes_time_t time, nes_time_t end_time ) +{ + if ( !output ) + return; + + const int volume = this->volume(); + const int period = this->period(); + int offset = period >> (regs [1] & shift_mask); + if ( regs [1] & negate_flag ) + offset = 0; + + const int timer_period = (period + 1) * 2; + if ( volume == 0 || period < 8 || (period + offset) >= 0x800 ) + { + if ( last_amp ) { + synth.offset( time, -last_amp, output ); + last_amp = 0; + } + + time += delay; + if ( time < end_time ) + { + // maintain proper phase + int count = (end_time - time + timer_period - 1) / timer_period; + phase = (phase + count) & (phase_range - 1); + time += (long) count * timer_period; + } + } + else + { + // handle duty select + int duty_select = (regs [0] >> 6) & 3; + int duty = 1 << duty_select; // 1, 2, 4, 2 + int amp = 0; + if ( duty_select == 3 ) { + duty = 2; // negated 25% + amp = volume; + } + if ( phase < duty ) + amp ^= volume; + + int delta = update_amp( amp ); + if ( delta ) + synth.offset( time, delta, output ); + + time += delay; + if ( time < end_time ) + { + Blip_Buffer* const output = this->output; + const Synth& synth = this->synth; + int delta = amp * 2 - volume; + int phase = this->phase; + + do { + phase = (phase + 1) & (phase_range - 1); + if ( phase == 0 || phase == duty ) { + delta = -delta; + synth.offset_inline( time, delta, output ); + } + time += timer_period; + } + while ( time < end_time ); + + last_amp = (delta + volume) >> 1; + this->phase = phase; + } + } + + delay = time - end_time; +} + +// Nes_Triangle + +void Nes_Triangle::clock_linear_counter() +{ + if ( reg_written [3] ) + linear_counter = regs [0] & 0x7f; + else if ( linear_counter ) + linear_counter--; + + if ( !(regs [0] & 0x80) ) + reg_written [3] = false; +} + +inline int Nes_Triangle::calc_amp() const +{ + int amp = phase_range - phase; + if ( amp < 0 ) + amp = phase - (phase_range + 1); + return amp; +} + +void Nes_Triangle::run( nes_time_t time, nes_time_t end_time ) +{ + if ( !output ) + return; + + // to do: track phase when period < 3 + // to do: Output 7.5 on dac when period < 2? More accurate, but results in more clicks. + + int delta = update_amp( calc_amp() ); + if ( delta ) + synth.offset( time, delta, output ); + + time += delay; + const int timer_period = period() + 1; + if ( length_counter == 0 || linear_counter == 0 || timer_period < 3 ) + { + time = end_time; + } + else if ( time < end_time ) + { + Blip_Buffer* const output = this->output; + + int phase = this->phase; + int volume = 1; + if ( phase > phase_range ) { + phase -= phase_range; + volume = -volume; + } + + do { + if ( --phase == 0 ) { + phase = phase_range; + volume = -volume; + } + else { + synth.offset_inline( time, volume, output ); + } + + time += timer_period; + } + while ( time < end_time ); + + if ( volume < 0 ) + phase += phase_range; + this->phase = phase; + last_amp = calc_amp(); + } + delay = time - end_time; +} + +// Nes_Dmc + +void Nes_Dmc::reset() +{ + address = 0; + dac = 0; + buf = 0; + bits_remain = 1; + bits = 0; + buf_full = false; + silence = true; + next_irq = Nes_Apu::no_irq; + irq_flag = false; + irq_enabled = false; + + Nes_Osc::reset(); + period = 0x1ac; +} + +void Nes_Dmc::recalc_irq() +{ + nes_time_t irq = Nes_Apu::no_irq; + if ( irq_enabled && length_counter ) + irq = apu->last_dmc_time + delay + + ((length_counter - 1) * 8 + bits_remain - 1) * nes_time_t (period) + 1; + if ( irq != next_irq ) { + next_irq = irq; + apu->irq_changed(); + } +} + +int Nes_Dmc::count_reads( nes_time_t time, nes_time_t* last_read ) const +{ + if ( last_read ) + *last_read = time; + + if ( length_counter == 0 ) + return 0; // not reading + + long first_read = next_read_time(); + long avail = time - first_read; + if ( avail <= 0 ) + return 0; + + int count = (avail - 1) / (period * 8) + 1; + if ( !(regs [0] & loop_flag) && count > length_counter ) + count = length_counter; + + if ( last_read ) { + *last_read = first_read + (count - 1) * (period * 8) + 1; + assert( *last_read <= time ); + assert( count == count_reads( *last_read, NULL ) ); + assert( count - 1 == count_reads( *last_read - 1, NULL ) ); + } + + return count; +} + +static const short dmc_period_table [2] [16] = { + {0x1ac, 0x17c, 0x154, 0x140, 0x11e, 0x0fe, 0x0e2, 0x0d6, // NTSC + 0x0be, 0x0a0, 0x08e, 0x080, 0x06a, 0x054, 0x048, 0x036}, + + {0x18e, 0x161, 0x13c, 0x129, 0x10a, 0x0ec, 0x0d2, 0x0c7, // PAL (totally untested) + 0x0b1, 0x095, 0x084, 0x077, 0x062, 0x04e, 0x043, 0x032} // to do: verify PAL periods +}; + +inline void Nes_Dmc::reload_sample() +{ + address = 0x4000 + regs [2] * 0x40; + length_counter = regs [3] * 0x10 + 1; +} + +static const unsigned char dac_table [128] = +{ + 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 9,10,11,12,13,14, + 15,15,16,17,18,19,20,20,21,22,23,24,24,25,26,27, + 27,28,29,30,31,31,32,33,33,34,35,36,36,37,38,38, + 39,40,41,41,42,43,43,44,45,45,46,47,47,48,48,49, + 50,50,51,52,52,53,53,54,55,55,56,56,57,58,58,59, + 59,60,60,61,61,62,63,63,64,64,65,65,66,66,67,67, + 68,68,69,70,70,71,71,72,72,73,73,74,74,75,75,75, + 76,76,77,77,78,78,79,79,80,80,81,81,82,82,82,83, +}; + +void Nes_Dmc::write_register( int addr, int data ) +{ + if ( addr == 0 ) + { + period = dmc_period_table [pal_mode] [data & 15]; + irq_enabled = (data & 0xc0) == 0x80; // enabled only if loop disabled + irq_flag &= irq_enabled; + recalc_irq(); + } + else if ( addr == 1 ) + { + int old_dac = dac; + dac = data & 0x7F; + + // adjust last_amp so that "pop" amplitude will be properly non-linear + // with respect to change in dac + int faked_nonlinear = dac - (dac_table [dac] - dac_table [old_dac]); + if ( !nonlinear ) + last_amp = faked_nonlinear; + } +} + +void Nes_Dmc::start() +{ + reload_sample(); + fill_buffer(); + recalc_irq(); +} + +void Nes_Dmc::fill_buffer() +{ + if ( !buf_full && length_counter ) + { + require( rom_reader ); // rom_reader must be set + buf = rom_reader( rom_reader_data, 0x8000u + address ); + address = (address + 1) & 0x7FFF; + buf_full = true; + if ( --length_counter == 0 ) + { + if ( regs [0] & loop_flag ) { + reload_sample(); + } + else { + apu->osc_enables &= ~0x10; + irq_flag = irq_enabled; + next_irq = Nes_Apu::no_irq; + apu->irq_changed(); + } + } + } +} + +void Nes_Dmc::run( nes_time_t time, nes_time_t end_time ) +{ + int delta = update_amp( dac ); + if ( !output ) + silence = true; + else if ( delta ) + synth.offset( time, delta, output ); + + time += delay; + if ( time < end_time ) + { + int bits_remain = this->bits_remain; + if ( silence && !buf_full ) + { + int count = (end_time - time + period - 1) / period; + bits_remain = (bits_remain - 1 + 8 - (count % 8)) % 8 + 1; + time += count * period; + } + else + { + Blip_Buffer* const output = this->output; + const int period = this->period; + int bits = this->bits; + int dac = this->dac; + + do + { + if ( !silence ) + { + int step = (bits & 1) * 4 - 2; + bits >>= 1; + if ( unsigned (dac + step) <= 0x7F ) { + dac += step; + synth.offset_inline( time, step, output ); + } + } + + time += period; + + if ( --bits_remain == 0 ) + { + bits_remain = 8; + if ( !buf_full ) { + silence = true; + } + else { + silence = false; + bits = buf; + buf_full = false; + if ( !output ) + silence = true; + fill_buffer(); + } + } + } + while ( time < end_time ); + + this->dac = dac; + this->last_amp = dac; + this->bits = bits; + } + this->bits_remain = bits_remain; + } + delay = time - end_time; +} + +// Nes_Noise + +#include BLARGG_ENABLE_OPTIMIZER + +static const short noise_period_table [16] = { + 0x004, 0x008, 0x010, 0x020, 0x040, 0x060, 0x080, 0x0A0, + 0x0CA, 0x0FE, 0x17C, 0x1FC, 0x2FA, 0x3F8, 0x7F2, 0xFE4 +}; + +void Nes_Noise::run( nes_time_t time, nes_time_t end_time ) +{ + if ( !output ) + return; + + const int volume = this->volume(); + int amp = (noise & 1) ? volume : 0; + int delta = update_amp( amp ); + if ( delta ) + synth.offset( time, delta, output ); + + time += delay; + if ( time < end_time ) + { + const int mode_flag = 0x80; + + int period = noise_period_table [regs [2] & 15]; + if ( !volume ) + { + // round to next multiple of period + time += (end_time - time + period - 1) / period * period; + + // approximate noise cycling while muted, by shuffling up noise register + // to do: precise muted noise cycling? + if ( !(regs [2] & mode_flag) ) { + int feedback = (noise << 13) ^ (noise << 14); + noise = (feedback & 0x4000) | (noise >> 1); + } + } + else + { + Blip_Buffer* const output = this->output; + + // using resampled time avoids conversion in synth.offset() + blip_resampled_time_t rperiod = output->resampled_duration( period ); + blip_resampled_time_t rtime = output->resampled_time( time ); + + int noise = this->noise; + int delta = amp * 2 - volume; + const int tap = (regs [2] & mode_flag ? 8 : 13); + + do { + int feedback = (noise << tap) ^ (noise << 14); + time += period; + + if ( (noise + 1) & 2 ) { + // bits 0 and 1 of noise differ + delta = -delta; + synth.offset_resampled( rtime, delta, output ); + } + + rtime += rperiod; + noise = (feedback & 0x4000) | (noise >> 1); + } + while ( time < end_time ); + + last_amp = (delta + volume) >> 1; + this->noise = noise; + } + } + + delay = time - end_time; +} + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Nes_Oscs.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nes_Oscs.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,146 @@ + +// Private oscillators used by Nes_Apu + +// Nes_Snd_Emu 0.1.7 + +#ifndef NES_OSCS_H +#define NES_OSCS_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +class Nes_Apu; + +struct Nes_Osc +{ + unsigned char regs [4]; + bool reg_written [4]; + Blip_Buffer* output; + int length_counter;// length counter (0 if unused by oscillator) + int delay; // delay until next (potential) transition + int last_amp; // last amplitude oscillator was outputting + + void clock_length( int halt_mask ); + int period() const { + return (regs [3] & 7) * 0x100 + (regs [2] & 0xff); + } + void reset() { + delay = 0; + last_amp = 0; + } + int update_amp( int amp ) { + int delta = amp - last_amp; + last_amp = amp; + return delta; + } +}; + +struct Nes_Envelope : Nes_Osc +{ + int envelope; + int env_delay; + + void clock_envelope(); + int volume() const; + void reset() { + envelope = 0; + env_delay = 0; + Nes_Osc::reset(); + } +}; + +// Nes_Square +struct Nes_Square : Nes_Envelope +{ + enum { negate_flag = 0x08 }; + enum { shift_mask = 0x07 }; + enum { phase_range = 8 }; + int phase; + int sweep_delay; + + typedef Blip_Synth Synth; + Synth const& synth; // shared between squares + + Nes_Square( Synth const* s ) : synth( *s ) { } + + void clock_sweep( int adjust ); + void run( nes_time_t, nes_time_t ); + void reset() { + sweep_delay = 0; + Nes_Envelope::reset(); + } +}; + +// Nes_Triangle +struct Nes_Triangle : Nes_Osc +{ + enum { phase_range = 16 }; + int phase; + int linear_counter; + Blip_Synth synth; + + int calc_amp() const; + void run( nes_time_t, nes_time_t ); + void clock_linear_counter(); + void reset() { + linear_counter = 0; + phase = phase_range; + Nes_Osc::reset(); + } +}; + +// Nes_Noise +struct Nes_Noise : Nes_Envelope +{ + int noise; + Blip_Synth synth; + + void run( nes_time_t, nes_time_t ); + void reset() { + noise = 1 << 14; + Nes_Envelope::reset(); + } +}; + +// Nes_Dmc +struct Nes_Dmc : Nes_Osc +{ + int address; // address of next byte to read + int period; + //int length_counter; // bytes remaining to play (already defined in Nes_Osc) + int buf; + int bits_remain; + int bits; + bool buf_full; + bool silence; + + enum { loop_flag = 0x40 }; + + int dac; + + nes_time_t next_irq; + bool irq_enabled; + bool irq_flag; + bool pal_mode; + bool nonlinear; + + int (*rom_reader)( void*, nes_addr_t ); // needs to be initialized to rom read function + void* rom_reader_data; + + Nes_Apu* apu; + + Blip_Synth synth; + + void start(); + void write_register( int, int ); + void run( nes_time_t, nes_time_t ); + void recalc_irq(); + void fill_buffer(); + void reload_sample(); + void reset(); + int count_reads( nes_time_t, nes_time_t* ) const; + nes_time_t next_read_time() const; +}; + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Nes_Vrc6_Apu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nes_Vrc6_Apu.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,219 @@ + +// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/ + +#include "Nes_Vrc6_Apu.h" + +/* Copyright (C) 2003-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +Nes_Vrc6_Apu::Nes_Vrc6_Apu() +{ + output( NULL ); + volume( 1.0 ); + reset(); +} + +Nes_Vrc6_Apu::~Nes_Vrc6_Apu() +{ +} + +void Nes_Vrc6_Apu::reset() +{ + last_time = 0; + for ( int i = 0; i < osc_count; i++ ) + { + Vrc6_Osc& osc = oscs [i]; + for ( int j = 0; j < reg_count; j++ ) + osc.regs [j] = 0; + osc.delay = 0; + osc.last_amp = 0; + osc.phase = 1; + osc.amp = 0; + } +} + +void Nes_Vrc6_Apu::output( Blip_Buffer* buf ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, buf ); +} + +void Nes_Vrc6_Apu::run_until( nes_time_t time ) +{ + require( time >= last_time ); + run_square( oscs [0], time ); + run_square( oscs [1], time ); + run_saw( time ); + last_time = time; +} + +void Nes_Vrc6_Apu::write_osc( nes_time_t time, int osc_index, int reg, int data ) +{ + require( (unsigned) osc_index < osc_count ); + require( (unsigned) reg < reg_count ); + + run_until( time ); + oscs [osc_index].regs [reg] = data; +} + +void Nes_Vrc6_Apu::end_frame( nes_time_t time ) +{ + if ( time > last_time ) + run_until( time ); + + assert( last_time >= time ); + last_time -= time; +} + +void Nes_Vrc6_Apu::save_snapshot( vrc6_snapshot_t* out ) const +{ + out->saw_amp = oscs [2].amp; + for ( int i = 0; i < osc_count; i++ ) + { + Vrc6_Osc const& osc = oscs [i]; + for ( int r = 0; r < reg_count; r++ ) + out->regs [i] [r] = osc.regs [r]; + + out->delays [i] = osc.delay; + out->phases [i] = osc.phase; + } +} + +void Nes_Vrc6_Apu::load_snapshot( vrc6_snapshot_t const& in ) +{ + reset(); + oscs [2].amp = in.saw_amp; + for ( int i = 0; i < osc_count; i++ ) + { + Vrc6_Osc& osc = oscs [i]; + for ( int r = 0; r < reg_count; r++ ) + osc.regs [r] = in.regs [i] [r]; + + osc.delay = in.delays [i]; + osc.phase = in.phases [i]; + } + if ( !oscs [2].phase ) + oscs [2].phase = 1; +} + +#include BLARGG_ENABLE_OPTIMIZER + +void Nes_Vrc6_Apu::run_square( Vrc6_Osc& osc, nes_time_t end_time ) +{ + Blip_Buffer* output = osc.output; + if ( !output ) + return; + + int volume = osc.regs [0] & 15; + if ( !(osc.regs [2] & 0x80) ) + volume = 0; + + int gate = osc.regs [0] & 0x80; + int duty = ((osc.regs [0] >> 4) & 7) + 1; + int delta = ((gate || osc.phase < duty) ? volume : 0) - osc.last_amp; + nes_time_t time = last_time; + if ( delta ) + { + osc.last_amp += delta; + square_synth.offset( time, delta, output ); + } + + time += osc.delay; + osc.delay = 0; + int period = osc.period(); + if ( volume && !gate && period > 4 ) + { + if ( time < end_time ) + { + int phase = osc.phase; + + do + { + phase++; + if ( phase == 16 ) + { + phase = 0; + osc.last_amp = volume; + square_synth.offset( time, volume, output ); + } + if ( phase == duty ) + { + osc.last_amp = 0; + square_synth.offset( time, -volume, output ); + } + time += period; + } + while ( time < end_time ); + + osc.phase = phase; + } + osc.delay = time - end_time; + } +} + +void Nes_Vrc6_Apu::run_saw( nes_time_t end_time ) +{ + Vrc6_Osc& osc = oscs [2]; + Blip_Buffer* output = osc.output; + if ( !output ) + return; + + int amp = osc.amp; + int amp_step = osc.regs [0] & 0x3F; + nes_time_t time = last_time; + int last_amp = osc.last_amp; + if ( !(osc.regs [2] & 0x80) || !(amp_step | amp) ) + { + osc.delay = 0; + int delta = (amp >> 3) - last_amp; + last_amp = amp >> 3; + saw_synth.offset( time, delta, output ); + } + else + { + time += osc.delay; + if ( time < end_time ) + { + int period = osc.period() * 2; + int phase = osc.phase; + + do + { + if ( --phase == 0 ) + { + phase = 7; + amp = 0; + } + + int delta = (amp >> 3) - last_amp; + if ( delta ) + { + last_amp = amp >> 3; + saw_synth.offset( time, delta, output ); + } + + time += period; + amp = (amp + amp_step) & 0xFF; + } + while ( time < end_time ); + + osc.phase = phase; + osc.amp = amp; + } + + osc.delay = time - end_time; + } + + osc.last_amp = last_amp; +} + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Nes_Vrc6_Apu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nes_Vrc6_Apu.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,99 @@ + +// Konami VRC6 sound chip emulator + +// Nes_Snd_Emu 0.1.7 + +#ifndef NES_VRC6_APU_H +#define NES_VRC6_APU_H + +#include "Nes_Apu.h" +#include "Blip_Buffer.h" + +struct vrc6_snapshot_t; + +class Nes_Vrc6_Apu { +public: + Nes_Vrc6_Apu(); + ~Nes_Vrc6_Apu(); + + // See Nes_Apu.h for reference + void reset(); + void volume( double ); + void treble_eq( blip_eq_t const& ); + void output( Blip_Buffer* ); + enum { osc_count = 3 }; + void osc_output( int index, Blip_Buffer* ); + void end_frame( nes_time_t ); + void save_snapshot( vrc6_snapshot_t* ) const; + void load_snapshot( vrc6_snapshot_t const& ); + + // Oscillator 0 write-only registers are at $9000-$9002 + // Oscillator 1 write-only registers are at $A000-$A002 + // Oscillator 2 write-only registers are at $B000-$B002 + enum { reg_count = 3 }; + enum { base_addr = 0x9000 }; + enum { addr_step = 0x1000 }; + void write_osc( nes_time_t, int osc, int reg, int data ); + +private: + // noncopyable + Nes_Vrc6_Apu( const Nes_Vrc6_Apu& ); + Nes_Vrc6_Apu& operator = ( const Nes_Vrc6_Apu& ); + + struct Vrc6_Osc + { + BOOST::uint8_t regs [3]; + Blip_Buffer* output; + int delay; + int last_amp; + int phase; + int amp; // only used by saw + + int period() const + { + return (regs [2] & 0x0f) * 0x100L + regs [1] + 1; + } + }; + + Vrc6_Osc oscs [osc_count]; + nes_time_t last_time; + + Blip_Synth saw_synth; + Blip_Synth square_synth; + + void run_until( nes_time_t ); + void run_square( Vrc6_Osc& osc, nes_time_t ); + void run_saw( nes_time_t ); +}; + +struct vrc6_snapshot_t +{ + BOOST::uint8_t regs [3] [3]; + BOOST::uint8_t saw_amp; + BOOST::uint16_t delays [3]; + BOOST::uint8_t phases [3]; + BOOST::uint8_t unused; +}; +BOOST_STATIC_ASSERT( sizeof (vrc6_snapshot_t) == 20 ); + +inline void Nes_Vrc6_Apu::osc_output( int i, Blip_Buffer* buf ) +{ + assert( (unsigned) i < osc_count ); + oscs [i].output = buf; +} + +inline void Nes_Vrc6_Apu::volume( double v ) +{ + double const factor = 0.0967 * 2; + saw_synth.volume( factor / 31 * v ); + square_synth.volume( factor * 0.5 / 15 * v ); +} + +inline void Nes_Vrc6_Apu::treble_eq( blip_eq_t const& eq ) +{ + saw_synth.treble_eq( eq ); + square_synth.treble_eq( eq ); +} + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Nsf_Emu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nsf_Emu.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,622 @@ + +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ + +#include "Nsf_Emu.h" + +#include +#include + +#if !NSF_EMU_APU_ONLY + #include "Nes_Vrc6_Apu.h" + #include "Nes_Namco_Apu.h" + #include "Nes_Fme7_Apu.h" +#endif + +#include "blargg_endian.h" + +/* Copyright (C) 2003-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +#ifndef RUN_NES_CPU + #define RUN_NES_CPU( cpu, count ) cpu.run( count ) +#endif + +#ifndef NSF_BEGIN_FRAME + #define NSF_BEGIN_FRAME() +#endif + +const unsigned low_mem_size = 0x800; +const unsigned page_size = 0x1000; +const long ram_size = 0x10000; +const nes_addr_t rom_begin = 0x8000; +const nes_addr_t bank_select_addr = 0x5ff8; +const nes_addr_t exram_addr = bank_select_addr - (bank_select_addr % Nes_Cpu::page_size); +const int master_clock_divisor = 12; + +const int vrc6_flag = 0x01; +const int namco_flag = 0x10; +const int fme7_flag = 0x20; + +static BOOST::uint8_t unmapped_code [Nes_Cpu::page_size]; + +Nes_Emu::equalizer_t const Nes_Emu::nes_eq = { -1.0, 80 }; +Nes_Emu::equalizer_t const Nes_Emu::famicom_eq = { -15.0, 80 }; + +// ROM + +int Nsf_Emu::read_code( Nsf_Emu* emu, nes_addr_t addr ) +{ + return *emu->cpu.get_code( addr ); +} + +void Nsf_Emu::write_exram( Nsf_Emu* emu, nes_addr_t addr, int data ) +{ + unsigned bank = addr - bank_select_addr; + if ( bank < bank_count ) + { + if ( data < emu->total_banks ) + { + emu->cpu.map_code( (bank + 8) * page_size, page_size, + &emu->rom [data * page_size] ); + } + else + { + dprintf( "Bank %d out of range (%d banks total)\n", + data, (int) emu->total_banks ); + } + } +} + +// APU + +int Nsf_Emu::read_snd( Nsf_Emu* emu, nes_addr_t addr ) +{ + if ( addr == Nes_Apu::status_addr ) + return emu->apu.read_status( emu->cpu.time() ); + return addr >> 8; // high byte of address stays on bus +} + +void Nsf_Emu::write_snd( Nsf_Emu* emu, nes_addr_t addr, int data ) +{ + if ( unsigned (addr - Nes_Apu::start_addr) <= Nes_Apu::end_addr - Nes_Apu::start_addr ) + emu->apu.write_register( emu->cpu.time(), addr, data ); +} + +int Nsf_Emu::pcm_read( void* emu, nes_addr_t addr ) +{ + return ((Nsf_Emu*) emu)->cpu.read( addr ); +} + +// Low Mem + +int Nsf_Emu::read_low_mem( Nsf_Emu* emu, nes_addr_t addr ) +{ + return emu->cpu.low_mem [addr]; +} + +void Nsf_Emu::write_low_mem( Nsf_Emu* emu, nes_addr_t addr, int data ) +{ + emu->cpu.low_mem [addr] = data; +} + +// SRAM + +int Nsf_Emu::read_sram( Nsf_Emu* emu, nes_addr_t addr ) +{ + return emu->sram [addr & (sram_size - 1)]; +} + +void Nsf_Emu::write_sram( Nsf_Emu* emu, nes_addr_t addr, int data ) +{ + emu->sram [addr & (sram_size - 1)] = data; +} + +#if !NSF_EMU_APU_ONLY + +// Namco +int Nsf_Emu::read_namco( Nsf_Emu* emu, nes_addr_t addr ) +{ + if ( addr == Nes_Namco_Apu::data_reg_addr ) + return emu->namco->read_data(); + return addr >> 8; +} + +void Nsf_Emu::write_namco( Nsf_Emu* emu, nes_addr_t addr, int data ) +{ + if ( addr == Nes_Namco_Apu::data_reg_addr ) + emu->namco->write_data( emu->cpu.time(), data ); +} + +void Nsf_Emu::write_namco_addr( Nsf_Emu* emu, nes_addr_t addr, int data ) +{ + if ( addr == Nes_Namco_Apu::addr_reg_addr ) + emu->namco->write_addr( data ); +} + +// VRC6 +void Nsf_Emu::write_vrc6( Nsf_Emu* emu, nes_addr_t addr, int data ) +{ + unsigned reg = addr & (Nes_Vrc6_Apu::addr_step - 1); + unsigned osc = unsigned (addr - Nes_Vrc6_Apu::base_addr) / Nes_Vrc6_Apu::addr_step; + if ( osc < Nes_Vrc6_Apu::osc_count && reg < Nes_Vrc6_Apu::reg_count ) + emu->vrc6->write_osc( emu->cpu.time(), osc, reg, data ); +} + +// FME-7 +void Nsf_Emu::write_fme7( Nsf_Emu* emu, nes_addr_t addr, int data ) +{ + switch ( addr & Nes_Fme7_Apu::addr_mask ) + { + case Nes_Fme7_Apu::latch_addr: + emu->fme7->write_latch( data ); + break; + + case Nes_Fme7_Apu::data_addr: + emu->fme7->write_data( emu->cpu.time(), data ); + break; + } +} + +#endif + +// Unmapped +int Nsf_Emu::read_unmapped( Nsf_Emu*, nes_addr_t addr ) +{ + dprintf( "Read unmapped $%.4X\n", (unsigned) addr ); + return (addr >> 8) & 0xff; // high byte of address stays on bus +} + +void Nsf_Emu::write_unmapped( Nsf_Emu*, nes_addr_t addr, int data ) +{ + #ifdef NDEBUG + return; + #endif + + // some games write to $8000 and $8001 repeatedly + if ( addr == 0x8000 || addr == 0x8001 ) + return; + + // probably namco sound mistakenly turned on in mck + if ( addr == 0x4800 || addr == 0xF800 ) + return; + + // memory mapper? + if ( addr == 0xFFF8 ) + return; + + dprintf( "write_unmapped( 0x%04X, 0x%02X )\n", (unsigned) addr, (unsigned) data ); +} + +Nes_Emu::Nes_Emu( double gain_ ) +{ + cpu.set_emu( this ); + play_addr = 0; + gain = gain_; + apu.dmc_reader( pcm_read, this ); + vrc6 = NULL; + namco = NULL; + fme7 = NULL; + Music_Emu::set_equalizer( nes_eq ); + + // set unmapped code to illegal instruction + memset( unmapped_code, 0x32, sizeof unmapped_code ); +} + +Nes_Emu::~Nes_Emu() +{ + unload(); +} + +void Nsf_Emu::unload() +{ + #if !NSF_EMU_APU_ONLY + delete vrc6; + vrc6 = NULL; + + delete namco; + namco = NULL; + + delete fme7; + fme7 = NULL; + + #endif + + rom.clear(); +} + +const char** Nsf_Emu::voice_names() const +{ + static const char* base_names [] = { + "Square 1", "Square 2", "Triangle", "Noise", "DMC" + }; + static const char* namco_names [] = { + "Square 1", "Square 2", "Triangle", "Noise", "DMC", + "Namco 5&7", "Namco 4&6", "Namco 1-3" + }; + static const char* vrc6_names [] = { + "Square 1", "Square 2", "Triangle", "Noise", "DMC", + "VRC6 Square 1", "VRC6 Square 2", "VRC6 Saw" + }; + static const char* dual_names [] = { + "Square 1", "Square 2", "Triangle", "Noise", "DMC", + "VRC6.1,N106.5&7", "VRC6.2,N106.4&6", "VRC6.3,N106.1-3" + }; + + static const char* fme7_names [] = { + "Square 1", "Square 2", "Triangle", "Noise", "DMC", + "Square 3", "Square 4", "Square 5" + }; + + if ( namco ) + return vrc6 ? dual_names : namco_names; + + if ( vrc6 ) + return vrc6_names; + + if ( fme7 ) + return fme7_names; + + return base_names; +} + +blargg_err_t Nsf_Emu::init_sound() +{ + if ( exp_flags & ~(namco_flag | vrc6_flag | fme7_flag) ) + return "NSF requires unsupported expansion audio hardware"; + + // map memory + cpu.reset( unmapped_code, read_unmapped, write_unmapped ); + cpu.map_memory( 0, low_mem_size, read_low_mem, write_low_mem ); + cpu.map_code( 0, low_mem_size, cpu.low_mem ); + cpu.map_memory( 0x4000, Nes_Cpu::page_size, read_snd, write_snd ); + cpu.map_memory( exram_addr, Nes_Cpu::page_size, read_unmapped, write_exram ); + cpu.map_memory( 0x6000, sram_size, read_sram, write_sram ); + cpu.map_code ( 0x6000, sram_size, sram ); + cpu.map_memory( rom_begin, ram_size - rom_begin, read_code, write_unmapped ); + + set_voice_count( Nes_Apu::osc_count ); + + double adjusted_gain = gain; + + #if NSF_EMU_APU_ONLY + if ( exp_flags ) + return "NSF requires expansion audio hardware"; + #else + + if ( exp_flags ) + set_voice_count( Nes_Apu::osc_count + 3 ); + + // namco + if ( exp_flags & namco_flag ) + { + namco = BLARGG_NEW Nes_Namco_Apu; + BLARGG_CHECK_ALLOC( namco ); + + adjusted_gain *= 0.75; + cpu.map_memory( Nes_Namco_Apu::data_reg_addr, Nes_Cpu::page_size, + read_namco, write_namco ); + cpu.map_memory( Nes_Namco_Apu::addr_reg_addr, Nes_Cpu::page_size, + read_code, write_namco_addr ); + } + + // vrc6 + if ( exp_flags & vrc6_flag ) + { + vrc6 = BLARGG_NEW Nes_Vrc6_Apu; + BLARGG_CHECK_ALLOC( vrc6 ); + + adjusted_gain *= 0.75; + for ( int i = 0; i < Nes_Vrc6_Apu::osc_count; i++ ) + cpu.map_memory( Nes_Vrc6_Apu::base_addr + i * Nes_Vrc6_Apu::addr_step, + Nes_Cpu::page_size, read_code, write_vrc6 ); + } + + // fme7 + if ( exp_flags & fme7_flag ) + { + fme7 = BLARGG_NEW Nes_Fme7_Apu; + BLARGG_CHECK_ALLOC( fme7 ); + + adjusted_gain *= 0.75; + cpu.map_memory( fme7->latch_addr, ram_size - fme7->latch_addr, + read_code, write_fme7 ); + } + // to do: is gain adjustment even needed? other sound chip volumes should work + // naturally with the apu without change. + + if ( namco ) + namco->volume( adjusted_gain ); + + if ( vrc6 ) + vrc6->volume( adjusted_gain ); + + if ( fme7 ) + fme7->volume( adjusted_gain ); + +#endif + + apu.volume( adjusted_gain ); + + return blargg_success; +} + +void Nsf_Emu::update_eq( blip_eq_t const& eq ) +{ + apu.treble_eq( eq ); + + #if !NSF_EMU_APU_ONLY + if ( vrc6 ) + vrc6->treble_eq( eq ); + + if ( namco ) + namco->treble_eq( eq ); + + if ( fme7 ) + fme7->treble_eq( eq ); + #endif +} + +blargg_err_t Nsf_Emu::load( Data_Reader& in ) +{ + header_t h; + BLARGG_RETURN_ERR( in.read( &h, sizeof h ) ); + return load( h, in ); +} + +blargg_err_t Nsf_Emu::load( const header_t& h, Data_Reader& in ) +{ + header_ = h; + unload(); + + // check compatibility + if ( 0 != memcmp( header_.tag, "NESM\x1A", 5 ) ) + return "Not an NSF file"; + if ( header_.vers != 1 ) + return "Unsupported NSF format"; + + // sound and memory + exp_flags = header_.chip_flags; + blargg_err_t err = init_sound(); + if ( err ) + return err; + + // set up data + nes_addr_t load_addr = get_le16( header_.load_addr ); + init_addr = get_le16( header_.init_addr ); + play_addr = get_le16( header_.play_addr ); + if ( !load_addr ) load_addr = rom_begin; + if ( !init_addr ) init_addr = rom_begin; + if ( !play_addr ) play_addr = rom_begin; + if ( load_addr < rom_begin || init_addr < rom_begin ) + return "Invalid address in NSF"; + + // set up rom + total_banks = (in.remain() + load_addr % page_size + page_size - 1) / page_size; + BLARGG_RETURN_ERR( rom.resize( total_banks * page_size ) ); + memset( rom.begin(), 0, rom.size() ); + err = in.read( &rom [load_addr % page_size], in.remain() ); + if ( err ) + { + unload(); + return err; + } + + // bank switching + int first_bank = (load_addr - rom_begin) / page_size; + for ( int i = 0; i < bank_count; i++ ) + { + unsigned bank = i - first_bank; + initial_banks [i] = (bank < (unsigned) total_banks) ? bank : 0; + + if ( header_.banks [i] ) + { + // bank-switched + memcpy( initial_banks, header_.banks, sizeof initial_banks ); + break; + } + } + + // playback rate + unsigned playback_rate = get_le16( header_.ntsc_speed ); + unsigned standard_rate = 0x411A; + double clock_rate = 1789772.72727; + play_period = 262 * 341L * 4 + 2; + pal_only = false; + + // use pal speed if there is no ntsc speed + if ( (header_.speed_flags & 3) == 1 ) + { + pal_only = true; + play_period = 33247 * master_clock_divisor; + clock_rate = 1662607.125; + standard_rate = 0x4E20; + playback_rate = get_le16( header_.pal_speed ); + } + + // use custom playback rate if not the standard rate + if ( playback_rate && playback_rate != standard_rate ) + play_period = long (clock_rate * playback_rate * master_clock_divisor / + 1000000.0); + + // extra flags + int extra_flags = header_.speed_flags; + #if !NSF_EMU_EXTRA_FLAGS + extra_flags = 0; + #endif + needs_long_frames = (extra_flags & 0x10) != 0; + initial_pcm_dac = (extra_flags & 0x20) ? 0x3F : 0; + + set_track_count( header_.track_count ); + + return setup_buffer( (long) (clock_rate + 0.5) ); +} + +void Nsf_Emu::set_voice( int i, Blip_Buffer* buf, Blip_Buffer*, Blip_Buffer* ) +{ + if ( i < Nes_Apu::osc_count ) + { + apu.osc_output( i, buf ); + return; + } + + #if !NSF_EMU_APU_ONLY + if ( vrc6 ) + vrc6->osc_output( i - Nes_Apu::osc_count, buf ); + + if ( fme7 ) + fme7->osc_output( i - Nes_Apu::osc_count, buf ); + + if ( namco ) + { + if ( i < 7 ) + { + i &= 1; + namco->osc_output( i + 4, buf ); + namco->osc_output( i + 6, buf ); + } + else + { + for ( int n = 0; n < namco->osc_count / 2; n++ ) + namco->osc_output( n, buf ); + } + } + #endif +} + +void Nsf_Emu::start_track( int track ) +{ + require( rom.size() ); // file must be loaded + + Classic_Emu::start_track( track ); + + // clear memory + memset( cpu.low_mem, 0, sizeof cpu.low_mem ); + memset( sram, 0, sizeof sram ); + + // initial rom banks + for ( int i = 0; i < bank_count; ++i ) + cpu.write( bank_select_addr + i, initial_banks [i] ); + + // reset sound + apu.reset( pal_only, initial_pcm_dac ); + apu.write_register( 0, 0x4015, 0x0F ); + apu.write_register( 0, 0x4017, needs_long_frames ? 0x80 : 0 ); + + #if !NSF_EMU_APU_ONLY + if ( namco ) + namco->reset(); + + if ( vrc6 ) + vrc6->reset(); + + if ( fme7 ) + fme7->reset(); + #endif + + // reset cpu + cpu.r.pc = exram_addr; + cpu.r.a = track; + cpu.r.x = pal_only; + cpu.r.y = 0; + cpu.r.sp = 0xFF; + cpu.r.status = 0x04; // i flag + + // first call + cpu_jsr( init_addr, -1 ); + next_play = 0; + play_extra = 0; +} + +void Nsf_Emu::cpu_jsr( nes_addr_t pc, int adj ) +{ + unsigned addr = cpu.r.pc + adj; + cpu.r.pc = pc; + cpu.push_byte( addr >> 8 ); + cpu.push_byte( addr ); +} + +void Nsf_Emu::call_play() +{ + cpu_jsr( play_addr, -1 ); +} + +blip_time_t Nsf_Emu::run_clocks( blip_time_t duration, bool* ) +{ + // run cpu + cpu.set_time( 0 ); + bool first_illegal = true; // avoid swamping output with illegal instruction errors + while ( cpu.time() < duration ) + { + // check for idle cpu + if ( cpu.r.pc == exram_addr ) + { + if ( next_play > duration ) + { + cpu.set_time( duration ); + break; + } + + if ( next_play > cpu.time() ) + cpu.set_time( next_play ); + + nes_time_t period = (play_period + play_extra) / master_clock_divisor; + play_extra = play_period - period * master_clock_divisor; + next_play += period; + call_play(); + } + + Nes_Cpu::result_t result = RUN_NES_CPU( cpu, duration ); + if ( result == Nes_Cpu::result_badop && cpu.r.pc != exram_addr ) + { + if ( cpu.r.pc > 0xffff ) + { + cpu.r.pc &= 0xffff; + dprintf( "PC wrapped around\n" ); + } + else + { + cpu.r.pc = (cpu.r.pc + 1) & 0xffff; + cpu.set_time( cpu.time() + 4 ); + log_error(); + if ( first_illegal ) + { + first_illegal = false; + dprintf( "Bad opcode $%.2x at $%.4x\n", + (int) cpu.read( cpu.r.pc ), (int) cpu.r.pc ); + } + } + } + } + + // end time frame + duration = cpu.time(); + next_play -= duration; + if ( next_play < 0 ) // could go negative if routine is taking too long to return + next_play = 0; + apu.end_frame( duration ); + + #if !NSF_EMU_APU_ONLY + if ( namco ) + namco->end_frame( duration ); + + if ( vrc6 ) + vrc6->end_frame( duration ); + + if ( fme7 ) + fme7->end_frame( duration ); + + #endif + + return duration; +} + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Nsf_Emu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nsf_Emu.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,137 @@ + +// Nintendo Entertainment System (NES) NSF music file emulator + +// Game_Music_Emu 0.3.0 + +#ifndef NSF_EMU_H +#define NSF_EMU_H + +#include "Classic_Emu.h" +#include "Nes_Apu.h" +#include "Nes_Cpu.h" + +typedef Nes_Emu Nsf_Emu; + +class Nes_Emu : public Classic_Emu { +public: + + // Set internal gain, where 1.0 results in almost no clamping. Default gain + // roughly matches volume of other emulators. + Nes_Emu( double gain = 1.4 ); + + // NSF file header + struct header_t + { + char tag [5]; + byte vers; + byte track_count; + byte first_track; + byte load_addr [2]; + byte init_addr [2]; + byte play_addr [2]; + char game [32]; + char author [32]; + char copyright [32]; + byte ntsc_speed [2]; + byte banks [8]; + byte pal_speed [2]; + byte speed_flags; + byte chip_flags; + byte unused [4]; + + enum { song = 0 }; // no song titles + }; + BOOST_STATIC_ASSERT( sizeof (header_t) == 0x80 ); + + // Load NSF data + blargg_err_t load( Data_Reader& ); + + // Load NSF using already-loaded header and remaining data + blargg_err_t load( header_t const&, Data_Reader& ); + + // Header for currently loaded NSF + header_t const& header() const { return header_; } + + // Equalizer profiles for US NES and Japanese Famicom + static equalizer_t const nes_eq; + static equalizer_t const famicom_eq; + +public: + ~Nes_Emu(); + void start_track( int ); + Nes_Apu* apu_() { return &apu; } + const char** voice_names() const; +protected: + void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); + void update_eq( blip_eq_t const& ); + blip_time_t run_clocks( blip_time_t, bool* ); + virtual void call_play(); +protected: + // initial state + enum { bank_count = 8 }; + byte initial_banks [bank_count]; + int initial_pcm_dac; + double gain; + bool needs_long_frames; + bool pal_only; + unsigned init_addr; + unsigned play_addr; + int exp_flags; + + // timing + nes_time_t next_play; + long play_period; + int play_extra; + nes_time_t clock() const; + nes_time_t next_irq( nes_time_t end_time ); + static void irq_changed( void* ); + + // rom + int total_banks; + blargg_vector rom; + static int read_code( Nsf_Emu*, nes_addr_t ); + void unload(); + + blargg_err_t init_sound(); + + // expansion sound + + class Nes_Namco_Apu* namco; + static int read_namco( Nsf_Emu*, nes_addr_t ); + static void write_namco( Nsf_Emu*, nes_addr_t, int ); + static void write_namco_addr( Nsf_Emu*, nes_addr_t, int ); + + class Nes_Vrc6_Apu* vrc6; + static void write_vrc6( Nsf_Emu*, nes_addr_t, int ); + + class Nes_Fme7_Apu* fme7; + static void write_fme7( Nsf_Emu*, nes_addr_t, int ); + + // large objects + + header_t header_; + + // cpu + Nes_Cpu cpu; + void cpu_jsr( unsigned pc, int adj ); + static int read_low_mem( Nsf_Emu*, nes_addr_t ); + static void write_low_mem( Nsf_Emu*, nes_addr_t, int ); + static int read_unmapped( Nsf_Emu*, nes_addr_t ); + static void write_unmapped( Nsf_Emu*, nes_addr_t, int ); + static void write_exram( Nsf_Emu*, nes_addr_t, int ); + + // apu + Nes_Apu apu; + static int read_snd( Nsf_Emu*, nes_addr_t ); + static void write_snd( Nsf_Emu*, nes_addr_t, int ); + static int pcm_read( void*, nes_addr_t ); + + // sram + enum { sram_size = 0x2000 }; + byte sram [sram_size]; + static int read_sram( Nsf_Emu*, nes_addr_t ); + static void write_sram( Nsf_Emu*, nes_addr_t, int ); +}; + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Nsfe_Emu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nsfe_Emu.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,269 @@ + +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ + +#include "Nsfe_Emu.h" + +#include "blargg_endian.h" +#include + +/* Copyright (C) 2005-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +#define NSFE_TAG( a, b, c, d ) (d*0x1000000L + c*0x10000L + b*0x100L + a) + +Nsfe_Info::Nsfe_Info() +{ + playlist_enabled = false; +} + +Nsfe_Info::~Nsfe_Info() { } + +void Nsfe_Info::enable_playlist( bool b ) +{ + playlist_enabled = b; + info_.track_count = (b && playlist_size()) ? playlist_size() : track_count_; +} + +int Nsfe_Info::remap_track( int i ) const +{ + if ( !playlist_enabled || !playlist_size() ) + return i; + + return playlist_entry( i ); +} + +void Nsfe_Emu::start_track( int i ) +{ + Nsf_Emu::start_track( remap_track( i ) ); +} + +const char* Nsfe_Info::track_name( int i ) const +{ + i = remap_track( i ); + if ( i < track_names.size() ) + return track_names [i]; + + return ""; +} + +long Nsfe_Info::track_time( int i ) const +{ + i = remap_track( i ); + if ( i < track_times.size() ) + return track_times [i]; + + return 0; +} + +// Read little-endian 32-bit int +static blargg_err_t read_le32( Emu_Reader& in, long* out ) +{ + unsigned char buf [4]; + BLARGG_RETURN_ERR( in.read( buf, sizeof buf ) ); + *out = get_le32( buf ); + return blargg_success; +} + +// Read multiple strings and separate into individual strings +static blargg_err_t read_strs( Emu_Reader& in, long size, std::vector& chars, + std::vector& strs ) +{ + chars.resize( size + 1 ); + chars [size] = 0; // in case last string doesn't have terminator + BLARGG_RETURN_ERR( in.read( &chars [0], size ) ); + + for ( int i = 0; i < size; i++ ) + { + strs.push_back( &chars [i] ); + while ( i < size && chars [i] ) + i++; + } + + return blargg_success; +} + +// Copy in to out, where out has out_max characters allocated. Truncate to +// out_max - 1 characters. +static void copy_str( const char* in, char* out, int out_max ) +{ + out [out_max - 1] = 0; + strncpy( out, in, out_max - 1 ); +} + +struct nsfe_info_t { + unsigned char load_addr [2]; + unsigned char init_addr [2]; + unsigned char play_addr [2]; + unsigned char speed_flags; + unsigned char chip_flags; + unsigned char track_count; + unsigned char first_track; +}; +BOOST_STATIC_ASSERT( sizeof (nsfe_info_t) == 10 ); + +blargg_err_t Nsfe_Info::load( const header_t& nsfe_tag, Emu_Reader& in, Nsf_Emu* nsf_emu ) +{ + // check header + if ( memcmp( nsfe_tag.tag, "NSFE", 4 ) ) + return "Not an NSFE file"; + + // free previous info + track_name_data.clear(); + track_names.clear(); + playlist.clear(); + track_times.clear(); + + // default nsf header + static const Nsf_Emu::header_t base_header = + { + {'N','E','S','M','\x1A'},// tag + 1, // version + 1, 1, // track count, first track + {0,0},{0,0},{0,0}, // addresses + "","","", // strings + {0x1A, 0x41}, // NTSC rate + {0,0,0,0,0,0,0,0}, // banks + {0x20, 0x4E}, // PAL rate + 0, 0, // flags + {0,0,0,0} // unused + }; + Nsf_Emu::header_t& header = info_; + header = base_header; + + // parse tags + int phase = 0; + while ( phase != 3 ) + { + // read size and tag + long size = 0; + long tag = 0; + BLARGG_RETURN_ERR( read_le32( in, &size ) ); + BLARGG_RETURN_ERR( read_le32( in, &tag ) ); + + switch ( tag ) + { + case NSFE_TAG('I','N','F','O'): { + check( phase == 0 ); + if ( size < 8 ) + return "Bad NSFE file"; + + nsfe_info_t info; + info.track_count = 1; + info.first_track = 0; + + int s = size; + if ( s > (int) sizeof info ) + s = sizeof info; + BLARGG_RETURN_ERR( in.read( &info, s ) ); + BLARGG_RETURN_ERR( in.skip( size - s ) ); + phase = 1; + info_.speed_flags = info.speed_flags; + info_.chip_flags = info.chip_flags; + info_.track_count = info.track_count; + this->track_count_ = info.track_count; + info_.first_track = info.first_track; + std::memcpy( info_.load_addr, info.load_addr, 2 * 3 ); + break; + } + + case NSFE_TAG('B','A','N','K'): + if ( size > (int) sizeof info_.banks ) + return "Bad NSFE file"; + BLARGG_RETURN_ERR( in.read( info_.banks, size ) ); + break; + + case NSFE_TAG('a','u','t','h'): { + std::vector chars; + std::vector strs; + BLARGG_RETURN_ERR( read_strs( in, size, chars, strs ) ); + int n = strs.size(); + + if ( n > 3 ) + copy_str( strs [3], info_.ripper, sizeof info_.ripper ); + + if ( n > 2 ) + copy_str( strs [2], info_.copyright, sizeof info_.copyright ); + + if ( n > 1 ) + copy_str( strs [1], info_.author, sizeof info_.author ); + + if ( n > 0 ) + copy_str( strs [0], info_.game, sizeof info_.game ); + + break; + } + + case NSFE_TAG('t','i','m','e'): { + track_times.resize( size / 4 ); + for ( int i = 0; i < track_times.size(); i++ ) + BLARGG_RETURN_ERR( read_le32( in, &track_times [i] ) ); + break; + } + + case NSFE_TAG('t','l','b','l'): + BLARGG_RETURN_ERR( read_strs( in, size, track_name_data, track_names ) ); + break; + + case NSFE_TAG('p','l','s','t'): + playlist.resize( size ); + BLARGG_RETURN_ERR( in.read( &playlist [0], size ) ); + break; + + case NSFE_TAG('D','A','T','A'): { + check( phase == 1 ); + phase = 2; + if ( !nsf_emu ) + { + in.skip( size ); + } + else + { + Subset_Reader sub( &in, size ); // limit emu to nsf data + BLARGG_RETURN_ERR( nsf_emu->load( info_, sub ) ); + check( sub.remain() == 0 ); + } + break; + } + + case NSFE_TAG('N','E','N','D'): + check( phase == 2 ); + phase = 3; + break; + + default: + // tags that can be skipped start with a lowercase character + check( std::islower( (tag >> 24) & 0xff ) ); + BLARGG_RETURN_ERR( in.skip( size ) ); + break; + } + } + + enable_playlist( playlist_enabled ); + + return blargg_success; +} + +blargg_err_t Nsfe_Info::load( Emu_Reader& in, Nsf_Emu* nsf_emu ) +{ + header_t h; + BLARGG_RETURN_ERR( in.read( &h, sizeof h ) ); + return load( h, in, nsf_emu ); +} + +blargg_err_t Nsfe_Info::load_file( const char* path, Nsf_Emu* emu ) +{ + Std_File_Reader in; + BLARGG_RETURN_ERR( in.open( path ) ); + return load( in, emu ); +} + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Nsfe_Emu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nsfe_Emu.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,103 @@ + +// Nintendo Entertainment System (NES) NSFE-format game music file emulator + +// Game_Music_Emu 0.3.0 + +#ifndef NSFE_EMU_H +#define NSFE_EMU_H + +#include "blargg_common.h" +#include "Nsf_Emu.h" + +// to do: eliminate dependence on bloated std vector +#include + +class Nsfe_Info { +public: + struct header_t + { + char tag [4]; // 'N', 'S', 'F', 'E' + }; + BOOST_STATIC_ASSERT( sizeof (header_t) == 4 ); + + // Load NSFE info and optionally load file into Nsf_Emu + blargg_err_t load_file( const char* path, Nsf_Emu* = 0 ); + + // Load NSFE info and optionally load file into Nsf_Emu + blargg_err_t load( Data_Reader&, Nsf_Emu* = 0 ); + + // Load NSFE info and optionally load file into Nsf_Emu + blargg_err_t load( header_t const&, Data_Reader&, Nsf_Emu* = 0 ); + + // Information about current file + struct info_t : Nsf_Emu::header_t + { + // These (longer) fields hide those in Nsf_Emu::header_t + char game [256]; + char author [256]; + char copyright [256]; + char ripper [256]; + }; + const info_t& info() const { return info_; } + + // All track indicies are 0-based + + // Name of track [i], or "" if none available + const char* track_name( int i ) const; + + // Duration of track [i] in milliseconds, negative if endless, or 0 if none available + long track_time( int i ) const; + + // Optional playlist consisting of track indicies + int playlist_size() const { return playlist.size(); } + int playlist_entry( int i ) const { return playlist [i]; } + + // If true and playlist is present in NSFE file, remap track numbers using it + void enable_playlist( bool = true ); + +public: + Nsfe_Info(); + ~Nsfe_Info(); + int track_count() const { return info_.track_count; } +private: + std::vector track_name_data; + std::vector track_names; + std::vector playlist; + std::vector track_times; + int track_count_; + info_t info_; + bool playlist_enabled; + + int remap_track( int i ) const; + friend class Nsfe_Emu; +}; + +class Nsfe_Emu : public Nsf_Emu, public Nsfe_Info { +public: + // See Nsf_Emu.h for further information + + Nsfe_Emu( double gain = 1.4 ) : Nsf_Emu( gain ) { } + + typedef Nsfe_Info::header_t header_t; + + // Load NSFE data + blargg_err_t load( Emu_Reader& r ) { return Nsfe_Info::load( r, this ); } + + // Load NSFE using already-loaded header and remaining data + blargg_err_t load( header_t const& h, Emu_Reader& r ) { return Nsfe_Info::load( h, r, this ); } + +public: + Nsf_Emu::track_count; + Nsf_Emu::load_file; + void start_track( int ); + void enable_playlist( bool = true ); +}; + +inline void Nsfe_Emu::enable_playlist( bool b ) +{ + Nsfe_Info::enable_playlist( b ); + set_track_count( info().track_count ); +} + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Sms_Apu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Sms_Apu.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,319 @@ + +// Sms_Snd_Emu 0.1.3. http://www.slack.net/~ant/ + +#include "Sms_Apu.h" + +/* Copyright (C) 2003-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +// Sms_Osc + +Sms_Osc::Sms_Osc() +{ + output = NULL; + outputs [0] = NULL; // always stays NULL + outputs [1] = NULL; + outputs [2] = NULL; + outputs [3] = NULL; +} + +void Sms_Osc::reset() +{ + delay = 0; + last_amp = 0; + volume = 0; + output_select = 3; + output = outputs [3]; +} + +// Sms_Square + +inline void Sms_Square::reset() +{ + period = 0; + phase = 0; + Sms_Osc::reset(); +} + +void Sms_Square::run( sms_time_t time, sms_time_t end_time ) +{ + if ( !volume || period <= 128 ) + { + // ignore 16kHz and higher + if ( last_amp ) + { + synth->offset( time, -last_amp, output ); + last_amp = 0; + } + time += delay; + if ( !period ) + { + time = end_time; + } + else if ( time < end_time ) + { + // keep calculating phase + int count = (end_time - time + period - 1) / period; + phase = (phase + count) & 1; + time += count * period; + } + } + else + { + int amp = phase ? volume : -volume; + int delta = amp - last_amp; + if ( delta ) + { + last_amp = amp; + synth->offset( time, delta, output ); + } + + time += delay; + if ( time < end_time ) + { + Blip_Buffer* const output = this->output; + int delta = amp * 2; + do + { + delta = -delta; + synth->offset_inline( time, delta, output ); + time += period; + phase ^= 1; + } + while ( time < end_time ); + this->last_amp = phase ? volume : -volume; + } + } + delay = time - end_time; +} + +// Sms_Noise + +static const int noise_periods [3] = { 0x100, 0x200, 0x400 }; + +inline void Sms_Noise::reset() +{ + period = &noise_periods [0]; + shifter = 0x8000; + tap = 12; + Sms_Osc::reset(); +} + +void Sms_Noise::run( sms_time_t time, sms_time_t end_time ) +{ + int amp = volume; + if ( shifter & 1 ) + amp = -amp; + + int delta = amp - last_amp; + if ( delta ) + { + last_amp = amp; + synth.offset( time, delta, output ); + } + + time += delay; + if ( !volume ) + time = end_time; + + if ( time < end_time ) + { + Blip_Buffer* const output = this->output; + unsigned shifter = this->shifter; + int delta = amp * 2; + int period = *this->period * 2; + if ( !period ) + period = 16; + + do + { + int changed = (shifter + 1) & 2; // set if prev and next bits differ + shifter = (((shifter << 15) ^ (shifter << tap)) & 0x8000) | (shifter >> 1); + if ( changed ) + { + delta = -delta; + synth.offset_inline( time, delta, output ); + } + time += period; + } + while ( time < end_time ); + + this->shifter = shifter; + this->last_amp = delta >> 1; + } + delay = time - end_time; +} + +// Sms_Apu + +Sms_Apu::Sms_Apu() +{ + for ( int i = 0; i < 3; i++ ) + { + squares [i].synth = &square_synth; + oscs [i] = &squares [i]; + } + oscs [3] = &noise; + + volume( 1.0 ); + reset(); +} + +Sms_Apu::~Sms_Apu() +{ +} + +void Sms_Apu::volume( double vol ) +{ + vol *= 0.85 / (osc_count * 64 * 2); + square_synth.volume( vol ); + noise.synth.volume( vol ); +} + +void Sms_Apu::treble_eq( const blip_eq_t& eq ) +{ + square_synth.treble_eq( eq ); + noise.synth.treble_eq( eq ); +} + +void Sms_Apu::osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +{ + require( (unsigned) index < osc_count ); + require( (center && left && right) || (!center && !left && !right) ); + Sms_Osc& osc = *oscs [index]; + osc.outputs [1] = right; + osc.outputs [2] = left; + osc.outputs [3] = center; + osc.output = osc.outputs [osc.output_select]; +} + +void Sms_Apu::output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, center, left, right ); +} + +void Sms_Apu::reset() +{ + stereo_found = false; + last_time = 0; + latch = 0; + + squares [0].reset(); + squares [1].reset(); + squares [2].reset(); + noise.reset(); +} + +void Sms_Apu::run_until( sms_time_t end_time ) +{ + require( end_time >= last_time ); // end_time must not be before previous time + + if ( end_time > last_time ) + { + // run oscillators + for ( int i = 0; i < osc_count; ++i ) + { + Sms_Osc& osc = *oscs [i]; + if ( osc.output ) + { + if ( osc.output != osc.outputs [3] ) + stereo_found = true; // playing on side output + + if ( i < 3 ) + squares [i].run( last_time, end_time ); + else + noise.run( last_time, end_time ); + } + } + + last_time = end_time; + } +} + +bool Sms_Apu::end_frame( sms_time_t end_time ) +{ + if ( end_time > last_time ) + run_until( end_time ); + + assert( last_time >= end_time ); + last_time -= end_time; + + bool result = stereo_found; + stereo_found = false; + return result; +} + +void Sms_Apu::write_ggstereo( sms_time_t time, int data ) +{ + require( (unsigned) data <= 0xFF ); + + run_until( time ); + + for ( int i = 0; i < osc_count; i++ ) + { + Sms_Osc& osc = *oscs [i]; + int flags = data >> i; + Blip_Buffer* old_output = osc.output; + osc.output_select = (flags >> 3 & 2) | (flags & 1); + osc.output = osc.outputs [osc.output_select]; + if ( osc.output != old_output && osc.last_amp ) + { + if ( old_output ) + square_synth.offset( time, -osc.last_amp, old_output ); + osc.last_amp = 0; + } + } +} + +static const unsigned char volumes [16] = { + // volumes [i] = 64 * pow( 1.26, 15 - i ) / pow( 1.26, 15 ) + 64, 50, 39, 31, 24, 19, 15, 12, 9, 7, 5, 4, 3, 2, 1, 0 +}; + +void Sms_Apu::write_data( sms_time_t time, int data ) +{ + require( (unsigned) data <= 0xFF ); + + run_until( time ); + + if ( data & 0x80 ) + latch = data; + + int index = (latch >> 5) & 3; + if ( latch & 0x10 ) + { + oscs [index]->volume = volumes [data & 15]; + } + else if ( index < 3 ) + { + Sms_Square& sq = squares [index]; + if ( data & 0x80 ) + sq.period = (sq.period & 0xFF00) | (data << 4 & 0x00FF); + else + sq.period = (sq.period & 0x00FF) | (data << 8 & 0x3F00); + } + else + { + int select = data & 3; + if ( select < 3 ) + noise.period = &noise_periods [select]; + else + noise.period = &squares [2].period; + + int const tap_disabled = 16; + noise.tap = (data & 0x04) ? 12 : tap_disabled; + noise.shifter = 0x8000; + } +} + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Sms_Apu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Sms_Apu.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,81 @@ + +// Sega Master System SN76489 PSG sound chip emulator + +// Sms_Snd_Emu 0.1.3 + +#ifndef SMS_APU_H +#define SMS_APU_H + +typedef long sms_time_t; // clock cycle count + +#include "Sms_Oscs.h" + +class Sms_Apu { +public: + // Set overall volume of all oscillators, where 1.0 is full volume + void volume( double ); + + // Set treble equalization + void treble_eq( const blip_eq_t& ); + + // Outputs can be assigned to a single buffer for mono output, or to three + // buffers for stereo output (using Stereo_Buffer to do the mixing). + + // Assign all oscillator outputs to specified buffer(s). If buffer + // is NULL, silences all oscillators. + void output( Blip_Buffer* mono ); + void output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ); + + // Assign single oscillator output to buffer(s). Valid indicies are 0 to 3, + // which refer to Square 1, Square 2, Square 3, and Noise. If buffer is NULL, + // silences oscillator. + enum { osc_count = 4 }; + void osc_output( int index, Blip_Buffer* mono ); + void osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ); + + // Reset oscillators and internal state + void reset(); + + // Write GameGear left/right assignment byte + void write_ggstereo( sms_time_t, int ); + + // Write to data port + void write_data( sms_time_t, int ); + + // Run all oscillators up to specified time, end current frame, then + // start a new frame at time 0. Returns true if any oscillators added + // sound to one of the left/right buffers, false if they only added + // to the center buffer. + bool end_frame( sms_time_t ); + +public: + Sms_Apu(); + ~Sms_Apu(); +private: + // noncopyable + Sms_Apu( const Sms_Apu& ); + Sms_Apu& operator = ( const Sms_Apu& ); + + Sms_Osc* oscs [osc_count]; + Sms_Square squares [3]; + Sms_Square::Synth square_synth; // used by squares + sms_time_t last_time; + int latch; + bool stereo_found; + Sms_Noise noise; + + void run_until( sms_time_t ); +}; + +struct sms_apu_state_t +{ + unsigned char regs [8] [2]; + unsigned char latch; +}; + +inline void Sms_Apu::output( Blip_Buffer* b ) { output( b, b, b ); } + +inline void Sms_Apu::osc_output( int i, Blip_Buffer* b ) { osc_output( i, b, b, b ); } + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Sms_Oscs.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Sms_Oscs.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,52 @@ + +// Private oscillators used by Sms_Apu + +// Sms_Snd_Emu 0.1.3 + +#ifndef SMS_OSCS_H +#define SMS_OSCS_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +struct Sms_Osc +{ + Blip_Buffer* outputs [4]; // NULL, right, left, center + Blip_Buffer* output; + int output_select; + + int delay; + int last_amp; + int volume; + + Sms_Osc(); + void reset(); +}; + +struct Sms_Square : Sms_Osc +{ + int period; + int phase; + + typedef Blip_Synth Synth; + const Synth* synth; + + void reset(); + void run( sms_time_t, sms_time_t ); +}; + +struct Sms_Noise : Sms_Osc +{ + const int* period; + unsigned shifter; + unsigned tap; + + typedef Blip_Synth Synth; + Synth synth; + + void reset(); + void run( sms_time_t, sms_time_t ); +}; + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Snes_Spc.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Snes_Spc.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,475 @@ + +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ + +#include "Snes_Spc.h" + +#include +#include + +/* Copyright (C) 2004-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +// always in the future (CPU time can go over 0, but not by this much) +int const timer_disabled_time = 127; + +Snes_Spc::Snes_Spc() : dsp( ram ), cpu( this, ram ) +{ + timer [0].shift = 7; // 8 kHz + timer [1].shift = 7; // 8 kHz + timer [2].shift = 4; // 64 kHz + + // Put STOP instruction past end of memory to catch PC overflow. + memset( ram + ram_size, 0xff, (sizeof ram) - ram_size ); +} + +// Load + +blargg_err_t Snes_Spc::load_spc( const void* data, long size, bool clear_echo_ ) +{ + struct spc_file_t { + char signature [27]; + char unused [10]; + uint8_t pc [2]; + uint8_t a; + uint8_t x; + uint8_t y; + uint8_t status; + uint8_t sp; + char unused2 [212]; + uint8_t ram [0x10000]; + uint8_t dsp [128]; + }; + BOOST_STATIC_ASSERT( sizeof (spc_file_t) == spc_file_size ); + + const spc_file_t* spc = (spc_file_t*) data; + + if ( size < spc_file_size ) + return "Not an SPC file"; + + if ( strncmp( spc->signature, "SNES-SPC700 Sound File Data", 27 ) != 0 ) + return "Not an SPC file"; + + registers_t regs; + regs.pc = spc->pc [1] * 0x100 + spc->pc [0]; + regs.a = spc->a; + regs.x = spc->x; + regs.y = spc->y; + regs.status = spc->status; + regs.sp = spc->sp; + + const char* error = load_state( regs, spc->ram, spc->dsp ); + + echo_accessed = false; + + if ( clear_echo_ ) + clear_echo(); + + return error; +} + +void Snes_Spc::clear_echo() +{ + if ( !(dsp.read( 0x6c ) & 0x20) ) + { + unsigned addr = 0x100 * dsp.read( 0x6d ); + unsigned size = 0x800 * dsp.read( 0x7d ); + unsigned limit = ram_size - addr; + memset( ram + addr, 0xff, (size < limit) ? size : limit ); + } +} + +// Handle other file formats (emulator save states) in user code, not here. + +blargg_err_t Snes_Spc::load_state( const registers_t& cpu_state, const void* new_ram, + const void* dsp_state ) +{ + // cpu + cpu.r = cpu_state; + + // Allow DSP to generate one sample before code starts + // (Tengai Makyo Zero, Tenjin's Table Toss first notes are lost since it + // clears KON 31 cycles from starting execution. It works on the SNES + // since the SPC player adds a few extra cycles delay after restoring + // KON from the DSP registers at the end of an SPC file). + extra_cycles = 32; + + // ram + memcpy( ram, new_ram, ram_size ); + memcpy( extra_ram, ram + rom_addr, sizeof extra_ram ); + + // boot rom (have to force enable_rom() to update it) + rom_enabled = !(ram [0xf1] & 0x80); + enable_rom( !rom_enabled ); + + // dsp + dsp.reset(); + int i; + for ( i = 0; i < Spc_Dsp::register_count; i++ ) + dsp.write( i, ((uint8_t*) dsp_state) [i] ); + + // timers + for ( i = 0; i < timer_count; i++ ) + { + Timer& t = timer [i]; + + t.next_tick = 0; + t.enabled = (ram [0xf1] >> i) & 1; + if ( !t.enabled ) + t.next_tick = timer_disabled_time; + t.count = 0; + t.counter = ram [0xfd + i] & 15; + + int p = ram [0xfa + i]; + t.period = p ? p : 0x100; + } + + // Handle registers which already give 0 when read by setting RAM and not changing it. + // Put STOP instruction in registers which can be read, to catch attempted CPU execution. + ram [0xf0] = 0; + ram [0xf1] = 0; + ram [0xf3] = 0xff; + ram [0xfa] = 0; + ram [0xfb] = 0; + ram [0xfc] = 0; + ram [0xfd] = 0xff; + ram [0xfe] = 0xff; + ram [0xff] = 0xff; + + return NULL; // success +} + +// Hardware + +// Current time starts negative and ends at 0 +inline spc_time_t Snes_Spc::time() const +{ + return -cpu.remain(); +} + +// Keep track of next time to run and avoid a function call if it hasn't been reached. + +// Timers + +void Snes_Spc::Timer::run_until_( spc_time_t time ) +{ + if ( !enabled ) + dprintf( "next_tick: %ld, time: %ld", (long) next_tick, (long) time ); + assert( enabled ); // when disabled, next_tick should always be in the future + + int elapsed = ((time - next_tick) >> shift) + 1; + next_tick += elapsed << shift; + elapsed += count; + if ( elapsed >= period ) { // avoid costly divide + int n = elapsed / period; + elapsed -= n * period; + counter = (counter + n) & 15; + } + count = elapsed; +} + +// DSP + +const int clocks_per_sample = 32; // 1.024 MHz CPU clock / 32000 samples per second + +void Snes_Spc::run_dsp_( spc_time_t time ) +{ + int count = ((time - next_dsp) >> 5) + 1; // divide by clocks_per_sample + sample_t* buf = sample_buf; + if ( buf ) { + sample_buf = buf + count * 2; // stereo + assert( sample_buf <= buf_end ); + } + next_dsp += count * clocks_per_sample; + dsp.run( count, buf ); +} + +inline void Snes_Spc::run_dsp( spc_time_t time ) +{ + if ( time >= next_dsp ) + run_dsp_( time ); +} + +// 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. +inline void Snes_Spc::check_for_echo_access( spc_addr_t addr ) +{ + if ( !echo_accessed && !(dsp.read( 0x6c ) & 0x20) ) + { + // ** If echo accesses are found that require running the DSP, cache + // the start and end address on DSP writes to speed up checking. + + unsigned start = 0x100 * dsp.read( 0x6d ); + unsigned end = start + 0x800 * dsp.read( 0x7d ); + if ( start <= addr && addr < end ) { + echo_accessed = true; + dprintf( "Read/write at $%04X within echo buffer\n", (unsigned) addr ); + } + } +} + +// Read + +int Snes_Spc::read( spc_addr_t addr ) +{ + // zero page ram is used most often + if ( addr < 0xf0 ) + return ram [addr]; + + // dsp + if ( addr == 0xf3 ) { + run_dsp( time() ); + if ( ram [0xf2] >= Spc_Dsp::register_count ) + dprintf( "DSP read from $%02X\n", (int) ram [0xf2] ); + return dsp.read( ram [0xf2] & 0x7f ); + } + + // counters + unsigned i = addr - 0xfd; // negative converts to large positive unsigned + if ( i < timer_count ) { + Timer& t = timer [i]; + t.run_until( time() ); + int result = t.counter; + t.counter = 0; + return result; + } + + if ( addr == 0xf0 || addr == 0xf1 || addr == 0xf8 || + addr == 0xf9 || addr == 0xfa ) + dprintf( "Read from register $%02X\n", (int) addr ); + + // Registers which always read as 0 are handled by setting ram [reg] to 0 + // at startup then never changing that value. + + check(( check_for_echo_access( addr ), true )); + + // ram + return ram [addr]; +} + + +// Write + +const unsigned char Snes_Spc::boot_rom [rom_size] = { // verified + 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 +}; + +void Snes_Spc::enable_rom( bool enable ) +{ + if ( rom_enabled != enable ) + { + rom_enabled = enable; + memcpy( ram + rom_addr, (enable ? boot_rom : extra_ram), rom_size ); + } +} + +void Snes_Spc::write( spc_addr_t addr, int data ) +{ + // first page is very common + if ( addr < 0xf0 ) { + ram [addr] = data; + } + else switch ( addr ) + { + // RAM + default: + check(( check_for_echo_access( addr ), true )); + if ( addr < rom_addr ) { + ram [addr] = data; + } + else { + extra_ram [addr - rom_addr] = data; + if ( !rom_enabled ) + ram [addr] = data; + } + break; + + // DSP + //case 0xf2: // mapped to RAM + case 0xf3: { + run_dsp( time() ); + int reg = ram [0xf2]; + if ( next_dsp > 0 ) { + // skip mode + + // key press + if ( reg == 0x4C ) + keys_pressed |= data & ~dsp.read( 0x5C ); + + // key release + if ( reg == 0x5C ) { + keys_released |= data; + keys_pressed &= ~data; + } + } + if ( reg < Spc_Dsp::register_count ) { + dsp.write( reg, data ); + } + else { + dprintf( "DSP write to $%02X\n", (int) reg ); + } + break; + } + + case 0xf0: // Test register + dprintf( "Wrote $%02X to $F0\n", (int) data ); + break; + + // Config + case 0xf1: + { + // timers + for ( int i = 0; i < timer_count; i++ ) + { + Timer& t = timer [i]; + if ( !(data & (1 << i)) ) { + t.enabled = 0; + t.next_tick = timer_disabled_time; + } + else if ( !t.enabled ) { + // just enabled + t.enabled = 1; + t.counter = 0; + t.count = 0; + t.next_tick = time(); + } + } + + // port clears + if ( data & 0x10 ) { + ram [0xf4] = 0; + ram [0xf5] = 0; + } + if ( data & 0x20 ) { + ram [0xf6] = 0; + ram [0xf7] = 0; + } + + enable_rom( data & 0x80 ); + + break; + } + + // Ports + case 0xf4: + case 0xf5: + case 0xf6: + case 0xf7: + // to do: handle output ports + break; + + //case 0xf8: // verified on SNES that these are read/write (RAM) + //case 0xf9: + + // Timers + case 0xfa: + case 0xfb: + case 0xfc: { + Timer& t = timer [addr - 0xfa]; + if ( (t.period & 0xff) != data ) { + t.run_until( time() ); + t.period = data ? data : 0x100; + } + break; + } + + // Counters (cleared on write) + case 0xfd: + case 0xfe: + case 0xff: + dprintf( "Wrote to counter $%02X\n", (int) addr ); + timer [addr - 0xfd].counter = 0; + break; + } +} + +// Play + +blargg_err_t Snes_Spc::skip( long count ) +{ + if ( count > 4 * 32000L ) + { + // don't run DSP for long durations (2-3 times faster) + + const long sync_count = 32000L * 2; + + // keep track of any keys pressed/released (and not subsequently released) + keys_pressed = 0; + keys_released = 0; + // sentinel tells play to ignore DSP + BLARGG_RETURN_ERR( play( count - sync_count, skip_sentinel ) ); + + // press/release keys now + dsp.write( 0x5C, keys_released & ~keys_pressed ); + dsp.write( 0x4C, keys_pressed ); + + clear_echo(); + + // play the last few seconds normally to help synchronize DSP + count = sync_count; + } + + return play( count ); +} + +blargg_err_t Snes_Spc::play( long count, sample_t* out ) +{ + require( count % 2 == 0 ); // output is always in pairs of samples + + // CPU time() runs from -duration to 0 + spc_time_t duration = (count / 2) * clocks_per_sample; + + // DSP output is made on-the-fly when the CPU reads/writes DSP registers + sample_buf = out; + buf_end = out + (out && out != skip_sentinel ? count : 0); + next_dsp = (out == skip_sentinel) ? clocks_per_sample : -duration + clocks_per_sample; + + // Localize timer next_tick times and run them to the present to prevent a running + // but ignored timer's next_tick from getting too far behind and overflowing. + for ( int i = 0; i < timer_count; i++ ) + { + Timer& t = timer [i]; + if ( t.enabled ) + { + t.next_tick -= duration; + t.run_until( -duration ); + } + } + + // Run CPU for duration, reduced by any extra cycles from previous run + int elapsed = cpu.run( duration - extra_cycles ); + if ( elapsed > 0 ) + { + dprintf( "Unhandled instruction $%02X, pc = $%04X\n", + (int) cpu.read( cpu.r.pc ), (unsigned) cpu.r.pc ); + return "Emulation error"; + } + extra_cycles = -elapsed; + + // Catch DSP up to present. + run_dsp( 0 ); + if ( out ) { + assert( next_dsp == clocks_per_sample ); + assert( out == skip_sentinel || sample_buf - out == count ); + } + buf_end = NULL; + + return blargg_success; +} + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Snes_Spc.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Snes_Spc.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,113 @@ + +// Super Nintendo (SNES) SPC-700 APU Emulator + +// Game_Music_Emu 0.3.0 + +#ifndef SNES_SPC_H +#define SNES_SPC_H + +#include "blargg_common.h" +#include "Spc_Cpu.h" +#include "Spc_Dsp.h" + +class Snes_Spc { +public: + typedef BOOST::uint8_t uint8_t; + + Snes_Spc(); + + // Load copy of SPC data into emulator. Clear echo buffer if 'clear_echo' is true. + enum { spc_file_size = 0x10180 }; + blargg_err_t load_spc( const void* spc, long spc_size, bool clear_echo = 1 ); + + // Load copy of state into emulator. + typedef Spc_Cpu::registers_t registers_t; + blargg_err_t load_state( const registers_t& cpu_state, const void* ram_64k, + const void* dsp_regs_128 ); + + // Clear echo buffer + void clear_echo(); + + // Mute voice n if bit n (1 << n) of mask is set + enum { voice_count = Spc_Dsp::voice_count }; + void mute_voices( int mask ); + + // Generate 'count' samples and optionally write to 'buf'. Count must be even. + // Sample output is 16-bit 32kHz, signed stereo pairs with the left channel first. + typedef short sample_t; + blargg_err_t play( long count, sample_t* buf = NULL ); + + // Skip forward by the specified number of samples (64000 samples = 1 second) + blargg_err_t skip( long count ); + + // Set gain, where 1.0 is normal. When greater than 1.0, output is clamped the + // 16-bit sample range. + void set_gain( double ); + + // If true, prevent channels and global volumes from being phase-negated + void disable_surround( bool disable ); + +// End of public interface +private: + // timers + struct Timer + { + spc_time_t next_tick; + int period; + int count; + int shift; + int counter; + int enabled; + + void run_until_( spc_time_t ); + void run_until( spc_time_t time ) + { + if ( time >= next_tick ) + run_until_( time ); + } + }; + enum { timer_count = 3 }; + Timer timer [timer_count]; + + // hardware + int extra_cycles; + spc_time_t time() const; + int read( spc_addr_t ); + void write( spc_addr_t, int ); + friend class Spc_Cpu; + + // dsp + sample_t* sample_buf; + sample_t* buf_end; // to do: remove this once possible bug resolved + spc_time_t next_dsp; + Spc_Dsp dsp; + int keys_pressed; + int keys_released; + sample_t skip_sentinel [1]; // special value for play() passed by skip() + void run_dsp( spc_time_t ); + void run_dsp_( spc_time_t ); + bool echo_accessed; + void check_for_echo_access( spc_addr_t ); + + // boot rom + enum { rom_size = 64 }; + enum { rom_addr = 0xffc0 }; + bool rom_enabled; + uint8_t extra_ram [rom_size]; + static const uint8_t boot_rom [rom_size]; + void enable_rom( bool ); + + // CPU and RAM (at end because it's large) + Spc_Cpu cpu; + enum { ram_size = 0x10000 }; + uint8_t ram [ram_size + 0x100]; // padding for catching jumps past end +}; + +inline void Snes_Spc::disable_surround( bool disable ) { dsp.disable_surround( disable ); } + +inline void Snes_Spc::mute_voices( int mask ) { dsp.mute_voices( mask ); } + +inline void Snes_Spc::set_gain( double v ) { dsp.set_gain( v ); } + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Spc_Cpu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Spc_Cpu.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,1066 @@ + +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ + +#include "Spc_Cpu.h" + +#include + +#include "blargg_endian.h" +#include "Snes_Spc.h" + +/* Copyright (C) 2004-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +// Several instructions are commented out (or not even implemented). These aren't +// used by the SPC files tested. + +// Optimize performance for the most common instructions, and size for the rest: +// +// 15% 0xF0 BEQ rel +// 8% 0xE4 MOV A,dp +// 4% 0xF5 MOV A,abs+X +// 4% 0xD0 BNE rel +// 4% 0x6F RET +// 4% 0x3F CALL addr +// 4% 0xF4 MOV A,dp+X +// 3% 0xC4 MOV dp,A +// 2% 0xEB MOV Y,dp +// 2% 0x3D INC X +// 2% 0xF6 MOV A,abs+Y +// (1% and below not shown) + +Spc_Cpu::Spc_Cpu( Snes_Spc* e, uint8_t* ram_in ) : ram( ram_in ), emu( *e ) +{ + remain_ = 0; + BOOST_STATIC_ASSERT( sizeof (int) >= 4 ); +} + +#define READ( addr ) (emu.read( addr )) +#define WRITE( addr, value ) (emu.write( addr, value )) + +#define READ_DP( addr ) READ( (addr) + dp ) +#define WRITE_DP( addr, value ) WRITE( (addr) + dp, value ) + +#define READ_PROG( addr ) (ram [addr]) +#define READ_PROG16( addr ) GET_LE16( &READ_PROG( addr ) ) + +int Spc_Cpu::read( spc_addr_t addr ) +{ + return READ( addr ); +} + +void Spc_Cpu::write( spc_addr_t addr, int data ) +{ + WRITE( addr, data ); +} + +// Cycle table derived from text copy of SPC-700 manual (using regular expressions) +static const unsigned char cycle_table [0x100] = { +// 0 1 2 3 4 5 6 7 8 9 A B C D E F + 2,8,4,5,3,4,3,6,2,6,5,4,5,4,6,8, // 0 + 2,8,4,5,4,5,5,6,5,5,6,5,2,2,4,6, // 1 + 2,8,4,5,3,4,3,6,2,6,5,4,5,4,5,4, // 2 + 2,8,4,5,4,5,5,6,5,5,6,5,2,2,3,8, // 3 + 2,8,4,5,3,4,3,6,2,6,4,4,5,4,6,6, // 4 + 2,8,4,5,4,5,5,6,5,5,4,5,2,2,4,3, // 5 + 2,8,4,5,3,4,3,6,2,6,4,4,5,4,5,5, // 6 + 2,8,4,5,4,5,5,6,5,5,5,5,2,2,3,6, // 7 + 2,8,4,5,3,4,3,6,2,6,5,4,5,2,4,5, // 8 + 2,8,4,5,4,5,5,6,5,5,5,5,2,2,12,5,// 9 + 3,8,4,5,3,4,3,6,2,6,4,4,5,2,4,4, // A + 2,8,4,5,4,5,5,6,5,5,5,5,2,2,3,4, // B + 3,8,4,5,4,5,4,7,2,5,6,4,5,2,4,9, // C + 2,8,4,5,5,6,6,7,4,5,4,5,2,2,6,3, // D + 2,8,4,5,3,4,3,6,2,4,5,3,4,3,4,3, // E + 2,8,4,5,4,5,5,6,3,4,5,4,2,2,4,3 // F +}; + +// The C,mem instructions are hardly used, so a non-inline function is used for +// the common access code. +unsigned Spc_Cpu::mem_bit( spc_addr_t pc ) +{ + unsigned addr = READ_PROG16( pc ); + unsigned t = READ( addr & 0x1fff ) >> (addr >> 13); + return (t << 8) & 0x100; +} + +#include BLARGG_ENABLE_OPTIMIZER + +spc_time_t Spc_Cpu::run( spc_time_t cycle_count ) +{ + remain_ = cycle_count; + + uint8_t* const ram = this->ram; // cache + + // Stack pointer is kept one greater than usual SPC stack pointer to allow + // common pre-decrement and post-increment memory instructions that some + // processors have. Address wrap-around isn't supported. + #define PUSH( v ) (*--sp = (v)) + #define PUSH16( v ) (sp -= 2, SET_LE16( sp, v )) + #define POP() (*sp++) + #define SET_SP( v ) (sp = ram + 0x101 + (v)) + #define GET_SP() (sp - 0x101 - ram) + + uint8_t* sp; + SET_SP( r.sp ); + + // registers + unsigned pc = r.pc; + int a = r.a; + int x = r.x; + int y = r.y; + + // status flags + + const int st_n = 0x80; + const int st_v = 0x40; + const int st_p = 0x20; + const int st_b = 0x10; + const int st_h = 0x08; + const int st_i = 0x04; + const int st_z = 0x02; + const int st_c = 0x01; + + #define IS_NEG (nz & 0x880) + + #define CALC_STATUS( out ) do { \ + out = status & ~(st_n | st_z | st_c); \ + out |= (c >> 8) & st_c; \ + out |= (dp >> 3) & st_p; \ + if ( IS_NEG ) out |= st_n; \ + if ( !(nz & 0xFF) ) out |= st_z; \ + } while ( 0 ) + + #define SET_STATUS( in ) do { \ + status = in & ~(st_n | st_z | st_c | st_p); \ + c = in << 8; \ + nz = (in << 4) & 0x800; \ + nz |= ~in & st_z; \ + dp = (in << 3) & 0x100; \ + } while ( 0 ) + + uint8_t status; + int c; // store C as 'c' & 0x100. + int nz; // Z set if (nz & 0xff) == 0, N set if (nz & 0x880) != 0 + unsigned dp; // direct page base + { + int temp = r.status; + SET_STATUS( temp ); + } + + goto loop; + + unsigned data; // first operand of instruction and temporary across function calls + + // Common endings for instructions +cbranch_taken_loop: // compare and branch + pc += (BOOST::int8_t) READ_PROG( pc ); + remain_ -= 2; +inc_pc_loop: // end of instruction with an operand + pc++; +loop: + + check( (unsigned) pc < 0x10000 ); + check( (unsigned) GET_SP() < 0x100 ); + + assert( (unsigned) a < 0x100 ); + assert( (unsigned) x < 0x100 ); + assert( (unsigned) y < 0x100 ); + + unsigned opcode = READ_PROG( pc ); + pc++; + // to do: if pc is at end of memory, this will get wrong byte + data = READ_PROG( pc ); + + if ( remain_ <= 0 ) + goto stop; + + remain_ -= cycle_table [opcode]; + + // Use 'data' for temporaries whose lifetime crosses read/write calls, otherwise + // use a local temporary. + switch ( opcode ) + { + + #define BRANCH( cond ) { \ + pc++; \ + int offset = (BOOST::int8_t) data; \ + if ( cond ) { \ + pc += offset; \ + remain_ -= 2; \ + } \ + goto loop; \ + } + +// Most-Common + + case 0xF0: // BEQ (most common) + BRANCH( !(uint8_t) nz ) + + case 0xD0: // BNE + BRANCH( (uint8_t) nz ) + + case 0x3F: // CALL + PUSH16( pc + 2 ); + pc = READ_PROG16( pc ); + goto loop; + + case 0x6F: // RET + pc = POP(); + pc += POP() * 0x100; + goto loop; + +#define CASE( n ) 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; \ + CASE( op - 0x03 ) /* abs */ \ + abs_##op: \ + pc++; \ + data += 0x100 * READ_PROG( pc );\ + goto end_##op; \ + CASE( op + 0x0C ) /* dp+X */ \ + data = uint8_t (data + x); \ + CASE( op - 0x04 ) /* dp */ \ + data += dp; \ + end_##op: + +// 1. 8-bit Data Transmission Commands. Group I + + ADDR_MODES( 0xE8 ) // MOV A,addr + // case 0xE4: // MOV a,dp (most common) + mov_a_addr: + a = nz = READ( data ); + goto inc_pc_loop; + case 0xBF: // MOV A,(X)+ + data = x + dp; + x = uint8_t (x + 1); + pc--; + goto mov_a_addr; + + case 0xE8: // MOV A,imm + a = data; + nz = data; + goto inc_pc_loop; + + case 0xF9: // MOV X,dp+Y + data = uint8_t (data + y); + case 0xF8: // MOV X,dp + data += dp; + goto mov_x_addr; + case 0xE9: // MOV X,abs + data = READ_PROG16( pc ); + pc++; + mov_x_addr: + data = READ( data ); + case 0xCD: // MOV X,imm + x = data; + nz = data; + goto inc_pc_loop; + + case 0xFB: // MOV Y,dp+X + data = uint8_t (data + x); + case 0xEB: // MOV Y,dp + data += dp; + goto mov_y_addr; + case 0xEC: // MOV Y,abs + data = READ_PROG16( pc ); + pc++; + mov_y_addr: + data = READ( data ); + case 0x8D: // MOV Y,imm + y = data; + nz = data; + goto inc_pc_loop; + +// 2. 8-BIT DATA TRANSMISSION COMMANDS, GROUP 2 + + ADDR_MODES( 0xC8 ) // MOV addr,A + WRITE( 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( READ_PROG16( pc ), temp ); + pc += 2; + goto loop; + } + + case 0xD9: // MOV dp+Y,X + data = uint8_t (data + y); + case 0xD8: // MOV dp,X + WRITE( data + dp, x ); + goto inc_pc_loop; + + case 0xDB: // MOV dp+X,Y + data = uint8_t (data + x); + case 0xCB: // MOV dp,Y + WRITE( data + dp, y ); + goto inc_pc_loop; + + case 0xFA: // MOV dp,dp + data = READ( data + dp ); + case 0x8F: // MOV dp,#imm + pc++; + WRITE_DP( READ_PROG( pc ), data ); + 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( x, a ); + x++; + goto loop; + +// 5. 8-BIT LOGIC OPERATION COMMANDS + +#define LOGICAL_OP( op, func ) \ + ADDR_MODES( op ) /* addr */ \ + data = READ( data ); \ + case op: /* imm */ \ + nz = a func##= data; \ + goto inc_pc_loop; \ + { unsigned addr; \ + case op + 0x11: /* X,Y */ \ + data = READ_DP( y ); \ + addr = x + dp; \ + pc--; \ + goto addr_##op; \ + case op + 0x01: /* dp,dp */ \ + data = READ_DP( data ); \ + case op + 0x10: /*dp,imm*/\ + pc++; \ + addr = READ_PROG( pc ) + dp;\ + addr_##op: \ + nz = data func READ( addr );\ + WRITE( addr, nz ); \ + goto inc_pc_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( data ); + case 0x68: // CMP imm + nz = a - data; + c = ~nz; + nz &= 0xff; + goto inc_pc_loop; + + case 0x79: // CMP (X),(Y) + data = READ_DP( x ); + nz = data - READ_DP( y ); + c = ~nz; + nz &= 0xff; + goto loop; + + case 0x69: // CMP (dp),(dp) + data = READ_DP( data ); + case 0x78: // CMP dp,imm + pc++; + nz = READ_DP( READ_PROG( 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_PROG16( pc ); + pc++; + cmp_x_addr: + data = READ( data ); + 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_PROG16( pc ); + pc++; + cmp_y_addr: + data = READ( data ); + 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( x ); + addr = y + dp; + goto adc_addr; + case 0xA9: // SBC dp,dp + case 0x89: // ADC dp,dp + data = READ_DP( data ); + case 0xB8: // SBC dp,imm + case 0x98: // ADC dp,imm + pc++; + addr = READ_PROG( pc ) + dp; + adc_addr: + nz = READ( 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( data ); + case 0xA8: // SBC imm + case 0x88: // ADC imm + addr = -1; // A + nz = a; + adc_data: { + if ( opcode & 0x20 ) + data ^= 0xff; // SBC + int carry = (c >> 8) & 1; + int ov = (nz ^ 0x80) + carry + (BOOST::int8_t) data; // sign-extend + int hc = (nz & 15) + carry; + c = nz += data + carry; + hc = (nz & 15) - hc; + status = (status & ~(st_v | st_h)) | ((ov >> 2) & st_v) | ((hc >> 1) & st_h); + if ( addr < 0 ) { + a = (uint8_t) nz; + goto inc_pc_loop; + } + WRITE( addr, (uint8_t) nz ); + goto inc_pc_loop; + } + + } + +// 6. ADDITION & SUBTRACTION COMMANDS + +#define INC_DEC_REG( reg, n ) \ + nz = reg + n; \ + 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); + case 0x8B: // DEC dp + case 0xAB: // INC dp + data += dp; + goto inc_abs; + case 0x8C: // DEC abs + case 0xAC: // INC abs + data = READ_PROG16( pc ); + pc++; + inc_abs: + nz = ((opcode >> 4) & 2) - 1; + nz += READ( data ); + WRITE( data, (uint8_t) nz ); + goto inc_pc_loop; + +// 7. SHIFT, ROTATION COMMANDS + + case 0x5C: // LSR A + c = 0; + case 0x7C:{// ROR A + nz = ((c >> 1) & 0x80) | (a >> 1); + c = a << 8; + a = nz; + goto loop; + } + + case 0x1C: // ASL A + c = 0; + 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; + case 0x3B: // ROL dp+X + data = uint8_t (data + x); + case 0x2B: // ROL dp + data += dp; + goto rol_mem; + case 0x0C: // ASL abs + c = 0; + case 0x2C: // ROL abs + data = READ_PROG16( pc ); + pc++; + rol_mem: + nz = (c >> 8) & 1; + nz |= (c = READ( data ) << 1); + WRITE( 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; + case 0x7B: // ROR dp+X + data = uint8_t (data + x); + case 0x6B: // ROR dp + data += dp; + goto ror_mem; + case 0x4C: // LSR abs + c = 0; + case 0x6C: // ROR abs + data = READ_PROG16( pc ); + pc++; + ror_mem: { + int temp = READ( data ); + nz = ((c >> 1) & 0x80) | (temp >> 1); + c = temp << 8; + WRITE( 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( data ); + nz = (a & 0x7f) | (a >> 1); + y = READ_DP( uint8_t (data + 1) ); + nz |= y; + goto inc_pc_loop; + + case 0xDA: // MOVW dp,YA + WRITE_DP( data, a ); + WRITE_DP( uint8_t (data + 1), y ); + goto inc_pc_loop; + +// 9. 16-BIT OPERATION COMMANDS + + case 0x3A: // INCW dp + case 0x1A:{// DECW dp + data += dp; + + // low byte + int temp = READ( data ); + temp += ((opcode >> 4) & 2) - 1; // +1 for INCW, -1 for DECW + nz = ((temp >> 1) | temp) & 0x7f; + WRITE( data, (uint8_t) temp ); + + // high byte + data = uint8_t (data + 1) + dp; + temp >>= 8; + temp = uint8_t (temp + READ( data )); + nz |= temp; + WRITE( data, temp ); + + goto inc_pc_loop; + } + + case 0x9A: // SUBW YA,dp + case 0x7A: // ADDW YA,dp + { + // read 16-bit addend + int temp = READ_DP( data ); + int sign = READ_DP( uint8_t (data + 1) ); + temp += 0x100 * sign; + status &= ~(st_v | st_h); + + // to do: fix half-carry for SUBW (it's probably wrong) + + // for SUBW, negate and truncate to 16 bits + if ( opcode & 0x80 ) { + temp = (temp ^ 0xFFFF) + 1; + sign = temp >> 8; + } + + // add low byte (A) + temp += a; + a = (uint8_t) temp; + nz = (temp | (temp >> 1)) & 0x7f; + + // add high byte (Y) + temp >>= 8; + c = y + temp; + nz = (nz | c) & 0xff; + + // half-carry (temporary avoids CodeWarrior optimizer bug) + unsigned hc = (c & 15) - (y & 15); + status |= (hc >> 4) & st_h; + + // overflow if sign of YA changed when previous sign and addend sign were same + status |= (((c ^ y) & ~(y ^ sign)) >> 1) & st_v; + + y = (uint8_t) c; + + goto inc_pc_loop; + } + + case 0x5A: { // CMPW YA,dp + int temp = a - READ_DP( data ); + nz = ((temp >> 1) | temp) & 0x7f; + temp = y + (temp >> 8); + temp -= READ_DP( 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 = temp >> 8; + nz |= y; + goto loop; + } + + case 0x9E: // DIV YA,X + { + // behavior based on SPC CPU tests + + status &= ~(st_h | st_v); + + if ( (y & 15) >= (x & 15) ) + status |= st_h; + + if ( y >= x ) + status |= st_v; + + unsigned ya = y * 0x100 + a; + 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; + + goto loop; + } + +// 11. DECIMAL COMPENSATION COMMANDS + + // seem unused + // case 0xDF: // DAA + // case 0xBE: // DAS + +// 12. BRANCHING COMMANDS + + case 0x2F: // BRA rel + pc += (BOOST::int8_t) data; + goto inc_pc_loop; + + case 0x30: // BMI + BRANCH( IS_NEG ) + + case 0x10: // BPL + BRANCH( !IS_NEG ) + + case 0xB0: // BCS + BRANCH( c & 0x100 ) + + case 0x90: // BCC + BRANCH( !(c & 0x100) ) + + case 0x70: // BVS + BRANCH( status & st_v ) + + case 0x50: // BVC + BRANCH( !(status & st_v) ) + + case 0x03: // BBS dp.bit,rel + case 0x23: + case 0x43: + case 0x63: + case 0x83: + case 0xA3: + case 0xC3: + case 0xE3: + pc++; + if ( (READ_DP( data ) >> (opcode >> 5)) & 1 ) + goto cbranch_taken_loop; + goto inc_pc_loop; + + case 0x13: // BBC dp.bit,rel + case 0x33: + case 0x53: + case 0x73: + case 0x93: + case 0xB3: + case 0xD3: + case 0xF3: + pc++; + if ( !((READ_DP( data ) >> (opcode >> 5)) & 1) ) + goto cbranch_taken_loop; + goto inc_pc_loop; + + case 0xDE: // CBNE dp+X,rel + data = uint8_t (data + x); + // fall through + case 0x2E: // CBNE dp,rel + pc++; + if ( READ_DP( data ) != a ) + goto cbranch_taken_loop; + goto inc_pc_loop; + + case 0xFE: // DBNZ Y,rel + y = uint8_t (y - 1); + BRANCH( y ) + + case 0x6E: { // DBNZ dp,rel + pc++; + unsigned temp = READ_DP( data ) - 1; + WRITE_DP( (uint8_t) data, (uint8_t) temp ); + if ( temp ) + goto cbranch_taken_loop; + goto inc_pc_loop; + } + + case 0x1F: // JMP (abs+X) + pc = READ_PROG16( pc ) + x; + // fall through + case 0x5F: // JMP abs + pc = READ_PROG16( pc ); + goto loop; + +// 13. SUB-ROUTINE CALL RETURN COMMANDS + + case 0x0F: // BRK + check( false ); // untested + PUSH16( pc + 1 ); + pc = READ_PROG16( 0xffde ); // vector address verified + int temp; + CALC_STATUS( temp ); + PUSH( temp ); + status = (status | st_b) & ~st_i; + goto loop; + + case 0x4F: // PCALL offset + pc++; + PUSH16( pc ); + pc = 0xff00 + data; + 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: + PUSH16( pc ); + pc = READ_PROG16( 0xffde - (opcode >> 3) ); + goto loop; + +// 14. STACK OPERATION COMMANDS + + { + int temp; + case 0x7F: // RET1 + temp = POP(); + pc = POP(); + pc |= POP() << 8; + goto set_status; + case 0x8E: // POP PSW + temp = POP(); + set_status: + SET_STATUS( temp ); + goto loop; + } + + case 0x0D: { // PUSH PSW + int temp; + CALC_STATUS( 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 + a = POP(); + goto loop; + + case 0xCE: // POP X + x = POP(); + goto loop; + + case 0xEE: // POP Y + y = POP(); + 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: { + data += dp; + int bit = 1 << (opcode >> 5); + int mask = ~bit; + if ( opcode & 0x10 ) + bit = 0; + WRITE( data, (READ( data ) & mask) | bit ); + goto inc_pc_loop; + } + + case 0x0E: // TSET1 abs + case 0x4E:{// TCLR1 abs + data = READ_PROG16( pc ); + pc += 2; + unsigned temp = READ( data ); + nz = temp & a; + temp &= ~a; + if ( !(opcode & 0x40) ) + temp |= a; + WRITE( data, temp ); + goto loop; + } + + case 0x4A: // AND1 C,mem.bit + c &= mem_bit( pc ); + pc += 2; + goto loop; + + case 0x6A: // AND1 C,/mem.bit + check( false ); // untested + c &= ~mem_bit( pc ); + pc += 2; + goto loop; + + case 0x0A: // OR1 C,mem.bit + check( false ); // untested + c |= mem_bit( pc ); + pc += 2; + goto loop; + + case 0x2A: // OR1 C,/mem.bit + check( false ); // untested + c |= ~mem_bit( pc ); + pc += 2; + goto loop; + + case 0x8A: // EOR1 C,mem.bit + c ^= mem_bit( pc ); + pc += 2; + goto loop; + + case 0xEA: { // NOT1 mem.bit + data = READ_PROG16( pc ); + pc += 2; + unsigned temp = READ( data & 0x1fff ); + temp ^= 1 << (data >> 13); + WRITE( data & 0x1fff, temp ); + goto loop; + } + + case 0xCA: { // MOV1 mem.bit,C + data = READ_PROG16( pc ); + pc += 2; + unsigned temp = READ( data & 0x1fff ); + unsigned bit = data >> 13; + temp = (temp & ~(1 << bit)) | (((c >> 8) & 1) << bit); + WRITE( data & 0x1fff, temp ); + goto loop; + } + + case 0xAA: // MOV1 C,mem.bit + c = mem_bit( pc ); + pc += 2; + goto loop; + +// 16. PROGRAM STATUS 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 + status &= ~(st_v | st_h); + goto loop; + + case 0x20: // CLRP + dp = 0; + goto loop; + + case 0x40: // SETP + dp = 0x100; + goto loop; + + case 0xA0: // EI + check( false ); // untested + status |= st_i; + goto loop; + + case 0xC0: // DI + check( false ); // untested + status &= ~st_i; + goto loop; + +// 17. OTHER COMMANDS + + case 0x00: // NOP + goto loop; + + //case 0xEF: // SLEEP + //case 0xFF: // STOP + + } // switch + + // unhandled instructions fall out of switch so emulator can catch them + +stop: + pc--; + + { + int temp; + CALC_STATUS( temp ); + r.status = temp; + } + + r.pc = pc; + r.sp = GET_SP(); + r.a = a; + r.x = x; + r.y = y; + + return remain_; +} + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Spc_Cpu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Spc_Cpu.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,60 @@ + +// Super Nintendo (SNES) SPC-700 CPU emulator + +// Game_Music_Emu 0.3.0 + +#ifndef SPC_CPU_H +#define SPC_CPU_H + +#include "blargg_common.h" + +typedef unsigned spc_addr_t; +typedef long spc_time_t; + +class Snes_Spc; + +class Spc_Cpu { + typedef BOOST::uint8_t uint8_t; + uint8_t* const ram; +public: + // Keeps pointer to 64K RAM + Spc_Cpu( Snes_Spc* spc, uint8_t* ram ); + + // SPC-700 registers. *Not* kept updated during a call to run(). + struct registers_t { + long pc; // more than 16 bits to allow overflow detection + uint8_t a; + uint8_t x; + uint8_t y; + uint8_t status; + uint8_t sp; + } r; + + // Run CPU for at least 'count' cycles. Return the number of cycles remaining + // when emulation stopped (negative if extra cycles were emulated). Emulation + // stops when there are no more remaining cycles or an unhandled instruction + // is encountered (STOP, SLEEP, and any others not yet implemented). In the + // latter case, the return value is greater than zero. + spc_time_t run( spc_time_t count ); + + // Number of clock cycles remaining for current run() call + spc_time_t remain() const; + + // Access memory as the emulated CPU does + int read ( spc_addr_t ); + void write( spc_addr_t, int ); + +private: + // noncopyable + Spc_Cpu( const Spc_Cpu& ); + Spc_Cpu& operator = ( const Spc_Cpu& ); + unsigned mem_bit( spc_addr_t ); + + spc_time_t remain_; + Snes_Spc& emu; +}; + +inline spc_time_t Spc_Cpu::remain() const { return remain_; } + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Spc_Dsp.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Spc_Dsp.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,666 @@ + +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ + +// Based on Brad Martin's OpenSPC DSP emulator + +#include "Spc_Dsp.h" + +#include + +#include "blargg_endian.h" + +/* Copyright (C) 2002 Brad Martin */ +/* Copyright (C) 2004-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +Spc_Dsp::Spc_Dsp( uint8_t* ram_ ) : ram( ram_ ) +{ + set_gain( 1.0 ); + mute_voices( 0 ); + disable_surround( false ); + + BOOST_STATIC_ASSERT( sizeof (g) == register_count && sizeof (voice) == register_count ); +} + +void Spc_Dsp::mute_voices( int mask ) +{ + for ( int i = 0; i < voice_count; i++ ) + voice_state [i].enabled = (mask >> i & 1) ? 31 : 7; +} + +void Spc_Dsp::reset() +{ + keys = 0; + echo_ptr = 0; + noise_count = 0; + noise = 1; + fir_offset = 0; + + g.flags = 0xE0; // reset, mute, echo off + g.key_ons = 0; + + for ( int i = 0; i < voice_count; i++ ) + { + voice_t& v = voice_state [i]; + v.on_cnt = 0; + v.volume [0] = 0; + v.volume [1] = 0; + v.envstate = state_release; + } + + memset( fir_buf, 0, sizeof fir_buf ); +} + +void Spc_Dsp::write( int i, int data ) +{ + require( (unsigned) i < register_count ); + + reg [i] = data; + int high = i >> 4; + switch ( i & 0x0f ) + { + // voice volume + case 0: + case 1: { + short* volume = voice_state [high].volume; + int left = (int8_t) reg [i & ~1]; + int right = (int8_t) reg [i | 1]; + volume [0] = left; + volume [1] = right; + // kill surround only if signs of volumes differ + if ( left * right < surround_threshold ) + { + if ( left < 0 ) + volume [0] = -left; + else + volume [1] = -right; + } + break; + } + + // fir coefficients + case 0x0f: + fir_coeff [high] = (int8_t) data; // sign-extend + break; + } +} + +// This table is for envelope timing. It represents the number of counts +// that should be subtracted from the counter each sample period (32kHz). +// The counter starts at 30720 (0x7800). Each count divides exactly into +// 0x7800 without remainder. +const int env_rate_init = 0x7800; +static const short env_rates [0x20] = +{ + 0x0000, 0x000F, 0x0014, 0x0018, 0x001E, 0x0028, 0x0030, 0x003C, + 0x0050, 0x0060, 0x0078, 0x00A0, 0x00C0, 0x00F0, 0x0140, 0x0180, + 0x01E0, 0x0280, 0x0300, 0x03C0, 0x0500, 0x0600, 0x0780, 0x0A00, + 0x0C00, 0x0F00, 0x1400, 0x1800, 0x1E00, 0x2800, 0x3C00, 0x7800 +}; + +const int env_range = 0x800; + +inline int Spc_Dsp::clock_envelope( int v ) +{ /* Return value is current + * ENVX */ + raw_voice_t& raw_voice = this->voice [v]; + voice_t& voice = voice_state [v]; + + int envx = voice.envx; + if ( voice.envstate == state_release ) + { + /* + * Docs: "When in the state of "key off". the "click" sound is + * prevented by the addition of the fixed value 1/256" WTF??? + * Alright, I'm going to choose to interpret that this way: + * When a note is keyed off, start the RELEASE state, which + * subtracts 1/256th each sample period (32kHz). Note there's + * no need for a count because it always happens every update. + */ + envx -= env_range / 256; + if ( envx <= 0 ) + { + envx = 0; + keys &= ~(1 << v); + return -1; + } + voice.envx = envx; + raw_voice.envx = envx >> 8; + return envx; + } + + int cnt = voice.envcnt; + int adsr1 = raw_voice.adsr [0]; + if ( adsr1 & 0x80 ) + { + switch ( voice.envstate ) + { + case state_attack: { + // increase envelope by 1/64 each step + int t = adsr1 & 15; + if ( t == 15 ) + { + envx += env_range / 2; + } + else + { + cnt -= env_rates [t * 2 + 1]; + if ( cnt > 0 ) + break; + envx += env_range / 64; + cnt = env_rate_init; + } + if ( envx >= env_range ) + { + envx = env_range - 1; + voice.envstate = state_decay; + } + voice.envx = envx; + break; + } + + case state_decay: { + // Docs: "DR... [is multiplied] by the fixed value + // 1-1/256." Well, at least that makes some sense. + // Multiplying ENVX by 255/256 every time DECAY is + // updated. + cnt -= env_rates [((adsr1 >> 3) & 0xE) + 0x10]; + if ( cnt <= 0 ) + { + cnt = env_rate_init; + envx -= ((envx - 1) >> 8) + 1; + voice.envx = envx; + } + int sustain_level = raw_voice.adsr [1] >> 5; + + if ( envx <= (sustain_level + 1) * 0x100 ) + voice.envstate = state_sustain; + break; + } + + case state_sustain: + // Docs: "SR [is multiplied] by the fixed value 1-1/256." + // Multiplying ENVX by 255/256 every time SUSTAIN is + // updated. + cnt -= env_rates [raw_voice.adsr [1] & 0x1f]; + if ( cnt <= 0 ) + { + cnt = env_rate_init; + envx -= ((envx - 1) >> 8) + 1; + voice.envx = envx; + } + break; + + case state_release: + // handled above + break; + } + } + else + { /* GAIN mode is set */ + /* + * Note: if the game switches between ADSR and GAIN modes + * partway through, should the count be reset, or should it + * continue from where it was? Does the DSP actually watch for + * that bit to change, or does it just go along with whatever + * it sees when it performs the update? I'm going to assume + * the latter and not update the count, unless I see a game + * that obviously wants the other behavior. The effect would + * be pretty subtle, in any case. + */ + int t = raw_voice.gain; + if (t < 0x80) + { + envx = voice.envx = t << 4; + } + else switch (t >> 5) + { + case 4: /* Docs: "Decrease (linear): Subtraction + * of the fixed value 1/64." */ + cnt -= env_rates [t & 0x1F]; + if (cnt > 0) + break; + cnt = env_rate_init; + envx -= env_range / 64; + if ( envx < 0 ) + { + envx = 0; + if ( voice.envstate == state_attack ) + voice.envstate = state_decay; + } + voice.envx = envx; + break; + case 5: /* Docs: "Drecrease (exponential): + * Multiplication by the fixed value + * 1-1/256." */ + cnt -= env_rates [t & 0x1F]; + if (cnt > 0) + break; + cnt = env_rate_init; + envx -= ((envx - 1) >> 8) + 1; + if ( envx < 0 ) + { + envx = 0; + if ( voice.envstate == state_attack ) + voice.envstate = state_decay; + } + voice.envx = envx; + break; + case 6: /* Docs: "Increase (linear): Addition of + * the fixed value 1/64." */ + cnt -= env_rates [t & 0x1F]; + if (cnt > 0) + break; + cnt = env_rate_init; + envx += env_range / 64; + if ( envx >= env_range ) + envx = env_range - 1; + voice.envx = envx; + break; + case 7: /* Docs: "Increase (bent line): Addition + * of the constant 1/64 up to .75 of the + * constaint 1/256 from .75 to 1." */ + cnt -= env_rates [t & 0x1F]; + if (cnt > 0) + break; + cnt = env_rate_init; + if ( envx < env_range * 3 / 4 ) + envx += env_range / 64; + else + envx += env_range / 256; + if ( envx >= env_range ) + envx = env_range - 1; + voice.envx = envx; + break; + } + } + voice.envcnt = cnt; + raw_voice.envx = envx >> 4; + return envx; +} + +// Clamp n into range -32768 <= n <= 32767 +inline int clamp_16( int n ) +{ + if ( (BOOST::int16_t) n != n ) + n = BOOST::int16_t (0x7FFF - (n >> 31)); + return n; +} + +void Spc_Dsp::run( long count, short* out_buf ) +{ + // to do: make clock_envelope() inline so that this becomes a leaf function? + + // Should we just fill the buffer with silence? Flags won't be cleared + // during this run so it seems it should keep resetting every sample. + if ( g.flags & 0x80 ) + reset(); + + struct src_dir { + char start [2]; + char loop [2]; + }; + + const src_dir* const sd = (src_dir*) &ram [g.wave_page * 0x100]; + + int left_volume = g.left_volume; + int right_volume = g.right_volume; + if ( left_volume * right_volume < surround_threshold ) + right_volume = -right_volume; // kill global surround + left_volume *= emu_gain; + right_volume *= emu_gain; + + while ( --count >= 0 ) + { + // Here we check for keys on/off. Docs say that successive writes + // to KON/KOF must be separated by at least 2 Ts periods or risk + // being neglected. Therefore DSP only looks at these during an + // update, and not at the time of the write. Only need to do this + // once however, since the regs haven't changed over the whole + // period we need to catch up with. + + g.wave_ended &= ~g.key_ons; // Keying on a voice resets that bit in ENDX. + + if ( g.noise_enables ) + { + noise_count -= env_rates [g.flags & 0x1F]; + if ( noise_count <= 0 ) + { + noise_count = env_rate_init; + + noise_amp = BOOST::int16_t (noise * 2); + + int feedback = (noise << 13) ^ (noise << 14); + noise = (feedback & 0x4000) | (noise >> 1); + } + } + + // What is the expected behavior when pitch modulation is enabled on + // voice 0? Jurassic Park 2 does this. Assume 0 for now. + long prev_outx = 0; + + int echol = 0; + int echor = 0; + int left = 0; + int right = 0; + for ( int vidx = 0; vidx < voice_count; vidx++ ) + { + const int vbit = 1 << vidx; + raw_voice_t& raw_voice = voice [vidx]; + voice_t& voice = voice_state [vidx]; + + if ( voice.on_cnt && !--voice.on_cnt ) + { + // key on + keys |= vbit; + voice.addr = GET_LE16( sd [raw_voice.waveform].start ); + voice.block_remain = 1; + voice.envx = 0; + voice.block_header = 0; + voice.fraction = 0x3fff; // decode three samples immediately + voice.interp0 = 0; // BRR decoder filter uses previous two samples + voice.interp1 = 0; + + // NOTE: Real SNES does *not* appear to initialize the + // envelope counter to anything in particular. The first + // cycle always seems to come at a random time sooner than + // expected; as yet, I have been unable to find any + // pattern. I doubt it will matter though, so we'll go + // ahead and do the full time for now. + voice.envcnt = env_rate_init; + voice.envstate = state_attack; + } + + if ( g.key_ons & vbit & ~g.key_offs ) + { + // voice doesn't come on if key off is set + g.key_ons &= ~vbit; + voice.on_cnt = 8; + } + + if ( keys & g.key_offs & vbit ) + { + // key off + voice.envstate = state_release; + voice.on_cnt = 0; + } + + int envx; + if ( !(keys & vbit) || (envx = clock_envelope( vidx )) < 0 ) + { + raw_voice.envx = 0; + raw_voice.outx = 0; + prev_outx = 0; + continue; + } + + // Decode samples when fraction >= 1.0 (0x1000) + for ( int n = voice.fraction >> 12; --n >= 0; ) + { + if ( !--voice.block_remain ) + { + if ( voice.block_header & 1 ) + { + g.wave_ended |= vbit; + + if ( voice.block_header & 2 ) + { + // verified (played endless looping sample and ENDX was set) + voice.addr = GET_LE16( sd [raw_voice.waveform].loop ); + } + else + { + // first block was end block; don't play anything (verified) + goto sample_ended; // to do: find alternative to goto + } + } + + voice.block_header = ram [voice.addr++]; + voice.block_remain = 16; // nybbles + } + + // if next block has end flag set, *this* block ends *early* (verified) + if ( voice.block_remain == 9 && (ram [voice.addr + 5] & 3) == 1 && + (voice.block_header & 3) != 3 ) + { + sample_ended: + g.wave_ended |= vbit; + keys &= ~vbit; + raw_voice.envx = 0; + voice.envx = 0; + // add silence samples to interpolation buffer + do + { + voice.interp3 = voice.interp2; + voice.interp2 = voice.interp1; + voice.interp1 = voice.interp0; + voice.interp0 = 0; + } + while ( --n >= 0 ); + break; + } + + int delta = ram [voice.addr]; + if ( voice.block_remain & 1 ) + { + delta <<= 4; // use lower nybble + voice.addr++; + } + + // Use sign-extended upper nybble + delta = int8_t (delta) >> 4; + + // For invalid ranges (D,E,F): if the nybble is negative, + // the result is F000. If positive, 0000. Nothing else + // like previous range, etc seems to have any effect. If + // range is valid, do the shift normally. Note these are + // both shifted right once to do the filters properly, but + // the output will be shifted back again at the end. + int shift = voice.block_header >> 4; + delta = (delta << shift) >> 1; + if ( shift > 0x0C ) + delta = (delta >> 14) & ~0x7FF; + + // One, two and three point IIR filters + int smp1 = voice.interp0; + int smp2 = voice.interp1; + switch ( (voice.block_header >> 2) & 3 ) + { + case 0: + break; + + case 1: + delta += smp1 >> 1; + delta += (-smp1) >> 5; + break; + + case 2: + delta += smp1; + delta += (-(smp1 + (smp1 >> 1))) >> 5; + delta -= smp2 >> 1; + delta += smp2 >> 5; + break; + + case 3: + delta += smp1; + delta += (-(smp1 + (smp1 << 2) + (smp1 << 3))) >> 7; + delta -= smp2 >> 1; + delta += (smp2 + (smp2 >> 1)) >> 4; + break; + } + + voice.interp3 = voice.interp2; + voice.interp2 = smp2; + voice.interp1 = smp1; + voice.interp0 = BOOST::int16_t (clamp_16( delta ) * 2); // sign-extend + } + + // rate (with possible modulation) + int rate = GET_LE16( raw_voice.rate ) & 0x3FFF; + if ( g.pitch_mods & vbit ) + rate = (rate * (prev_outx + 32768)) >> 15; + + // Gaussian interpolation using most recent 4 samples + int index = voice.fraction >> 2 & 0x3FC; + voice.fraction = (voice.fraction & 0x0FFF) + rate; + const BOOST::int16_t* table = (BOOST::int16_t*) ((char*) gauss + index); + const BOOST::int16_t* table2 = (BOOST::int16_t*) ((char*) gauss + (255*4 - index)); + int s = ((table [0] * voice.interp3) >> 12) + + ((table [1] * voice.interp2) >> 12); + s += ((table2 [1] * voice.interp1) >> 12) + + // to do: should clamp here + ((table2 [0] * voice.interp0) >> 12); + int output = noise_amp; // noise is rarely used + if ( !(g.noise_enables & vbit) ) + output = clamp_16( s * 2 ); + + // scale output and set outx values + output = (output * envx) >> 11 & ~1; + + // output and apply muting (by setting voice.enabled to 31) + // if voice is externally disabled (not a SNES feature) + int l = (voice.volume [0] * output) >> voice.enabled; + int r = (voice.volume [1] * output) >> voice.enabled; + prev_outx = output; + raw_voice.outx = output >> 8; + if ( g.echo_ons & vbit ) + { + echol += l; + echor += r; + } + left += l; + right += r; + } + // end of channel loop + + // main volume control + left = (left * left_volume ) >> (7 + emu_gain_bits); + right = (right * right_volume) >> (7 + emu_gain_bits); + + // Echo FIR filter + + // read feedback from echo buffer + int echo_ptr = this->echo_ptr; + uint8_t* echo_buf = &ram [(g.echo_page * 0x100 + echo_ptr) & 0xFFFF]; + echo_ptr += 4; + if ( echo_ptr >= (g.echo_delay & 15) * 0x800 ) + echo_ptr = 0; + int fb_left = (BOOST::int16_t) GET_LE16( echo_buf ); // sign-extend + int fb_right = (BOOST::int16_t) GET_LE16( echo_buf + 2 ); // sign-extend + this->echo_ptr = echo_ptr; + + // put samples in history ring buffer + const int fir_offset = this->fir_offset; + short (*fir_pos) [2] = &fir_buf [fir_offset]; + this->fir_offset = (fir_offset + 7) & 7; // move backwards one step + fir_pos [0] [0] = fb_left; + fir_pos [0] [1] = fb_right; + fir_pos [8] [0] = fb_left; // duplicate at +8 eliminates wrap checking below + fir_pos [8] [1] = fb_right; + + // FIR + fb_left = fb_left * fir_coeff [7] + + fir_pos [1] [0] * fir_coeff [6] + + fir_pos [2] [0] * fir_coeff [5] + + fir_pos [3] [0] * fir_coeff [4] + + fir_pos [4] [0] * fir_coeff [3] + + fir_pos [5] [0] * fir_coeff [2] + + fir_pos [6] [0] * fir_coeff [1] + + fir_pos [7] [0] * fir_coeff [0]; + + fb_right = fb_right * fir_coeff [7] + + fir_pos [1] [1] * fir_coeff [6] + + fir_pos [2] [1] * fir_coeff [5] + + fir_pos [3] [1] * fir_coeff [4] + + fir_pos [4] [1] * fir_coeff [3] + + fir_pos [5] [1] * fir_coeff [2] + + fir_pos [6] [1] * fir_coeff [1] + + fir_pos [7] [1] * fir_coeff [0]; + + left += (fb_left * g.left_echo_volume ) >> 14; + right += (fb_right * g.right_echo_volume) >> 14; + + // echo buffer feedback + if ( !(g.flags & 0x20) ) + { + echol += (fb_left * g.echo_feedback) >> 14; + echor += (fb_right * g.echo_feedback) >> 14; + SET_LE16( echo_buf , clamp_16( echol ) ); + SET_LE16( echo_buf + 2, clamp_16( echor ) ); + } + + if ( out_buf ) + { + // write final samples + + left = clamp_16( left ); + right = clamp_16( right ); + + int mute = g.flags & 0x40; + + out_buf [0] = left; + out_buf [1] = right; + out_buf += 2; + + // muting + if ( mute ) + { + out_buf [-2] = 0; + out_buf [-1] = 0; + } + } + } +} + +// Base normal_gauss table is almost exactly (with an error of 0 or -1 for each entry): +// int normal_gauss [512]; +// normal_gauss [i] = exp((i-511)*(i-511)*-9.975e-6)*pow(sin(0.00307096*i),1.7358)*1304.45 + +// Interleved gauss table (to improve cache coherency). +// gauss [i * 2 + j] = normal_gauss [(1 - j) * 256 + i] +const BOOST::int16_t Spc_Dsp::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, +}; + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Spc_Dsp.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Spc_Dsp.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,155 @@ + +// Super Nintendo (SNES) SPC DSP emulator + +// Game_Music_Emu 0.3.0 + +#ifndef SPC_DSP_H +#define SPC_DSP_H + +#include "blargg_common.h" + +class Spc_Dsp { + typedef BOOST::int8_t int8_t; + typedef BOOST::uint8_t uint8_t; +public: + + // Keeps pointer to 64K ram + Spc_Dsp( uint8_t* ram ); + + // Mute voice n if bit n (1 << n) of mask is clear. + enum { voice_count = 8 }; + void mute_voices( int mask ); + + // Clear state and silence everything. + void reset(); + + // Set gain, where 1.0 is normal. When greater than 1.0, output is clamped to + // the 16-bit sample range. + void set_gain( double ); + + // If true, prevent channels and global volumes from being phase-negated + void disable_surround( bool disable ); + + // Read/write register 'n', where n ranges from 0 to register_count - 1. + enum { register_count = 128 }; + int read ( int n ); + void write( int n, int ); + + // Run DSP for 'count' samples. Write resulting samples to 'buf' if not NULL. + void run( long count, short* buf = NULL ); + + +// End of public interface +private: + + struct raw_voice_t { + int8_t left_vol; + int8_t right_vol; + uint8_t rate [2]; + uint8_t waveform; + uint8_t adsr [2]; // envelope rates for attack, decay, and sustain + uint8_t gain; // envelope gain (if not using ADSR) + int8_t envx; // current envelope level + int8_t outx; // current sample + int8_t unused [6]; + }; + + union { + raw_voice_t voice [voice_count]; + + uint8_t reg [register_count]; + + struct { + int8_t unused1 [12]; + int8_t left_volume; // 0C Main Volume Left (-.7) + int8_t echo_feedback; // 0D Echo Feedback (-.7) + int8_t unused2 [14]; + int8_t right_volume; // 1C Main Volume Right (-.7) + int8_t unused3 [15]; + int8_t left_echo_volume; // 2C Echo Volume Left (-.7) + uint8_t pitch_mods; // 2D Pitch Modulation on/off for each voice + int8_t unused4 [14]; + int8_t right_echo_volume; // 3C Echo Volume Right (-.7) + uint8_t noise_enables; // 3D Noise output on/off for each voice + int8_t unused5 [14]; + uint8_t key_ons; // 4C Key On for each voice + uint8_t echo_ons; // 4D Echo on/off for each voice + int8_t unused6 [14]; + uint8_t key_offs; // 5C key off for each voice (instantiates release mode) + uint8_t wave_page; // 5D source directory (wave table offsets) + int8_t unused7 [14]; + uint8_t flags; // 6C flags and noise freq + uint8_t echo_page; // 6D + int8_t unused8 [14]; + uint8_t wave_ended; // 7C + uint8_t echo_delay; // 7D ms >> 4 + char unused9 [2]; + } g; + }; + + uint8_t* const ram; + + // Cache of echo FIR values for faster access + short fir_coeff [voice_count]; + + // fir_buf [i + 8] == fir_buf [i], to avoid wrap checking in FIR code + short fir_buf [16] [2]; + int fir_offset; // (0 to 7) + + enum { emu_gain_bits = 8 }; + int emu_gain; + + int keyed_on; // 8-bits for 8 voices + int keys; + + int echo_ptr; + int noise_amp; + int noise; + int noise_count; + + int surround_threshold; + + static const BOOST::int16_t gauss []; + + enum state_t { + state_attack, + state_decay, + state_sustain, + state_release + }; + + struct voice_t { + short volume [2]; + short fraction;// 12-bit fractional position + short interp3; // most recent four decoded samples + short interp2; + short interp1; + short interp0; + short block_remain; // number of nybbles remaining in current block + unsigned short addr; + short block_header; // header byte from current block + short envcnt; + short envx; + short on_cnt; + short enabled; // 7 if enabled, 31 if disabled + short envstate; + short unused; // pad to power of 2 + }; + + voice_t voice_state [voice_count]; + + int clock_envelope( int ); +}; + +inline void Spc_Dsp::disable_surround( bool disable ) { surround_threshold = disable ? 0 : -0x7FFF; } + +inline void Spc_Dsp::set_gain( double v ) { emu_gain = (int) (v * (1 << emu_gain_bits)); } + +inline int Spc_Dsp::read( int i ) +{ + assert( (unsigned) i < register_count ); + return reg [i]; +} + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Spc_Emu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Spc_Emu.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,133 @@ + +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ + +#include "Spc_Emu.h" + +#include +#include +#include "blargg_endian.h" + +/* Copyright (C) 2004-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +Spc_Emu::Spc_Emu( double gain ) +{ + apu.set_gain( gain ); +} + +Spc_Emu::~Spc_Emu() +{ +} + +const char** Spc_Emu::voice_names() const +{ + static const char* names [] = { + "DSP 1", "DSP 2", "DSP 3", "DSP 4", "DSP 5", "DSP 6", "DSP 7", "DSP 8" + }; + return names; +} + +void Spc_Emu::mute_voices( int m ) +{ + Music_Emu::mute_voices( m ); + apu.mute_voices( m ); +} + +blargg_err_t Spc_Emu::set_sample_rate( long sample_rate ) +{ + if ( sample_rate != native_sample_rate ) + { + BLARGG_RETURN_ERR( resampler.buffer_size( native_sample_rate / 20 * 2 ) ); + resampler.time_ratio( (double) native_sample_rate / sample_rate, 0.9965 ); + } + return Music_Emu::set_sample_rate( sample_rate ); +} + +blargg_err_t Spc_Emu::load( Data_Reader& in ) +{ + header_t h; + BLARGG_RETURN_ERR( in.read( &h, sizeof h ) ); + return load( h, in ); +} + +blargg_err_t Spc_Emu::load( const header_t& h, Data_Reader& in ) +{ + if ( in.remain() < Snes_Spc::spc_file_size - (int) sizeof h ) + return "Not an SPC file"; + + if ( strncmp( h.tag, "SNES-SPC700 Sound File Data", 27 ) != 0 ) + return "Not an SPC file"; + + long remain = in.remain(); + long size = remain + sizeof h; + if ( size < trailer_offset ) + size = trailer_offset; + BLARGG_RETURN_ERR( spc_data.resize( size ) ); + + set_track_count( 1 ); + set_voice_count( Snes_Spc::voice_count ); + + memcpy( spc_data.begin(), &h, sizeof h ); + return in.read( &spc_data [sizeof h], remain ); +} + +void Spc_Emu::start_track( int track ) +{ + Music_Emu::start_track( track ); + + resampler.clear(); + if ( apu.load_spc( spc_data.begin(), spc_data.size() ) ) + check( false ); +} + +void Spc_Emu::skip( long count ) +{ + count = long (count * resampler.ratio()) & ~1; + + count -= resampler.skip_input( count ); + if ( count > 0 ) + apu.skip( count ); + + // eliminate pop due to resampler + const int resampler_latency = 64; + sample_t buf [resampler_latency]; + play( resampler_latency, buf ); +} + +void Spc_Emu::play( long count, sample_t* out ) +{ + require( track_count() ); // file must be loaded + + if ( sample_rate() == native_sample_rate ) + { + if ( apu.play( count, out ) ) + log_error(); + return; + } + + long remain = count; + while ( remain > 0 ) + { + remain -= resampler.read( &out [count - remain], remain ); + if ( remain > 0 ) + { + long n = resampler.max_write(); + if ( apu.play( n, resampler.buffer() ) ) + log_error(); + resampler.write( n ); + } + } + + assert( remain == 0 ); +} + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Spc_Emu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Spc_Emu.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,86 @@ + +// Super Nintendo (SNES) SPC music file emulator + +// Game_Music_Emu 0.3.0 + +#ifndef SPC_EMU_H +#define SPC_EMU_H + +#include "Fir_Resampler.h" +#include "Music_Emu.h" +#include "Snes_Spc.h" + +class Spc_Emu : public Music_Emu { + enum { trailer_offset = 0x10200 }; +public: + // A gain of 1.0 results in almost no clamping. Default gain roughly + // matches volume of other emulators. + Spc_Emu( double gain = 1.4 ); + + // 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 + 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]; + char len_secs [3]; + byte fade_msec [5]; + char author [32]; + byte mute_mask; + byte emulator; + byte unused2 [45]; + + enum { track_count = 1 }; + enum { copyright = 0 }; // no copyright field + }; + BOOST_STATIC_ASSERT( sizeof (header_t) == 0x100 ); + + // Load SPC data + blargg_err_t load( Data_Reader& ); + + // Load SPC using already-loaded header and remaining data + blargg_err_t load( header_t const&, Data_Reader& ); + + // Header for currently loaded SPC + header_t const& header() const { return *(header_t*) spc_data.begin(); } + + // Pointer and size for trailer data + byte const* trailer() const { return &spc_data [trailer_offset]; } + long trailer_size() const { return spc_data.size() - trailer_offset; } + + // If true, prevents channels and global volumes from being phase-negated + void disable_surround( bool disable = true ); + +public: + ~Spc_Emu(); + blargg_err_t set_sample_rate( long ); + void mute_voices( int ); + void start_track( int ); + void play( long, sample_t* ); + void skip( long ); + const char** voice_names() const; +public: + // deprecated + blargg_err_t init( long r, double gain = 1.4 ) { return set_sample_rate( r ); } +private: + blargg_vector spc_data; + Fir_Resampler<24> resampler; + Snes_Spc apu; +}; + +inline void Spc_Emu::disable_surround( bool b ) { apu.disable_surround( b ); } + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Track_Emu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Track_Emu.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,178 @@ + +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ + +#include "Track_Emu.h" + +#include +#include + +/* Copyright (C) 2005-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +int const stereo = 2; // channels for a stereo signal +int const fade_block_size = 512; +int const fade_length = 8000; // msec +int const silence_max = 6; // seconds +int const silence_threshold = 0x10; + +long Track_Emu::msec_to_samples( long msec ) const +{ + long rate = emu->sample_rate() * stereo; + return (msec / 1000L) * rate + (msec % 1000L) * rate / 1000; +} + +void Track_Emu::sync( long time ) +{ + buf_count = 0; + silence_count = 0; + emu_time = time; + out_time = time; + silence_time = time; + track_ended = time > fade_time + fade_length * stereo * emu->sample_rate(); +} + +void Track_Emu::restart_track() +{ + emu->start_track( track ); + emu_time = 0; + + // skip initial silence + for ( int n = 40 * stereo * emu->sample_rate() / buf_size; n--; ) + { + fill_buf( true ); + if ( buf_count || track_ended ) + break; + } + sync( 0 ); +} + +void Track_Emu::seek( long time ) +{ + long pos = msec_to_samples( time ) & ~1; + if ( pos < out_time ) + restart_track(); + emu->skip( pos - emu_time ); + sync( pos ); +} + +void Track_Emu::start_track( Music_Emu* e, int t, long length, bool ds ) +{ +// to do: remove +//length = 50 * 1000; +//ds = true; +//t = 23; + + emu = e; + track = t; + detect_silence = ds; + fade_factor = pow( 0.005, 1.0 / msec_to_samples( fade_length ) ); + fade_time = msec_to_samples( length ); + restart_track(); +} + +static bool is_silence( const Music_Emu::sample_t* p, int count ) +{ + while ( count-- ) + { + if ( (unsigned) (*p++ + silence_threshold / 2) > (unsigned) silence_threshold ) + return false; + } + return true; +} + +void Track_Emu::fill_buf( bool check_silence ) +{ + if ( !buf_count && !track_ended && + emu_time - out_time < silence_max * stereo * emu->sample_rate() ) + { + emu->play( buf_size, buf ); + emu_time += buf_size; + if ( (check_silence || emu_time > fade_time) && is_silence( buf, buf_size ) ) + { + silence_count += buf_size; + } + else + { + silence_time = emu_time; + buf_count = buf_size; + } + if ( emu->track_ended() || emu->error_count() ) + track_ended = true; + } +} + +inline void Track_Emu::end_track() +{ + silence_count = 0; + buf_count = 0; + track_ended = true; +} + +bool Track_Emu::play( int out_count, Music_Emu::sample_t* out ) +{ + assert( out_count % 2 == 0 ); + assert( emu ); + + int pos = 0; + while ( pos < out_count ) + { + // fill with any remaining silence + int count = min( silence_count, out_count - pos ); + if ( count ) + { + silence_count -= count; + memset( &out [pos], 0, count * sizeof *out ); + } + else + { + // empty internal buffer + count = min( buf_count, out_count - pos ); + if ( !count && track_ended ) + { + memset( &out [pos], 0, (out_count - pos) * sizeof *out ); + return true; + } + + memcpy( &out [pos], &buf [buf_size - buf_count], count * sizeof *out ); + buf_count -= count; + } + pos += count; + + // keep internal buffer full and possibly run ahead + for ( int n = 6; n--; ) + fill_buf( detect_silence ); + } + out_time += out_count; + + if ( emu_time - silence_time > silence_max * stereo * emu->sample_rate() && silence_time ) + end_track(); + + // fade if track is ending + if ( out_time > fade_time ) + { + for ( int i = 0; i < out_count; i += fade_block_size ) + { + double gain = pow( fade_factor, (double) (out_time + i - fade_time) ); + if ( gain < 0.005 ) + end_track(); + + int count = min( fade_block_size, out_count - i ); + int igain = gain * (1 << 15); + for ( int j = 0; j < count; j++ ) + out [i + j] = (out [i + j] * igain) >> 15; + } + } + + return !silence_count && !buf_count && track_ended; +} + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Track_Emu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Track_Emu.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,48 @@ + +// Music track emulator that handles fading and lookahead silence detection + +#ifndef TRACK_EMU_H +#define TRACK_EMU_H + +#include "Music_Emu.h" + +class Track_Emu { +public: + // Start track and start fade at fade_time. If detect_silence is true, + // continually checks for end-of-track silence of more than around 6 + // seconds. Keeps pointer to emulator. + void start_track( Music_Emu*, int track, long fade_time_msec, bool detect_silence ); + + // Seek to new time in track + void seek( long msec ); + + // Play for 'count' samples and write to output buffer. Returns true when track + // has ended. + bool play( int count, Music_Emu::sample_t* out ); + +private: + Music_Emu* emu; + Music_Emu::sample_t* buffer; + double fade_factor; + long emu_time; // number of samples emulator has generated since start of track + long out_time; // number of samples played since start of track + long silence_time; // number of samples where most recent silence began + long fade_time; // number of samples to begin fading at + int silence_count; // number of samples of silence to play before using bud + int buf_count; // number of samples left in buffer + int track; + bool detect_silence; + bool track_ended; + enum { buf_size = 1024 }; + Music_Emu::sample_t buf [buf_size]; + + void end_track(); + void restart_track(); + void sync( long time ); + void fill_buf( bool check_silence ); + long msec_to_samples( long msec ) const; +}; + + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Vfs_File.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Vfs_File.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,54 @@ + +#include "Vfs_File.h" + +#include "libaudacious/vfs.h" + +Vfs_File_Reader::Vfs_File_Reader() : file_( NULL ) { } + +Vfs_File_Reader::~Vfs_File_Reader() { close(); } + +Vfs_File_Reader::error_t Vfs_File_Reader::open( const char* path ) +{ + file_ = vfs_fopen( path, "rb" ); + if ( !file_ ) + return "Couldn't open file"; + return 0; +} + +long Vfs_File_Reader::size() const +{ + long pos = tell(); + vfs_fseek( (VFSFile*) file_, 0, SEEK_END ); + long result = tell(); + vfs_fseek( (VFSFile*) file_, pos, SEEK_SET ); + return result; +} + +long Vfs_File_Reader::read_avail( void* p, long s ) +{ + return (long) vfs_fread( p, 1, s, (VFSFile*) file_ ); +} + +long Vfs_File_Reader::tell() const +{ + return vfs_ftell( (VFSFile*) file_ ); +} + +Vfs_File_Reader::error_t Vfs_File_Reader::seek( long n ) +{ + if ( n == 0 ) // optimization + vfs_rewind( (VFSFile*) file_ ); + else if ( vfs_fseek( (VFSFile*) file_, n, SEEK_SET ) != 0 ) + return "Error seeking in file"; + return 0; +} + +void Vfs_File_Reader::close() +{ + if ( file_ ) + { + vfs_fclose( (VFSFile*) file_ ); + file_ = 0; + } +} + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Vfs_File.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Vfs_File.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,23 @@ + +// File_Reader based on a VFSFile + +#ifndef VFS_FILE_H +#define VFS_FILE_H + +#include "abstract_file.h" + +class Vfs_File_Reader : public File_Reader { + void* file_; +public: + Vfs_File_Reader(); + ~Vfs_File_Reader(); + error_t open( const char* ); + long size() const; + long read_avail( void*, long ); + long tell() const; + error_t seek( long ); + void close(); +}; + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Vgm_Emu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Vgm_Emu.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,298 @@ + +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ + +#include "Vgm_Emu.h" + +#include +#include +#include "blargg_endian.h" + +/* Copyright (C) 2003-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +double const gain = 3.0; // FM emulators are internally quieter to avoid 16-bit overflow +double const rolloff = 0.990; +double const oversample_factor = 1.5; + +Vgm_Emu::Vgm_Emu( bool os, double tempo ) +{ + oversample = os; + pos = NULL; + data = NULL; + uses_fm = false; + vgm_rate = (long) (header_t::time_rate * tempo + 0.5); + + static equalizer_t const eq = { -14.0, 80 }; + set_equalizer( eq ); + psg.volume( 1.0 ); +} + +Vgm_Emu::~Vgm_Emu() +{ + unload(); +} + +void Vgm_Emu::unload() +{ + data = NULL; + pos = NULL; + set_track_ended( false ); + mem.clear(); +} + +blargg_err_t Vgm_Emu::set_sample_rate( long sample_rate ) +{ + BLARGG_RETURN_ERR( blip_buf.set_sample_rate( sample_rate, 1000 / 30 ) ); + return Classic_Emu::set_sample_rate( sample_rate ); +} + +BOOST::uint8_t const* Vgm_Emu::gd3_data( int* size ) const +{ + if ( size ) + *size = 0; + + long gd3_offset = get_le32( header_.gd3_offset ); + if ( !gd3_offset ) + return NULL; + + gd3_offset -= 0x40 - offsetof (header_t,gd3_offset); + if ( gd3_offset < 0 ) + return NULL; + + byte const* gd3 = data + gd3_offset; + if ( data_end - gd3 < 16 || 0 != memcmp( gd3, "Gd3 ", 4 ) || get_le32( gd3 + 4 ) >= 0x200 ) + return NULL; + + long gd3_size = get_le32( gd3 + 8 ); + if ( data_end - gd3 < gd3_size - 12 ) + return NULL; + + if ( size ) + *size = data_end - gd3; + return gd3; +} + +void Vgm_Emu::update_eq( blip_eq_t const& eq ) +{ + psg.treble_eq( eq ); + dac_synth.treble_eq( eq ); +} + +void Vgm_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) +{ + if ( i < psg.osc_count ) + psg.osc_output( i, c, l, r ); +} + +const char** Vgm_Emu::voice_names() const +{ + static const char* fm_names [] = { + "FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PCM", "PSG" + }; + if ( uses_fm ) + return fm_names; + + static const char* psg_names [] = { "Square 1", "Square 2", "Square 3", "Noise" }; + return psg_names; +} + +void Vgm_Emu::mute_voices( int mask ) +{ + Classic_Emu::mute_voices( mask ); + dac_synth.output( &blip_buf ); + if ( uses_fm ) + { + psg.output( (mask & 0x80) ? 0 : &blip_buf ); + if ( ym2612.enabled() ) + { + dac_synth.volume( (mask & 0x40) ? 0.0 : 0.1115 / 256 * gain ); + ym2612.mute_voices( mask ); + } + + if ( ym2413.enabled() ) + { + int m = mask & 0x3f; + if ( mask & 0x20 ) + m |= 0x01e0; // channels 5-8 + if ( mask & 0x40 ) + m |= 0x3e00; + ym2413.mute_voices( m ); + } + } +} + +blargg_err_t Vgm_Emu::load_( const header_t& h, void const* new_data, long new_size ) +{ + header_ = h; + + // compatibility + if ( 0 != memcmp( header_.tag, "Vgm ", 4 ) ) + return "Not a VGM file"; + check( get_le32( header_.version ) <= 0x150 ); + + // psg rate + long psg_rate = get_le32( header_.psg_rate ); + if ( !psg_rate ) + psg_rate = 3579545; + blip_time_factor = (long) floor( (double) (1L << blip_time_bits) / vgm_rate * psg_rate + 0.5 ); + blip_buf.clock_rate( psg_rate ); + + data = (byte*) new_data; + data_end = data + new_size; + + // get loop + loop_begin = data_end; + if ( get_le32( header_.loop_offset ) ) + loop_begin = &data [get_le32( header_.loop_offset ) + offsetof (header_t,loop_offset) - 0x40]; + + set_voice_count( psg.osc_count ); + set_track_count( 1 ); + + BLARGG_RETURN_ERR( setup_fm() ); + + // do after FM in case output buffer is changed + BLARGG_RETURN_ERR( Classic_Emu::setup_buffer( psg_rate ) ); + + return blargg_success; +} + +blargg_err_t Vgm_Emu::setup_fm() +{ + long ym2612_rate = get_le32( header_.ym2612_rate ); + long ym2413_rate = get_le32( header_.ym2413_rate ); + if ( ym2413_rate && get_le32( header_.version ) < 0x110 ) + update_fm_rates( &ym2413_rate, &ym2612_rate ); + + uses_fm = false; + + double fm_rate = blip_buf.sample_rate() * oversample_factor; + + if ( ym2612_rate ) + { + uses_fm = true; + if ( !oversample ) + fm_rate = ym2612_rate / 144.0; + Dual_Resampler::setup( fm_rate / blip_buf.sample_rate(), rolloff, gain ); + BLARGG_RETURN_ERR( ym2612.set_rate( fm_rate, ym2612_rate ) ); + ym2612.enable( true ); + set_voice_count( 8 ); + } + + if ( !uses_fm && ym2413_rate ) + { + uses_fm = true; + if ( !oversample ) + fm_rate = ym2413_rate / 72.0; + Dual_Resampler::setup( fm_rate / blip_buf.sample_rate(), rolloff, gain ); + int result = ym2413.set_rate( fm_rate, ym2413_rate ); + if ( result == 2 ) + return "YM2413 FM sound isn't supported"; + BLARGG_CHECK_ALLOC( !result ); + ym2413.enable( true ); + set_voice_count( 8 ); + } + + if ( uses_fm ) + { + //dprintf( "fm_rate: %f\n", fm_rate ); + fm_time_factor = 2 + (long) floor( fm_rate * (1L << fm_time_bits) / vgm_rate + 0.5 ); + BLARGG_RETURN_ERR( Dual_Resampler::resize( blip_buf.length() * blip_buf.sample_rate() / 1000 ) ); + psg.volume( 0.135 * gain ); + } + else + { + ym2612.enable( false ); + ym2413.enable( false ); + psg.volume( 1.0 ); + } + + return blargg_success; +} + +blargg_err_t Vgm_Emu::load( Data_Reader& reader ) +{ + header_t h; + BLARGG_RETURN_ERR( reader.read( &h, sizeof h ) ); + return load( h, reader ); +} + +blargg_err_t Vgm_Emu::load( const header_t& h, Data_Reader& reader ) +{ + unload(); + + // allocate and read data + long data_size = reader.remain(); + int const padding = 8; + BLARGG_RETURN_ERR( mem.resize( data_size + padding ) ); + blargg_err_t err = reader.read( mem.begin(), data_size ); + if ( err ) { + unload(); + return err; + } + memset( &mem [data_size], 0x66, padding ); // pad with end command + + return load_( h, mem.begin(), data_size ); +} + +void Vgm_Emu::start_track( int track ) +{ + require( data ); // file must have been loaded + + Classic_Emu::start_track( track ); + psg.reset(); + + dac_disabled = -1; + pcm_data = data; + pcm_pos = data; + dac_amp = -1; + vgm_time = 0; + pos = data; + if ( get_le32( header_.version ) >= 0x150 ) + { + long data_offset = get_le32( header_.data_offset ); + check( data_offset ); + if ( data_offset ) + pos += data_offset + offsetof (header_t,data_offset) - 0x40; + } + + if ( uses_fm ) + { + if ( ym2413.enabled() ) + ym2413.reset(); + + if ( ym2612.enabled() ) + ym2612.reset(); + + fm_time_offset = 0; + blip_buf.clear(); + Dual_Resampler::clear(); + } +} + +long Vgm_Emu::run( int msec, bool* added_stereo ) +{ + blip_time_t psg_end = run_commands( msec * vgm_rate / 1000 ); + *added_stereo = psg.end_frame( psg_end ); + return psg_end; +} + +void Vgm_Emu::play( long count, sample_t* out ) +{ + require( pos ); // track must have been started + + if ( uses_fm ) + Dual_Resampler::play( count, out, blip_buf ); + else + Classic_Emu::play( count, out ); +} + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Vgm_Emu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Vgm_Emu.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,118 @@ + +// Multi-format VGM music emulator with support for SMS PSG and Mega Drive FM + +// Game_Music_Emu 0.3.0 + +#ifndef VGM_EMU_H +#define VGM_EMU_H + +#include "abstract_file.h" +#include "Vgm_Emu_Impl.h" + +// Emulates VGM music using SN76489/SN76496 PSG, YM2612, and YM2413 FM sound chips. +// Supports custom sound buffer and frequency equalization when VGM uses just the PSG. +// FM sound chips can be run at their proper rates, or slightly higher to reduce +// aliasing on high notes. Currently YM2413 support requires that you supply a +// YM2413 sound chip emulator. I can provide one I've modified to work with the library. +class Vgm_Emu : public Vgm_Emu_Impl { +public: + + // Oversample runs FM chips at higher than normal rate. Tempo adjusts speed of + // music, but not pitch. + Vgm_Emu( bool oversample = true, double tempo = 1.0 ); + + // VGM header format + struct header_t + { + char tag [4]; + byte data_size [4]; + byte version [4]; + byte psg_rate [4]; + byte ym2413_rate [4]; + byte gd3_offset [4]; + byte track_duration [4]; + byte loop_offset [4]; + byte loop_duration [4]; + byte frame_rate [4]; + byte noise_feedback [2]; + byte noise_width; + byte unused1; + byte ym2612_rate [4]; + byte ym2151_rate [4]; + byte data_offset [4]; + byte unused2 [8]; + + enum { track_count = 1 }; // one track per file + enum { time_rate = 44100 }; // all times specified at this rate + + // track information is in gd3 data + enum { game = 0 }; + enum { song = 0 }; + enum { author = 0 }; + enum { copyright = 0 }; + }; + BOOST_STATIC_ASSERT( sizeof (header_t) == 64 ); + + // Load VGM data + blargg_err_t load( Data_Reader& ); + + // Load VGM using already-loaded header and remaining data + blargg_err_t load( header_t const&, Data_Reader& ); + + // Load VGM using pointer to file data. Keeps pointer to data. + blargg_err_t load( void const* data, long size ); + + // Header for currently loaded VGM + header_t const& header() const { return header_; } + + // Pointer to gd3 data, or NULL if none. Optionally returns size of data. + // Checks for GD3 header and that version is less than 2.0. + byte const* gd3_data( int* size_out = NULL ) const; + + // to do: find better name for this + // True if Classic_Emu operations are supported + bool is_classic_emu() const { return !uses_fm; } + +public: + ~Vgm_Emu(); + blargg_err_t set_sample_rate( long sample_rate ); + void start_track( int ); + void mute_voices( int mask ); + const char** voice_names() const; + void play( long count, sample_t* ); +public: + // deprecated + int track_length( const byte** end_out = NULL, int* remain_out = NULL ) const + { + return (header().track_duration [3]*0x1000000L + + header().track_duration [2]*0x0010000L + + header().track_duration [1]*0x0000100L + + header().track_duration [0]) / header_t::time_rate; + } +protected: + // Classic_Emu + void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); + void update_eq( blip_eq_t const& ); + blip_time_t run( int, bool* ); +private: + header_t header_; + blargg_vector mem; + long vgm_rate; + bool oversample; + bool uses_fm; + + blargg_err_t init_( long sample_rate ); + blargg_err_t load_( const header_t&, void const* data, long size ); + blargg_err_t setup_fm(); + void unload(); +}; + +inline blargg_err_t Vgm_Emu::load( void const* data, long size ) +{ + unload(); + return load_( *(header_t*) data, (char*) data + sizeof (header_t), + size - sizeof (header_t) ); +} + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Vgm_Emu_Impl.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Vgm_Emu_Impl.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,312 @@ + +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ + +#include "Vgm_Emu.h" + +#include +#include +#include "blargg_endian.h" + +/* Copyright (C) 2003-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +enum { + cmd_gg_stereo = 0x4F, + cmd_psg = 0x50, + cmd_ym2413 = 0x51, + cmd_ym2612_port0 = 0x52, + cmd_ym2612_port1 = 0x53, + cmd_ym2151 = 0x54, + cmd_delay = 0x61, + cmd_delay_735 = 0x62, + cmd_delay_882 = 0x63, + cmd_byte_delay = 0x64, + cmd_end = 0x66, + cmd_data_block = 0x67, + cmd_short_delay = 0x70, + cmd_pcm_delay = 0x80, + cmd_pcm_seek = 0xE0, + + pcm_block_type = 0x00, + ym2612_dac_port = 0x2A +}; + +inline int command_len( int command ) +{ + switch ( command >> 4 ) + { + case 0x03: + case 0x04: + return 2; + + case 0x05: + case 0x0A: + case 0x0B: + return 3; + + case 0x0C: + case 0x0D: + return 4; + + case 0x0E: + case 0x0F: + return 5; + } + + return 0; +} + +template +inline void Ym_Emu::begin_frame( short* p ) +{ + require( enabled() ); + out = p; + last_time = 0; +} + +template +inline int Ym_Emu::run_until( int time ) +{ + int count = time - last_time; + if ( count > 0 ) + { + if ( last_time < 0 ) + return false; + last_time = time; + short* p = out; + out += count * Emu::out_chan_count; + Emu::run( count, p ); + } + return true; +} + +inline Vgm_Emu_Impl::fm_time_t Vgm_Emu_Impl::to_fm_time( vgm_time_t t ) const +{ + return (t * fm_time_factor + fm_time_offset) >> fm_time_bits; +} + +inline blip_time_t Vgm_Emu_Impl::to_blip_time( vgm_time_t t ) const +{ + return (t * blip_time_factor) >> blip_time_bits; +} + +void Vgm_Emu_Impl::write_pcm( vgm_time_t vgm_time, int amp ) +{ + blip_time_t blip_time = to_blip_time( vgm_time ); + int old = dac_amp; + int delta = amp - old; + dac_amp = amp; + if ( old >= 0 ) + dac_synth.offset_inline( blip_time, delta, &blip_buf ); + else + dac_amp |= dac_disabled; +} + +blip_time_t Vgm_Emu_Impl::run_commands( vgm_time_t end_time ) +{ + vgm_time_t vgm_time = this->vgm_time; + byte const* pos = this->pos; + if ( pos >= data_end ) + { + set_track_ended(); + if ( pos > data_end ) + log_error(); + } + + while ( vgm_time < end_time && pos < data_end ) + { + switch ( *pos++ ) + { + case cmd_end: + pos = loop_begin; // if not looped, loop_begin == data_end + break; + + case cmd_delay_735: + vgm_time += 735; + break; + + case cmd_delay_882: + vgm_time += 882; + break; + + case cmd_gg_stereo: + psg.write_ggstereo( to_blip_time( vgm_time ), *pos++ ); + break; + + case cmd_psg: + psg.write_data( to_blip_time( vgm_time ), *pos++ ); + break; + + case cmd_delay: + vgm_time += pos [1] * 0x100L + pos [0]; + pos += 2; + break; + + case cmd_byte_delay: + vgm_time += *pos++; + break; + + case cmd_ym2413: + if ( ym2413.run_until( to_fm_time( vgm_time ) ) ) + ym2413.write( pos [0], pos [1] ); + pos += 2; + break; + + case cmd_ym2612_port0: + if ( pos [0] == ym2612_dac_port ) + { + write_pcm( vgm_time, pos [1] ); + } + else if ( ym2612.run_until( to_fm_time( vgm_time ) ) ) + { + if ( pos [0] == 0x2B ) + { + dac_disabled = (pos [1] >> 7 & 1) - 1; + dac_amp |= dac_disabled; + } + ym2612.write0( pos [0], pos [1] ); + } + pos += 2; + break; + + case cmd_ym2612_port1: + if ( ym2612.run_until( to_fm_time( vgm_time ) ) ) + ym2612.write1( pos [0], pos [1] ); + pos += 2; + break; + + case cmd_data_block: { + check( *pos == cmd_end ); + int type = pos [1]; + long size = get_le32( pos + 2 ); + pos += 6; + if ( type == pcm_block_type ) + pcm_data = pos; + pos += size; + break; + } + + case cmd_pcm_seek: + pcm_pos = pcm_data + pos [3] * 0x1000000L + pos [2] * 0x10000L + + pos [1] * 0x100L + pos [0]; + pos += 4; + break; + + default: + int cmd = pos [-1]; + switch ( cmd & 0xf0 ) + { + case cmd_pcm_delay: + vgm_time += cmd & 0x0f; + write_pcm( vgm_time, *pcm_pos++ ); + break; + + case cmd_short_delay: + vgm_time += (cmd & 0x0f) + 1; + break; + + case 0x50: + pos += 2; + break; + + default: + pos += command_len( cmd ) - 1; + log_error(); + } + } + } + vgm_time -= end_time; + this->pos = pos; + this->vgm_time = vgm_time; + + return to_blip_time( end_time ); +} + +int Vgm_Emu_Impl::play_frame( blip_time_t blip_time, int sample_count, sample_t* buf ) +{ + // to do: timing is working mostly by luck + + int min_pairs = sample_count >> 1; + int vgm_time = ((long) min_pairs << fm_time_bits) / fm_time_factor - 1; + assert( to_fm_time( vgm_time ) <= min_pairs ); + int pairs = min_pairs; + while ( (pairs = to_fm_time( vgm_time )) < min_pairs ) + vgm_time++; + + if ( ym2612.enabled() ) + { + ym2612.begin_frame( buf ); + memset( buf, 0, pairs * stereo * sizeof *buf ); + } + else if ( ym2413.enabled() ) + { + ym2413.begin_frame( buf ); + } + + run_commands( vgm_time ); + ym2612.run_until( pairs ); + ym2413.run_until( pairs ); + + fm_time_offset = (vgm_time * fm_time_factor + fm_time_offset) - + ((long) pairs << fm_time_bits); + + psg.end_frame( blip_time ); + + return pairs * stereo; +} + +// Update pre-1.10 header FM rates by scanning commands +void Vgm_Emu_Impl::update_fm_rates( long* ym2413_rate, long* ym2612_rate ) const +{ + byte const* p = data; + while ( p < data_end ) + { + switch ( *p ) + { + case cmd_end: + return; + + case cmd_psg: + case cmd_byte_delay: + p += 2; + break; + + case cmd_delay: + p += 3; + break; + + case cmd_data_block: + p += 7 + get_le32( p + 3 ); + break; + + case cmd_ym2413: + *ym2612_rate = 0; + return; + + case cmd_ym2612_port0: + case cmd_ym2612_port1: + *ym2612_rate = *ym2413_rate; + *ym2413_rate = 0; + return; + + case cmd_ym2151: + *ym2413_rate = 0; + *ym2612_rate = 0; + return; + + default: + p += command_len( *p ); + } + } +} + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Vgm_Emu_Impl.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Vgm_Emu_Impl.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,74 @@ + +// Game_Music_Emu 0.3.0 + +#ifndef VGM_EMU_IMPL_H +#define VGM_EMU_IMPL_H + +#include "Dual_Resampler.h" +#include "Classic_Emu.h" +#include "Ym2413_Emu.h" +#include "Ym2612_Emu.h" +#include "Sms_Apu.h" + +template +class Ym_Emu : public Emu { +protected: + int last_time; + short* out; + enum { disabled_time = -1 }; +public: + Ym_Emu() : last_time( disabled_time ), out( NULL ) { } + void enable( bool b ) { last_time = b ? 0 : disabled_time; } + bool enabled() const { return last_time != disabled_time; } + void begin_frame( short* p ); + int run_until( int time ); +}; + +class Vgm_Emu_Impl : public Classic_Emu, private Dual_Resampler { +public: + typedef Classic_Emu::sample_t sample_t; + typedef BOOST::uint8_t byte; + +protected: + enum { stereo = 2 }; + + typedef int vgm_time_t; + + enum { fm_time_bits = 12 }; + typedef int fm_time_t; + long fm_time_offset; + int fm_time_factor; + fm_time_t to_fm_time( vgm_time_t ) const; + + enum { blip_time_bits = 12 }; + int blip_time_factor; + blip_time_t to_blip_time( vgm_time_t ) const; + + byte const* data; + byte const* loop_begin; + byte const* data_end; + void update_fm_rates( long* ym2413_rate, long* ym2612_rate ) const; + + vgm_time_t vgm_time; + byte const* pos; + blip_time_t run_commands( vgm_time_t ); + int play_frame( blip_time_t blip_time, int sample_count, sample_t* buf ); + + byte const* pcm_data; + byte const* pcm_pos; + int dac_amp; + int dac_disabled; // -1 if disabled + void write_pcm( vgm_time_t, int amp ); + + Ym_Emu ym2612; + Ym_Emu ym2413; + + Blip_Buffer blip_buf; + Sms_Apu psg; + Blip_Synth dac_synth; + + friend class Vgm_Emu; +}; + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Ym2413_Emu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Ym2413_Emu.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,21 @@ + +// Use in place of Ym2413_Emu.cpp and ym2413.c to disable support for this chip + +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ + +#include "Ym2413_Emu.h" + +Ym2413_Emu::Ym2413_Emu() { } + +Ym2413_Emu::~Ym2413_Emu() { } + +int Ym2413_Emu::set_rate( double, double ) { return 2; } + +void Ym2413_Emu::reset() { } + +void Ym2413_Emu::write( int, int ) { } + +void Ym2413_Emu::mute_voices( int ) { } + +void Ym2413_Emu::run( int, sample_t* ) { } + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Ym2413_Emu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Ym2413_Emu.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,36 @@ + +// YM2413 FM sound chip emulator interface + +// Game_Music_Emu 0.3.0 + +#ifndef YM2413_EMU_H +#define YM2413_EMU_H + +class Ym2413_Emu { + struct OPLL* opll; +public: + Ym2413_Emu(); + ~Ym2413_Emu(); + + // Set output sample rate and chip clock rates, in Hz. Returns non-zero + // if error. + int set_rate( double sample_rate, double clock_rate ); + + // Reset to power-up state + void reset(); + + // Mute voice n if bit n (1 << n) of mask is set + enum { channel_count = 14 }; + void mute_voices( int mask ); + + // Write 'data' to 'addr' + void write( int addr, int data ); + + // Run and write pair_count samples to output + typedef short sample_t; + enum { out_chan_count = 2 }; // stereo + void run( int pair_count, sample_t* out ); +}; + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Ym2612_Emu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Ym2612_Emu.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,1321 @@ + +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ + +// Based on Gens 2.10 ym2612.c + +#include "Ym2612_Emu.h" + +#include +#include +#include +#include +#include +#include + +/* Copyright (C) 2002 Stéphane Dallongeville (gens AT consolemul.com) */ +/* Copyright (C) 2004-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +// This is mostly the original source in its C style and all. +// +// Somewhat optimized and simplified. Uses a template to generate the many +// variants of Update_Chan. Rewrote header file. In need of full rewrite by +// someone more familiar with FM sound and the YM2612. Has some inaccuracies +// compared to the Sega Genesis sound, particularly being mixed at such a +// high sample accuracy (the Genesis sounds like it has only 8 bit samples). +// - Shay + +const int output_bits = 14; + +struct slot_t +{ + const int *DT; // parametre detune + int MUL; // parametre "multiple de frequence" + int TL; // Total Level = volume lorsque l'enveloppe est au plus haut + int TLL; // Total Level ajusted + int SLL; // Sustin Level (ajusted) = volume où l'enveloppe termine sa premiere phase de regression + int KSR_S; // Key Scale Rate Shift = facteur de prise en compte du KSL dans la variations de l'enveloppe + int KSR; // Key Scale Rate = cette valeur est calculee par rapport à la frequence actuelle, elle va influer + // sur les differents parametres de l'enveloppe comme l'attaque, le decay ... comme dans la realite ! + int SEG; // Type enveloppe SSG + int env_xor; + int env_max; + + const int *AR; // Attack Rate (table pointeur) = Taux d'attaque (AR[KSR]) + const int *DR; // Decay Rate (table pointeur) = Taux pour la regression (DR[KSR]) + const int *SR; // Sustin Rate (table pointeur) = Taux pour le maintien (SR[KSR]) + const int *RR; // Release Rate (table pointeur) = Taux pour le rel'chement (RR[KSR]) + int Fcnt; // Frequency Count = compteur-frequence pour determiner l'amplitude actuelle (SIN[Finc >> 16]) + int Finc; // frequency step = pas d'incrementation du compteur-frequence + // plus le pas est grand, plus la frequence est aïgu (ou haute) + int Ecurp; // Envelope current phase = cette variable permet de savoir dans quelle phase + // de l'enveloppe on se trouve, par exemple phase d'attaque ou phase de maintenue ... + // en fonction de la valeur de cette variable, on va appeler une fonction permettant + // de mettre à jour l'enveloppe courante. + int Ecnt; // Envelope counter = le compteur-enveloppe permet de savoir où l'on se trouve dans l'enveloppe + int Einc; // Envelope step courant + int Ecmp; // Envelope counter limite pour la prochaine phase + int EincA; // Envelope step for Attack = pas d'incrementation du compteur durant la phase d'attaque + // cette valeur est egal à AR[KSR] + int EincD; // Envelope step for Decay = pas d'incrementation du compteur durant la phase de regression + // cette valeur est egal à DR[KSR] + int EincS; // Envelope step for Sustain = pas d'incrementation du compteur durant la phase de maintenue + // cette valeur est egal à SR[KSR] + int EincR; // Envelope step for Release = pas d'incrementation du compteur durant la phase de rel'chement + // cette valeur est egal à RR[KSR] + int *OUTp; // pointeur of SLOT output = pointeur permettant de connecter la sortie de ce slot à l'entree + // d'un autre ou carrement à la sortie de la voie + int INd; // input data of the slot = donnees en entree du slot + int ChgEnM; // Change envelop mask. + int AMS; // AMS depth level of this SLOT = degre de modulation de l'amplitude par le LFO + int AMSon; // AMS enable flag = drapeau d'activation de l'AMS +}; + +struct channel_t +{ + int S0_OUT[4]; // anciennes sorties slot 0 (pour le feed back) + int LEFT; // LEFT enable flag + int RIGHT; // RIGHT enable flag + int ALGO; // Algorythm = determine les connections entre les operateurs + int FB; // shift count of self feed back = degre de "Feed-Back" du SLOT 1 (il est son unique entree) + int FMS; // Frequency Modulation Sensitivity of channel = degre de modulation de la frequence sur la voie par le LFO + int AMS; // Amplitude Modulation Sensitivity of channel = degre de modulation de l'amplitude sur la voie par le LFO + int FNUM[4]; // hauteur frequence de la voie (+ 3 pour le mode special) + int FOCT[4]; // octave de la voie (+ 3 pour le mode special) + int KC[4]; // Key Code = valeur fonction de la frequence (voir KSR pour les slots, KSR = KC >> KSR_S) + slot_t SLOT[4]; // four slot.operators = les 4 slots de la voie + int FFlag; // Frequency step recalculation flag +}; + +struct state_t +{ + int TimerBase; // TimerBase calculation + int Status; // YM2612 Status (timer overflow) + int TimerA; // timerA limit = valeur jusqu'à laquelle le timer A doit compter + int TimerAL; + int TimerAcnt; // timerA counter = valeur courante du Timer A + int TimerB; // timerB limit = valeur jusqu'à laquelle le timer B doit compter + int TimerBL; + int TimerBcnt; // timerB counter = valeur courante du Timer B + int Mode; // Mode actuel des voie 3 et 6 (normal / special) + int DAC; // DAC enabled flag + channel_t CHANNEL[Ym2612_Emu::channel_count]; // Les 6 voies du YM2612 + int REG[2][0x100]; // Sauvegardes des valeurs de tout les registres, c'est facultatif + // cela nous rend le debuggage plus facile +}; + +#ifndef PI +#define PI 3.14159265358979323846 +#endif + +#define ATTACK 0 +#define DECAY 1 +#define SUBSTAIN 2 +#define RELEASE 3 + +// SIN_LBITS <= 16 +// LFO_HBITS <= 16 +// (SIN_LBITS + SIN_HBITS) <= 26 +// (ENV_LBITS + ENV_HBITS) <= 28 +// (LFO_LBITS + LFO_HBITS) <= 28 + +#define SIN_HBITS 12 // Sinus phase counter int part +#define SIN_LBITS (26 - SIN_HBITS) // Sinus phase counter float part (best setting) + +#if (SIN_LBITS > 16) +#define SIN_LBITS 16 // Can't be greater than 16 bits +#endif + +#define ENV_HBITS 12 // Env phase counter int part +#define ENV_LBITS (28 - ENV_HBITS) // Env phase counter float part (best setting) + +#define LFO_HBITS 10 // LFO phase counter int part +#define LFO_LBITS (28 - LFO_HBITS) // LFO phase counter float part (best setting) + +#define SIN_LENGHT (1 << SIN_HBITS) +#define ENV_LENGHT (1 << ENV_HBITS) +#define LFO_LENGHT (1 << LFO_HBITS) + +#define TL_LENGHT (ENV_LENGHT * 3) // Env + TL scaling + LFO + +#define SIN_MASK (SIN_LENGHT - 1) +#define ENV_MASK (ENV_LENGHT - 1) +#define LFO_MASK (LFO_LENGHT - 1) + +#define ENV_STEP (96.0 / ENV_LENGHT) // ENV_MAX = 96 dB + +#define ENV_ATTACK ((ENV_LENGHT * 0) << ENV_LBITS) +#define ENV_DECAY ((ENV_LENGHT * 1) << ENV_LBITS) +#define ENV_END ((ENV_LENGHT * 2) << ENV_LBITS) + +#define MAX_OUT_BITS (SIN_HBITS + SIN_LBITS + 2) // Modulation = -4 <--> +4 +#define MAX_OUT ((1 << MAX_OUT_BITS) - 1) + +#define PG_CUT_OFF ((int) (78.0 / ENV_STEP)) +#define ENV_CUT_OFF ((int) (68.0 / ENV_STEP)) + +#define AR_RATE 399128 +#define DR_RATE 5514396 + +//#define AR_RATE 426136 +//#define DR_RATE (AR_RATE * 12) + +#define LFO_FMS_LBITS 9 // FIXED (LFO_FMS_BASE gives somethink as 1) +#define LFO_FMS_BASE ((int) (0.05946309436 * 0.0338 * (double) (1 << LFO_FMS_LBITS))) + +#define S0 0 // Stupid typo of the YM2612 +#define S1 2 +#define S2 1 +#define S3 3 + +inline void set_seg( slot_t& s, int seg ) +{ + s.env_xor = 0; + s.env_max = INT_MAX; + s.SEG = seg; + if ( seg & 4 ) + { + s.env_xor = ENV_MASK; + s.env_max = ENV_MASK; + } +} + +struct tables_t +{ + short SIN_TAB [SIN_LENGHT]; // SINUS TABLE (offset into TL TABLE) + int LFOcnt; // LFO counter = compteur-frequence pour le LFO + int LFOinc; // LFO step counter = pas d'incrementation du compteur-frequence du LFO + // plus le pas est grand, plus la frequence est grande + unsigned int AR_TAB [128]; // Attack rate table + unsigned int DR_TAB [96]; // Decay rate table + unsigned int DT_TAB [8] [32]; // Detune table + unsigned int SL_TAB [16]; // Substain level table + unsigned int NULL_RATE [32]; // Table for NULL rate + int LFO_INC_TAB [8]; // LFO step table + + short ENV_TAB [2 * ENV_LENGHT + 8]; // ENV CURVE TABLE (attack & decay) + + short LFO_ENV_TAB [LFO_LENGHT]; // LFO AMS TABLE (adjusted for 11.8 dB) + short LFO_FREQ_TAB [LFO_LENGHT]; // LFO FMS TABLE + int TL_TAB [TL_LENGHT * 2]; // TOTAL LEVEL TABLE (positif and minus) + unsigned int DECAY_TO_ATTACK [ENV_LENGHT]; // Conversion from decay to attack phase + unsigned int FINC_TAB [2048]; // Frequency step table +}; + +static const unsigned char DT_DEF_TAB [4 * 32] = +{ +// FD = 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + +// FD = 1 + 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, + 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 8, 8, 8, 8, + +// FD = 2 + 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, + 5, 6, 6, 7, 8, 8, 9, 10, 11, 12, 13, 14, 16, 16, 16, 16, + +// FD = 3 + 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, + 8 , 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 20, 22, 22, 22, 22 +}; + +static const unsigned char FKEY_TAB [16] = +{ + 0, 0, 0, 0, + 0, 0, 0, 1, + 2, 3, 3, 3, + 3, 3, 3, 3 +}; + +static const unsigned char LFO_AMS_TAB [4] = +{ + 31, 4, 1, 0 +}; + +static const unsigned char LFO_FMS_TAB [8] = +{ + LFO_FMS_BASE * 0, LFO_FMS_BASE * 1, + LFO_FMS_BASE * 2, LFO_FMS_BASE * 3, + LFO_FMS_BASE * 4, LFO_FMS_BASE * 6, + LFO_FMS_BASE * 12, LFO_FMS_BASE * 24 +}; + +inline void YM2612_Special_Update() { } + +struct Ym2612_Impl +{ + enum { channel_count = Ym2612_Emu::channel_count }; + + state_t YM2612; + int mute_mask; + tables_t g; + + void KEY_ON( channel_t&, int ); + void KEY_OFF( channel_t&, int ); + int SLOT_SET( int, int ); + int CHANNEL_SET( int, int ); + int YM_SET( int, int ); + + void set_rate( double sample_rate, double clock_factor ); + void reset(); + void write0( int addr, int data ); + void write1( int addr, int data ); + void run_timer( int ); + void run( int pair_count, Ym2612_Emu::sample_t* ); +}; + +void Ym2612_Impl::KEY_ON( channel_t& ch, int nsl) +{ + slot_t *SL = &(ch.SLOT [nsl]); // on recupere le bon pointeur de slot + + if (SL->Ecurp == RELEASE) // la touche est-elle rel'chee ? + { + SL->Fcnt = 0; + + // Fix Ecco 2 splash sound + + SL->Ecnt = (g.DECAY_TO_ATTACK [g.ENV_TAB [SL->Ecnt >> ENV_LBITS]] + ENV_ATTACK) & SL->ChgEnM; + SL->ChgEnM = ~0; + +// SL->Ecnt = g.DECAY_TO_ATTACK [g.ENV_TAB [SL->Ecnt >> ENV_LBITS]] + ENV_ATTACK; +// SL->Ecnt = 0; + + SL->Einc = SL->EincA; + SL->Ecmp = ENV_DECAY; + SL->Ecurp = ATTACK; + } +} + + +void Ym2612_Impl::KEY_OFF(channel_t& ch, int nsl) +{ + slot_t *SL = &(ch.SLOT [nsl]); // on recupere le bon pointeur de slot + + if (SL->Ecurp != RELEASE) // la touche est-elle appuyee ? + { + if (SL->Ecnt < ENV_DECAY) // attack phase ? + { + SL->Ecnt = (g.ENV_TAB [SL->Ecnt >> ENV_LBITS] << ENV_LBITS) + ENV_DECAY; + } + + SL->Einc = SL->EincR; + SL->Ecmp = ENV_END; + SL->Ecurp = RELEASE; + } +} + + +int Ym2612_Impl::SLOT_SET( int Adr, int data ) +{ + int nch = Adr & 3; + if ( nch == 3 ) + return 1; + + channel_t& ch = YM2612.CHANNEL [nch + (Adr & 0x100 ? 3 : 0)]; + slot_t& sl = ch.SLOT [(Adr >> 2) & 3]; + + switch ( Adr & 0xF0 ) + { + case 0x30: + if ( (sl.MUL = (data & 0x0F)) != 0 ) sl.MUL <<= 1; + else sl.MUL = 1; + + sl.DT = (int*) g.DT_TAB [(data >> 4) & 7]; + + ch.SLOT [0].Finc = -1; + + break; + + case 0x40: + sl.TL = data & 0x7F; + + // SOR2 do a lot of TL adjustement and this fix R.Shinobi jump sound... + YM2612_Special_Update(); + +#if ((ENV_HBITS - 7) < 0) + sl.TLL = sl.TL >> (7 - ENV_HBITS); +#else + sl.TLL = sl.TL << (ENV_HBITS - 7); +#endif + + break; + + case 0x50: + sl.KSR_S = 3 - (data >> 6); + + ch.SLOT [0].Finc = -1; + + if (data &= 0x1F) sl.AR = (int*) &g.AR_TAB [data << 1]; + else sl.AR = (int*) &g.NULL_RATE [0]; + + sl.EincA = sl.AR [sl.KSR]; + if (sl.Ecurp == ATTACK) sl.Einc = sl.EincA; + break; + + case 0x60: + if ( (sl.AMSon = (data & 0x80)) != 0 ) sl.AMS = ch.AMS; + else sl.AMS = 31; + + if (data &= 0x1F) sl.DR = (int*) &g.DR_TAB [data << 1]; + else sl.DR = (int*) &g.NULL_RATE [0]; + + sl.EincD = sl.DR [sl.KSR]; + if (sl.Ecurp == DECAY) sl.Einc = sl.EincD; + break; + + case 0x70: + if (data &= 0x1F) sl.SR = (int*) &g.DR_TAB [data << 1]; + else sl.SR = (int*) &g.NULL_RATE [0]; + + sl.EincS = sl.SR [sl.KSR]; + if ((sl.Ecurp == SUBSTAIN) && (sl.Ecnt < ENV_END)) sl.Einc = sl.EincS; + break; + + case 0x80: + sl.SLL = g.SL_TAB [data >> 4]; + + sl.RR = (int*) &g.DR_TAB [((data & 0xF) << 2) + 2]; + + sl.EincR = sl.RR [sl.KSR]; + if ((sl.Ecurp == RELEASE) && (sl.Ecnt < ENV_END)) sl.Einc = sl.EincR; + break; + + case 0x90: + // SSG-EG envelope shapes : + /* + E At Al H + + 1 0 0 0 \\\\ + 1 0 0 1 \___ + 1 0 1 0 \/\/ + 1 0 1 1 \ + 1 1 0 0 //// + 1 1 0 1 / + 1 1 1 0 /\/\ + 1 1 1 1 /___ + + E = SSG-EG enable + At = Start negate + Al = Altern + H = Hold */ + + set_seg( sl, (data & 8) ? (data & 0x0F) : 0 ); + break; + } + + return 0; +} + + +int Ym2612_Impl::CHANNEL_SET( int Adr, int data ) +{ + int num = Adr & 3; + if ( num == 3 ) + return 1; + + channel_t& ch = YM2612.CHANNEL [num + (Adr & 0x100 ? 3 : 0)]; + + switch ( Adr & 0xFC ) + { + case 0xA0: + YM2612_Special_Update(); + + ch.FNUM [0] = (ch.FNUM [0] & 0x700) + data; + ch.KC [0] = (ch.FOCT [0] << 2) | FKEY_TAB [ch.FNUM [0] >> 7]; + + ch.SLOT [0].Finc = -1; + break; + + case 0xA4: + YM2612_Special_Update(); + + ch.FNUM [0] = (ch.FNUM [0] & 0x0FF) + ((data & 0x07) << 8); + ch.FOCT [0] = (data & 0x38) >> 3; + ch.KC [0] = (ch.FOCT [0] << 2) | FKEY_TAB [ch.FNUM [0] >> 7]; + + ch.SLOT [0].Finc = -1; + break; + + case 0xA8: + if ( Adr < 0x100 ) + { + num++; + + YM2612_Special_Update(); + + YM2612.CHANNEL [2].FNUM [num] = (YM2612.CHANNEL [2].FNUM [num] & 0x700) + data; + YM2612.CHANNEL [2].KC [num] = (YM2612.CHANNEL [2].FOCT [num] << 2) | + FKEY_TAB [YM2612.CHANNEL [2].FNUM [num] >> 7]; + + YM2612.CHANNEL [2].SLOT [0].Finc = -1; + } + break; + + case 0xAC: + if ( Adr < 0x100 ) + { + num++; + + YM2612_Special_Update(); + + YM2612.CHANNEL [2].FNUM [num] = (YM2612.CHANNEL [2].FNUM [num] & 0x0FF) + ((data & 0x07) << 8); + YM2612.CHANNEL [2].FOCT [num] = (data & 0x38) >> 3; + YM2612.CHANNEL [2].KC [num] = (YM2612.CHANNEL [2].FOCT [num] << 2) | + FKEY_TAB [YM2612.CHANNEL [2].FNUM [num] >> 7]; + + YM2612.CHANNEL [2].SLOT [0].Finc = -1; + } + break; + + case 0xB0: + if ( ch.ALGO != (data & 7) ) + { + // Fix VectorMan 2 heli sound (level 1) + YM2612_Special_Update(); + + ch.ALGO = data & 7; + + ch.SLOT [0].ChgEnM = 0; + ch.SLOT [1].ChgEnM = 0; + ch.SLOT [2].ChgEnM = 0; + ch.SLOT [3].ChgEnM = 0; + } + + ch.FB = 9 - ((data >> 3) & 7); // Real thing ? + +// if (ch.FB = ((data >> 3) & 7)) ch.FB = 9 - ch.FB; // Thunder force 4 (music stage 8), Gynoug, Aladdin bug sound... +// else ch.FB = 31; + break; + + case 0xB4: { + YM2612_Special_Update(); + + ch.LEFT = 0 - ((data >> 7) & 1); + ch.RIGHT = 0 - ((data >> 6) & 1); + + ch.AMS = LFO_AMS_TAB [(data >> 4) & 3]; + ch.FMS = LFO_FMS_TAB [data & 7]; + + for ( int i = 0; i < 4; i++ ) + { + slot_t& sl = ch.SLOT [i]; + sl.AMS = (sl.AMSon ? ch.AMS : 31); + } + break; + } + } + + return 0; +} + + +int Ym2612_Impl::YM_SET(int Adr, int data) +{ + switch ( Adr ) + { + case 0x22: + if (data & 8) // LFO enable + { + // Cool Spot music 1, LFO modified severals time which + // distord the sound, have to check that on a real genesis... + + g.LFOinc = g.LFO_INC_TAB [data & 7]; + } + else + { + g.LFOinc = g.LFOcnt = 0; + } + break; + + case 0x24: + YM2612.TimerA = (YM2612.TimerA & 0x003) | (((int) data) << 2); + + if (YM2612.TimerAL != (1024 - YM2612.TimerA) << 12) + { + YM2612.TimerAcnt = YM2612.TimerAL = (1024 - YM2612.TimerA) << 12; + } + break; + + case 0x25: + YM2612.TimerA = (YM2612.TimerA & 0x3fc) | (data & 3); + + if (YM2612.TimerAL != (1024 - YM2612.TimerA) << 12) + { + YM2612.TimerAcnt = YM2612.TimerAL = (1024 - YM2612.TimerA) << 12; + } + break; + + case 0x26: + YM2612.TimerB = data; + + if (YM2612.TimerBL != (256 - YM2612.TimerB) << (4 + 12)) + { + YM2612.TimerBcnt = YM2612.TimerBL = (256 - YM2612.TimerB) << (4 + 12); + } + break; + + case 0x27: + // Parametre divers + // b7 = CSM MODE + // b6 = 3 slot mode + // b5 = reset b + // b4 = reset a + // b3 = timer enable b + // b2 = timer enable a + // b1 = load b + // b0 = load a + + if ((data ^ YM2612.Mode) & 0x40) + { + // We changed the channel 2 mode, so recalculate phase step + // This fix the punch sound in Street of Rage 2 + + YM2612_Special_Update(); + + YM2612.CHANNEL [2].SLOT [0].Finc = -1; // recalculate phase step + } + +// if ((data & 2) && (YM2612.Status & 2)) YM2612.TimerBcnt = YM2612.TimerBL; +// if ((data & 1) && (YM2612.Status & 1)) YM2612.TimerAcnt = YM2612.TimerAL; + +// YM2612.Status &= (~data >> 4); // Reset du Status au cas ou c'est demande + YM2612.Status &= (~data >> 4) & (data >> 2); // Reset Status + + YM2612.Mode = data; + break; + + case 0x28: { + int nch = data & 3; + if ( nch == 3 ) + return 1; + if ( data & 4 ) + nch += 3; + channel_t& ch = YM2612.CHANNEL [nch]; + + YM2612_Special_Update(); + + if (data & 0x10) KEY_ON(ch, S0); // On appuie sur la touche pour le slot 1 + else KEY_OFF(ch, S0); // On rel'che la touche pour le slot 1 + if (data & 0x20) KEY_ON(ch, S1); // On appuie sur la touche pour le slot 3 + else KEY_OFF(ch, S1); // On rel'che la touche pour le slot 3 + if (data & 0x40) KEY_ON(ch, S2); // On appuie sur la touche pour le slot 2 + else KEY_OFF(ch, S2); // On rel'che la touche pour le slot 2 + if (data & 0x80) KEY_ON(ch, S3); // On appuie sur la touche pour le slot 4 + else KEY_OFF(ch, S3); // On rel'che la touche pour le slot 4 + break; + } + + case 0x2B: + if (YM2612.DAC ^ (data & 0x80)) YM2612_Special_Update(); + + YM2612.DAC = data & 0x80; // activation/desactivation du DAC + break; + } + + return 0; +} + +void Ym2612_Impl::set_rate( double sample_rate, double clock_rate ) +{ + assert( sample_rate ); + assert( clock_rate > sample_rate ); + + int i; + + // 144 = 12 * (prescale * 2) = 12 * 6 * 2 + // prescale set to 6 by default + + double Frequence = clock_rate / sample_rate / 144.0; + //dprintf( "Frequence: %.40f\n", Frequence ); + if ( fabs( Frequence - 1.0 ) < 0.0000001 ) + Frequence = 1.0; + YM2612.TimerBase = int (Frequence * 4096.0); + + // Tableau TL : + // [0 - 4095] = +output [4095 - ...] = +output overflow (fill with 0) + // [12288 - 16383] = -output [16384 - ...] = -output overflow (fill with 0) + + for(i = 0; i < TL_LENGHT; i++) + { + if (i >= PG_CUT_OFF) // YM2612 cut off sound after 78 dB (14 bits output ?) + { + g.TL_TAB [TL_LENGHT + i] = g.TL_TAB [i] = 0; + } + else + { + double x = MAX_OUT; // Max output + x /= pow( 10.0, (ENV_STEP * i) / 20.0 ); // Decibel -> Voltage + + g.TL_TAB [i] = (int) x; + g.TL_TAB [TL_LENGHT + i] = -g.TL_TAB [i]; + } + } + + // Tableau SIN : + // g.SIN_TAB [x] [y] = sin(x) * y; + // x = phase and y = volume + + g.SIN_TAB [0] = g.SIN_TAB [SIN_LENGHT / 2] = PG_CUT_OFF; + + for(i = 1; i <= SIN_LENGHT / 4; i++) + { + double x = sin(2.0 * PI * (double) (i) / (double) (SIN_LENGHT)); // Sinus + x = 20 * log10(1 / x); // convert to dB + + int j = (int) (x / ENV_STEP); // Get TL range + + if (j > PG_CUT_OFF) j = (int) PG_CUT_OFF; + + g.SIN_TAB [i] = g.SIN_TAB [(SIN_LENGHT / 2) - i] = j; + g.SIN_TAB [(SIN_LENGHT / 2) + i] = g.SIN_TAB [SIN_LENGHT - i] = TL_LENGHT + j; + } + + // Tableau LFO (LFO wav) : + + for(i = 0; i < LFO_LENGHT; i++) + { + double x = sin(2.0 * PI * (double) (i) / (double) (LFO_LENGHT)); // Sinus + x += 1.0; + x /= 2.0; // positive only + x *= 11.8 / ENV_STEP; // ajusted to MAX enveloppe modulation + + g.LFO_ENV_TAB [i] = (int) x; + + x = sin(2.0 * PI * (double) (i) / (double) (LFO_LENGHT)); // Sinus + x *= (double) ((1 << (LFO_HBITS - 1)) - 1); + + g.LFO_FREQ_TAB [i] = (int) x; + + } + + // Tableau Enveloppe : + // g.ENV_TAB [0] -> g.ENV_TAB [ENV_LENGHT - 1] = attack curve + // g.ENV_TAB [ENV_LENGHT] -> g.ENV_TAB [2 * ENV_LENGHT - 1] = decay curve + + for(i = 0; i < ENV_LENGHT; i++) + { + // Attack curve (x^8 - music level 2 Vectorman 2) + double x = pow(((double) ((ENV_LENGHT - 1) - i) / (double) (ENV_LENGHT)), 8); + x *= ENV_LENGHT; + + g.ENV_TAB [i] = (int) x; + + // Decay curve (just linear) + x = pow(((double) (i) / (double) (ENV_LENGHT)), 1); + x *= ENV_LENGHT; + + g.ENV_TAB [ENV_LENGHT + i] = (int) x; + } + for ( i = 0; i < 8; i++ ) + g.ENV_TAB [i + ENV_LENGHT * 2] = 0; + + g.ENV_TAB [ENV_END >> ENV_LBITS] = ENV_LENGHT - 1; // for the stopped state + + // Tableau pour la conversion Attack -> Decay and Decay -> Attack + + int j = ENV_LENGHT - 1; + for ( i = 0; i < ENV_LENGHT; i++ ) + { + while ( j && g.ENV_TAB [j] < i ) + j--; + + g.DECAY_TO_ATTACK [i] = j << ENV_LBITS; + } + + // Tableau pour le Substain Level + + for(i = 0; i < 15; i++) + { + double x = i * 3; // 3 and not 6 (Mickey Mania first music for test) + x /= ENV_STEP; + + int j = (int) x; + j <<= ENV_LBITS; + + g.SL_TAB [i] = j + ENV_DECAY; + } + + g.SL_TAB [15] = ((ENV_LENGHT - 1) << ENV_LBITS) + ENV_DECAY; // special case : volume off + + // Tableau Frequency Step + + for(i = 0; i < 2048; i++) + { + double x = (double) (i) * Frequence; + +#if ((SIN_LBITS + SIN_HBITS - (21 - 7)) < 0) + x /= (double) (1 << ((21 - 7) - SIN_LBITS - SIN_HBITS)); +#else + x *= (double) (1 << (SIN_LBITS + SIN_HBITS - (21 - 7))); +#endif + + x /= 2.0; // because MUL = value * 2 + + g.FINC_TAB [i] = (unsigned int) x; + } + + // Tableaux Attack & Decay Rate + + for(i = 0; i < 4; i++) + { + g.AR_TAB [i] = 0; + g.DR_TAB [i] = 0; + } + + for(i = 0; i < 60; i++) + { + double x = Frequence; + + x *= 1.0 + ((i & 3) * 0.25); // bits 0-1 : x1.00, x1.25, x1.50, x1.75 + x *= (double) (1 << ((i >> 2))); // bits 2-5 : shift bits (x2^0 - x2^15) + x *= (double) (ENV_LENGHT << ENV_LBITS); // on ajuste pour le tableau g.ENV_TAB + + g.AR_TAB [i + 4] = (unsigned int) (x / AR_RATE); + g.DR_TAB [i + 4] = (unsigned int) (x / DR_RATE); + } + + for(i = 64; i < 96; i++) + { + g.AR_TAB [i] = g.AR_TAB [63]; + g.DR_TAB [i] = g.DR_TAB [63]; + + g.NULL_RATE [i - 64] = 0; + } + + for ( i = 96; i < 128; i++ ) + g.AR_TAB [i] = 0; + + // Tableau Detune + + for(i = 0; i < 4; i++) + { + for (int j = 0; j < 32; j++) + { +#if ((SIN_LBITS + SIN_HBITS - 21) < 0) + double x = (double) DT_DEF_TAB [(i << 5) + j] * Frequence / (double) (1 << (21 - SIN_LBITS - SIN_HBITS)); +#else + double x = (double) DT_DEF_TAB [(i << 5) + j] * Frequence * (double) (1 << (SIN_LBITS + SIN_HBITS - 21)); +#endif + + g.DT_TAB [i + 0] [j] = (int) x; + g.DT_TAB [i + 4] [j] = (int) -x; + } + } + + // Tableau LFO + g.LFO_INC_TAB [0] = (unsigned int) (3.98 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + g.LFO_INC_TAB [1] = (unsigned int) (5.56 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + g.LFO_INC_TAB [2] = (unsigned int) (6.02 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + g.LFO_INC_TAB [3] = (unsigned int) (6.37 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + g.LFO_INC_TAB [4] = (unsigned int) (6.88 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + g.LFO_INC_TAB [5] = (unsigned int) (9.63 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + g.LFO_INC_TAB [6] = (unsigned int) (48.1 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + g.LFO_INC_TAB [7] = (unsigned int) (72.2 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + + reset(); +} + +const char* Ym2612_Emu::set_rate( double sample_rate, double clock_rate ) +{ + if ( !impl ) + { + impl = (Ym2612_Impl*) malloc( sizeof *impl ); + if ( !impl ) + return "Out of memory"; + impl->mute_mask = 0; + } + memset( &impl->YM2612, 0, sizeof impl->YM2612 ); + + impl->set_rate( sample_rate, clock_rate ); + + return 0; +} + +Ym2612_Emu::~Ym2612_Emu() +{ + free( impl ); +} + +inline void Ym2612_Impl::write0( int opn_addr, int data ) +{ + assert( (unsigned) data <= 0xFF ); + + if ( opn_addr < 0x30 ) + { + YM2612.REG [0] [opn_addr] = data; + YM_SET( opn_addr, data ); + } + else if ( YM2612.REG [0] [opn_addr] != data ) + { + YM2612.REG [0] [opn_addr] = data; + + if ( opn_addr < 0xA0 ) + SLOT_SET( opn_addr, data ); + else + CHANNEL_SET( opn_addr, data ); + } +} + +inline void Ym2612_Impl::write1( int opn_addr, int data ) +{ + assert( (unsigned) data <= 0xFF ); + + if ( opn_addr >= 0x30 && YM2612.REG [1] [opn_addr] != data ) + { + YM2612.REG [1] [opn_addr] = data; + + if ( opn_addr < 0xA0 ) + SLOT_SET( opn_addr + 0x100, data ); + else + CHANNEL_SET( opn_addr + 0x100, data ); + } +} + +void Ym2612_Emu::reset() +{ + impl->reset(); +} + +void Ym2612_Impl::reset() +{ + g.LFOcnt = 0; + YM2612.TimerA = 0; + YM2612.TimerAL = 0; + YM2612.TimerAcnt = 0; + YM2612.TimerB = 0; + YM2612.TimerBL = 0; + YM2612.TimerBcnt = 0; + YM2612.DAC = 0; + + YM2612.Status = 0; + + int i; + for ( i = 0; i < channel_count; i++ ) + { + channel_t& ch = YM2612.CHANNEL [i]; + + ch.LEFT = ~0; + ch.RIGHT = ~0; + ch.ALGO = 0; + ch.FB = 31; + ch.FMS = 0; + ch.AMS = 0; + + for ( int j = 0 ;j < 4 ; j++ ) + { + ch.S0_OUT [j] = 0; + ch.FNUM [j] = 0; + ch.FOCT [j] = 0; + ch.KC [j] = 0; + + ch.SLOT [j].Fcnt = 0; + ch.SLOT [j].Finc = 0; + ch.SLOT [j].Ecnt = ENV_END; // Put it at the end of Decay phase... + ch.SLOT [j].Einc = 0; + ch.SLOT [j].Ecmp = 0; + ch.SLOT [j].Ecurp = RELEASE; + + ch.SLOT [j].ChgEnM = 0; + } + } + + for ( i = 0; i < 0x100; i++ ) + { + YM2612.REG [0] [i] = -1; + YM2612.REG [1] [i] = -1; + } + + for ( i = 0xB6; i >= 0xB4; i-- ) + { + write0( i, 0xC0 ); + write1( i, 0xC0 ); + } + + for ( i = 0xB2; i >= 0x22; i-- ) + { + write0( i, 0 ); + write1( i, 0 ); + } + + write0( 0x2A, 0x80 ); +} + +void Ym2612_Emu::write0( int addr, int data ) +{ + impl->write0( addr, data ); +} + +void Ym2612_Emu::write1( int addr, int data ) +{ + impl->write1( addr, data ); +} + +void Ym2612_Emu::mute_voices( int mask ) { impl->mute_mask = mask; } + +static void update_envelope_( slot_t* sl ) +{ + switch ( sl->Ecurp ) + { + case 0: + // Env_Attack_Next + + // Verified with Gynoug even in HQ (explode SFX) + sl->Ecnt = ENV_DECAY; + + sl->Einc = sl->EincD; + sl->Ecmp = sl->SLL; + sl->Ecurp = DECAY; + break; + + case 1: + // Env_Decay_Next + + // Verified with Gynoug even in HQ (explode SFX) + sl->Ecnt = sl->SLL; + + sl->Einc = sl->EincS; + sl->Ecmp = ENV_END; + sl->Ecurp = SUBSTAIN; + break; + + case 2: + // Env_Substain_Next(slot_t *SL) + if (sl->SEG & 8) // SSG envelope type + { + int release = sl->SEG & 1; + + if ( !release ) + { + // re KEY ON + + // sl->Fcnt = 0; + // sl->ChgEnM = ~0; + + sl->Ecnt = 0; + sl->Einc = sl->EincA; + sl->Ecmp = ENV_DECAY; + sl->Ecurp = ATTACK; + } + + set_seg( *sl, (sl->SEG << 1) & 4 ); + + if ( !release ) + break; + } + // fall through + + case 3: + // Env_Release_Next + sl->Ecnt = ENV_END; + sl->Einc = 0; + sl->Ecmp = ENV_END + 1; + break; + + // default: no op + } +} + +inline void update_envelope( slot_t& sl ) +{ + int ecmp = sl.Ecmp; + if ( (sl.Ecnt += sl.Einc) >= ecmp ) + update_envelope_( &sl ); +} + +template +struct ym2612_update_chan { + static void func( tables_t&, channel_t&, Ym2612_Emu::sample_t*, int ); +}; + +typedef void (*ym2612_update_chan_t)( tables_t&, channel_t&, Ym2612_Emu::sample_t*, int ); + +template +void ym2612_update_chan::func( tables_t& g, channel_t& ch, + Ym2612_Emu::sample_t* buf, int length ) +{ + int not_end = ch.SLOT [S3].Ecnt - ENV_END; + + // algo is a compile-time constant, so all conditions based on it are resolved + // during compilation + + // special cases + if ( algo == 7 ) + not_end |= ch.SLOT [S0].Ecnt - ENV_END; + + if ( algo >= 5 ) + not_end |= ch.SLOT [S2].Ecnt - ENV_END; + + if ( algo >= 4 ) + not_end |= ch.SLOT [S1].Ecnt - ENV_END; + + int CH_S0_OUT_1 = ch.S0_OUT [1]; + + int in0 = ch.SLOT [S0].Fcnt; + int in1 = ch.SLOT [S1].Fcnt; + int in2 = ch.SLOT [S2].Fcnt; + int in3 = ch.SLOT [S3].Fcnt; + + int YM2612_LFOinc = g.LFOinc; + int YM2612_LFOcnt = g.LFOcnt + YM2612_LFOinc; + + if ( !not_end ) + return; + + do + { + // envelope + int const env_LFO = g.LFO_ENV_TAB [YM2612_LFOcnt >> LFO_LBITS & LFO_MASK]; + + short const* const ENV_TAB = g.ENV_TAB; + + #define CALC_EN( x ) \ + int temp##x = ENV_TAB [ch.SLOT [S##x].Ecnt >> ENV_LBITS] + ch.SLOT [S##x].TLL; \ + int en##x = ((temp##x ^ ch.SLOT [S##x].env_xor) + (env_LFO >> ch.SLOT [S##x].AMS)) & \ + ((temp##x - ch.SLOT [S##x].env_max) >> 31); + + CALC_EN( 0 ) + CALC_EN( 1 ) + CALC_EN( 2 ) + CALC_EN( 3 ) + + int const* const TL_TAB = g.TL_TAB; + + #define SINT( i, o ) (TL_TAB [g.SIN_TAB [(i)] + (o)]) + + // feedback + int CH_S0_OUT_0 = ch.S0_OUT [0]; + { + int temp = in0 + ((CH_S0_OUT_0 + CH_S0_OUT_1) >> ch.FB); + CH_S0_OUT_1 = CH_S0_OUT_0; + CH_S0_OUT_0 = SINT( (temp >> SIN_LBITS) & SIN_MASK, en0 ); + } + + int CH_OUTd; + if ( algo == 0 ) + { + int temp = in1 + CH_S0_OUT_1; + temp = in2 + SINT( (temp >> SIN_LBITS) & SIN_MASK, en1 ); + temp = in3 + SINT( (temp >> SIN_LBITS) & SIN_MASK, en2 ); + CH_OUTd = SINT( (temp >> SIN_LBITS) & SIN_MASK, en3 ); + } + else if ( algo == 1 ) + { + int temp = in2 + CH_S0_OUT_1 + SINT( (in1 >> SIN_LBITS) & SIN_MASK, en1 ); + temp = in3 + SINT( (temp >> SIN_LBITS) & SIN_MASK, en2 ); + CH_OUTd = SINT( (temp >> SIN_LBITS) & SIN_MASK, en3 ); + } + else if ( algo == 2 ) + { + int temp = in2 + SINT( (in1 >> SIN_LBITS) & SIN_MASK, en1 ); + temp = in3 + CH_S0_OUT_1 + SINT( (temp >> SIN_LBITS) & SIN_MASK, en2 ); + CH_OUTd = SINT( (temp >> SIN_LBITS) & SIN_MASK, en3 ); + } + else if ( algo == 3 ) + { + int temp = in1 + CH_S0_OUT_1; + temp = in3 + SINT( (temp >> SIN_LBITS) & SIN_MASK, en1 ) + + SINT( (in2 >> SIN_LBITS) & SIN_MASK, en2 ); + CH_OUTd = SINT( (temp >> SIN_LBITS) & SIN_MASK, en3 ); + } + else if ( algo == 4 ) + { + int temp = in3 + SINT( (in2 >> SIN_LBITS) & SIN_MASK, en2 ); + CH_OUTd = SINT( (temp >> SIN_LBITS) & SIN_MASK, en3 ) + + SINT( ((in1 + CH_S0_OUT_1) >> SIN_LBITS) & SIN_MASK, en1 ); + //DO_LIMIT + } + else if ( algo == 5 ) + { + int temp = CH_S0_OUT_1; + CH_OUTd = SINT( ((in3 + temp) >> SIN_LBITS) & SIN_MASK, en3 ) + + SINT( ((in1 + temp) >> SIN_LBITS) & SIN_MASK, en1 ) + + SINT( ((in2 + temp) >> SIN_LBITS) & SIN_MASK, en2 ); + //DO_LIMIT + } + else if ( algo == 6 ) + { + CH_OUTd = SINT( (in3 >> SIN_LBITS) & SIN_MASK, en3 ) + + SINT( ((in1 + CH_S0_OUT_1) >> SIN_LBITS) & SIN_MASK, en1 ) + + SINT( (in2 >> SIN_LBITS) & SIN_MASK, en2 ); + //DO_LIMIT + } + else if ( algo == 7 ) + { + CH_OUTd = SINT( (in3 >> SIN_LBITS) & SIN_MASK, en3 ) + + SINT( (in1 >> SIN_LBITS) & SIN_MASK, en1 ) + + SINT( (in2 >> SIN_LBITS) & SIN_MASK, en2 ) + CH_S0_OUT_1; + //DO_LIMIT + } + + CH_OUTd >>= MAX_OUT_BITS - output_bits + 2; + + // update phase + unsigned freq_LFO = ((g.LFO_FREQ_TAB [YM2612_LFOcnt >> LFO_LBITS & LFO_MASK] * + ch.FMS) >> (LFO_HBITS - 1 + 1)) + (1L << (LFO_FMS_LBITS - 1)); + YM2612_LFOcnt += YM2612_LFOinc; + in0 += (ch.SLOT [S0].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1); + in1 += (ch.SLOT [S1].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1); + in2 += (ch.SLOT [S2].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1); + in3 += (ch.SLOT [S3].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1); + + int t0 = buf [0] + (CH_OUTd & ch.LEFT); + int t1 = buf [1] + (CH_OUTd & ch.RIGHT); + + update_envelope( ch.SLOT [0] ); + update_envelope( ch.SLOT [1] ); + update_envelope( ch.SLOT [2] ); + update_envelope( ch.SLOT [3] ); + + ch.S0_OUT [0] = CH_S0_OUT_0; + buf [0] = t0; + buf [1] = t1; + buf += 2; + } + while ( --length ); + + ch.S0_OUT [1] = CH_S0_OUT_1; + + ch.SLOT [S0].Fcnt = in0; + ch.SLOT [S1].Fcnt = in1; + ch.SLOT [S2].Fcnt = in2; + ch.SLOT [S3].Fcnt = in3; +} + +static const ym2612_update_chan_t UPDATE_CHAN [8] = { + &ym2612_update_chan<0>::func, + &ym2612_update_chan<1>::func, + &ym2612_update_chan<2>::func, + &ym2612_update_chan<3>::func, + &ym2612_update_chan<4>::func, + &ym2612_update_chan<5>::func, + &ym2612_update_chan<6>::func, + &ym2612_update_chan<7>::func +}; + +void Ym2612_Impl::run_timer( int length ) +{ + int const step = 6; + int remain = length; + do + { + int n = step; + if ( n > remain ) + n = remain; + remain -= n; + + long i = n * YM2612.TimerBase; + if (YM2612.Mode & 1) // Timer A ON ? + { + // if ((YM2612.TimerAcnt -= 14073) <= 0) // 13879=NTSC (old: 14475=NTSC 14586=PAL) + if ((YM2612.TimerAcnt -= i) <= 0) + { + // timer a overflow + + YM2612.Status |= (YM2612.Mode & 0x04) >> 2; + YM2612.TimerAcnt += YM2612.TimerAL; + + if (YM2612.Mode & 0x80) + { + KEY_ON( YM2612.CHANNEL [2], 0 ); + KEY_ON( YM2612.CHANNEL [2], 1 ); + KEY_ON( YM2612.CHANNEL [2], 2 ); + KEY_ON( YM2612.CHANNEL [2], 3 ); + } + } + } + + if (YM2612.Mode & 2) // Timer B ON ? + { + // if ((YM2612.TimerBcnt -= 14073) <= 0) // 13879=NTSC (old: 14475=NTSC 14586=PAL) + if ((YM2612.TimerBcnt -= i) <= 0) + { + // timer b overflow + YM2612.Status |= (YM2612.Mode & 0x08) >> 2; + YM2612.TimerBcnt += YM2612.TimerBL; + } + } + } + while ( remain > 0 ); +} + +void Ym2612_Impl::run( int pair_count, Ym2612_Emu::sample_t* out ) +{ + if ( pair_count <= 0 ) + return; + + if ( YM2612.Mode & 3 ) + run_timer( pair_count ); + + // Mise à jour des pas des compteurs-frequences s'ils ont ete modifies + + for ( int chi = 0; chi < channel_count; chi++ ) + { + channel_t& ch = YM2612.CHANNEL [chi]; + if ( ch.SLOT [0].Finc != -1 ) + continue; + + int i2 = 0; + if ( chi == 2 && (YM2612.Mode & 0x40) ) + i2 = 2; + + for ( int i = 0; i < 4; i++ ) + { + // static int seq [4] = { 2, 1, 3, 0 }; + // if ( i2 ) i2 = seq [i]; + + slot_t& sl = ch.SLOT [i]; + int finc = g.FINC_TAB [ch.FNUM [i2]] >> (7 - ch.FOCT [i2]); + int ksr = ch.KC [i2] >> sl.KSR_S; // keycode attenuation + sl.Finc = (finc + sl.DT [ch.KC [i2]]) * sl.MUL; + if (sl.KSR != ksr) // si le KSR a change alors + { // les differents taux pour l'enveloppe sont mis à jour + sl.KSR = ksr; + + sl.EincA = sl.AR [ksr]; + sl.EincD = sl.DR [ksr]; + sl.EincS = sl.SR [ksr]; + sl.EincR = sl.RR [ksr]; + + if (sl.Ecurp == ATTACK) + { + sl.Einc = sl.EincA; + } + else if (sl.Ecurp == DECAY) + { + sl.Einc = sl.EincD; + } + else if (sl.Ecnt < ENV_END) + { + if (sl.Ecurp == SUBSTAIN) + sl.Einc = sl.EincS; + else if (sl.Ecurp == RELEASE) + sl.Einc = sl.EincR; + } + } + + if ( i2 ) + i2 = (i2 ^ 2) ^ (i2 >> 1); + } + } + + for ( int i = 0; i < channel_count; i++ ) + { + if ( !(mute_mask & (1 << i)) && (i != 5 || !YM2612.DAC) ) + UPDATE_CHAN [YM2612.CHANNEL [i].ALGO]( g, YM2612.CHANNEL [i], out, pair_count ); + } + + g.LFOcnt += g.LFOinc * pair_count; +} + +void Ym2612_Emu::run( int pair_count, sample_t* out ) { impl->run( pair_count, out ); } + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/Ym2612_Emu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Ym2612_Emu.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,41 @@ + +// YM2612 FM sound chip emulator interface + +// Game_Music_Emu 0.3.0 + +#ifndef YM2612_EMU_H +#define YM2612_EMU_H + +struct Ym2612_Impl; + +class Ym2612_Emu { + Ym2612_Impl* impl; +public: + Ym2612_Emu() { impl = 0; } + ~Ym2612_Emu(); + + // Set output sample rate and chip clock rates, in Hz. Returns non-zero + // if error. + const char* set_rate( double sample_rate, double clock_rate ); + + // Reset to power-up state + void reset(); + + // Mute voice n if bit n (1 << n) of mask is set + enum { channel_count = 6 }; + void mute_voices( int mask ); + + // Write addr to register 0 then data to register 1 + void write0( int addr, int data ); + + // Write addr to register 2 then data to register 3 + void write1( int addr, int data ); + + // Run and add pair_count samples into current output buffer contents + typedef short sample_t; + enum { out_chan_count = 2 }; // stereo + void run( int pair_count, sample_t* out ); +}; + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/abstract_file.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/abstract_file.cpp Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,282 @@ + +#include "abstract_file.h" + +#include +#include +#include +#include + +/* Copyright (C) 2005 Shay Green. Permission is hereby granted, free of +charge, to any person obtaining a copy of this software module and associated +documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and +to permit persons to whom the Software is furnished to do so, subject to the +following conditions: The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. THE +SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + +// to do: remove? +#ifndef RAISE_ERROR + #define RAISE_ERROR( str ) return str +#endif + +typedef Data_Reader::error_t error_t; + +error_t Data_Writer::write( const void*, long ) { return NULL; } + +void Data_Writer::satisfy_lame_linker_() { } + +error_t Data_Reader::read( void* p, long s ) +{ + long result = read_avail( p, s ); + if ( result != s ) + { + if ( result >= 0 && result < s ) + RAISE_ERROR( "Unexpected end-of-file" ); + + RAISE_ERROR( "Read error" ); + } + + return NULL; +} + +error_t Data_Reader::skip( long count ) +{ + char buf [512]; + while ( count ) + { + int n = sizeof buf; + if ( n > count ) + n = count; + count -= n; + RAISE_ERROR( read( buf, n ) ); + } + return NULL; +} + +long File_Reader::remain() const +{ + return size() - tell(); +} + +error_t File_Reader::skip( long n ) +{ + assert( n >= 0 ); + if ( n ) + RAISE_ERROR( seek( tell() + n ) ); + + return NULL; +} + + +// Subset_Reader + +Subset_Reader::Subset_Reader( Data_Reader* in_, long size ) : + in( in_ ), + remain_( in_->remain() ) +{ + if ( remain_ > size ) + remain_ = size; +} + +long Subset_Reader::remain() const { + return remain_; +} + +long Subset_Reader::read_avail( void* p, long s ) +{ + if ( s > remain_ ) + s = remain_; + remain_ -= s; + return in->read_avail( p, s ); +} + +// Mem_File_Reader + +Mem_File_Reader::Mem_File_Reader( const void* p, long s ) : + begin( (const char*) p ), + pos( 0 ), + size_( s ) +{ +} + +long Mem_File_Reader::size() const { + return size_; +} + +long Mem_File_Reader::read_avail( void* p, long s ) +{ + long r = remain(); + if ( s > r ) + s = r; + memcpy( p, begin + pos, s ); + pos += s; + return s; +} + +long Mem_File_Reader::tell() const { + return pos; +} + +error_t Mem_File_Reader::seek( long n ) +{ + if ( n > size_ ) + RAISE_ERROR( "Tried to go past end of file" ); + pos = n; + return NULL; +} + +// Std_File_Reader + +Std_File_Reader::Std_File_Reader() : file_( NULL ) { +} + +Std_File_Reader::~Std_File_Reader() { + close(); +} + +error_t Std_File_Reader::open( const char* path ) +{ + file_ = fopen( path, "rb" ); + if ( !file_ ) + RAISE_ERROR( "Couldn't open file" ); + return NULL; +} + +long Std_File_Reader::size() const +{ + long pos = tell(); + fseek( file_, 0, SEEK_END ); + long result = tell(); + fseek( file_, pos, SEEK_SET ); + return result; +} + +long Std_File_Reader::read_avail( void* p, long s ) { + return (long) fread( p, 1, s, file_ ); +} + +long Std_File_Reader::tell() const { + return ftell( file_ ); +} + +error_t Std_File_Reader::seek( long n ) +{ + if ( fseek( file_, n, SEEK_SET ) != 0 ) + RAISE_ERROR( "Error seeking in file" ); + return NULL; +} + +void Std_File_Reader::close() +{ + if ( file_ ) { + fclose( file_ ); + file_ = NULL; + } +} + +// Std_File_Writer + +Std_File_Writer::Std_File_Writer() : file_( NULL ) { +} + +Std_File_Writer::~Std_File_Writer() { + close(); +} + +error_t Std_File_Writer::open( const char* path ) +{ + file_ = fopen( path, "wb" ); + if ( !file_ ) + RAISE_ERROR( "Couldn't open file for writing" ); + + // to do: increase file buffer size + //setvbuf( file_, NULL, _IOFBF, 32 * 1024L ); + + return NULL; +} + +error_t Std_File_Writer::write( const void* p, long s ) +{ + long result = (long) fwrite( p, 1, s, file_ ); + if ( result != s ) + RAISE_ERROR( "Couldn't write to file" ); + return NULL; +} + +void Std_File_Writer::close() +{ + if ( file_ ) { + fclose( file_ ); + file_ = NULL; + } +} + +// Mem_Writer + +Mem_Writer::Mem_Writer( void* p, long s, int b ) +{ + data_ = (char*) p; + size_ = 0; + allocated = s; + mode = b ? ignore_excess : fixed; +} + +Mem_Writer::Mem_Writer() +{ + data_ = NULL; + size_ = 0; + allocated = 0; + mode = expanding; +} + +Mem_Writer::~Mem_Writer() +{ + if ( mode == expanding ) + free( data_ ); +} + +error_t Mem_Writer::write( const void* p, long s ) +{ + long remain = allocated - size_; + if ( s > remain ) + { + if ( mode == fixed ) + RAISE_ERROR( "Tried to write more data than expected" ); + + if ( mode == ignore_excess ) + { + s = remain; + } + else // expanding + { + long new_allocated = size_ + s; + new_allocated += (new_allocated >> 1) + 2048; + void* p = realloc( data_, new_allocated ); + if ( !p ) + RAISE_ERROR( "Out of memory" ); + data_ = (char*) p; + allocated = new_allocated; + } + } + + assert( size_ + s <= allocated ); + memcpy( data_ + size_, p, s ); + size_ += s; + + return NULL; +} + +// Null_Writer + +error_t Null_Writer::write( const void*, long ) +{ + return NULL; +} + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/abstract_file.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/abstract_file.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,177 @@ + +// Abstract file access interfaces + +#ifndef ABSTRACT_FILE_H +#define ABSTRACT_FILE_H + +#include + +// Supports reading and finding out how many bytes are remaining +class Data_Reader { +public: + Data_Reader() { } + virtual ~Data_Reader() { } + + // NULL on success, otherwise error string + typedef const char* error_t; + + // Read at most 'n' bytes. Return number of bytes read, zero or negative + // if error. + virtual long read_avail( void*, long n ) = 0; + + // Read exactly 'n' bytes (error if fewer are available). + virtual error_t read( void*, long ); + + // Number of bytes remaining + virtual long remain() const = 0; + + // Skip forwards by 'n' bytes. + virtual error_t skip( long n ); + + // to do: bytes remaining = LONG_MAX when unknown? + +private: + // noncopyable + Data_Reader( const Data_Reader& ); + Data_Reader& operator = ( const Data_Reader& ); +}; + +// Adds seeking operations +class File_Reader : public Data_Reader { +public: + // Size of file + virtual long size() const = 0; + + // Current position in file + virtual long tell() const = 0; + + // Change position in file + virtual error_t seek( long ) = 0; + + virtual long remain() const; + + error_t skip( long n ); +}; + +// Limit access to a subset of data +class Subset_Reader : public Data_Reader { + Data_Reader* in; + long remain_; +public: + Subset_Reader( Data_Reader*, long size ); + long remain() const; + long read_avail( void*, long ); +}; + +// Treat range of memory as a file +class Mem_File_Reader : public File_Reader { + const char* const begin; + long pos; + const long size_; +public: + Mem_File_Reader( const void*, long size ); + + long size() const; + long read_avail( void*, long ); + + long tell() const; + error_t seek( long ); +}; + +// File reader based on C FILE +class Std_File_Reader : public File_Reader { + FILE* file_; +protected: + void reset( FILE* f ) { file_ = f; } + //FILE* owned_file; +public: + Std_File_Reader(); + ~Std_File_Reader(); + + error_t open( const char* ); + + FILE* file() const { return file_; } + + // Forward read requests to file. Caller must close file later. + //void forward( FILE* ); + + long size() const; + long read_avail( void*, long ); + + long tell() const; + error_t seek( long ); + + void close(); +}; + +// Supports writing +class Data_Writer { +public: + Data_Writer() { } + virtual ~Data_Writer() { } + + typedef const char* error_t; + + // Write 'n' bytes. NULL on success, otherwise error string. + virtual error_t write( const void*, long n ) = 0; + + void satisfy_lame_linker_(); +private: + // noncopyable + Data_Writer( const Data_Writer& ); + Data_Writer& operator = ( const Data_Writer& ); +}; + +class Std_File_Writer : public Data_Writer { + FILE* file_; +protected: + void reset( FILE* f ) { file_ = f; } +public: + Std_File_Writer(); + ~Std_File_Writer(); + + error_t open( const char* ); + + FILE* file() const { return file_; } + + // Forward writes to file. Caller must close file later. + //void forward( FILE* ); + + error_t write( const void*, long ); + + void close(); +}; + +// Write data to memory +class Mem_Writer : public Data_Writer { + char* data_; + long size_; + long allocated; + enum { expanding, fixed, ignore_excess } mode; +public: + // Keep all written data in expanding block of memory + Mem_Writer(); + + // Write to fixed-size block of memory. If ignore_excess is false, returns + // error if more than 'size' data is written, otherwise ignores any excess. + Mem_Writer( void*, long size, int ignore_excess = 0 ); + + error_t write( const void*, long ); + + // Pointer to beginning of written data + char* data() { return data_; } + + // Number of bytes written + long size() const { return size_; } + + ~Mem_Writer(); +}; + +// Written data is ignored +class Null_Writer : public Data_Writer { +public: + error_t write( const void*, long ); +}; + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/blargg_common.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/blargg_common.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,242 @@ + +// Sets up common environment for Shay Green's libraries. +// +// To change configuration options, modify blargg_config.h, not this file. + +#ifndef BLARGG_COMMON_H +#define BLARGG_COMMON_H + +// HAVE_CONFIG_H: If defined, include user's "config.h" first (which *can* +// re-include blargg_common.h if it needs to) +#ifdef HAVE_CONFIG_H + #undef BLARGG_COMMON_H + #include "config.h" + #define BLARGG_COMMON_H +#endif + +// BLARGG_NONPORTABLE: If defined to 1, platform-specific (and possibly non-portable) +// optimizations are used. Defaults to off. Report any problems that occur only when +// this is enabled. +#ifndef BLARGG_NONPORTABLE + #define BLARGG_NONPORTABLE 0 +#endif + +// BLARGG_BIG_ENDIAN, BLARGG_LITTLE_ENDIAN: Determined automatically, otherwise only +// one must be #defined to 1. Only needed if something actually depends on byte order. +#if !defined (BLARGG_BIG_ENDIAN) && !defined (BLARGG_LITTLE_ENDIAN) + #if defined (MSB_FIRST) || defined (__powerc) || defined (macintosh) || \ + defined (WORDS_BIGENDIAN) || defined (__BIG_ENDIAN__) + #define BLARGG_BIG_ENDIAN 1 + #else + #define BLARGG_LITTLE_ENDIAN 1 + #endif +#endif + +// Determine compiler's language support + +// Metrowerks CodeWarrior +#if defined (__MWERKS__) + #define BLARGG_COMPILER_HAS_NAMESPACE 1 + #if !__option(bool) + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif + #define STATIC_CAST(T,expr) static_cast< T > (expr) + +// Microsoft Visual C++ +#elif defined (_MSC_VER) + #if _MSC_VER < 1100 + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif + +// GNU C++ +#elif defined (__GNUC__) + #if __GNUC__ > 2 + #define BLARGG_COMPILER_HAS_NAMESPACE 1 + #endif + +// Mingw +#elif defined (__MINGW32__) + // empty + +// Pre-ISO C++ compiler +#elif __cplusplus < 199711 + #ifndef BLARGG_COMPILER_HAS_BOOL + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif + +#endif + +/* BLARGG_COMPILER_HAS_BOOL: If 0, provides bool support for old compilers. + If errors occur here, add the following line to your config.h file: + #define BLARGG_COMPILER_HAS_BOOL 0 +*/ +#if defined (BLARGG_COMPILER_HAS_BOOL) && !BLARGG_COMPILER_HAS_BOOL + typedef int bool; + const bool true = 1; + const bool false = 0; +#endif + +// BLARGG_USE_NAMESPACE: If 1, use headers rather than +#if BLARGG_USE_NAMESPACE || (!defined (BLARGG_USE_NAMESPACE) && BLARGG_COMPILER_HAS_NAMESPACE) + #include + #include + #include + #include + #define STD std +#else + #include + #include + #include + #include + #define STD +#endif + +// BLARGG_NEW is used in place of 'new' to create objects. By default, plain new is used. +// To prevent an exception if out of memory, #define BLARGG_NEW new (std::nothrow) +#ifndef BLARGG_NEW + #define BLARGG_NEW new +#endif + +// BOOST::int8_t etc. + +// HAVE_STDINT_H: If defined, use for int8_t etc. +#if defined (HAVE_STDINT_H) + #include + #define BOOST + +// HAVE_INTTYPES_H: If defined, use for int8_t etc. +#elif defined (HAVE_INTTYPES_H) + #include + #define BOOST + +#else + struct BOOST + { + #if UCHAR_MAX == 0xFF && SCHAR_MAX == 0x7F + typedef signed char int8_t; + typedef unsigned char uint8_t; + #else + // No suitable 8-bit type available + typedef struct see_blargg_common_h int8_t; + typedef struct see_blargg_common_h uint8_t; + #endif + + #if USHRT_MAX == 0xFFFF + typedef short int16_t; + typedef unsigned short uint16_t; + #else + // No suitable 16-bit type available + typedef struct see_blargg_common_h int16_t; + typedef struct see_blargg_common_h uint16_t; + #endif + + #if ULONG_MAX == 0xFFFFFFFF + typedef long int32_t; + typedef unsigned long uint32_t; + #elif UINT_MAX == 0xFFFFFFFF + typedef int int32_t; + typedef unsigned int uint32_t; + #else + // No suitable 32-bit type available + typedef struct see_blargg_common_h int32_t; + typedef struct see_blargg_common_h uint32_t; + #endif + }; +#endif + +// BLARGG_SOURCE_BEGIN: Library sources #include this after other #includes. +#ifndef BLARGG_SOURCE_BEGIN + #define BLARGG_SOURCE_BEGIN "blargg_source.h" +#endif + +// BLARGG_ENABLE_OPTIMIZER: Library sources #include this for speed-critical code +#ifndef BLARGG_ENABLE_OPTIMIZER + #define BLARGG_ENABLE_OPTIMIZER "blargg_common.h" +#endif + +// BLARGG_CPU_*: Used to select between some optimizations +#if !defined (BLARGG_CPU_POWERPC) && !defined (BLARGG_CPU_X86) + #if defined (__powerc) + #define BLARGG_CPU_POWERPC 1 + #elif defined (_MSC_VER) && defined (_M_IX86) + #define BLARGG_CPU_X86 1 + #endif +#endif + +// BOOST_STATIC_ASSERT( expr ): Generates compile error if expr is 0. +#ifndef BOOST_STATIC_ASSERT + #ifdef _MSC_VER + // MSVC6 (_MSC_VER < 1300) fails for use of __LINE__ when /Zl is specified + #define BOOST_STATIC_ASSERT( expr ) \ + void blargg_failed_( int (*arg) [2 / ((expr) ? 1 : 0) - 1] ) + #else + // Some other compilers fail when declaring same function multiple times in class, + // so differentiate them by line + #define BOOST_STATIC_ASSERT( expr ) \ + void blargg_failed_( int (*arg) [2 / ((expr) ? 1 : 0) - 1] [__LINE__] ) + #endif +#endif + +// STATIC_CAST(T,expr): Used in place of static_cast (expr) +#ifndef STATIC_CAST + #define STATIC_CAST(T,expr) ((T) (expr)) +#endif + +// blargg_err_t (NULL on success, otherwise error string) +#ifndef blargg_err_t + typedef const char* blargg_err_t; +#endif +const char* const blargg_success = 0; + +// blargg_vector: Simple array that does *not* work for types with a constructor (non-POD). +template +class blargg_vector { + T* begin_; + STD::size_t size_; +public: + blargg_vector() : begin_( 0 ), size_( 0 ) { } + ~blargg_vector() { STD::free( begin_ ); } + + typedef STD::size_t size_type; + + blargg_err_t resize( size_type n ) + { + void* p = STD::realloc( begin_, n * sizeof (T) ); + if ( !p && n ) + return "Out of memory"; + begin_ = (T*) p; + size_ = n; + return 0; + } + + void clear() + { + void* p = begin_; + begin_ = 0; + size_ = 0; + STD::free( p ); + } + + size_type size() const { return size_; } + + T* begin() { return begin_; } + T* end() { return begin_ + size_; } + + const T* begin() const { return begin_; } + const T* end() const { return begin_ + size_; } + + T& operator [] ( size_type n ) + { + assert( n <= size_ ); // allow for past-the-end value + return begin_ [n]; + } + + const T& operator [] ( size_type n ) const + { + assert( n <= size_ ); // allow for past-the-end value + return begin_ [n]; + } +}; + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/blargg_endian.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/blargg_endian.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,156 @@ + +// CPU Byte Order Utilities + +// Game_Music_Emu 0.3.0 + +#ifndef BLARGG_ENDIAN +#define BLARGG_ENDIAN + +#include "blargg_common.h" + +#if 0 + // Read 16/32-bit little-endian integer from memory + unsigned GET_LE16( void const* ); + unsigned long GET_LE32( void const* ); + + // Read 16/32-bit big-endian integer from memory + unsigned GET_BE16( void const* ); + unsigned long GET_BE32( void const* ); + + // Write 16/32-bit integer to memory in little-endian format + void SET_LE16( void*, unsigned ); + void SET_LE32( void*, unsigned ); + + // Write 16/32-bit integer to memory in big-endian format + void SET_BE16( void*, unsigned long ); + void SET_BE32( void*, unsigned long ); +#endif + +inline unsigned get_le16( void const* p ) +{ + return ((unsigned char*) p) [1] * 0x100 + + ((unsigned char*) p) [0]; +} + +inline unsigned get_be16( void const* p ) +{ + return ((unsigned char*) p) [0] * 0x100 + + ((unsigned char*) p) [1]; +} + +inline unsigned long get_le32( void const* p ) +{ + return ((unsigned char*) p) [3] * 0x01000000 + + ((unsigned char*) p) [2] * 0x00010000 + + ((unsigned char*) p) [1] * 0x00000100 + + ((unsigned char*) p) [0]; +} + +inline unsigned long get_be32( void const* p ) +{ + return ((unsigned char*) p) [0] * 0x01000000 + + ((unsigned char*) p) [1] * 0x00010000 + + ((unsigned char*) p) [2] * 0x00000100 + + ((unsigned char*) p) [3]; +} + +inline void set_le16( void* p, unsigned n ) +{ + ((unsigned char*) p) [1] = (unsigned char) (n >> 8); + ((unsigned char*) p) [0] = (unsigned char) n; +} + +inline void set_be16( void* p, unsigned n ) +{ + ((unsigned char*) p) [0] = (unsigned char) (n >> 8); + ((unsigned char*) p) [1] = (unsigned char) n; +} + +inline void set_le32( void* p, unsigned long n ) +{ + ((unsigned char*) p) [3] = (unsigned char) (n >> 24); + ((unsigned char*) p) [2] = (unsigned char) (n >> 16); + ((unsigned char*) p) [1] = (unsigned char) (n >> 8); + ((unsigned char*) p) [0] = (unsigned char) n; +} + +inline void set_be32( void* p, unsigned long n ) +{ + ((unsigned char*) p) [0] = (unsigned char) (n >> 24); + ((unsigned char*) p) [1] = (unsigned char) (n >> 16); + ((unsigned char*) p) [2] = (unsigned char) (n >> 8); + ((unsigned char*) p) [3] = (unsigned char) n; +} + +#ifndef GET_LE16 + // Optimized implementation if byte order is known + #if BLARGG_NONPORTABLE && BLARGG_LITTLE_ENDIAN + #define GET_LE16( addr ) (*(BOOST::uint16_t*) (addr)) + #define GET_LE32( addr ) (*(BOOST::uint32_t*) (addr)) + #define SET_LE16( addr, data ) (void (*(BOOST::uint16_t*) (addr) = (data))) + #define SET_LE32( addr, data ) (void (*(BOOST::uint32_t*) (addr) = (data))) + + #elif BLARGG_NONPORTABLE && BLARGG_CPU_POWERPC + // PowerPC has special byte-reversed instructions + // to do: assumes that PowerPC is running in big-endian mode + #define GET_LE16( addr ) (__lhbrx( (addr), 0 )) + #define GET_LE32( addr ) (__lwbrx( (addr), 0 )) + #define SET_LE16( addr, data ) (__sthbrx( (data), (addr), 0 )) + #define SET_LE32( addr, data ) (__stwbrx( (data), (addr), 0 )) + + #define GET_BE16( addr ) (*(BOOST::uint16_t*) (addr)) + #define GET_BE32( addr ) (*(BOOST::uint32_t*) (addr)) + #define SET_BE16( addr, data ) (void (*(BOOST::uint16_t*) (addr) = (data))) + #define SET_BE32( addr, data ) (void (*(BOOST::uint32_t*) (addr) = (data))) + + #endif +#endif + +#ifndef GET_LE16 + #define GET_LE16( addr ) get_le16( addr ) +#endif + +#ifndef GET_LE32 + #define GET_LE32( addr ) get_le32( addr ) +#endif + +#ifndef SET_LE16 + #define SET_LE16( addr, data ) set_le16( addr, data ) +#endif + +#ifndef SET_LE32 + #define SET_LE32( addr, data ) set_le32( addr, data ) +#endif + +#ifndef GET_BE16 + #define GET_BE16( addr ) get_be16( addr ) +#endif + +#ifndef GET_BE32 + #define GET_BE32( addr ) get_be32( addr ) +#endif + +#ifndef SET_BE16 + #define SET_BE16( addr, data ) set_be16( addr, data ) +#endif + +#ifndef SET_BE32 + #define SET_BE32( addr, data ) set_be32( addr, data ) +#endif + +// auto-selecting versions + +inline void set_le( BOOST::uint16_t* p, unsigned n ) { SET_LE16( p, n ); } +inline void set_le( BOOST::uint32_t* p, unsigned long n ) { SET_LE32( p, n ); } + +inline void set_be( BOOST::uint16_t* p, unsigned n ) { SET_BE16( p, n ); } +inline void set_be( BOOST::uint32_t* p, unsigned long n ) { SET_BE32( p, n ); } + +inline unsigned get_le( BOOST::uint16_t* p ) { return GET_LE16( p ); } +inline unsigned long get_le( BOOST::uint32_t* p ) { return GET_LE32( p ); } + +inline unsigned get_be( BOOST::uint16_t* p ) { return GET_BE16( p ); } +inline unsigned long get_be( BOOST::uint32_t* p ) { return GET_BE32( p ); } + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/blargg_source.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/blargg_source.h Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,76 @@ + +// By default, #included at beginning of library source files. +// Can be overridden by #defining BLARGG_SOURCE_BEGIN to path of alternate file. + +// Copyright (C) 2005 Shay Green. + +#ifndef BLARGG_SOURCE_H +#define BLARGG_SOURCE_H + +// If debugging is enabled, abort program if expr is false. Meant for checking +// internal state and consistency. A failed assertion indicates a bug in the module. +// void assert( bool expr ); +#include + +// If debugging is enabled and expr is false, abort program. Meant for checking +// caller-supplied parameters and operations that are outside the control of the +// module. A failed requirement indicates a bug outside the module. +// void require( bool expr ); +#undef require +#define require( expr ) assert( expr ) + +// Like printf() except output goes to debug log file. Might be defined to do +// nothing (not even evaluate its arguments). +// void dprintf( const char* format, ... ); +#undef dprintf +#ifdef BLARGG_DPRINTF + #define dprintf BLARGG_DPRINTF +#else + inline void blargg_dprintf_( const char*, ... ) { } + #define dprintf (1) ? (void) 0 : blargg_dprintf_ +#endif + +// If enabled, evaluate expr and if false, make debug log entry with source file +// and line. Meant for finding situations that should be examined further, but that +// don't indicate a problem. In all cases, execution continues normally. +#undef check +#ifdef BLARGG_CHECK + #define check( expr ) BLARGG_CHECK( expr ) +#else + #define check( expr ) ((void) 0) +#endif + +// If expr returns non-NULL error string, return it from current function, otherwise continue. +#define BLARGG_RETURN_ERR( expr ) do { \ + blargg_err_t blargg_return_err_ = (expr); \ + if ( blargg_return_err_ ) return blargg_return_err_; \ + } while ( 0 ) + +// If ptr is NULL, return out of memory error string. +#define BLARGG_CHECK_ALLOC( ptr ) do { if ( (ptr) == 0 ) return "Out of memory"; } while ( 0 ) + +// Avoid any macros which evaluate their arguments multiple times +#undef min +#undef max + +// using const references generates crappy code, and I am currenly only using these +// for built-in types, so they take arguments by value + +template +inline T min( T x, T y ) +{ + if ( x < y ) + return x; + return y; +} + +template +inline T max( T x, T y ) +{ + if ( x < y ) + return y; + return x; +} + +#endif + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/gme_notes.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/gme_notes.txt Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,181 @@ +Game_Music_Emu 0.3.0 Notes +-------------------------- +Author : Shay Green +Website: http://www.slack.net/~ant/ +Forum : http://groups.google.com/group/blargg-sound-libs + + +Overview +-------- +This library is composed of several independent game music emulators +derived from the common Music_Emu interface. Each emulator can load a +game music file and play from any track. To play a game music file, do +the following: + +- Determine file's type +- Create appropriate emulator +- Set sample rate +- Load file into emulator +- Start desired track +- When samples are needed, call play() +- When done, delete emulator + +See Music_Emu.h for reference. + + +Information Fields +------------------ +Game music files include text fields with information about the game and +track. These are stored in the file's header or in an embedded block. +Text fields in most game music formats do *not* have a nul terminator if +the string completely fills the field. The simplest way to handle this +is to copy the string out and manually add a nul terminator at the end. + +This library is currently focused only on actual emulation, so it +doesn't provide a common interface to the different schemes each game +music file format uses. Refer to the file's official specification for +further information. + + +Modular Construction +-------------------- +The library is made of many fairly independent modules. If you're using +only one music file emulator, you can eliminate many of the library +sources from your program. Refer to the files list in readme.txt to get +a general idea of what can be removed. Post to the forum if you'd like +me to put together a smaller version for a particular use, as this only +takes me a few minutes to do. + +If you want to use one of the individual sound chip emulators in your +console emulator, first check the libraries page on my website since I +have released several of them as standalone libraries with included +documentation and examples on their use. + +The "classic" sound chips use my Blip_Buffer library, which greatly +simplifies their implementation and efficiently handles band-limited +synthesis. It is also available as a standalone library with +documentation and many examples. + + +Sound Parameters +---------------- +All emulators support adjustable output sampling rate, set with +Music_Emu::set_sample_rate(). A rate of 44100 should work well on most +systems. Since band-limited synthesis is used, a sampling rate above +48000 Hz is not necessary. + +Some emulators support adjustable treble and bass frequency equalization +(NSF, GBS, VGM) using Music_Emu::set_equalizer(). Parameters are +specified using Music_Emu::equalizer_t eq = { treble_dB, bass_freq }. +Treble_dB sets the treble level (in dB), where 0.0 dB gives normal +treble; -200.0 dB is quite muffled, and 5.0 dB emphasizes treble for an +extra crisp sound. Bass_freq sets the frequency where bass response +starts to diminish; 15 Hz is normal, 0 Hz gives maximum bass, and 15000 +Hz removes all bass. For example, the following makes the sound +extra-crisp but lacking bass: + + Music_Emu::equalizer_t eq = { 5.0, 1000 }; + music_emu->set_equalizer( eq ); + +Each emulator's equalization defaults to a profile that approximates its +particular console's sound quality; this default can be determined by +calling Music_Emu::equalizer() just after creating the emulator. Some +emulators include other profiles for different versions of the system. +The Music_Emu::tv_eq profile gives sound as if coming from a TV speaker. +For example, to use Famicom sound equalization with the NSF emulator, do +the following: + + nsf_emu->set_equalizer( Nsf_Emu::famicom_eq ); + + +VGM/GYM YM2413 & YM2612 FM Sound +-------------------------------- +The library plays Sega Genesis/Mega Drive music using a YM2612 FM sound +chip emulator based on Gens. Because this has some inaccuracies, other +YM2612 emulators can be used in its place by reimplementing the +interface in YM2612_Emu.h. Available on my website is a modified version +of MAME's YM2612 emulator, which sounds better in some ways and whose +author is still making improvements. + +VGM music files using the YM2413 FM sound chip are also supported, but a +YM2413 emulator isn't included. Similar to above, I have put one of the +available YM2413 emulators on my website that can be used directly. + + +Misc +---- +Some emulators have constructor parameters which can be specified when +creating the object. For example, this creates a Vgm_Emu with +oversampling off and a tempo of 83%: + + Vgm_Emu* emu = new Vgm_Emu( false, 0.83 ); + +For a full example of using Game_Music_Emu see the source code for Game +Music Box, a full-featured game music player for Mac OS: + + http://www.slack.net/~ant/game-music-box/dev.html + + +Thanks +------ +Big thanks to Chris Moeller (kode54) for help with library testing and +feedback, for maintaining the Foobar2000 plugin foo_gep based on it, and +for original work on openspc++ that was used when developing Spc_Emu. +Brad Martin's excellent OpenSPC SNES DSP emulator worked well from the +start. Also thanks to Richard Bannister, Mahendra Tallur, Shazz, and the +Audacious team for testing and using the library in their game music +players. + + +Solving Problems +---------------- +If you're having problems, try the following: + +- Enable debugging support in your environment. This enables assertions +and other run-time checks. + +- Turn the compiler's optimizer is off. Sometimes an optimizer generates +bad code. + +- If multiple threads are being used, ensure that only one at a time is +accessing a given set of objects from the library. This library is not +in general thread-safe, though independent objects can be used in +separate threads. + +- If all else fails, see if the demos work. + + +Error handling +-------------- +Functions which can fail have a return type of blargg_err_t, which is a +pointer to an error string (const char*). If the function is successful +it returns blargg_success (NULL), otherwise it returns a pointer to an +error string. Errors which the caller can easily detect are only checked +with debug assertions; blargg_err_t returns values are only used for +genuine run-time errors that can't be easily predicted in advance (out +of memory, I/O errors, incompatible file data). + +To allow compatibility with older C++ compilers, no exceptions are +thrown by any of the modules and code is generally exception-safe. Any +exceptions thrown by the standard library or caller-supplied functions +are allowed to propagate normally. + + +Configuration +------------- +The header "blargg_common.h" is used to establish a common environment, +and is #included at the beginning of all library headers and sources. It +attempts to automatically determine the features of the environment, but +might need help. Refer to "blargg_common.h" for descriptions of +features. + +If defined HAVE_CONFIG_H in the compiler command-line, the user-provided +"config.h" is included at the beginning of each library header file, +allowing configuration options for the library to be set. I have +attempted to design the library so that configuration can be done +*without* modifying any of the library sources and header files. This +makes it easy to upgrade to a new version without losing any +customizations to its configuration. + +Post to the forum if you have problems or suggestions. + diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/gme_readme.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/gme_readme.txt Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,136 @@ +Game_Music_Emu 0.3.0: Game Music Emulators +------------------------------------------ +Game_Music_Emu is a collection of portable video game music emulators. Its +modular design allows elimination of any unneeded emulators and features. +Modules are included supporting the following file formats: + +GBS Nintendo Game Boy +VGM/VGZ Sega Master System/Genesis/Mega Drive/Mark III/BBC Micro +GYM Sega Genesis +SPC Super Nintendo +NSF Nintendo NES (with VRC6, N106, and FME-7 sound) + +This library has been used in game music players for Win32, Linux x86-32/64, +Mac OS X, Mac OS Classic, MorphOS (Amiga), PlayStation Portable, and GP2X. + +Author : Shay Green +Website: http://www.slack.net/~ant/ +Forum : http://groups.google.com/group/blargg-sound-libs +License: GNU Lesser General Public License (LGPL) + + +Getting Started +--------------- +Build a program consisting of demo/basics.cpp, demo/Wave_Writer.cpp, and all +source files in gme/ except Gzip_File.cpp. Be sure "test.nsf" is in the same +directory. Running the program should generate a WAVE sound file "out.wav" of +music. + +See notes.txt for more information, and respective header (.h) files for +reference. Post to the discussion forum for assistance. + + +Files +----- +notes.txt General notes about the library +changes.txt Changes made since previous releases +design.txt Library design notes +LGPL.txt GNU Lesser General Public License + +test.nsf Test file for NSF emulator + +demo/ + basics.cpp Loads game music file and records to wave sound file + info_fields.cpp Reads information tags from files + multi_format.cpp Handles multiple game music types + custom_reader.cpp Loads music data from gzip file and memory block + stereo_effects.cpp Uses Effects_Buffer to add stereo echo + + simple_player.cpp Uses Music_Player to make simple player + Music_Player.cpp Simple game music player module using SDL sound + Music_Player.h + + Wave_Writer.h WAVE sound file writer used for demo output + Wave_Writer.cpp + +gme/ + Effects_Buffer.h Sound buffer with adjustable stereo echo and panning + Effects_Buffer.cpp + + Gzip_File.h Gzip reader for transparent access to gzipped files + Gzip_File.cpp + + Music_Emu.h Game music emulator interface + + Nsf_Emu.h Nintendo NES NSF emulator + Nsf_Emu.cpp + Nes_Apu.cpp + Nes_Apu.h + Nes_Cpu.cpp + Nes_Cpu.h + Nes_Oscs.cpp + Nes_Oscs.h + Nes_Fme7_Apu.cpp + Nes_Fme7_Apu.h + Nes_Namco_Apu.cpp + Nes_Namco_Apu.h + Nes_Vrc6_Apu.cpp + Nes_Vrc6_Apu.h + + Gbs_Emu.h Nintendo Game Boy GBS emulator + Gbs_Emu.cpp + Gb_Apu.cpp + Gb_Apu.h + Gb_Cpu.cpp + Gb_Cpu.h + Gb_Oscs.cpp + Gb_Oscs.h + + Spc_Emu.h Super Nintendo SPC emulator + Spc_Emu.cpp + Snes_Spc.cpp + Snes_Spc.h + Spc_Cpu.cpp + Spc_Cpu.h + Spc_Dsp.cpp + Spc_Dsp.h + Fir_Resampler.cpp + Fir_Resampler.h + + Gym_Emu.h Sega Genesis GYM emulator + Gym_Emu.cpp + Vgm_Emu.h Sega VGM emulator + Vgm_Emu_Impl.cpp + Vgm_Emu_Impl.h + Vgm_Emu.cpp + Ym2413_Emu.cpp + Ym2413_Emu.h + Sms_Apu.cpp Common Sega emulator files + Sms_Apu.h + Sms_Oscs.h + Ym2612_Emu.cpp + Ym2612_Emu.h + Dual_Resampler.cpp + Dual_Resampler.h + Fir_Resampler.cpp + Fir_Resampler.h + + blargg_common.h Common files + blargg_endian.h + blargg_source.h + Blip_Buffer.cpp + Blip_Buffer.h + Music_Emu.cpp + Classic_Emu.h + Classic_Emu.cpp + Multi_Buffer.h + Multi_Buffer.cpp + abstract_file.cpp + abstract_file.h + + +Legal +----- +Game_Music_Emu library copyright (C) 2003-2006 Shay Green. +SNES SPC DSP emulator based on OpenSPC, copyright (C) 2002 Brad Martin. +Sega Genesis YM2612 emulator copyright (C) 2002 Stephane Dallongeville. diff -r ccb68bad47b2 -r c04dff121e1d Plugins/Input/console/notes.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/notes.txt Tue Jan 24 20:19:01 2006 -0800 @@ -0,0 +1,77 @@ +Audacious Console Game Music Driver +----------------------------------- +Contact: Shay Green + +Notes +----- +- This is a fairly rough version. I'm sending it so we can decide more +concretely on the desired features. I don't have Unix (or even Mac OS X) +so I've only tested this lightly with a quick framework I wrote to +simulate the Audacious environment (as best as I could determine based +on the limited documentation). + +- The most significant missing feature is a way to select the track +number of multi-track formats (NSF, NSFE, GBS). I've implemented +internal support for this and marked the places where the track number +is needed from an external source. + +- Seeking should be tested carefully. It might be too slow for some +formats. + +- Currently text fields are treated as they are already in UTF-8 format +(which they aren't), but they should probably be converted from Windows +charset to UTF-8. VGM files have 16-bit chars in an unknown encoding, +currently just truncated to 8-bits. + +- Errors in Audacious_Driver.cpp are checked and generally result in +exit of the current operation and no overall effect. Information about +the cause of the error is consistently lost, so it would be difficult to +switch over to a model of actually reporting the error so the user can +know about it and take useful action. + +- Each track may contain any of the following: preferred play length, +intro length, loop length. If play length is present, it is used as the +track time. If not present, the default play length from the config file +is used. A more sophisticated algorithm could be used that takes into +account the loop length. + +- File opening and reading has been significantly minimized. Bytes read +for file identification are preserved for when the rest of header is +read. When playing a track, file information is obtained using +already-loaded data in the emulator, eliminating extra reading. + + +Change Log +---------- +- Marked things to be addressed with "// to do:" comments + +- Updated to Game_Music_Emu 0.3.0 and eliminated unnecessary source +files + +- Eliminated Audacious_Driver.h since it served no purpose + +- Added support for NSFE files + +- Added Vfs_File, a wrapper for the vfs_* file functions. This allows +all the emulators to access files in this manner. + +- Updated Makefile.am but didn't add line to link with zlib + +- Added fading at end of tracks + +- Added end-of-track silence detection for tracks without timing +information. Stops track when 6 seconds of silence have passed, but +looks ahead so that the user only experiences about 1 second of silence +before the track ends. + +- Added beginning-of-track silence removal, as some tracks have many +seconds of silence (Zelda Link's Awakening.gbs track 61 has 20 seconds +of silence, making you think it's not a music track without this +feature). + + +To Do +----- +- Separate track info handling from Audacious_Driver.cpp, since the +current complexity is a good source of bugs +