Mercurial > audlegacy
changeset 491:7c5e886205ef trunk
[svn] New code drop from blargg. Needs some work.
line wrap: on
line diff
--- a/Plugins/Input/console/Audacious_Driver.cpp Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Audacious_Driver.cpp Tue Jan 24 19:10:07 2006 -0800 @@ -6,526 +6,669 @@ * http://www.slack.net/~ant/libs/ */ -#include "Audacious_Driver.h" - -extern "C" { - #include <glib.h> #include <glib/gi18n.h> #include <gtk/gtk.h> #include "libaudacious/configdb.h" #include "libaudacious/util.h" #include "libaudacious/titlestring.h" -#include "libaudacious/vfs.h" -#include "audacious/input.h" +extern "C" { #include "audacious/output.h" -#include "libaudcore/playback.h" +} +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <math.h> + +// 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; }; -struct AudaciousConsoleConfig audcfg = { 180, FALSE, 32000 }; - -#include <cstring> -#include <cstdlib> -#include <cctype> +// NSFE -static Spc_Emu *spc = NULL; -static Nsf_Emu *nsf = NULL; -static Gbs_Emu *gbs = NULL; -static Gym_Emu *gym = NULL; -static Vgm_Emu *vgm = NULL; -static GThread *decode_thread; -GStaticMutex playback_mutex = G_STATIC_MUTEX_INIT; +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; +} -static void *play_loop_spc(gpointer arg); -static void *play_loop_nsf(gpointer arg); -static void *play_loop_gbs(gpointer arg); -static void *play_loop_gym(gpointer arg); -static void *play_loop_vgm(gpointer arg); -static void console_init(void); -extern "C" void console_aboutbox(void); -static void console_stop(void); -static void console_pause(gshort p); -static int get_time(void); -static gboolean console_ip_is_going; -extern InputPlugin console_ip; -static int playing_type; +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; +} -static int is_our_file(gchar *filename) +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 ) { - VFSFile *file; - gchar magic[4]; - if (file = vfs_fopen(filename, "rb")) { - vfs_fread(magic, 1, 4, file); - if (!strncmp(magic, "SNES", 4)) { - vfs_fclose(file); - return PLAY_TYPE_SPC; - } - if (!strncmp(magic, "NESM", 4)) { - vfs_fclose(file); - return PLAY_TYPE_NSF; - } - if (!strncmp(magic, "GYMX", 4)) { - vfs_fclose(file); - return PLAY_TYPE_GYM; - } - if (!strncmp(magic, "GBS", 3)) { - vfs_fclose(file); - return PLAY_TYPE_GBS; - } - if (!strncmp(magic, "Vgm", 3)) { - vfs_fclose(file); - return PLAY_TYPE_VGM; - } - vfs_fclose(file); + 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 ); } - return 0; } -static gint strtoi(gchar *buf,size_t len) { - guint num = 0; - while (len && isdigit(*buf)) { - num *= 10; - num += *buf - '0'; - buf++; - len--; +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; } - return num; +} + +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; } -static gchar *get_title_spc(gchar *filename) -{ - gchar *title; - Spc_Reader reader; - Spc_Emu::header_t header; +// SPC - reader.open(filename); - reader.read(&header, sizeof(header)); - - if (header.version > 10) +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 ) { - TitleInput *tinput; - - tinput = bmp_title_input_new(); + // 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; - header.author[31] = '\0'; - header.game[31] = '\0'; - header.song[31] = '\0'; - - tinput->performer = g_strdup(header.author); - tinput->album_name = g_strdup(header.game); - tinput->track_name = g_strdup(header.song); + // 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; + } + } + } +} - tinput->file_name = g_path_get_basename(filename); - tinput->file_path = g_path_get_dirname(filename); - - title = xmms_get_titlestring(xmms_get_gentitle_format(), - tinput); +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 ); +} - g_free(tinput); - } - else - title = g_path_get_basename(filename); - - return title; +inline void get_info_emu( Spc_Emu& emu, track_info_t* out ) +{ + get_spc_info_( emu.header(), emu.trailer(), emu.trailer_size(), out ); } -static gchar *get_title_nsf(gchar *filename) +inline void get_file_info( Spc_Emu::header_t const& h, Data_Reader& in, track_info_t* out ) { - gchar *title; - Nsf_Reader reader; - Nsf_Emu::header_t header; + // 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<byte> 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 + } +} - reader.open(filename); - reader.read(&header, sizeof(header)); +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 ); +} - if (header.game) +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 ) { - TitleInput *tinput; - - tinput = bmp_title_input_new(); + out->length = length * 10 / 441; // 1000 / 44100 (VGM files used 44100 as timebase) + out->loop = 0; - header.author[31] = '\0'; - header.copyright[31] = '\0'; - header.game[31] = '\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; + } + } +} - tinput->performer = g_strdup(header.author); - tinput->album_name = g_strdup(header.copyright); - tinput->track_name = g_strdup(header.game); +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 ); +} - tinput->file_name = g_path_get_basename(filename); - tinput->file_path = g_path_get_dirname(filename); - - title = xmms_get_titlestring(xmms_get_gentitle_format(), - tinput); - - g_free(tinput); - } - else - title = g_path_get_basename(filename); - - return title; +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<byte> gd3; + if ( gd3.resize( gd3_size ) || in.read( gd3.begin(), gd3.size() ) ) + return; + + get_vgm_gd3( gd3.begin(), gd3.size(), out ); } -static gchar *get_title_gbs(gchar *filename) -{ - gchar *title; - Gbs_Reader reader; - Gbs_Emu::header_t header; +// 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]; - reader.open(filename); - reader.read(&header, sizeof(header)); +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; +} - if (header.game) - { - TitleInput *tinput; +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 - tinput = bmp_title_input_new(); - - header.author[31] = '\0'; - header.copyright[31] = '\0'; - header.game[31] = '\0'; - - tinput->performer = g_strdup(header.author); - tinput->album_name = g_strdup(header.copyright); - tinput->track_name = g_strdup(header.game); +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; +} - tinput->file_name = g_path_get_basename(filename); - tinput->file_path = g_path_get_dirname(filename); - - title = xmms_get_titlestring(xmms_get_gentitle_format(), - tinput); - - g_free(tinput); - } - else - title = g_path_get_basename(filename); - - return title; +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; } -static gchar *get_title_gym(gchar *filename) +template<class Header> +inline void get_info_t( tag_t tag, Data_Reader& in, track_info_t* out, Header* ) { - gchar *title; - Gym_Reader reader; - Gym_Emu::header_t header; - - reader.open(filename); - reader.read(&header, sizeof(header)); - - if (header.song) - { - TitleInput *tinput; - - tinput = bmp_title_input_new(); - - header.game[31] = '\0'; - header.copyright[31] = '\0'; - header.song[31] = '\0'; - - tinput->performer = g_strdup(header.game); - tinput->album_name = g_strdup(header.copyright); - tinput->track_name = g_strdup(header.song); - - tinput->file_name = g_path_get_basename(filename); - tinput->file_path = g_path_get_dirname(filename); - - title = xmms_get_titlestring(xmms_get_gentitle_format(), - tinput); - - g_free(tinput); - } - else - title = g_path_get_basename(filename); - - return title; + 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 gchar *get_title_vgm(gchar *filename) +static void get_song_info( char* path, char** title, int* length ) { - gchar *title; - title = g_path_get_basename(filename); - return title; + 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 ); } -static gchar *get_title(gchar *filename) +// Playback + +static int silence_pending; + +static void* play_loop_track( gpointer ) { - switch (is_our_file(filename)) + g_static_mutex_lock( &playback_mutex ); + + while ( console_ip_is_going ) { - case PLAY_TYPE_SPC: - return get_title_spc(filename); - break; - case PLAY_TYPE_NSF: - return get_title_nsf(filename); - break; - case PLAY_TYPE_GBS: - return get_title_gbs(filename); - break; - case PLAY_TYPE_GYM: - return get_title_gym(filename); - break; - case PLAY_TYPE_VGM: - return get_title_vgm(filename); - break; + 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; } -static void get_song_info(char *filename, char **title, int *length) -{ - (*title) = get_title(filename); - - if (audcfg.loop_length) - (*length) = audcfg.loop_length; - else - (*length) = -1; -} - -static void play_file_spc(char *filename) +template<class Emu> +void load_file( tag_t tag, Data_Reader& in, long rate, track_info_t* out, Emu* dummy ) { - gchar *name; - Spc_Reader reader; - Spc_Emu::header_t header; - gint samplerate; - - if (audcfg.resample == TRUE) - samplerate = audcfg.resample_rate; - else - samplerate = Spc_Emu::native_sample_rate; - - reader.open(filename); - reader.read(&header, sizeof(header)); - - spc = new Spc_Emu; - spc->init(samplerate); - spc->load(header, reader); - spc->start_track(0); - - console_ip_is_going = TRUE; - - name = get_title(filename); - - spc->length = strtoi(header.len_secs,3); - - if (spc->length > 0) - console_ip.set_info(name, spc->length * 1000, - spc->voice_count() * 1000, samplerate, 2); - else if (audcfg.loop_length) - console_ip.set_info(name, audcfg.loop_length * 1000, - spc->voice_count() * 1000, samplerate, 2); - else - console_ip.set_info(name, -1, spc->voice_count() * 1000, - samplerate, 2); - - g_free(name); - - if (!console_ip.output->open_audio(FMT_S16_NE, samplerate, 2)) - return; - - playing_type = PLAY_TYPE_SPC; - - decode_thread = g_thread_create(play_loop_spc, spc, TRUE, NULL); -} - -static void play_file_nsf(char *filename) -{ - gchar *name; - Nsf_Reader reader; - Nsf_Emu::header_t header; - gint samplerate; - - if (audcfg.resample == TRUE) - samplerate = audcfg.resample_rate; - else - samplerate = 44100; - - reader.open(filename); - reader.read(&header, sizeof(header)); - - nsf = new Nsf_Emu; - nsf->init(samplerate); - nsf->load(header, reader); - nsf->start_track(0); - - console_ip_is_going = TRUE; - - name = get_title(filename); - - if (audcfg.loop_length) - console_ip.set_info(name, audcfg.loop_length * 1000, - nsf->voice_count() * 1000, samplerate, 2); - else - console_ip.set_info(name, -1, nsf->voice_count() * 1000, - samplerate, 2); - - g_free(name); - - if (!console_ip.output->open_audio(FMT_S16_NE, samplerate, 2)) - return; - - playing_type = PLAY_TYPE_NSF; - - decode_thread = g_thread_create(play_loop_nsf, nsf, TRUE, NULL); + 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_gbs(char *filename) +static void play_file( char* path ) { - gchar *name; - Gbs_Reader reader; - Gbs_Emu::header_t header; - gint samplerate; - - if (audcfg.resample == TRUE) - samplerate = audcfg.resample_rate; - else - samplerate = 44100; - - reader.open(filename); - reader.read(&header, sizeof(header)); - - gbs = new Gbs_Emu; - gbs->init(samplerate); - gbs->load(header, reader); - gbs->start_track(0); + 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; - - name = get_title(filename); - - if (audcfg.loop_length) - console_ip.set_info(name, audcfg.loop_length * 1000, - gbs->voice_count() * 1000, samplerate, 2); - else - console_ip.set_info(name, -1, gbs->voice_count() * 1000, - samplerate, 2); - - g_free(name); - - if (!console_ip.output->open_audio(FMT_S16_NE, samplerate, 2)) - return; - - playing_type = PLAY_TYPE_GBS; - - decode_thread = g_thread_create(play_loop_gbs, gbs, TRUE, NULL); + decode_thread = g_thread_create( play_loop_track, NULL, TRUE, NULL ); } -static void play_file_gym(char *filename) +static void seek( gint time ) { - gchar *name; - Gym_Reader reader; - Gym_Emu::header_t header; - gint samplerate; - - if (audcfg.resample == TRUE) - samplerate = audcfg.resample_rate; - else - samplerate = 44100; - - reader.open(filename); - reader.read(&header, sizeof(header)); - - gym = new Gym_Emu; - gym->init(samplerate); - gym->load(header, reader); - gym->start_track(0); + // to do: be sure seek works at all + // to do: disallow seek on slow formats (SPC, GYM, VGM using FM)? + pending_seek = time; +} - console_ip_is_going = TRUE; - - name = get_title(filename); +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(); +} - if (gym->track_length() > 0) - console_ip.set_info(name, gym->track_length() * 1000, - gym->voice_count() * 1000, samplerate, 2); - else if (audcfg.loop_length) - console_ip.set_info(name, audcfg.loop_length * 1000, - gym->voice_count() * 1000, samplerate, 2); - else - console_ip.set_info(name, -1, gym->voice_count() * 1000, - samplerate, 2); +static void console_pause(gshort p) +{ + console_ip.output->pause(p); +} - g_free(name); - - if (!console_ip.output->open_audio(FMT_S16_NE, samplerate, 2)) - return; - - playing_type = PLAY_TYPE_GYM; - - decode_thread = g_thread_create(play_loop_gym, gym, TRUE, NULL); +static int get_time(void) +{ + return console_ip_is_going ? console_ip.output->output_time() : -1; } -static void play_file_vgm(char *filename) -{ - gchar *name; - Vgm_Reader reader; - Vgm_Emu::header_t header; - gint samplerate; +// Setup - if (audcfg.resample == TRUE) - samplerate = audcfg.resample_rate; - else - samplerate = 44100; - - reader.open(filename); - reader.read(&header, sizeof(header)); - - vgm = new Vgm_Emu; - vgm->init(samplerate); - vgm->load(header, reader); - vgm->start_track(0); +static void console_init(void) +{ + ConfigDb *db; - console_ip_is_going = TRUE; - - name = get_title(filename); + db = bmp_cfg_db_open(); - if (vgm->track_length() > 0) - console_ip.set_info(name, vgm->track_length() * 1000, - vgm->voice_count() * 1000, samplerate, 2); - else if (audcfg.loop_length) - console_ip.set_info(name, audcfg.loop_length * 1000, - vgm->voice_count() * 1000, samplerate, 2); - else - console_ip.set_info(name, -1, vgm->voice_count() * 1000, - samplerate, 2); - - g_free(name); - - if (!console_ip.output->open_audio(FMT_S16_NE, samplerate, 2)) - return; - - playing_type = PLAY_TYPE_VGM; - - decode_thread = g_thread_create(play_loop_vgm, vgm, TRUE, NULL); + 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); } -static void play_file(char *filename) +extern "C" void console_aboutbox(void) { - switch (is_our_file(filename)) - { - case PLAY_TYPE_SPC: - play_file_spc(filename); - break; - case PLAY_TYPE_NSF: - play_file_nsf(filename); - break; - case PLAY_TYPE_GBS: - play_file_gbs(filename); - break; - case PLAY_TYPE_GYM: - play_file_gym(filename); - break; - case PLAY_TYPE_VGM: - play_file_vgm(filename); - break; - } + 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 <nenolod@nenolod.net>, " + // Please do not put my hotpop.com address in the clear (I hate spam) + "Shay Green <hotpop.com@blargg>"), + _("Ok"), + FALSE, NULL, NULL); } -static void seek(gint time) +InputPlugin console_ip = { - // XXX: Not yet implemented -} - -InputPlugin console_ip = { NULL, NULL, NULL, @@ -542,7 +685,7 @@ get_time, NULL, NULL, - NULL, + NULL, NULL, NULL, NULL, @@ -552,219 +695,9 @@ NULL }; -static void console_stop(void) -{ - console_ip_is_going = FALSE; - g_thread_join(decode_thread); - console_ip.output->close_audio(); -} - extern "C" InputPlugin *get_iplugin_info(void) { - console_ip.description = g_strdup_printf(_("SPC, GYM, NSF, VGM and GBS module decoder")); - return &console_ip; -} - -static void console_pause(gshort p) -{ - console_ip.output->pause(p); -} - -static void *play_loop_spc(gpointer arg) -{ - g_static_mutex_lock(&playback_mutex); - Spc_Emu *my_spc = (Spc_Emu *) arg; - Music_Emu::sample_t buf[1024]; - - for (;;) - { - if (!console_ip_is_going) - break; - - my_spc->play(1024, buf); - - if ((console_ip.output->output_time() / 1000) > - spc->length && spc->length != 0) - break; - if ((console_ip.output->output_time() / 1000) > - audcfg.loop_length && audcfg.loop_length != 0) - break; - produce_audio(console_ip.output->written_time(), - FMT_S16_NE, 1, 2048, buf, NULL); - while(console_ip.output->buffer_free() < 2048) - xmms_usleep(10000); - } - - delete spc; - console_ip.output->close_audio(); - console_ip_is_going = FALSE; - playing_type = PLAY_TYPE_NONE; - g_static_mutex_unlock(&playback_mutex); - g_thread_exit(NULL); - - return NULL; -} - -static void *play_loop_nsf(gpointer arg) -{ - g_static_mutex_lock(&playback_mutex); - Nsf_Emu *my_nsf = (Nsf_Emu *) arg; - Music_Emu::sample_t buf[1024]; - - for (;;) - { - if (!console_ip_is_going) - break; - - my_nsf->play(1024, buf); - - if ((console_ip.output->output_time() / 1000) > - audcfg.loop_length && audcfg.loop_length != 0) - break; - produce_audio(console_ip.output->written_time(), - FMT_S16_NE, 1, 2048, buf, NULL); - while(console_ip.output->buffer_free() < 2048) - xmms_usleep(10000); - } - - delete nsf; - console_ip.output->close_audio(); - console_ip_is_going = FALSE; - playing_type = PLAY_TYPE_NONE; - g_static_mutex_unlock(&playback_mutex); - g_thread_exit(NULL); - - return NULL; + console_ip.description = g_strdup_printf(_("SPC, VGM, NSF/NSFE, GBS, and GYM module decoder")); + return &console_ip; } -static void *play_loop_gbs(gpointer arg) -{ - g_static_mutex_lock(&playback_mutex); - Gbs_Emu *my_gbs = (Gbs_Emu *) arg; - Music_Emu::sample_t buf[1024]; - - for (;;) - { - if (!console_ip_is_going) - break; - - my_gbs->play(1024, buf); - - if ((console_ip.output->output_time() / 1000) > - audcfg.loop_length && audcfg.loop_length != 0) - break; - produce_audio(console_ip.output->written_time(), - FMT_S16_NE, 1, 2048, buf, NULL); - while(console_ip.output->buffer_free() < 2048) - xmms_usleep(10000); - } - - delete gbs; - console_ip.output->close_audio(); - console_ip_is_going = FALSE; - playing_type = PLAY_TYPE_NONE; - g_static_mutex_unlock(&playback_mutex); - g_thread_exit(NULL); - - return NULL; -} - -static void *play_loop_gym(gpointer arg) -{ - g_static_mutex_lock(&playback_mutex); - Gym_Emu *my_gym = (Gym_Emu *) arg; - Music_Emu::sample_t buf[1024]; - - for (;;) - { - if (!console_ip_is_going) - break; - - my_gym->play(1024, buf); - - if ((console_ip.output->output_time() / 1000) > - gym->track_length() && gym->track_length() != 0) - break; - if ((console_ip.output->output_time() / 1000) > - audcfg.loop_length && audcfg.loop_length != 0) - break; - produce_audio(console_ip.output->written_time(), - FMT_S16_NE, 1, 2048, buf, NULL); - while(console_ip.output->buffer_free() < 2048) - xmms_usleep(10000); - } - - delete gym; - console_ip.output->close_audio(); - console_ip_is_going = FALSE; - playing_type = PLAY_TYPE_NONE; - g_static_mutex_unlock(&playback_mutex); - g_thread_exit(NULL); - - return NULL; -} - -static void *play_loop_vgm(gpointer arg) -{ - g_static_mutex_lock(&playback_mutex); - Vgm_Emu *my_vgm = (Vgm_Emu *) arg; - Music_Emu::sample_t buf[1024]; - - for (;;) - { - if (!console_ip_is_going) - break; - - my_vgm->play(1024, buf); - - if ((console_ip.output->output_time() / 1000) > - vgm->track_length() && vgm->track_length() != 0) - break; - if ((console_ip.output->output_time() / 1000) > - audcfg.loop_length && audcfg.loop_length != 0) - break; - produce_audio(console_ip.output->written_time(), - FMT_S16_NE, 1, 2048, buf, NULL); - while(console_ip.output->buffer_free() < 2048) - xmms_usleep(10000); - } - - delete vgm; - console_ip.output->close_audio(); - console_ip_is_going = FALSE; - playing_type = PLAY_TYPE_NONE; - g_static_mutex_unlock(&playback_mutex); - g_thread_exit(NULL); - - return NULL; -} - -static int get_time(void) -{ - if (console_ip_is_going == TRUE) - return console_ip.output->output_time(); - else - return -1; -} - -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_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.2.4.\n" - "Audacious implementation by: William Pitcock <nenolod@nenolod.net>"), - _("Ok"), - FALSE, NULL, NULL); -}
--- a/Plugins/Input/console/Audacious_Driver.h Tue Jan 24 13:57:22 2006 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +0,0 @@ -/* - * Audacious: Cross platform media player - * Copyright (c) 2005 Audacious team - * - * Header for the Audacious plugin. - */ - -#ifndef AUDACIOUS_DRIVER_H -#define AUDACIOUS_DRIVER_H - -#include <glib.h> - -#include "Blip_Buffer.h" -#include "Blip_Synth.h" -#include "Classic_Emu.h" -#include "Effects_Buffer.h" -#include "Fir_Resampler.h" -#include "Gb_Apu.h" -#include "Gb_Cpu.h" -#include "Gb_Oscs.h" -#include "Gbs_Emu.h" -#include "Gym_Emu.h" -#include "Multi_Buffer.h" -#include "Music_Emu.h" -#include "Nes_Apu.h" -#include "Nes_Cpu.h" -#include "Nes_Namco.h" -#include "Nes_Oscs.h" -#include "Nes_Vrc6.h" -#include "Nsf_Emu.h" -#include "Panning_Buffer.h" -#include "Sms_Apu.h" -#include "Sms_Oscs.h" -#include "Snes_Spc.h" -#include "Spc_Cpu.h" -#include "Spc_Dsp.h" -#include "Spc_Emu.h" -#include "Tagged_Data.h" -#include "Vgm_Emu.h" -#include "abstract_file.h" -#include "blargg_common.h" -#include "blargg_endian.h" -#include "blargg_source.h" -#include "ym2612.h" - -struct AudaciousConsoleConfig { - gint loop_length; // length to loop in seconds - gboolean resample; // whether or not to resample - gint resample_rate; // rate to resample at -}; - -#define PLAY_TYPE_NONE 0 -#define PLAY_TYPE_SPC 1 -#define PLAY_TYPE_NSF 2 -#define PLAY_TYPE_VGM 3 -#define PLAY_TYPE_GBS 4 -#define PLAY_TYPE_GYM 5 - -#endif
--- a/Plugins/Input/console/Blip_Buffer.cpp Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Blip_Buffer.cpp Tue Jan 24 19:10:07 2006 -0800 @@ -1,12 +1,15 @@ -// Blip_Buffer 0.3.3. http://www.slack.net/~ant/libs/ +// Blip_Buffer 0.4.0. http://www.slack.net/~ant/ #include "Blip_Buffer.h" +#include <assert.h> +#include <limits.h> #include <string.h> +#include <stdlib.h> #include <math.h> -/* Copyright (C) 2003-2005 Shay Green. This module is free software; you +/* 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 @@ -17,378 +20,387 @@ 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 buffer_extra = blip_widest_impulse_ + 2; Blip_Buffer::Blip_Buffer() { - samples_per_sec = 44100; - buffer_ = NULL; - - // try to cause assertion failure if buffer is used before these are set - clocks_per_sec = 0; - factor_ = ~0ul; + 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; - bass_freq_ = 16; + // 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 } -void Blip_Buffer::clear( bool entire_buffer ) +Blip_Buffer::~Blip_Buffer() { - long count = (entire_buffer ? buffer_size_ : samples_avail()); + free( buffer_ ); +} + +void Blip_Buffer::clear( int entire_buffer ) +{ offset_ = 0; reader_accum = 0; - memset( buffer_, sample_offset & 0xFF, (count + widest_impulse_) * sizeof (buf_t_) ); + if ( buffer_ ) + { + long count = (entire_buffer ? buffer_size_ : samples_avail()); + memset( buffer_, 0, (count + buffer_extra) * sizeof (buf_t_) ); + } } -blargg_err_t Blip_Buffer::sample_rate( long new_rate, int msec ) +Blip_Buffer::blargg_err_t Blip_Buffer::set_sample_rate( long new_rate, int msec ) { - size_t new_size = (ULONG_MAX >> BLIP_BUFFER_ACCURACY) + 1 - widest_impulse_ - 64; - if ( msec != blip_default_length ) + // 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 ) { - size_t s = (new_rate * (msec + 1) + 999) / 1000; + long s = (new_rate * (msec + 1) + 999) / 1000; if ( s < new_size ) new_size = s; else - require( false ); // requested buffer length exceeds limit + assert( 0 ); // fails if requested buffer length exceeds limit } if ( buffer_size_ != new_size ) { - delete [] buffer_; - buffer_ = NULL; // allow for exception in allocation below - buffer_size_ = 0; - offset_ = 0; - - buffer_ = new buf_t_ [new_size + widest_impulse_]; - if ( !buffer_ ) + 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 - - samples_per_sec = new_rate; - if ( clocks_per_sec ) - clock_rate( clocks_per_sec ); // recalculate factor - - bass_freq( bass_freq_ ); // recalculate shift + if ( clock_rate_ ) + clock_rate( clock_rate_ ); + bass_freq( bass_freq_ ); clear(); - return blargg_success; + return 0; // success } -void Blip_Buffer::clock_rate( long cps ) +blip_resampled_time_t Blip_Buffer::clock_rate_factor( long clock_rate ) const { - clocks_per_sec = cps; - factor_ = (unsigned long) floor( (double) samples_per_sec / cps * - (1L << BLIP_BUFFER_ACCURACY) + 0.5 ); - require( factor_ > 0 ); // clock_rate/sample_rate ratio is too large -} - -Blip_Buffer::~Blip_Buffer() -{ - delete [] buffer_; + 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; - if ( freq == 0 ) { - bass_shift = 31; // 32 or greater invokes undefined behavior elsewhere - return; + int shift = 31; + if ( freq > 0 ) + { + shift = 13; + long f = (freq << 16) / sample_rate_; + while ( (f >>= 1) && --shift ) { } } - bass_shift = 1 + (int) floor( 1.442695041 * log( 0.124 * samples_per_sec / freq ) ); - if ( bass_shift < 0 ) - bass_shift = 0; - if ( bass_shift > 24 ) - bass_shift = 24; -} - -long Blip_Buffer::count_samples( blip_time_t t ) const { - return (resampled_time( t ) >> BLIP_BUFFER_ACCURACY) - (offset_ >> BLIP_BUFFER_ACCURACY); -} - -void Blip_Impulse_::init( blip_pair_t_* imps, int w, int r, int fb ) -{ - fine_bits = fb; - width = w; - impulses = (imp_t*) imps; - generate = true; - volume_unit_ = -1.0; - res = r; - buf = NULL; - - impulse = &impulses [width * res * 2 * (fine_bits ? 2 : 1)]; - offset = 0; + bass_shift = shift; } -const int impulse_bits = 15; -const long impulse_amp = 1L << impulse_bits; -const long impulse_offset = impulse_amp / 2; - -void Blip_Impulse_::scale_impulse( int unit, imp_t* imp_in ) const +void Blip_Buffer::end_frame( blip_time_t t ) { - long offset = ((long) unit << impulse_bits) - impulse_offset * unit + - (1 << (impulse_bits - 1)); - imp_t* imp = imp_in; - imp_t* fimp = impulse; - for ( int n = res / 2 + 1; n--; ) - { - int error = unit; - for ( int nn = width; nn--; ) - { - long a = ((long) *fimp++ * unit + offset) >> impulse_bits; - error -= a - unit; - *imp++ = (imp_t) a; - } - - // add error to middle - imp [-width / 2 - 1] += (imp_t) error; - } - - if ( res > 2 ) { - // second half is mirror-image - const imp_t* rev = imp - width - 1; - for ( int nn = (res / 2 - 1) * width - 1; nn--; ) - *imp++ = *--rev; - *imp++ = (imp_t) unit; - } - - // copy to odd offset - *imp++ = (imp_t) unit; - memcpy( imp, imp_in, (res * width - 1) * sizeof *imp ); -} - -const int max_res = 1 << blip_res_bits_; - -void Blip_Impulse_::fine_volume_unit() -{ - // to do: find way of merging in-place without temporary buffer - - imp_t temp [max_res * 2 * Blip_Buffer::widest_impulse_]; - scale_impulse( (offset & 0xffff) << fine_bits, temp ); - imp_t* imp2 = impulses + res * 2 * width; - scale_impulse( offset & 0xffff, imp2 ); - - // merge impulses - imp_t* imp = impulses; - imp_t* src2 = temp; - for ( int n = res / 2 * 2 * width; n--; ) { - *imp++ = *imp2++; - *imp++ = *imp2++; - *imp++ = *src2++; - *imp++ = *src2++; - } + offset_ += t * factor_; + assert( samples_avail() <= (long) buffer_size_ ); // time outside buffer length } -void Blip_Impulse_::volume_unit( double new_unit ) +void Blip_Buffer::remove_silence( long count ) { - if ( new_unit == volume_unit_ ) - return; - - if ( generate ) - treble_eq( blip_eq_t( -8.87, 8800, 44100 ) ); - - volume_unit_ = new_unit; - - offset = 0x10001 * (unsigned long) floor( volume_unit_ * 0x10000 + 0.5 ); - - if ( fine_bits ) - fine_volume_unit(); - else - scale_impulse( offset & 0xffff, impulses ); + assert( count <= samples_avail() ); // tried to remove more samples than available + offset_ -= (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY; } -static const double pi = 3.1415926535897932384626433832795029L; - -void Blip_Impulse_::treble_eq( const blip_eq_t& new_eq ) +long Blip_Buffer::count_samples( blip_time_t t ) const { - if ( !generate && new_eq.treble == eq.treble && new_eq.cutoff == eq.cutoff && - new_eq.sample_rate == eq.sample_rate ) - return; // already calculated with same parameters - - generate = false; - eq = new_eq; - - double treble = pow( 10.0, 1.0 / 20 * eq.treble ); // dB (-6dB = 0.50) - if ( treble < 0.000005 ) - treble = 0.000005; - - const double treble_freq = 22050.0; // treble level at 22 kHz harmonic - const double sample_rate = eq.sample_rate; - const double pt = treble_freq * 2 / sample_rate; - double cutoff = eq.cutoff * 2 / sample_rate; - if ( cutoff >= pt * 0.95 || cutoff >= 0.95 ) { - cutoff = 0.5; - treble = 1.0; - } - - // DSF Synthesis (See T. Stilson & J. Smith (1996), - // Alias-free digital synthesis of classic analog waveforms) - - // reduce adjacent impulse interference by using small part of wide impulse - const double n_harm = 4096; - const double rolloff = pow( treble, 1.0 / (n_harm * pt - n_harm * cutoff) ); - const double rescale = 1.0 / pow( rolloff, n_harm * cutoff ); - - const double pow_a_n = rescale * pow( rolloff, n_harm ); - const double pow_a_nc = rescale * pow( rolloff, n_harm * cutoff ); - - double total = 0.0; - const double to_angle = pi / 2 / n_harm / max_res; - - float buf [max_res * (Blip_Buffer::widest_impulse_ - 2) / 2]; - const int size = max_res * (width - 2) / 2; - for ( int i = size; i--; ) - { - double angle = (i * 2 + 1) * to_angle; - - // equivalent - //double y = dsf( angle, n_harm * cutoff, 1.0 ); - //y -= rescale * dsf( angle, n_harm * cutoff, rolloff ); - //y += rescale * dsf( angle, n_harm, rolloff ); - - const double cos_angle = cos( angle ); - const double cos_nc_angle = cos( n_harm * cutoff * angle ); - const double cos_nc1_angle = cos( (n_harm * cutoff - 1.0) * angle ); - - double b = 2.0 - 2.0 * cos_angle; - double a = 1.0 - cos_angle - cos_nc_angle + cos_nc1_angle; - - double d = 1.0 + rolloff * (rolloff - 2.0 * cos_angle); - double c = pow_a_n * rolloff * cos( (n_harm - 1.0) * angle ) - - pow_a_n * cos( n_harm * angle ) - - pow_a_nc * rolloff * cos_nc1_angle + - pow_a_nc * cos_nc_angle; - - // optimization of a / b + c / d - double y = (a * d + c * b) / (b * d); - - // fixed window which affects wider impulses more - if ( width > 12 ) { - double window = cos( n_harm / 1.25 / Blip_Buffer::widest_impulse_ * angle ); - y *= window * window; - } - - total += (float) y; - buf [i] = (float) y; - } - - // integrate runs of length 'max_res' - double factor = impulse_amp * 0.5 / total; // 0.5 accounts for other mirrored half - imp_t* imp = impulse; - const int step = max_res / res; - int offset = res > 1 ? max_res : max_res / 2; - for ( int n = res / 2 + 1; n--; offset -= step ) - { - for ( int w = -width / 2; w < width / 2; w++ ) - { - double sum = 0; - for ( int i = max_res; i--; ) - { - int index = w * max_res + offset + i; - if ( index < 0 ) - index = -index - 1; - if ( index < size ) - sum += buf [index]; - } - *imp++ = (imp_t) floor( sum * factor + (impulse_offset + 0.5) ); - } - } - - // rescale - double unit = volume_unit_; - if ( unit >= 0 ) { - volume_unit_ = -1; - volume_unit( unit ); - } + 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 ) { - require( buffer_ ); // sample rate must have been set + 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 ( !count ) // optimization - return; - - remove_silence( count ); + if ( treble < -300.0 ) + treble = -300.0; + if ( treble > 5.0 ) + treble = 5.0; - // copy remaining samples to beginning and clear old samples - long remain = samples_avail() + widest_impulse_; - if ( count >= remain ) - memmove( buffer_, buffer_ + count, remain * sizeof (buf_t_) ); - else - memcpy( buffer_, buffer_ + count, remain * sizeof (buf_t_) ); - memset( buffer_ + remain, sample_offset & 0xFF, count * sizeof (buf_t_) ); + 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 ); } -#include BLARGG_ENABLE_OPTIMIZER +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] ); +} -long Blip_Buffer::read_samples( blip_sample_t* out, long max_samples, bool stereo ) +void Blip_Synth_::treble_eq( blip_eq_t const& eq ) { - require( buffer_ ); // sample rate must have been set + 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 ) - return 0; // optimization - - int sample_offset = this->sample_offset; - int bass_shift = this->bass_shift; - buf_t_* buf = buffer_; - long accum = reader_accum; - - if ( !stereo ) { - for ( long n = count; n--; ) { - long s = accum >> accum_fract; - accum -= accum >> bass_shift; - accum += (long (*buf++) - sample_offset) << accum_fract; - *out++ = (blip_sample_t) s; - - // clamp sample - if ( (BOOST::int16_t) s != s ) - out [-1] = blip_sample_t (0x7FFF - (s >> 24)); + 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 ); } - else { - for ( long n = count; n--; ) { - long s = accum >> accum_fract; - accum -= accum >> bass_shift; - accum += (long (*buf++) - sample_offset) << accum_fract; - *out = (blip_sample_t) s; - out += 2; - - // clamp sample - if ( (BOOST::int16_t) s != s ) - out [-2] = blip_sample_t (0x7FFF - (s >> 24)); - } - } - - reader_accum = accum; - - remove_samples( count ); - return count; } -void Blip_Buffer::mix_samples( const blip_sample_t* in, long count ) +void Blip_Buffer::mix_samples( blip_sample_t const* in, long count ) { - buf_t_* buf = &buffer_ [(offset_ >> BLIP_BUFFER_ACCURACY) + (widest_impulse_ / 2 - 1)]; + 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-- ) { - int s = *in++; - *buf += s - prev; + while ( count-- ) + { + long s = (long) *in++ << sample_shift; + *out += s - prev; prev = s; - ++buf; + ++out; } - *buf -= *--in; + *out -= prev; }
--- a/Plugins/Input/console/Blip_Buffer.h Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Blip_Buffer.h Tue Jan 24 19:10:07 2006 -0800 @@ -1,248 +1,354 @@ -// Buffer of sound samples into which band-limited waveforms can be synthesized -// using Blip_Wave or Blip_Synth. +// Band-limited sound synthesis and buffering -// Blip_Buffer 0.3.3. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. +// Blip_Buffer 0.4.0 #ifndef BLIP_BUFFER_H #define BLIP_BUFFER_H -#include "blargg_common.h" - -class Blip_Reader; - -// Source time unit. +// Time unit at source clock rate typedef long blip_time_t; -// Type of sample produced. Signed 16-bit format. -typedef BOOST::int16_t blip_sample_t; - -// Make buffer as large as possible (currently about 65000 samples) -const int blip_default_length = 0; +// 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: - // Construct an empty buffer. - Blip_Buffer(); - ~Blip_Buffer(); + 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 ); - // Set output sample rate and buffer length in milliseconds (1/1000 sec), - // then clear buffer. If length is not specified, make as large as possible. - // If there is insufficient memory for the buffer, sets the buffer length - // to 0 and returns error string (or propagates exception if compiler supports it). - blargg_err_t sample_rate( long samples_per_sec, int msec_length = blip_default_length ); + // 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; - // Current output sample rate - long sample_rate() const; - // Number of source time units per second - void clock_rate( long ); long clock_rate() const; - // Set frequency at which high-pass filter attenuation passes -3dB + // Set frequency high-pass filter frequency, where higher values reduce bass more void bass_freq( int frequency ); - // Remove all available samples and clear buffer to silence. If 'entire_buffer' is - // false, just clear out any samples waiting rather than the entire buffer. - void clear( bool entire_buffer = true ); + // Number of samples delay from synthesis to samples read out + int output_latency() const; - // End current time frame of specified duration and make its samples available - // (along with any still-unread samples) for reading with read_samples(). Begin - // a new time frame at the end of the current frame. All transitions must have - // been added before 'time'. - void end_frame( blip_time_t time ); + // 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; - // Read at most 'max_samples' out of buffer into 'dest', removing them from from - // the buffer. Return number of samples actually read and removed. If stereo is - // true, increment '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, bool stereo = false ); - // Remove 'count' samples from those waiting to be read void remove_samples( long count ); - // Number of samples delay from synthesis to samples read out - int output_latency() const; - +// Experimental features - // Experimental external buffer mixing support - - // Number of raw samples that can be mixed within frame of specified duration + // 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( const blip_sample_t* buf, long count ); + 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 ); - - typedef unsigned long resampled_time_t; + 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(); - resampled_time_t resampled_time( blip_time_t t ) const { - return t * resampled_time_t (factor_) + offset_; - } - - resampled_time_t resampled_duration( int t ) const { - return t * resampled_time_t (factor_); - } - + // 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& ); - - // Don't use the following members. They are public only for technical reasons. - public: - enum { widest_impulse_ = 24 }; - typedef BOOST::uint16_t buf_t_; - - unsigned long factor_; - resampled_time_t offset_; - buf_t_* buffer_; - unsigned buffer_size_; - private: - long reader_accum; - int bass_shift; - long samples_per_sec; - long clocks_per_sec; - int bass_freq_; - int length_; - - enum { accum_fract = 15 }; // less than 16 to give extra sample range - enum { sample_offset = 0x7F7F }; // repeated byte allows memset to clear buffer - - friend class Blip_Reader; +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; }; -// Low-pass equalization parameters (see notes.txt) -class blip_eq_t { -public: - blip_eq_t( double treble = 0 ); - blip_eq_t( double treble, long cutoff, long sample_rate ); -private: - double treble; - long cutoff; - long sample_rate; - friend class Blip_Impulse_; -}; +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif -// not documented yet (see Multi_Buffer.cpp for an example of use) -class Blip_Reader { - const Blip_Buffer::buf_t_* buf; - long accum; - #ifdef __MWERKS__ - void operator = ( struct foobar ); // helps optimizer - #endif -public: - // avoid anything which might cause optimizer to put object in memory - - int begin( Blip_Buffer& blip_buf ) { - buf = blip_buf.buffer_; - accum = blip_buf.reader_accum; - return blip_buf.bass_shift; - } - - int read() const { - return accum >> Blip_Buffer::accum_fract; - } - - void next( int bass_shift = 9 ) { - accum -= accum >> bass_shift; - accum += ((long) *buf++ - Blip_Buffer::sample_offset) << Blip_Buffer::accum_fract; - } - - void end( Blip_Buffer& blip_buf ) { - blip_buf.reader_accum = accum; - } -}; - - - -// End of public interface - +// 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 -const int blip_res_bits_ = 5; +// 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 -typedef BOOST::uint32_t blip_pair_t_; - -class Blip_Impulse_ { - typedef BOOST::uint16_t imp_t; + // 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; - blip_eq_t eq; - double volume_unit_; - imp_t* impulses; - imp_t* impulse; - int width; - int fine_bits; - int res; - bool generate; + 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<int quality,int range> +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 ); } - void fine_volume_unit(); - void scale_impulse( int unit, imp_t* ) const; + // 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_Buffer* buf; - BOOST::uint32_t offset; - - void init( blip_pair_t_* impulses, int width, int res, int fine_bits = 0 ); - void volume_unit( double ); - void treble_eq( const blip_eq_t& ); + Blip_Synth() : impl( impulses, quality ) { } +private: + typedef short imp_t; + imp_t impulses [blip_res * (quality / 2) + 1]; + Blip_Synth_ impl; }; -inline blip_eq_t::blip_eq_t( double t ) : - treble( t ), cutoff( 0 ), sample_rate( 44100 ) { -} +// 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 <assert.h> -inline blip_eq_t::blip_eq_t( double t, long c, long sr ) : - treble( t ), cutoff( c ), sample_rate( sr ) { +// 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<int quality,int range> +inline void Blip_Synth<quality,range>::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; } -inline int Blip_Buffer::length() const { - return length_; -} +#undef BLIP_FWD +#undef BLIP_REV -inline long Blip_Buffer::samples_avail() const { - return long (offset_ >> BLIP_BUFFER_ACCURACY); +template<int quality,int range> +void Blip_Synth<quality,range>::offset( blip_time_t t, int delta, Blip_Buffer* buf ) const +{ + offset_resampled( t * buf->factor_ + buf->offset_, delta, buf ); } -inline long Blip_Buffer::sample_rate() const { - return samples_per_sec; -} - -inline void Blip_Buffer::end_frame( blip_time_t t ) { - offset_ += t * factor_; - assert(( "Blip_Buffer::end_frame(): Frame went past end of buffer", - samples_avail() <= buffer_size_ )); +template<int quality,int range> +void Blip_Synth<quality,range>::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 void Blip_Buffer::remove_silence( long count ) { - assert(( "Blip_Buffer::remove_silence(): Tried to remove more samples than available", - count <= samples_avail() )); - offset_ -= resampled_time_t (count) << BLIP_BUFFER_ACCURACY; +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; } -inline int Blip_Buffer::output_latency() const { - return widest_impulse_ / 2; -} - -inline long Blip_Buffer::clock_rate() const { - return clocks_per_sec; -} - -// MSVC6 fix -typedef Blip_Buffer::resampled_time_t blip_resampled_time_t; - -#include "Blip_Synth.h" +int const blip_max_length = 0; +int const blip_default_length = 250; #endif
--- a/Plugins/Input/console/Blip_Synth.h Tue Jan 24 13:57:22 2006 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,204 +0,0 @@ - -// Blip_Synth and Blip_Wave are waveform transition synthesizers for adding -// waveforms to a Blip_Buffer. - -// Blip_Buffer 0.3.3. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. - -#ifndef BLIP_SYNTH_H -#define BLIP_SYNTH_H - -#ifndef BLIP_BUFFER_H - #include "Blip_Buffer.h" -#endif - -// Quality level. Higher levels are slower, and worse in a few cases. -// Use blip_good_quality as a starting point. -const int blip_low_quality = 1; -const int blip_med_quality = 2; -const int blip_good_quality = 3; -const int blip_high_quality = 4; - -// Blip_Synth is a transition waveform synthesizer which adds band-limited -// offsets (transitions) into a Blip_Buffer. For a simpler interface, use -// Blip_Wave (below). -// -// Range specifies the greatest expected offset that will occur. For a -// waveform that goes between +amp and -amp, range should be amp * 2 (half -// that if it only goes between +amp and 0). When range is large, a higher -// accuracy scheme is used; to force this even when range is small, pass -// the negative of range (i.e. -range). -template<int quality,int range> -class Blip_Synth { - BOOST_STATIC_ASSERT( 1 <= quality && quality <= 5 ); - BOOST_STATIC_ASSERT( -32768 <= range && range <= 32767 ); - enum { - abs_range = (range < 0) ? -range : range, - fine_mode = (range > 512 || range < 0), - width = (quality < 5 ? quality * 4 : Blip_Buffer::widest_impulse_), - res = 1 << blip_res_bits_, - impulse_size = width / 2 * (fine_mode + 1), - base_impulses_size = width / 2 * (res / 2 + 1), - fine_bits = (fine_mode ? (abs_range <= 64 ? 2 : abs_range <= 128 ? 3 : - abs_range <= 256 ? 4 : abs_range <= 512 ? 5 : abs_range <= 1024 ? 6 : - abs_range <= 2048 ? 7 : 8) : 0) - }; - blip_pair_t_ impulses [impulse_size * res * 2 + base_impulses_size]; - Blip_Impulse_ impulse; -public: - Blip_Synth() { impulse.init( impulses, width, res, fine_bits ); } - - // Configure low-pass filter (see notes.txt). Not optimized for real-time control - void treble_eq( const blip_eq_t& eq ) { impulse.treble_eq( eq ); } - - // Set volume of a transition at amplitude 'range' by setting volume_unit - // to v / range - void volume( double v ) { impulse.volume_unit( v * (1.0 / abs_range) ); } - - // Set base volume unit of transitions, where 1.0 is a full swing between the - // positive and negative extremes. Not optimized for real-time control. - void volume_unit( double unit ) { impulse.volume_unit( unit ); } - - // Default Blip_Buffer used for output when none is specified for a given call - Blip_Buffer* output() const { return impulse.buf; } - void output( Blip_Buffer* b ) { impulse.buf = b; } - - // Add an amplitude offset (transition) with an amplitude of delta * volume_unit - // into the specified buffer (default buffer if none specified) at the - // specified source time. Amplitude can be positive or negative. To increase - // performance by inlining code at the call site, use offset_inline(). - void offset( blip_time_t, int delta, Blip_Buffer* ) const; - - void offset_resampled( blip_resampled_time_t, int delta, Blip_Buffer* ) const; - void offset_resampled( blip_resampled_time_t t, int o ) const { - offset_resampled( t, o, impulse.buf ); - } - void offset( blip_time_t t, int delta ) const { - offset( t, delta, impulse.buf ); - } - void offset_inline( blip_time_t time, int delta, Blip_Buffer* buf ) const { - offset_resampled( time * buf->factor_ + buf->offset_, delta, buf ); - } - void offset_inline( blip_time_t time, int delta ) const { - offset_inline( time, delta, impulse.buf ); - } -}; - -// Blip_Wave is a synthesizer for adding a *single* waveform to a Blip_Buffer. -// A wave is built from a series of delays and new amplitudes. This provides a -// simpler interface than Blip_Synth. -template<int quality,int range> -class Blip_Wave { - Blip_Synth<quality,range> synth; - blip_time_t time_; - int last_amp; -public: - // Start wave at time 0 and amplitude 0 - Blip_Wave() : time_( 0 ), last_amp( 0 ) { } - - // See Blip_Synth for description - void volume( double v ) { synth.volume( v ); } - void volume_unit( double v ) { synth.volume_unit( v ); } - void treble_eq( const blip_eq_t& eq){ synth.treble_eq( eq ); } - Blip_Buffer* output() const { return synth.output(); } - void output( Blip_Buffer* b ) { synth.output( b ); if ( !b ) time_ = last_amp = 0; } - - // Current time in frame - blip_time_t time() const { return time_; } - void time( blip_time_t t ) { time_ = t; } - - // Current amplitude of wave - int amplitude() const { return last_amp; } - void amplitude( int ); - - // Move forward by 't' time units - void delay( blip_time_t t ) { time_ += t; } - - // End time frame of specified duration. Localize time to new frame. - void end_frame( blip_time_t duration ) { - assert(( "Blip_Wave::end_frame(): Wave hadn't yet been run for entire frame", - duration <= time_ )); - time_ -= duration; - } -}; - - - -// End of public interface - - template<int quality,int range> - void Blip_Wave<quality,range>::amplitude( int amp ) { - int delta = amp - last_amp; - last_amp = amp; - synth.offset_inline( time_, delta ); - } - - template<int quality,int range> - inline void Blip_Synth<quality,range>::offset_resampled( blip_resampled_time_t time, - int delta, Blip_Buffer* blip_buf ) const - { - typedef blip_pair_t_ pair_t; - - unsigned sample_index = (time >> BLIP_BUFFER_ACCURACY) & ~1; - assert(( "Blip_Synth/Blip_wave: Went past end of buffer", - sample_index < blip_buf->buffer_size_ )); - enum { const_offset = Blip_Buffer::widest_impulse_ / 2 - width / 2 }; - pair_t* buf = (pair_t*) &blip_buf->buffer_ [const_offset + sample_index]; - - enum { shift = BLIP_BUFFER_ACCURACY - blip_res_bits_ }; - enum { mask = res * 2 - 1 }; - const pair_t* imp = &impulses [((time >> shift) & mask) * impulse_size]; - - pair_t offset = impulse.offset * delta; - - if ( !fine_bits ) - { - // normal mode - for ( int n = width / 4; n; --n ) - { - pair_t t0 = buf [0] - offset; - pair_t t1 = buf [1] - offset; - - t0 += imp [0] * delta; - t1 += imp [1] * delta; - imp += 2; - - buf [0] = t0; - buf [1] = t1; - buf += 2; - } - } - else - { - // fine mode - enum { sub_range = 1 << fine_bits }; - delta += sub_range / 2; - int delta2 = (delta & (sub_range - 1)) - sub_range / 2; - delta >>= fine_bits; - - for ( int n = width / 4; n; --n ) - { - pair_t t0 = buf [0] - offset; - pair_t t1 = buf [1] - offset; - - t0 += imp [0] * delta2; - t0 += imp [1] * delta; - - t1 += imp [2] * delta2; - t1 += imp [3] * delta; - - imp += 4; - - buf [0] = t0; - buf [1] = t1; - buf += 2; - } - } - } - - template<int quality,int range> - void Blip_Synth<quality,range>::offset( blip_time_t time, int delta, Blip_Buffer* buf ) const { - offset_resampled( time * buf->factor_ + buf->offset_, delta, buf ); - } - -#endif -
--- a/Plugins/Input/console/Classic_Emu.cpp Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Classic_Emu.cpp Tue Jan 24 19:10:07 2006 -0800 @@ -1,11 +1,11 @@ -// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ #include "Classic_Emu.h" #include "Multi_Buffer.h" -/* Copyright (C) 2003-2005 Shay Green. This module is free software; you +/* 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 @@ -21,82 +21,86 @@ Classic_Emu::Classic_Emu() { buf = NULL; - std_buf = NULL; - set_equalizer( equalizer_t( -8.87, 8800 ) ); + stereo_buffer = NULL; } Classic_Emu::~Classic_Emu() { - delete std_buf; + delete stereo_buffer; } -void Classic_Emu::update_eq_() +void Classic_Emu::set_equalizer( equalizer_t const& eq ) { - update_eq( blip_eq_t( equalizer_.treble, equalizer_.cutoff, buf->sample_rate() ) ); - buf->bass_freq( equalizer_.bass ); -} - -void Classic_Emu::set_equalizer( const equalizer_t& eq ) -{ - equalizer_ = eq; + Music_Emu::set_equalizer( eq ); + update_eq( eq.treble ); if ( buf ) - update_eq_(); + buf->bass_freq( equalizer().bass ); } -blargg_err_t Classic_Emu::init( long sample_rate ) +blargg_err_t Classic_Emu::set_sample_rate( long sample_rate ) { - buf = NULL; - delete std_buf; - std_buf = NULL; + if ( !buf ) + { + if ( !stereo_buffer ) + BLARGG_CHECK_ALLOC( stereo_buffer = BLARGG_NEW Stereo_Buffer ); + buf = stereo_buffer; + } - Stereo_Buffer* sb = new Stereo_Buffer; - if ( !sb ) - return "Out of memory"; - std_buf = sb; - - BLARGG_RETURN_ERR( sb->sample_rate( sample_rate, 1000 / 20 ) ); - - buf = std_buf; - return blargg_success; + 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 ); // init() must have been called + require( buf ); // set_sample_rate() must have been called - mute_mask_ = mask; + Music_Emu::mute_voices( mask ); for ( int i = voice_count(); i--; ) { - if ( mask & (1 << i) ) { - set_voice( i, NULL ); + if ( mask & (1 << i) ) + { + set_voice( i, NULL, NULL, NULL ); } - else { + 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 clock_rate ) +blargg_err_t Classic_Emu::setup_buffer( long new_clock_rate ) { - require( buf ); // init() must have been called + require( sample_rate() ); // fails if set_sample_rate() hasn't been called yet + clock_rate = new_clock_rate; buf->clock_rate( clock_rate ); - update_eq_(); - return buf->set_channel_count( voice_count() ); + BLARGG_RETURN_ERR( buf->set_channel_count( voice_count() ) ); + set_equalizer( equalizer() ); + remute_voices(); + return blargg_success; } -void Classic_Emu::starting_track() +void Classic_Emu::start_track( int track ) { - require( buf ); // init() must have been called - - mute_voices( 0 ); + Music_Emu::start_track( track ); buf->clear(); } -blargg_err_t Classic_Emu::play( long count, sample_t* out ) +blip_time_t Classic_Emu::run_clocks( blip_time_t t, bool* ) { - require( buf ); // init() must have been called + 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 ) @@ -105,12 +109,9 @@ if ( remain ) { bool added_stereo = false; - blip_time_t cyc = run( buf->length(), &added_stereo ); - if ( !cyc ) - return "Emulation error"; - buf->end_frame( cyc, added_stereo ); + blip_time_t clocks_emulated = run( buf->length(), &added_stereo ); + buf->end_frame( clocks_emulated, added_stereo ); } } - return blargg_success; }
--- a/Plugins/Input/console/Classic_Emu.h Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Classic_Emu.h Tue Jan 24 19:10:07 2006 -0800 @@ -2,7 +2,7 @@ // Classic game music emulator interface base class for emulators which use Blip_Buffer // for sound output. -// Game_Music_Emu 0.2.4. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. +// Game_Music_Emu 0.3.0 #ifndef CLASSIC_EMU_H #define CLASSIC_EMU_H @@ -10,60 +10,39 @@ #include "Music_Emu.h" class Blip_Buffer; class blip_eq_t; -class Multi_Buffer; +typedef long blip_time_t; class Classic_Emu : public Music_Emu { public: Classic_Emu(); ~Classic_Emu(); - - // Initialize emulator with specified sample rate. Sample output is in stereo. - virtual blargg_err_t init( long sample_rate ); - - // Initialize emulator using custom output buffer - blargg_err_t init( Multi_Buffer* buf ); - - // Frequency equalizer parameters (see notes.txt) - struct equalizer_t { - double treble; // treble level at 22kHz, in dB (-3.0dB = 0.50) - long cutoff; // beginning of low-pass rolloff, in Hz - long bass; // high-pass breakpoint, in Hz - equalizer_t( double treble_ = 0, long cutoff_ = 0, int bass_ = 33 ) : - treble( treble_ ), cutoff( cutoff_ ), bass( bass_ ) { } - }; - - // Current frequency equalizater parameters - const equalizer_t& equalizer() const; - - // Set frequency equalizer parameters - void set_equalizer( const equalizer_t& ); - - // See Music_Emu.h + blargg_err_t set_sample_rate( long sample_rate ); + void set_buffer( Multi_Buffer* ); void mute_voices( int ); - blargg_err_t play( long, sample_t* ); - - -// End of public interface + 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 void starting_track(); virtual blargg_err_t setup_buffer( long clock_rate ); virtual void set_voice( int index, Blip_Buffer* center, - Blip_Buffer* left = NULL, Blip_Buffer* right = NULL ) = 0; - virtual long run( int msec, bool* added_stereo = NULL ) = 0; + 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* std_buf; // owned - equalizer_t equalizer_; - void update_eq_(); + Multi_Buffer* stereo_buffer; + long clock_rate; }; -inline blargg_err_t Classic_Emu::init( Multi_Buffer* buf_ ) { - buf = buf_; - return blargg_success; +inline void Classic_Emu::set_buffer( Multi_Buffer* new_buf ) +{ + assert( !buf && new_buf ); + buf = new_buf; } -inline const Classic_Emu::equalizer_t& Classic_Emu::equalizer() const { - return equalizer_; -} + #endif
--- a/Plugins/Input/console/Effects_Buffer.cpp Tue Jan 24 13:57:22 2006 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,466 +0,0 @@ - -// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ - -#include "Effects_Buffer.h" - -#include <string.h> - -/* Copyright (C) 2003-2005 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 - -typedef long fixed_t; - -#define TO_FIXED( f ) fixed_t ((f) * (1L << 15) + 0.5) -#define FMUL( x, y ) (((x) * (y)) >> 15) - -const unsigned echo_size = 4096; -const unsigned echo_mask = echo_size - 1; -BOOST_STATIC_ASSERT( (echo_size & echo_mask) == 0 ); // must be power of 2 - -const unsigned reverb_size = 8192 * 2; -const unsigned reverb_mask = reverb_size - 1; -BOOST_STATIC_ASSERT( (reverb_size & reverb_mask) == 0 ); // must be power of 2 - -Effects_Buffer::config_t::config_t() -{ - pan_1 = 0.0; - pan_2 = 0.0; - reverb_delay = 88; - reverb_level = 0.10; - echo_delay = 61; - echo_level = 0.12; - delay_variance = 18; - effects_enabled = false; -} - -Effects_Buffer::Effects_Buffer() -{ - echo_buf = NULL; - echo_pos = 0; - - reverb_buf = NULL; - reverb_pos = 0; - - stereo_remain = 0; - effect_remain = 0; - effects_enabled = false; - config( config_t() ); -} - -Effects_Buffer::~Effects_Buffer() -{ - delete [] echo_buf; - delete [] reverb_buf; -} - -blargg_err_t Effects_Buffer::sample_rate( long rate, int msec ) -{ - if ( !echo_buf ) - { - echo_buf = new blip_sample_t [echo_size]; - if ( !echo_buf ) - return "Out of memory"; - } - - if ( !reverb_buf ) - { - reverb_buf = new blip_sample_t [reverb_size]; - if ( !reverb_buf ) - return "Out of memory"; - } - - for ( int i = 0; i < buf_count; i++ ) - BLARGG_RETURN_ERR( bufs [i].sample_rate( rate, msec ) ); - - length_ = msec; - sample_rate_ = rate; - - config( config_ ); - - return blargg_success; -} - -void Effects_Buffer::clock_rate( long rate ) -{ - for ( int i = 0; i < buf_count; i++ ) - bufs [i].clock_rate( rate ); -} - -void Effects_Buffer::bass_freq( int freq ) -{ - for ( int i = 0; i < buf_count; i++ ) - bufs [i].bass_freq( freq ); -} - -void Effects_Buffer::clear() -{ - stereo_remain = 0; - effect_remain = 0; - memset( echo_buf, 0, echo_size * sizeof (blip_sample_t) ); - memset( reverb_buf, 0, reverb_size * sizeof (blip_sample_t) ); - for ( int i = 0; i < buf_count; i++ ) - bufs [i].clear(); -} - -inline int pin_range( int n, int max, int min = 0 ) -{ - if ( n < min ) - return min; - if ( n > max ) - return max; - return n; -} - -void Effects_Buffer::config( const config_t& cfg ) -{ - // clear echo and reverb buffers - if ( !config_.effects_enabled && cfg.effects_enabled && echo_buf ) { - memset( echo_buf, 0, echo_size * sizeof (blip_sample_t) ); - memset( reverb_buf, 0, reverb_size * sizeof (blip_sample_t) ); - } - - config_ = cfg; - - if ( config_.effects_enabled ) - { - // convert to internal format - - chans.pan_1_levels [0] = TO_FIXED( 1 ) - TO_FIXED( config_.pan_1 ); - chans.pan_1_levels [1] = TO_FIXED( 2 ) - chans.pan_1_levels [0]; - - chans.pan_2_levels [0] = TO_FIXED( 1 ) - TO_FIXED( config_.pan_2 ); - chans.pan_2_levels [1] = TO_FIXED( 2 ) - chans.pan_2_levels [0]; - - chans.reverb_level = TO_FIXED( config_.reverb_level ); - chans.echo_level = TO_FIXED( config_.echo_level ); - - const int delay_offset = (int) config_.delay_variance * sample_rate_ / (1000 * 2); - - const int reverb_sample_delay = (int) config_.reverb_delay * sample_rate_ / 1000; - chans.reverb_delay_l = pin_range( reverb_size - - (reverb_sample_delay - delay_offset) * 2, reverb_size - 2, 0 ); - chans.reverb_delay_r = pin_range( reverb_size + 1 - - (reverb_sample_delay + delay_offset) * 2, reverb_size - 1, 1 ); - - const int echo_sample_delay = (int) config_.echo_delay * sample_rate_ / 1000; - chans.echo_delay_l = pin_range( echo_size - 1 - (echo_sample_delay - delay_offset), - echo_size - 1 ); - chans.echo_delay_r = pin_range( echo_size - 1 - (echo_sample_delay + delay_offset), - echo_size - 1 ); - - // set up outputs - for ( unsigned i = 0; i < chan_count; i++ ) { - channel_t& o = channels [i]; - if ( i < 2 ) { - o.center = &bufs [i]; - o.left = &bufs [3]; - o.right = &bufs [4]; - } - else { - o.center = &bufs [2]; - o.left = &bufs [5]; - o.right = &bufs [6]; - } - } - - } - else { - // set up outputs - for ( unsigned i = 0; i < chan_count; i++ ) { - channel_t& o = channels [i]; - o.center = &bufs [0]; - o.left = &bufs [1]; - o.right = &bufs [2]; - } - } -} - -void Effects_Buffer::end_frame( blip_time_t clock_count, bool stereo ) -{ - for ( int i = 0; i < buf_count; i++ ) - bufs [i].end_frame( clock_count ); - - if ( stereo ) - stereo_remain = bufs [0].samples_avail() + bufs [0].output_latency(); - - if ( effects_enabled || config_.effects_enabled ) - effect_remain = bufs [0].samples_avail() + bufs [0].output_latency(); - - effects_enabled = config_.effects_enabled; -} - -#include BLARGG_ENABLE_OPTIMIZER - -long Effects_Buffer::read_samples( blip_sample_t* out, long total_samples ) -{ - require( total_samples % 2 == 0 ); // count must be even - - long remain = bufs [0].samples_avail(); - if ( remain > (unsigned) total_samples / 2 ) - remain = (unsigned) total_samples / 2; - total_samples = remain; - while ( remain ) - { - int active_bufs = buf_count; - long count = remain; - - if ( effect_remain ) { - if ( count > effect_remain ) - count = effect_remain; - - if ( stereo_remain ) { - mix_enhanced( out, count ); - } - else { - mix_mono_enhanced( out, count ); - active_bufs = 3; - } - } - else if ( stereo_remain ) { - mix_stereo( out, count ); - active_bufs = 3; - } - else { - mix_mono( out, count ); - active_bufs = 1; - } - - out += count * 2; - remain -= count; - - stereo_remain -= count; - if ( stereo_remain < 0 ) - stereo_remain = 0; - - effect_remain -= count; - if ( effect_remain < 0 ) - effect_remain = 0; - - for ( int i = 0; i < buf_count; i++ ) { - if ( i < active_bufs ) - bufs [i].remove_samples( count ); - else - bufs [i].remove_silence( count ); // keep time synchronized - } - } - - return total_samples * 2; -} - -void Effects_Buffer::mix_mono( blip_sample_t* out, long count ) -{ - Blip_Reader c; - int shift = c.begin( bufs [0] ); - - // unrolled loop - for ( long n = count >> 1; n--; ) - { - long cs0 = c.read(); - c.next( shift ); - - long cs1 = c.read(); - c.next( shift ); - - if ( (BOOST::int16_t) cs0 != cs0 ) - cs0 = 0x7FFF - (cs0 >> 24); - ((BOOST::uint32_t*) out) [0] = ((BOOST::uint16_t) cs0) | (cs0 << 16); - - if ( (BOOST::int16_t) cs1 != cs1 ) - cs1 = 0x7FFF - (cs1 >> 24); - ((BOOST::uint32_t*) out) [1] = ((BOOST::uint16_t) cs1) | (cs1 << 16); - out += 4; - } - - if ( count & 1 ) { - int s = c.read(); - c.next( shift ); - out [0] = s; - out [1] = s; - if ( (BOOST::int16_t) s != s ) { - s = 0x7FFF - (s >> 24); - out [0] = s; - out [1] = s; - } - } - - c.end( bufs [0] ); -} - -void Effects_Buffer::mix_stereo( blip_sample_t* out, long count ) -{ - Blip_Reader l; l.begin( bufs [1] ); - Blip_Reader r; r.begin( bufs [2] ); - Blip_Reader c; - int shift = c.begin( bufs [0] ); - - while ( count-- ) { - int cs = c.read(); - c.next( shift ); - int left = cs + l.read(); - int right = cs + r.read(); - l.next( shift ); - r.next( shift ); - - if ( (BOOST::int16_t) left != left ) - left = 0x7FFF - (left >> 24); - - out [0] = left; - out [1] = right; - - out += 2; - - if ( (BOOST::int16_t) right != right ) - out [-1] = 0x7FFF - (right >> 24); - } - - c.end( bufs [0] ); - r.end( bufs [2] ); - l.end( bufs [1] ); -} - -void Effects_Buffer::mix_mono_enhanced( blip_sample_t* out, long count ) -{ - Blip_Reader sq1; sq1.begin( bufs [0] ); - Blip_Reader sq2; sq2.begin( bufs [1] ); - Blip_Reader center; - int shift = center.begin( bufs [2] ); - - int echo_pos = this->echo_pos; - int reverb_pos = this->reverb_pos; - - while ( count-- ) - { - int sum1_s = sq1.read(); - int sum2_s = sq2.read(); - - sq1.next( shift ); - sq2.next( shift ); - - int new_reverb_l = FMUL( sum1_s, chans.pan_1_levels [0] ) + - FMUL( sum2_s, chans.pan_2_levels [0] ) + - reverb_buf [(reverb_pos + chans.reverb_delay_l) & reverb_mask]; - - int new_reverb_r = FMUL( sum1_s, chans.pan_1_levels [1] ) + - FMUL( sum2_s, chans.pan_2_levels [1] ) + - reverb_buf [(reverb_pos + chans.reverb_delay_r) & reverb_mask]; - - fixed_t reverb_level = chans.reverb_level; - reverb_buf [reverb_pos] = FMUL( new_reverb_l, reverb_level ); - reverb_buf [reverb_pos + 1] = FMUL( new_reverb_r, reverb_level ); - reverb_pos = (reverb_pos + 2) & reverb_mask; - - int sum3_s = center.read(); - center.next( shift ); - - int left = new_reverb_l + sum3_s + FMUL( chans.echo_level, - echo_buf [(echo_pos + chans.echo_delay_l) & echo_mask] ); - int right = new_reverb_r + sum3_s + FMUL( chans.echo_level, - echo_buf [(echo_pos + chans.echo_delay_r) & echo_mask] ); - - echo_buf [echo_pos] = sum3_s; - echo_pos = (echo_pos + 1) & echo_mask; - - if ( (BOOST::int16_t) left != left ) - left = 0x7FFF - (left >> 24); - - out [0] = left; - out [1] = right; - - out += 2; - - if ( (BOOST::int16_t) right != right ) - out [-1] = 0x7FFF - (right >> 24); - } - this->reverb_pos = reverb_pos; - this->echo_pos = echo_pos; - - sq1.end( bufs [0] ); - sq2.end( bufs [1] ); - center.end( bufs [2] ); -} - -void Effects_Buffer::mix_enhanced( blip_sample_t* out, long count ) -{ - Blip_Reader l1; l1.begin( bufs [3] ); - Blip_Reader r1; r1.begin( bufs [4] ); - Blip_Reader l2; l2.begin( bufs [5] ); - Blip_Reader r2; r2.begin( bufs [6] ); - Blip_Reader sq1; sq1.begin( bufs [0] ); - Blip_Reader sq2; sq2.begin( bufs [1] ); - Blip_Reader center; - int shift = center.begin( bufs [2] ); - - int echo_pos = this->echo_pos; - int reverb_pos = this->reverb_pos; - - while ( count-- ) - { - int sum1_s = sq1.read(); - int sum2_s = sq2.read(); - - sq1.next( shift ); - sq2.next( shift ); - - int new_reverb_l = FMUL( sum1_s, chans.pan_1_levels [0] ) + - FMUL( sum2_s, chans.pan_2_levels [0] ) + l1.read() + - reverb_buf [(reverb_pos + chans.reverb_delay_l) & reverb_mask]; - - int new_reverb_r = FMUL( sum1_s, chans.pan_1_levels [1] ) + - FMUL( sum2_s, chans.pan_2_levels [1] ) + r1.read() + - reverb_buf [(reverb_pos + chans.reverb_delay_r) & reverb_mask]; - - l1.next( shift ); - r1.next( shift ); - - fixed_t reverb_level = chans.reverb_level; - reverb_buf [reverb_pos] = FMUL( new_reverb_l, reverb_level ); - reverb_buf [reverb_pos + 1] = FMUL( new_reverb_r, reverb_level ); - reverb_pos = (reverb_pos + 2) & reverb_mask; - - int sum3_s = center.read(); - center.next( shift ); - - int left = new_reverb_l + sum3_s + l2.read() + FMUL( chans.echo_level, - echo_buf [(echo_pos + chans.echo_delay_l) & echo_mask] ); - int right = new_reverb_r + sum3_s + r2.read() + FMUL( chans.echo_level, - echo_buf [(echo_pos + chans.echo_delay_r) & echo_mask] ); - - l2.next( shift ); - r2.next( shift ); - - echo_buf [echo_pos] = sum3_s; - echo_pos = (echo_pos + 1) & echo_mask; - - if ( (BOOST::int16_t) left != left ) - left = 0x7FFF - (left >> 24); - - out [0] = left; - out [1] = right; - - out += 2; - - if ( (BOOST::int16_t) right != right ) - out [-1] = 0x7FFF - (right >> 24); - } - this->reverb_pos = reverb_pos; - this->echo_pos = echo_pos; - - sq1.end( bufs [0] ); - sq2.end( bufs [1] ); - center.end( bufs [2] ); - l1.end( bufs [3] ); - r1.end( bufs [4] ); - l2.end( bufs [5] ); - r2.end( bufs [6] ); -} -
--- a/Plugins/Input/console/Effects_Buffer.h Tue Jan 24 13:57:22 2006 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,91 +0,0 @@ - -// Multi-channel effects buffer with panning, echo and reverb effects - -// Game_Music_Emu 0.2.4. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. - -#ifndef EFFECTS_BUFFER_H -#define EFFECTS_BUFFER_H - -#include "Multi_Buffer.h" - -// Effects_Buffer uses several buffers and outputs stereo sample pairs. -class Effects_Buffer : public Multi_Buffer { -public: - Effects_Buffer(); - ~Effects_Buffer(); - - // Channel Effect Center Pan - // --------------------------------- - // 0 reverb pan_1 - // 1 reverb pan_2 - // 2 echo - - // 3 echo - - // 4 echo - - - // Channel configuration - struct config_t { - double pan_1; // -1.0 = left, 0.0 = center, 1.0 = right - double pan_2; - double echo_delay; // msec - double echo_level; // 0.0 to 1.0 - double reverb_delay; // msec - double delay_variance; // difference between left/right delays (msec) - double reverb_level; // 0.0 to 1.0 - bool effects_enabled; // if false, use optimized simple mixer - config_t(); - }; - - // Set configuration of buffer - void config( const config_t& ); - - // See Multi_Buffer.h - blargg_err_t sample_rate( long samples_per_sec, int msec ); - void clock_rate( long ); - void bass_freq( int ); - void clear(); - channel_t channel( int ); - void end_frame( blip_time_t, bool was_stereo = true ); - long read_samples( blip_sample_t*, long ); - - -// End of public interface -private: - typedef long fixed_t; - - enum { buf_count = 7 }; - Blip_Buffer bufs [buf_count]; - enum { chan_count = 5 }; - channel_t channels [chan_count]; - config_t config_; - long stereo_remain; - long effect_remain; - bool effects_enabled; - - blip_sample_t* reverb_buf; - blip_sample_t* echo_buf; - int reverb_pos; - int echo_pos; - - struct { - fixed_t pan_1_levels [2]; - fixed_t pan_2_levels [2]; - int echo_delay_l; - int echo_delay_r; - fixed_t echo_level; - int reverb_delay_l; - int reverb_delay_r; - fixed_t reverb_level; - } chans; - - void mix_mono( blip_sample_t*, long ); - void mix_stereo( blip_sample_t*, long ); - void mix_enhanced( blip_sample_t*, long ); - void mix_mono_enhanced( blip_sample_t*, long ); -}; - - inline Effects_Buffer::channel_t Effects_Buffer::channel( int i ) { - return channels [i % chan_count]; - } - -#endif -
--- a/Plugins/Input/console/Fir_Resampler.cpp Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Fir_Resampler.cpp Tue Jan 24 19:10:07 2006 -0800 @@ -1,14 +1,14 @@ -// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ #include "Fir_Resampler.h" -#include "libaudacious/vfs.h" #include <string.h> #include <stdlib.h> +#include <stdio.h> #include <math.h> -/* Copyright (C) 2003-2005 Shay Green. This module is free software; you +/* 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 @@ -21,15 +21,20 @@ #include BLARGG_SOURCE_BEGIN -static const double pi = 3.1415926535897932384626433832795029L; +// 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 ) { + Dsf( double r ) : rolloff( r ) + { factor = 1.0; //if ( rolloff < 1.0 ) // factor = 1.0 / (*this)( 0 ); @@ -40,7 +45,7 @@ double const n_harm = 256; angle /= n_harm; double pow_a_n = pow( rolloff, n_harm ); - double rescale = 1.0 / n_harm; + //double rescale = 1.0 / n_harm; double num = 1.0 - rolloff * cos( angle ) - pow_a_n * cos( n_harm * angle ) + @@ -51,8 +56,8 @@ } }; -template<class T,class Sinc> -void gen_sinc( int width, double offset, double spacing, int count, double scale, T* p, +template<class Sinc> +void gen_sinc( int width, double offset, double spacing, int count, double scale, short* p, const Sinc& sinc ) { double range = pi * (width / 2); @@ -66,68 +71,72 @@ double y = 0.0; if ( fabs( w ) < 1.0 ) { - y = cos( pi * w ) * 0.5 + 0.5; - y *= sinc( a ); + double window = cos( pi * w ) * 0.5 + 0.5; + y = sinc( a ) * window; } - *p++ = (T) (y * scale); + *p++ = (short) (y * scale); a += step; } } -static double plain_sinc( double a ) { +static double plain_sinc( double a ) +{ return fabs( a ) < 0.00001 ? 1.0 : sin( a ) / a; } -Fir_Resampler::Fir_Resampler() +// 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 = 2; - buf = NULL; - write_pos = NULL; - buf_size = 0; + step = stereo; + ratio_ = 1.0; } -Fir_Resampler::~Fir_Resampler() { - free( buf ); +Fir_Resampler_::~Fir_Resampler_() +{ } -void Fir_Resampler::clear() +void Fir_Resampler_::clear() { imp = 0; - if ( buf ) { - write_pos = buf + latency; - memset( buf, 0, (write_pos - buf) * sizeof *buf ); + 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_err_t Fir_Resampler_::buffer_size( int new_size ) { - new_size += latency; - void* new_buf = realloc( buf, new_size * sizeof *buf ); - if ( !new_buf ) - return "Out of memory"; - buf = (sample_t*) new_buf; - buf_size = new_size; + BLARGG_RETURN_ERR( buf.resize( new_size + write_offset ) ); clear(); return blargg_success; } -double Fir_Resampler::time_ratio( double ratio, double rolloff, double volume ) +double Fir_Resampler_::time_ratio( double new_factor, double rolloff, double gain ) { - this->ratio = ratio; + 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; + 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 ) { + if ( error < least_error ) + { res = r; fstep = nearest / res; least_error = error; @@ -137,137 +146,108 @@ skip_bits = 0; - step = 2 * (int) floor( fstep ); + step = stereo * (int) floor( fstep ); - ratio = fstep; + ratio_ = fstep; fstep = fmod( fstep, 1.0 ); - double filter = (ratio < 1.0) ? 1.0 : 1.0 / ratio; + 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 * volume * filter), impulses [i], dsf ); + 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] [j] ); + if ( show_impulse ) + { + for ( int j = 0; j < width_; j++ ) + printf( "%d ", (int) impulses [i * width_ + j] ); printf( "\n" ); } pos += fstep; - if ( pos >= 0.9999999 ) { + input_per_cycle += step; + if ( pos >= 0.9999999 ) + { pos -= 1.0; skip_bits |= 1 << i; + input_per_cycle++; } } - if ( show_impulse ) { - printf( "skip = %X\n", skip_bits ); + if ( show_impulse ) + { + printf( "skip = %8lX\n", (long) skip_bits ); printf( "step = %d\n", step ); } clear(); - return ratio; + 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; } -#include BLARGG_ENABLE_OPTIMIZER - -int Fir_Resampler::read( sample_t* out_begin, int count ) +int Fir_Resampler_::avail_( long input_count ) const { - sample_t* out = out_begin; - const sample_t* in = buf; - sample_t* end_pos = write_pos; + 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; - sample_t const* imp = impulses [this->imp]; - int remain = res - this->imp; - int const step = this->step; - - count = (count >> 1) + 1; - - // to do: optimize loop to use a single counter rather than 'in' and 'count' - - if ( end_pos - in >= width * 2 ) + int remain = res - imp; + while ( input_count >= 0 ) { - end_pos -= width * 2; - do + input_count -= step + (skip & 1) * stereo; + skip >>= 1; + if ( !--remain ) { - count--; - - // accumulate in extended precision - long l = 0; - long r = 0; - - const sample_t* i = in; - if ( !count ) - break; - - for ( int n = width / 2; n--; ) - { - int pt0 = imp [0]; - int pt1 = imp [1]; - imp += 2; - l += (pt0 * i [0]) + (pt1 * i [2]); - r += (pt0 * i [1]) + (pt1 * i [3]); - i += 4; - } - - remain--; - - l >>= 15; - r >>= 15; - - in += step + ((skip * 2) & 2); - skip >>= 1; - - if ( !remain ) { - imp = impulses [0]; - skip = skip_bits; - remain = res; - } - - out [0] = l; - out [1] = r; - out += 2; + skip = skip_bits; + remain = res; } - while ( in <= end_pos ); + output_count += 2; } - - this->imp = res - remain; - - int left = write_pos - in; - write_pos = buf + left; - assert( unsigned (write_pos - buf) <= buf_size ); - memmove( buf, in, left * sizeof *in ); - - return out - out_begin; + return output_count; } -int Fir_Resampler::skip_input( int count ) +int Fir_Resampler_::skip_input( long count ) { - int remain = write_pos - buf; - int avail = remain - width * 2; + int remain = write_pos - buf.begin(); + int avail = remain - width_ * stereo; if ( count > avail ) count = avail; remain -= count; - write_pos = buf + remain; - assert( unsigned (write_pos - buf) <= buf_size ); - memmove( buf, buf + count, remain * sizeof *buf ); + write_pos = &buf [remain]; + memmove( buf.begin(), &buf [count], remain * sizeof buf [0] ); return count; } -/* -int Fir_Resampler::skip( int count ) -{ - count = int (count * ratio) & ~1; - count = skip_input( count ); - return int (count / ratio) & ~1; -} -*/
--- a/Plugins/Input/console/Fir_Resampler.h Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Fir_Resampler.h Tue Jan 24 19:10:07 2006 -0800 @@ -1,76 +1,174 @@ -// Finite Impulse Response (FIR) Resampler +// Finite impulse response (FIR) resampler with adjustable FIR size -// Game_Music_Emu 0.2.4. Copyright (C) 2004 Shay Green. GNU LGPL license. +// Game_Music_Emu 0.3.0 #ifndef FIR_RESAMPLER_H #define FIR_RESAMPLER_H #include "blargg_common.h" +#include <string.h> -class Fir_Resampler { - enum { width = 24 }; - enum { latency = (width - 1) * 2 }; +class Fir_Resampler_ { public: + + // Use Fir_Resampler<width> (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; - Fir_Resampler(); - ~Fir_Resampler(); - - // interface hasn't been stabilized yet - - // Set size of buffer. Return true if out of memory. + // Resize and clear input buffer blargg_err_t buffer_size( int ); - // Set input/output resampling ratio and frequency rolloff. Return - // actual (rounded) ratio used. - double time_ratio( double ratio, double rolloff = 0.999, double volume = 1.0 ); - - // Remove any buffered samples and clear buffer + // Clear input buffer. At least two output samples will be available after + // two input samples are written. void clear(); - // Pointer to buffer to write input samples to + // 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; } - // Maximum number of samples that can be written to buffer at current position - int max_write() const { return buf_size - (write_pos - buf); } - - // Number of unread input samples - int written() const { return write_pos - buf - latency; } + // Notify resampler that 'count' input samples have been written + void write( long count ); - // Advance buffer position by 'count' samples. Call after writing 'count' samples - // to buffer(). - void write( int count ); + // Number of input samples in buffer + int written() const { return write_pos - &buf [write_offset]; } - // True if there are there aren't enough input samples to read at least one - // output sample. - bool empty() const { return (write_pos - buf) <= latency; } + // Skip 'count' input samples. Returns number of samples actually skipped. + int skip_input( long count ); + +// Output - // Resample and output at most 'count' into 'buf', then remove input samples from - // buffer. Return number of samples written into 'buf'. - int read( sample_t* buf, int count ); + // Number of extra input samples needed until 'count' output samples are available + int input_needed( long count ) const; - // Skip at most 'count' *input* samples. Return number of samples actually skipped. - int skip_input( int count ); + // Number of output samples available + int avail() const { return avail_( write_pos - &buf [width_ * stereo] ); } -private: +public: + ~Fir_Resampler_(); +protected: + enum { stereo = 2 }; enum { max_res = 32 }; - - sample_t* buf; + blargg_vector<sample_t> buf; sample_t* write_pos; - double ratio; - int buf_size; int res; int imp; + int const width_; + int const write_offset; unsigned long skip_bits; int step; - sample_t impulses [max_res] [width]; + 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<int width> +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 ); }; - inline void Fir_Resampler::write( int count ) { - write_pos += count; - assert( unsigned (write_pos - buf) <= buf_size ); +// End of public interface + +inline void Fir_Resampler_::write( long count ) +{ + write_pos += count; + assert( write_pos <= buf.end() ); +} + +template<int width> +int Fir_Resampler<width>::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
--- a/Plugins/Input/console/Gb_Apu.cpp Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Gb_Apu.cpp Tue Jan 24 19:10:07 2006 -0800 @@ -1,12 +1,11 @@ -// Gb_Snd_Emu 0.1.3. http://www.slack.net/~ant/libs/ +// Gb_Snd_Emu 0.1.4. http://www.slack.net/~ant/ #include "Gb_Apu.h" -#include "libaudacious/vfs.h" #include <string.h> -/* Copyright (C) 2003-2005 Shay Green. This module is free software; you +/* 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 @@ -19,17 +18,32 @@ #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; - square1.has_sweep = true; + 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(); } @@ -41,16 +55,18 @@ void Gb_Apu::treble_eq( const blip_eq_t& eq ) { square_synth.treble_eq( eq ); - wave.synth.treble_eq( eq ); - noise.synth.treble_eq( eq ); + other_synth.treble_eq( eq ); } -void Gb_Apu::volume( double vol ) +void Gb_Apu::osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) { - vol *= 0.60 / osc_count; - square_synth.volume( vol ); - wave.synth.volume( vol ); - noise.synth.volume( vol ); + 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 ) @@ -59,6 +75,28 @@ 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; @@ -70,31 +108,19 @@ square2.reset(); wave.reset(); noise.reset(); + noise.bits = 1; + wave.wave_pos = 0; - memset( regs, 0, sizeof regs ); + // 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 ); } -void Gb_Apu::osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) -{ - require( (unsigned) index < osc_count ); - - Gb_Osc& osc = *oscs [index]; - if ( center && !left && !right ) - { - // mono - left = center; - right = center; - } - else - { - // must be silenced or stereo - require( (!left && !right) || (left && right) ); - } - osc.outputs [1] = right; - osc.outputs [2] = left; - osc.outputs [3] = center; - osc.output = osc.outputs [osc.output_select]; -} +// to do: remove +static unsigned long abs_time; void Gb_Apu::run_until( gb_time_t end_time ) { @@ -109,12 +135,24 @@ time = end_time; // run oscillators - for ( int i = 0; i < osc_count; ++i ) { + for ( int i = 0; i < osc_count; ++i ) + { Gb_Osc& osc = *oscs [i]; - if ( osc.output ) { + 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; - osc.run( last_time, time ); + 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; @@ -131,7 +169,8 @@ noise.clock_length(); frame_count = (frame_count + 1) & 3; - if ( frame_count == 0 ) { + if ( frame_count == 0 ) + { // 64 Hz actions square1.clock_envelope(); square2.clock_envelope(); @@ -145,11 +184,16 @@ bool Gb_Apu::end_frame( gb_time_t end_time ) { - run_until( 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( next_frame_time >= 0 ); - last_time = 0; + + assert( last_time >= end_time ); + last_time -= end_time; bool result = stereo_found; stereo_found = false; @@ -158,9 +202,7 @@ void Gb_Apu::write_register( gb_time_t time, gb_addr_t addr, int data ) { - // function now takes actual address, i.e. 0xFFXX - require( start_addr <= addr && addr <= end_addr ); - require( (unsigned) data <= 0xff ); + require( (unsigned) data < 0x100 ); int reg = addr - start_addr; if ( (unsigned) reg >= register_count ) @@ -168,56 +210,98 @@ run_until( time ); + int old_reg = regs [reg]; regs [reg] = data; - if ( addr < 0xff24 ) + if ( addr < vol_reg ) + { + write_osc( reg / 5, reg, data ); + } + else if ( addr == vol_reg && data != old_reg ) // global volume { - // oscillator - int index = reg / 5; - oscs [index]->write_register( reg - index * 5, data ); + // 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 == 0xff26 ) + else if ( addr == 0xFF25 || addr == status_reg ) { - int flags = (regs [0xff26 - start_addr] & 0x80) ? regs [0xff25 - start_addr] : 0; + 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_select = (bits >> 3 & 2) | (bits & 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 ); + 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 ) + else if ( addr >= 0xFF30 ) { - // separate samples now (simplifies oscillator) - int index = (addr & 0x0f) * 2; + + int index = (addr & 0x0F) * 2; wave.wave [index] = data >> 4; - wave.wave [index + 1] = data & 0x0f; + wave.wave [index + 1] = data & 0x0F; } } int Gb_Apu::read_register( gb_time_t time, gb_addr_t addr ) { - // function now takes actual address, i.e. 0xFFXX - require( start_addr <= addr && addr <= end_addr ); - run_until( time ); - int data = regs [addr - start_addr]; + int index = addr - start_addr; + require( (unsigned) index < register_count ); + int data = regs [index]; - if ( addr == 0xff26 ) { // status - data &= 0xf0; - for ( int i = 0; i < osc_count; i++ ) { + 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.length_enabled) ) + if ( osc.enabled && (osc.length || !(osc.regs [4] & osc.len_enabled_mask)) ) data |= 1 << i; } }
--- a/Plugins/Input/console/Gb_Apu.h Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Gb_Apu.h Tue Jan 24 19:10:07 2006 -0800 @@ -1,62 +1,63 @@ // Nintendo Game Boy PAPU sound chip emulator -// Gb_Snd_Emu 0.1.3. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. +// Gb_Snd_Emu 0.1.4 #ifndef GB_APU_H #define GB_APU_H -typedef long gb_time_t; // clock cycle count +typedef long gb_time_t; // clock cycle count typedef unsigned gb_addr_t; // 16-bit address #include "Gb_Oscs.h" class Gb_Apu { public: - Gb_Apu(); - ~Gb_Apu(); - // Overall volume of all oscillators, where 1.0 is full volume. + // Set overall volume of all oscillators, where 1.0 is full volume void volume( double ); - // Treble equalization (see notes.txt). + // Set treble equalization void treble_eq( const blip_eq_t& ); - // Reset oscillators and internal state. - void reset(); + // 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, silence all oscillators. + // is NULL, silences all oscillators. void output( Blip_Buffer* mono ); void output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ); - // Assign oscillator output to buffer(s). Valid indicies are 0 to - // osc_count - 1, which refer to Square 1, Square 2, Wave, and - // Noise, respectively. If buffer is NULL, silence oscillator. + // 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 { start_addr = 0xFF10 }; + enum { end_addr = 0xFF3f }; enum { register_count = end_addr - start_addr + 1 }; - // Write 'data' to address at specified time. Previous writes and reads - // within the current frame must not have specified a later time. + // Write 'data' to address at specified time void write_register( gb_time_t, gb_addr_t, int data ); - // Read from address at specified time. Previous writes and reads within - // the current frame must not have specified a later time. + // 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. Return true if any oscillators added + // 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 ); - static void begin_debug_log(); +public: + Gb_Apu(); + ~Gb_Apu(); private: // noncopyable Gb_Apu( const Gb_Apu& ); @@ -65,6 +66,7 @@ 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; @@ -72,20 +74,24 @@ Gb_Square square2; Gb_Wave wave; Gb_Noise noise; - Gb_Square::Synth square_synth; // shared between squares 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 ); - friend class Gb_Apu_Reflector; + void write_osc( int index, int reg, int data ); }; - inline void Gb_Apu::output( Blip_Buffer* mono ) { - output( mono, NULL, NULL ); - } +inline void Gb_Apu::output( Blip_Buffer* b ) { output( b, b, b ); } - inline void Gb_Apu::osc_output( int index, Blip_Buffer* mono ) { - osc_output( index, mono, NULL, NULL ); - } +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
--- a/Plugins/Input/console/Gb_Cpu.cpp Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Gb_Cpu.cpp Tue Jan 24 19:10:07 2006 -0800 @@ -1,5 +1,5 @@ -// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ #include "Gb_Cpu.h" @@ -8,7 +8,7 @@ #include "blargg_endian.h" -/* Copyright (C) 2003-2005 Shay Green. This module is free software; you +/* 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 @@ -41,27 +41,25 @@ // 10898 CB 37 SWAP A // 10208 CB 66 BIT 4,(HL) -static void write_unmapped( Gbs_Emu*, gb_addr_t, int ) -{ - check( false ); -} +#if BLARGG_NONPORTABLE + #define PAGE_OFFSET( addr ) (addr) +#else + #define PAGE_OFFSET( addr ) ((addr) & (page_size - 1)) +#endif -static int read_unmapped( Gbs_Emu*, gb_addr_t ) +Gb_Cpu::Gb_Cpu( Gbs_Emu* gbs_emu ) { - check( false ); - return 0; -} - -Gb_Cpu::Gb_Cpu() -{ + callback_data = gbs_emu; rst_base = 0; - callback_data = NULL; - data_writer [page_count] = write_unmapped; - data_reader [page_count] = read_unmapped; reset(); } -void Gb_Cpu::reset( const void* unmapped_code_page ) +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; @@ -78,32 +76,33 @@ r.l = 0; for ( int i = 0; i < page_count + 1; i++ ) - code_map [i] = (const uint8_t*) unmapped_code_page; - - map_memory( 0, 0x10000, read_unmapped, write_unmapped ); + { + 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 ) { - // start end end must fall on page bounadries - require( start % page_size == 0 && size % page_size == 0 ); + // 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--; ) - code_map [first_page + i] = (uint8_t*) data + i * page_size; + 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 ) { - // start end end must fall on page bounadries - require( start % page_size == 0 && size % page_size == 0 ); + // address range must begin and end on page boundaries + require( start % page_size == 0 ); + require( size % page_size == 0 ); - if ( !read ) - read = read_unmapped; - if ( !write ) - write = write_unmapped; unsigned first_page = start / page_size; - for ( unsigned i = size / page_size; i--; ) { + for ( unsigned i = size / page_size; i--; ) + { data_reader [first_page + i] = read; data_writer [first_page + i] = write; } @@ -115,19 +114,22 @@ #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] [(addr) & (page_size - 1)]) +#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 ) { +int Gb_Cpu::read( gb_addr_t addr ) +{ return READ( addr ); } -BOOST::uint8_t* Gb_Cpu::get_code( gb_addr_t addr ) { - return (uint8_t*) &READ_PROG( addr ); +void Gb_Cpu::write( gb_addr_t addr, int data ) +{ + WRITE( addr, data ); } -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 @@ -143,6 +145,7 @@ { const int cycles_per_instruction = 4; + // to do: use cycle table remain_ = cycle_count + cycles_per_instruction; Gb_Cpu::result_t result = result_cycles; @@ -161,12 +164,14 @@ 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." +# 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 }; @@ -183,53 +188,41 @@ unsigned sp = r.sp; unsigned flags = r.flags; - goto loop; - - unsigned data; +loop: -jr_taken: - pc += (BOOST::int8_t) data; -inc_pc_loop: - pc++; -loop: + int new_remain = remain_ - cycles_per_instruction; check( (unsigned) pc < 0x10000 ); check( (unsigned) sp < 0x10000 ); check( (flags & ~0xf0) == 0 ); - // Read opcode and first operand. Optimize if processor's byte order is known - // and non-portable constructs are allowed. -#if BLARGG_NONPORTABLE && BLARGG_BIG_ENDIAN - data = *(BOOST::uint16_t*) &READ_PROG( pc ); - pc++; - unsigned op = data >> 8; - data = (uint8_t) data; - -#elif BLARGG_NONPORTABLE && BLARGG_LITTLE_ENDIAN - data = *(BOOST::uint16_t*) &READ_PROG( pc ); + 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++; - unsigned op = (uint8_t) data; - data >>= 8; - -#else - unsigned op = READ_PROG( pc ); - pc++; - data = READ_PROG( pc ); -#endif - - if ( (remain_ -= cycles_per_instruction) <= 0 ) + 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 - if ( !(flags & z_flag) ) - goto jr_taken; - goto inc_pc_loop; + BRANCH( !(flags & z_flag) ) case 0x21: // LD HL,IMM (common) rp.hl = READ_PROG16( pc ); @@ -237,9 +230,7 @@ goto loop; case 0x28: // JR Z - if ( flags & z_flag ) - goto jr_taken; - goto inc_pc_loop; + BRANCH( flags & z_flag ) { unsigned temp; @@ -326,9 +317,9 @@ data = pc + 2; pc = READ_PROG16( pc ); push: - sp--; + sp = (sp - 1) & 0xFFFF; WRITE( sp, data >> 8 ); - sp--; + sp = (sp - 1) & 0xFFFF; WRITE( sp, data & 0xff ); goto loop; @@ -338,8 +329,8 @@ case 0xC9: // RET (most common) ret: pc = READ( sp ); - pc += 0x100 * READ( sp + 1 ); - sp += 2; + pc += 0x100 * READ( (sp + 1) & 0xFFFF ); + sp = (sp + 2) & 0xFFFF; goto loop; case 0x00: // NOP @@ -628,35 +619,43 @@ case 0x06: // LD B,IMM rg.b = data; - goto inc_pc_loop; + pc++; + goto loop; case 0x0E: // LD C,IMM rg.c = data; - goto inc_pc_loop; + pc++; + goto loop; case 0x16: // LD D,IMM rg.d = data; - goto inc_pc_loop; + pc++; + goto loop; case 0x1E: // LD E,IMM rg.e = data; - goto inc_pc_loop; + pc++; + goto loop; case 0x26: // LD H,IMM rg.h = data; - goto inc_pc_loop; + pc++; + goto loop; case 0x2E: // LD L,IMM rg.l = data; - goto inc_pc_loop; + pc++; + goto loop; case 0x36: // LD (HL),IMM WRITE( rp.hl, data ); - goto inc_pc_loop; + pc++; + goto loop; case 0x3E: // LD A,IMM rg.a = data; - goto inc_pc_loop; + pc++; + goto loop; // Increment/Decrement @@ -667,7 +666,7 @@ goto loop; case 0x33: // INC SP - sp++; + sp = (sp + 1) & 0xFFFF; goto loop; case 0x0B: // DEC BC @@ -677,7 +676,7 @@ goto loop; case 0x3B: // DEC SP - sp--; + sp = (sp - 1) & 0xFFFF; goto loop; case 0x34: // INC (HL) @@ -731,7 +730,7 @@ unsigned prev; case 0xF8: // LD HL,SP+imm - temp = BOOST::int8_t (data) & 0xffff; // sign-extend to 16 bits + temp = BOOST::int8_t (data); // sign-extend to 16 bits pc++; flags = 0; temp += sp; @@ -739,12 +738,12 @@ goto add_16_hl; case 0xE8: // ADD SP,IMM - temp = BOOST::int8_t (data) & 0xffff; // sign-extend to 16 bits + temp = BOOST::int8_t (data); // sign-extend to 16 bits pc++; flags = 0; temp += sp; prev = sp; - sp = temp; + sp = temp & 0xffff; goto add_16_comm; case 0x39: // ADD HL,SP @@ -934,15 +933,15 @@ case 0xD1: // POP DE case 0xE1:{// POP HL (common) int temp = READ( sp ); - r16 [(op >> 4) & 3] = temp + 0x100 * READ( sp + 1 ); - sp += 2; + 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 ) & 0xf0; - sp += 2; + flags = READ( (sp + 1) & 0xFFFF ) & 0xf0; + sp = (sp + 2) & 0xFFFF; goto loop; case 0xC5: // PUSH BC @@ -1007,17 +1006,13 @@ goto loop; case 0x18: // JR - goto jr_taken; + BRANCH( true ) case 0x30: // JR NC - if ( !(flags & c_flag) ) - goto jr_taken; - goto inc_pc_loop; + BRANCH( !(flags & c_flag) ) case 0x38: // JR C - if ( flags & c_flag ) - goto jr_taken; - goto inc_pc_loop; + BRANCH( flags & c_flag ) case 0xE9: // JP_HL pc = rp.hl; @@ -1093,7 +1088,8 @@ goto stop; } - assert( false ); // all opcodes should end with a goto + // If this fails then the case above is missing an opcode + assert( false ); stop: pc--;
--- a/Plugins/Input/console/Gb_Cpu.h Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Gb_Cpu.h Tue Jan 24 19:10:07 2006 -0800 @@ -1,14 +1,14 @@ // Nintendo Game Boy CPU emulator -// Game_Music_Emu 0.2.4. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. +// 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 address +typedef unsigned gb_addr_t; // 16-bit CPU address class Gbs_Emu; @@ -17,19 +17,19 @@ typedef BOOST::uint8_t uint8_t; enum { page_bits = 8 }; enum { page_count = 0x10000 >> page_bits }; - const uint8_t* code_map [page_count + 1]; + uint8_t const* code_map [page_count + 1]; long remain_; + Gbs_Emu* callback_data; public: - Gb_Cpu(); + + Gb_Cpu( Gbs_Emu* ); - // Set all registers to 0, unmap all memory, and map all code pages - // to unmapped_page. - void reset( const void* unmapped_page = NULL ); + // 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 ); - // Memory read/write function types. Memory reader return value must be 0 to 255. - Gbs_Emu* callback_data; // passed to memory read/write functions - typedef int (*reader_t)( Gbs_Emu* callback_data, gb_addr_t ); - typedef void (*writer_t)( Gbs_Emu* callback_data, gb_addr_t, int ); + // 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. @@ -44,11 +44,14 @@ // 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 ); // for use in a debugger + 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 { - BOOST::uint16_t pc; + long pc; // more than 16 bits to allow overflow detection BOOST::uint16_t sp; uint8_t flags; uint8_t a; @@ -58,12 +61,13 @@ uint8_t e; uint8_t h; uint8_t l; - } r; + }; + registers_t r; - // Interrupt enable flag set by EI and cleared by DI. + // Interrupt enable flag set by EI and cleared by DI bool interrupts_enabled; - // Base address for RST vectors (normally 0). + // Base address for RST vectors (normally 0) gb_addr_t rst_base; // Reasons that run() returns @@ -74,10 +78,10 @@ }; // Run CPU for at least 'count' cycles, or until one of the above conditions - // arises. Return reason for stopping. + // arises. Returns reason for stopping. result_t run( long count ); - // Number of clock cycles remaining for current run() call. + // Number of clock cycles remaining for most recent run() call long remain() const; private: @@ -85,13 +89,15 @@ Gb_Cpu( const Gb_Cpu& ); Gb_Cpu& operator = ( const Gb_Cpu& ); - reader_t data_reader [page_count + 1]; // extra entry to catch overflow addresses + 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_; - } +inline long Gb_Cpu::remain() const +{ + return remain_; +} #endif
--- a/Plugins/Input/console/Gb_Oscs.cpp Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Gb_Oscs.cpp Tue Jan 24 19:10:07 2006 -0800 @@ -1,11 +1,11 @@ -// Gb_Snd_Emu 0.1.3. http://www.slack.net/~ant/libs/ +// Gb_Snd_Emu 0.1.4. http://www.slack.net/~ant/ #include "Gb_Apu.h" #include <string.h> -/* Copyright (C) 2003-2005 Shay Green. This module is free software; you +/* 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 @@ -18,387 +18,316 @@ #include BLARGG_SOURCE_BEGIN -const int trigger = 0x80; - // Gb_Osc -Gb_Osc::Gb_Osc() -{ - output = NULL; - outputs [0] = NULL; - outputs [1] = NULL; - outputs [2] = NULL; - outputs [3] = NULL; -} - void Gb_Osc::reset() { delay = 0; last_amp = 0; - period = 2048; - volume = 0; - frequency = 0; length = 0; - enabled = false; - length_enabled = false; output_select = 3; output = outputs [output_select]; } void Gb_Osc::clock_length() { - if ( length_enabled && length ) - --length; -} - -void Gb_Osc::write_register( int reg, int value ) -{ - if ( reg == 4 ) - length_enabled = value & 0x40; + if ( (regs [4] & len_enabled_mask) && length ) + length--; } // Gb_Env -void Gb_Env::reset() -{ - env_period = 0; - env_dir = 0; - env_delay = 0; - new_env_period = 0; - new_env_dir = 0; - new_volume = 0; - Gb_Osc::reset(); -} - -Gb_Env::Gb_Env() { -} - void Gb_Env::clock_envelope() { - if ( env_delay && !--env_delay ) { - env_delay = env_period; - if ( env_dir ) { - if ( volume < 15 ) - ++volume; - } - else if ( volume > 0 ) { - --volume; - } + if ( env_delay && !--env_delay ) + { + env_delay = regs [2] & 7; + int v = volume - 1 + (regs [2] >> 2 & 2); + if ( (unsigned) v < 15 ) + volume = v; } } -void Gb_Env::write_register( int reg, int value ) +bool Gb_Env::write_register( int reg, int data ) { - if ( reg == 2 ) { - new_env_period = value & 7; - new_env_dir = value & 8; - new_volume = value >> 4; - if ( value == 0 && volume ) - // to do: find correct behavior - volume = 0; - //enabled = new_volume != 0; - enabled = true; + 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; + } } - else if ( reg == 4 && (value & trigger) ) { - env_period = new_env_period; - env_delay = new_env_period; - env_dir = new_env_dir; - volume = new_volume; - } - Gb_Osc::write_register( reg, value ); + return false; } // Gb_Square void Gb_Square::reset() { - phase = 1; - duty = 1; - - sweep_period = 0; - sweep_delay = 0; - sweep_shift = 0; - sweep_dir = 0; + phase = 0; sweep_freq = 0; - + sweep_delay = 0; Gb_Env::reset(); } -Gb_Square::Gb_Square() -{ - has_sweep = false; -} - void Gb_Square::clock_sweep() { - if ( sweep_period && sweep_delay && !--sweep_delay ) { + int sweep_period = (regs [0] & period_mask) >> 4; + if ( sweep_period && sweep_delay && !--sweep_delay ) + { sweep_delay = sweep_period; - frequency = sweep_freq; - period = (2048 - frequency) * 4; + regs [3] = sweep_freq & 0xFF; + regs [4] = (regs [4] & ~0x07) | (sweep_freq >> 8 & 0x07); - int offset = sweep_freq >> sweep_shift; - if ( sweep_dir ) + int offset = sweep_freq >> (regs [0] & shift_mask); + if ( regs [0] & 0x08 ) offset = -offset; sweep_freq += offset; - if ( sweep_freq < 0 || sweep_freq >= 2048 ) { - sweep_delay = 0; - sweep_freq = 2048; // stop sound output + + 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::write_register( int reg, int value ) +void Gb_Square::run( gb_time_t time, gb_time_t end_time, int playing ) { - switch ( reg ) { - case 0: - sweep_period = (value >> 4) & 3; - sweep_shift = value & 7; - sweep_dir = value & 0x08; - break; - - case 1: - length = 64 - (value & 0x3f); - duty = (value >> 5) & 6; // duty = { 1, 2, 4, 6 } - if ( !duty ) - duty = 1; - break; - - case 3: - frequency = (frequency & ~0xFF) + value; - break; - - case 4: - frequency = (value & 7) * 0x100 + (frequency & 0xFF); - if ( value & trigger ) { - sweep_freq = frequency; - if ( has_sweep && sweep_period && sweep_shift ) { - sweep_delay = 1; - clock_sweep(); - sweep_delay = sweep_period; - } - enabled = true; - } - break; + 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 ); } - period = (2048 - frequency) * 4; + time += delay; + if ( !playing ) + time = end_time; - Gb_Env::write_register( reg, value ); + 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; } -void Gb_Square::run( gb_time_t time, gb_time_t end_time ) +// Gb_Noise + +#include BLARGG_ENABLE_OPTIMIZER + +void Gb_Noise::run( gb_time_t time, gb_time_t end_time, int playing ) { - if ( !enabled || (!length && length_enabled) || !volume || sweep_freq == 2048 ) { - if ( last_amp ) { - synth->offset( time, -last_amp, output ); - last_amp = 0; - } - delay = 0; + 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 ); } - else + + time += delay; + if ( !playing ) + time = end_time; + + if ( time < end_time ) { - int amp = (phase < duty) ? volume : -volume; - if ( amp != last_amp ) { - synth->offset( time, amp - last_amp, output ); - last_amp = amp; - } + static unsigned char const table [8] = { 8, 16, 32, 48, 64, 80, 96, 112 }; + int period = table [regs [3] & 7] << (regs [3] >> 4); - time += delay; - if ( time < end_time ) + // 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 { - Blip_Buffer* const output = this->output; - const int duty = this->duty; - int phase = this->phase; - amp *= 2; - do { - phase = (phase + 1) & 7; - if ( phase == 0 || phase == duty ) { - amp = -amp; - synth->offset_inline( time, amp, output ); - } - time += period; + unsigned changed = (bits >> tap) + 1; + time += period; + bits <<= 1; + if ( changed & 2 ) + { + delta = -delta; + bits |= 1; + synth->offset_resampled( resampled_time, delta, output ); } - while ( time < end_time ); - - this->phase = phase; - last_amp = amp >> 1; + resampled_time += resampled_period; } - delay = time - end_time; + while ( time < end_time ); + + this->bits = bits; + last_amp = delta >> 1; } + delay = time - end_time; } - // Gb_Wave -void Gb_Wave::reset() -{ - volume_shift = 0; - wave_pos = 0; - memset( wave, 0, sizeof wave ); - Gb_Osc::reset(); -} - -Gb_Wave::Gb_Wave() { -} - -void Gb_Wave::write_register( int reg, int value ) +inline void Gb_Wave::write_register( int reg, int data ) { - switch ( reg ) { - case 0: - enabled = value & 0x80; - break; - - case 1: - length = 256 - value; - break; - - case 2: - volume = ((value >> 5) & 3); - volume_shift = (volume - 1) & 7; // silence = 7 - break; - - case 3: - frequency = (frequency & ~0xFF) + value; - break; - - case 4: - frequency = (value & 7) * 0x100 + (frequency & 0xFF); - //if ( value & trigger ) - // wave_pos = 0; - break; - - } + switch ( reg ) + { + case 0: + if ( !(data & 0x80) ) + enabled = false; + break; - period = (2048 - frequency) * 2; + case 1: + length = 256 - regs [1]; + break; - Gb_Osc::write_register( reg, value ); -} - -void Gb_Wave::run( gb_time_t time, gb_time_t end_time ) -{ - if ( !enabled || (!length && length_enabled) || !volume ) { - if ( last_amp ) { - synth.offset( time, -last_amp, output ); - last_amp = 0; - } - delay = 0; - } - else - { - // wave data or shift may have changed - int diff = (wave [wave_pos] >> volume_shift) * 2 - last_amp; - if ( diff ) { - last_amp += diff; - synth.offset( time, diff, output ); + case 2: + volume = data >> 5 & 3; + break; + + case 4: + if ( data & trigger & regs [0] ) + { + wave_pos = 0; + enabled = true; + if ( length == 0 ) + length = 256; } - - time += delay; - if ( time < end_time ) - { - unsigned wave_pos = this->wave_pos; - - do { - wave_pos = (wave_pos + 1) % wave_size; - int amp = (wave [wave_pos] >> volume_shift) * 2; - int diff = amp - last_amp; - if ( diff ) { - last_amp = amp; - synth.offset_inline( time, diff, output ); - } - time += period; - } - while ( time < end_time ); - - this->wave_pos = wave_pos; - } - delay = time - end_time; } } - -// Gb_Noise - -void Gb_Noise::reset() +void Gb_Wave::run( gb_time_t time, gb_time_t end_time, int playing ) { - bits = 1; - tap = 14; - Gb_Env::reset(); -} - -Gb_Noise::Gb_Noise() { -} - -void Gb_Noise::write_register( int reg, int value ) -{ - if ( reg == 1 ) { - length = 64 - (value & 0x3f); + 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; } - else if ( reg == 3 ) { - tap = 14 - (value & 8); - // noise formula and frequency tested against Metroid 2 and Zelda LA - int divisor = (value & 7) * 16; - if ( !divisor ) - divisor = 8; - period = divisor << (value >> 4); - } - else if ( reg == 4 && value & trigger ) { - bits = ~0u; + + int delta = amp - last_amp; + if ( delta ) + { + last_amp = amp; + synth->offset( time, delta, output ); } - Gb_Env::write_register( reg, value ); + 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; } -#include BLARGG_ENABLE_OPTIMIZER +// Gb_Apu::write_osc -void Gb_Noise::run( gb_time_t time, gb_time_t end_time ) +void Gb_Apu::write_osc( int index, int reg, int data ) { - if ( !enabled || (!length && length_enabled) || !volume ) { - if ( last_amp ) { - synth.offset( time, -last_amp, output ); - last_amp = 0; - } - delay = 0; - } - else + reg -= index * 5; + Gb_Square* sq = &square2; + switch ( index ) { - int amp = bits & 1 ? -volume : volume; - if ( amp != last_amp ) { - synth.offset( time, amp - last_amp, output ); - last_amp = amp; - } - - time += delay; - if ( time < end_time ) + case 0: + sq = &square1; + case 1: + if ( sq->write_register( reg, data ) && index == 0 ) { - Blip_Buffer* const output = this->output; - // keep parallel resampled time to eliminate multiplication in the loop - const Blip_Buffer::resampled_time_t resampled_period = - output->resampled_duration( period ); - Blip_Buffer::resampled_time_t resampled_time = output->resampled_time( time ); - const unsigned mask = ~(1u << tap); - unsigned bits = this->bits; - amp *= 2; - - do { - unsigned feedback = bits; - bits >>= 1; - feedback = 1 & (feedback ^ bits); - time += period; - bits = (feedback << tap) | (bits & mask); - // feedback just happens to be true only when the level needs to change - // (the previous and current bits are different) - if ( feedback ) { - amp = -amp; - synth.offset_resampled( resampled_time, amp, output ); - } - resampled_time += resampled_period; + 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(); } - while ( time < end_time ); - - this->bits = bits; - last_amp = amp >> 1; } - delay = time - end_time; + break; + + case 2: + wave.write_register( reg, data ); + break; + + case 3: + if ( noise.write_register( reg, data ) ) + noise.bits = 0x7FFF; } }
--- a/Plugins/Input/console/Gb_Oscs.h Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Gb_Oscs.h Tue Jan 24 19:10:07 2006 -0800 @@ -1,97 +1,86 @@ // Private oscillators used by Gb_Apu -// Gb_Snd_Emu 0.1.3. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. +// 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 { +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 period; int volume; - int frequency; int length; bool enabled; - bool length_enabled; - Gb_Osc(); - + void reset(); void clock_length(); - void reset(); - virtual void run( gb_time_t begin, gb_time_t end ) = 0; - virtual void write_register( int reg, int value ); + int frequency() const { return (regs [4] & 7) * 0x100 + regs [3]; } }; -struct Gb_Env : Gb_Osc { - int env_period; - int env_dir; +struct Gb_Env : Gb_Osc +{ int env_delay; - int new_env_period; - int new_env_dir; - int new_volume; - Gb_Env(); void reset(); void clock_envelope(); - void write_register( int, int ); + bool write_register( int, int ); }; -struct Gb_Square : Gb_Env { - int phase; - int duty; +struct Gb_Square : Gb_Env +{ + enum { period_mask = 0x70 }; + enum { shift_mask = 0x07 }; - int sweep_period; + typedef Blip_Synth<blip_good_quality,1> Synth; + Synth const* synth; int sweep_delay; - int sweep_shift; - int sweep_dir; int sweep_freq; - bool has_sweep; - - typedef Blip_Synth<blip_good_quality,15 * 2> Synth; - const Synth* synth; + int phase; - Gb_Square(); void reset(); - void run( gb_time_t, gb_time_t ); - void write_register( int, int ); void clock_sweep(); + void run( gb_time_t, gb_time_t, int playing ); }; -struct Gb_Wave : Gb_Osc { - int volume_shift; - unsigned wave_pos; +struct Gb_Noise : Gb_Env +{ + typedef Blip_Synth<blip_med_quality,1> Synth; + Synth const* synth; + unsigned bits; + + void run( gb_time_t, gb_time_t, int playing ); +}; + +struct Gb_Wave : Gb_Osc +{ + typedef Blip_Synth<blip_med_quality,1> Synth; + Synth const* synth; + int wave_pos; enum { wave_size = 32 }; BOOST::uint8_t wave [wave_size]; - typedef Blip_Synth<blip_med_quality,15 * 2> Synth; - Synth synth; - - Gb_Wave(); - void reset(); - void run( gb_time_t, gb_time_t ); void write_register( int, int ); + void run( gb_time_t, gb_time_t, int playing ); }; -struct Gb_Noise : Gb_Env { - unsigned bits; - int tap; - - typedef Blip_Synth<blip_med_quality,15 * 2> Synth; - Synth synth; - - Gb_Noise(); - void reset(); - void run( gb_time_t, gb_time_t ); - void write_register( int, int ); -}; +inline void Gb_Env::reset() +{ + env_delay = 0; + Gb_Osc::reset(); +} #endif
--- a/Plugins/Input/console/Gbs_Emu.cpp Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Gbs_Emu.cpp Tue Jan 24 19:10:07 2006 -0800 @@ -1,5 +1,5 @@ -// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ #include "Gbs_Emu.h" @@ -7,7 +7,7 @@ #include "blargg_endian.h" -/* Copyright (C) 2003-2005 Shay Green. This module is free software; you +/* 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 @@ -20,12 +20,18 @@ #include BLARGG_SOURCE_BEGIN -const long clock_rate = 4194304; +#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 = 0x9eff; +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 ) @@ -43,7 +49,7 @@ 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 (probably due to pull-up resistors) + return 0xFF; // open bus value } void Gbs_Emu::write_unmapped( Gbs_Emu*, gb_addr_t addr, int ) @@ -65,12 +71,18 @@ void Gbs_Emu::set_bank( int n ) { - if ( n >= bank_count ) { + if ( n >= bank_count ) + { n = 0; dprintf( "Set to non-existent bank %d\n", (int) n ); } if ( n == 0 && bank_count > 1 ) - dprintf( "Selected ROM bank 0\n" ); + { + // 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 ); } @@ -78,7 +90,7 @@ void Gbs_Emu::write_rom( Gbs_Emu* emu, gb_addr_t addr, int data ) { if ( unsigned (addr - 0x2000) < 0x2000 ) - emu->set_bank( data & 0x1f ); + emu->set_bank( data & 0x1F ); } // I/O: Timer, APU @@ -86,7 +98,10 @@ void Gbs_Emu::set_timer( int modulo, int rate ) { if ( timer_mode ) - play_period = gb_time_t (256 - modulo) << (((rate - 1) & 3) * 2 + 4 - double_speed); + { + 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 @@ -97,71 +112,67 @@ 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 ( 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 ) + if ( addr == 0xFF00 ) return 0; // joypad - dprintf( "Unhandled I/O read 0x%4x\n", (unsigned) addr ); + dprintf( "Unhandled I/O read 0x%4X\n", (unsigned) addr ); - return 0xff; + 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 ) { + 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; + else + { + emu->hi_page [addr & 0xFF] = data; - if ( addr == 0xff06 || addr == 0xff07 ) + if ( addr == 0xFF06 || addr == 0xFF07 ) emu->set_timer( emu->hi_page [6], emu->hi_page [7] ); - if ( addr == 0xffff ) - dprintf( "Wrote interrupt mask\n" ); + //if ( addr == 0xFFFF ) + // dprintf( "Wrote interrupt mask\n" ); } } -Gbs_Emu::Gbs_Emu( double gain ) +Gbs_Emu::Gbs_Emu( double gain ) : cpu( this ) { - rom = NULL; - apu.volume( gain ); - // to do: decide on equalization parameters - set_equalizer( equalizer_t( -32, 8000, 90 ) ); + 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.callback_data = this; - cpu.reset( unmapped_code ); + 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( 0x8000, 0x8000, read_unmapped, write_unmapped ); 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 ); + cpu.map_code( 0xFF00, 0x0100, hi_page ); + cpu.map_memory( 0xFF00, 0x0100, read_io, write_io ); } Gbs_Emu::~Gbs_Emu() { - unload(); } void Gbs_Emu::unload() { - delete [] rom; - rom = NULL; cpu.r.pc = halt_addr; + rom.clear(); } void Gbs_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) @@ -174,31 +185,39 @@ apu.treble_eq( eq ); } -blargg_err_t Gbs_Emu::load( const header_t& h, Emu_Reader& in ) +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( h.tag, "GBS", 3 ) ) + if ( 0 != memcmp( header_.tag, "GBS", 3 ) ) return "Not a GBS file"; - if ( h.vers != 1 ) + if ( header_.vers != 1 ) return "Unsupported GBS format"; // gather relevant fields - load_addr = get_le16( h.load_addr ); - init_addr = get_le16( h.init_addr ); - play_addr = get_le16( h.play_addr ); - stack_ptr = get_le16( h.stack_ptr ); - double_speed = (h.timer_mode & 0x80) != 0; - timer_modulo_init = h.timer_modulo; - timer_mode = h.timer_mode; + 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 ( h.timer_mode & 0x78 ) - dprintf( "TAC field has extra bits set: 0x%02x\n", (unsigned) h.timer_mode ); + 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 || @@ -209,25 +228,23 @@ // rom bank_count = (load_addr + in.remain() + bank_size - 1) / bank_size; - long rom_size = bank_count * bank_size; - rom = new BOOST::uint8_t [rom_size]; - if ( !rom ) - return "Out of memory"; - memset( rom, 0, rom_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 ) { + if ( err ) + { unload(); return err; } // cpu cpu.rst_base = load_addr; - cpu.map_code( 0x0000, 0x4000, rom ); + cpu.map_code( 0x0000, 0x4000, rom.begin() ); - voice_count_ = Gb_Apu::osc_count; - track_count_ = h.track_count; + set_voice_count( Gb_Apu::osc_count ); + set_track_count( header_.track_count ); - return setup_buffer( clock_rate ); + return setup_buffer( 4194304 ); } const char** Gbs_Emu::voice_names() const @@ -239,32 +256,28 @@ // 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 - + 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 + 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.write( --cpu.r.sp, cpu.r.pc&0xFF ); cpu.r.pc = addr; } -blargg_err_t Gbs_Emu::start_track( int track_index ) +void Gbs_Emu::start_track( int track_index ) { - require( rom ); // file must be loaded - require( (unsigned) track_index < track_count() ); + require( rom.size() ); // file must be loaded - starting_track(); + Classic_Emu::start_track( track_index ); apu.reset(); @@ -273,7 +286,7 @@ // configure hardware set_bank( bank_count > 1 ); - for ( int i = 0; i < sizeof sound_data; i++ ) + 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 @@ -291,22 +304,20 @@ cpu.r.pc = halt_addr; cpu.r.sp = stack_ptr; cpu_jsr( init_addr ); - - return blargg_success; } -blip_time_t Gbs_Emu::run( int msec, bool* added_stereo ) +blip_time_t Gbs_Emu::run_clocks( blip_time_t duration, bool* added_stereo ) { - require( rom ); // file must be loaded + require( rom.size() ); // file must be loaded - gb_time_t duration = (gb_time_t) (clock_rate * (1.0 / 1000.0) * msec); cpu_time = 0; while ( cpu_time < duration ) { // check for idle cpu if ( cpu.r.pc == halt_addr ) { - if ( next_play > duration ) { + if ( next_play > duration ) + { cpu_time = duration; break; } @@ -319,21 +330,24 @@ long count = duration - cpu_time; cpu_time = duration; - Gb_Cpu::result_t result = cpu.run( count ); + 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 ( result == Gb_Cpu::result_halt && cpu.r.pc < cpu.page_size ) + 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 ); - return 0; // error + cpu.r.pc = (cpu.r.pc + 1) & 0xFFFF; + cpu_time += 6; } } } @@ -350,25 +364,3 @@ return cpu_time; } -Gbs_Reader::Gbs_Reader() : file( NULL ) { -} - -Gbs_Reader::~Gbs_Reader() { - close(); -} - -blargg_err_t Gbs_Reader::read_head(Gbs_Emu::header_t *header) { - vfs_fread(&header->tag, 1, 3,file); - vfs_fread(&header->vers, 1, 1,file); - vfs_fread(&header->track_count,1, 1,file); - vfs_fread(&header->first_track,1, 1,file); - vfs_fread(&header->load_addr, 1, 2,file); - vfs_fread(&header->init_addr, 1, 2,file); - vfs_fread(&header->play_addr, 1, 2,file); - vfs_fread(&header->stack_ptr, 1, 2,file); - vfs_fread(&header->timer_modulo,1, 1,file); - vfs_fread(&header->timer_mode, 1, 2,file); - vfs_fread(&header->game, 1,32,file); - vfs_fread(&header->author, 1,32,file); - vfs_fread(&header->copyright, 1,32,file); -}
--- a/Plugins/Input/console/Gbs_Emu.h Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Gbs_Emu.h Tue Jan 24 19:10:07 2006 -0800 @@ -1,7 +1,7 @@ -// Game Boy GBS-format game music file emulator +// Nintendo Game Boy GBS music file emulator -// Game_Music_Emu 0.2.4. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. +// Game_Music_Emu 0.3.0 #ifndef GBS_EMU_H #define GBS_EMU_H @@ -12,12 +12,14 @@ 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.3 ); - ~Gbs_Emu(); + Gbs_Emu( double gain = 1.2 ); - struct header_t { + // GBS file header + struct header_t + { char tag [3]; byte vers; byte track_count; @@ -36,22 +38,31 @@ }; BOOST_STATIC_ASSERT( sizeof (header_t) == 112 ); - // Load GBS, given its header and reader for remaining data - blargg_err_t load( const header_t&, Emu_Reader& ); + // 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; - blargg_err_t start_track( int ); - - -// End of public interface + 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( int, bool* ); + blip_time_t run_clocks( blip_time_t, bool* ); private: // rom const byte* rom_bank; - byte* rom; + blargg_vector<byte> rom; void unload(); int bank_count; void set_bank( int ); @@ -75,14 +86,16 @@ // hardware Gb_Apu apu; - byte hi_page [0x100]; 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 ); - // cpu and ram + // large objects + + header_t header_; + byte hi_page [0x100]; Gb_Cpu cpu; void cpu_jsr( gb_addr_t ); gb_time_t clock() const; @@ -91,15 +104,5 @@ static void write_ram( Gbs_Emu*, gb_addr_t, int ); }; -class Gbs_Reader : public Std_File_Reader { - VFSFile* file; -public: - Gbs_Reader(); - ~Gbs_Reader(); - - // Custom reader for Gbs headers [tempfix] - blargg_err_t read_head( Gbs_Emu::header_t* ); -}; - #endif
--- a/Plugins/Input/console/Gym_Emu.cpp Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Gym_Emu.cpp Tue Jan 24 19:10:07 2006 -0800 @@ -1,13 +1,12 @@ -// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ #include "Gym_Emu.h" -#include "ym2612.h" +#include <string.h> +#include "blargg_endian.h" -#include <string.h> - -/* Copyright (C) 2003-2005 Shay Green. This module is free software; you +/* 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 @@ -20,6 +19,9 @@ #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; @@ -27,8 +29,6 @@ { data = NULL; pos = NULL; - mem = NULL; - pairs_per_frame = 0; } Gym_Emu::~Gym_Emu() @@ -38,50 +38,43 @@ void Gym_Emu::unload() { - delete [] mem; - mem = NULL; data = NULL; pos = NULL; - track_ended_ = false; + set_track_ended( false ); + mem.clear(); } -blargg_err_t Gym_Emu::init( long sample_rate, double gain, double oversample_ ) +blargg_err_t Gym_Emu::set_sample_rate( long sample_rate ) { - require( oversample_ <= 4.0 ); - blip_eq_t eq( -32, 8000, sample_rate ); apu.treble_eq( eq ); - apu.volume( 0.27 * gain ); + apu.volume( 0.135 * gain ); dac_synth.treble_eq( eq ); - dac_synth.volume( 0.25 * gain ); - oversample = resampler.time_ratio( oversample_, 0.990, gain ); - - pairs_per_frame = sample_rate / 60; - oversamples_per_frame = int (pairs_per_frame * oversample) * 2 + 2; - clocks_per_sample = (double) clock_rate / sample_rate; + dac_synth.volume( 0.125 / 256 * gain ); - BLARGG_RETURN_ERR( resampler.buffer_size( oversamples_per_frame + 256 ) ); - - BLARGG_RETURN_ERR( blip_buf.sample_rate( sample_rate, 1000 / 30 ) ); - - BLARGG_RETURN_ERR( fm.set_rate( (long) (sample_rate * oversample), base_clock / 7 ) ); - + BLARGG_RETURN_ERR( blip_buf.set_sample_rate( sample_rate, 1000 / 60 ) ); blip_buf.clock_rate( clock_rate ); - return blargg_success; + 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_disabled = mask & 0x40; + 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", "SN76489" + "FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PCM", "PSG" }; return names; } @@ -96,7 +89,8 @@ if ( data_offset ) *data_offset = sizeof h; } - else if ( h.tag [0] != 0 && h.tag [0] != 1 ) { + 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"; @@ -107,26 +101,20 @@ blargg_err_t Gym_Emu::load_( const void* file, long data_offset, long file_size ) { - require( pairs_per_frame ); + require( blip_buf.length() ); data = (const byte*) file + data_offset; data_end = (const byte*) file + file_size; loop_begin = NULL; - loop_offset = 0; if ( data_offset ) - { - const header_t& h = *(header_t*) file; - loop_offset = - h.loop [3] * 0x1000000L + - h.loop [2] * 0x10000L + - h.loop [1] * 0x100L + - h.loop [0]; - } + header_ = *(header_t*) file; + else + memset( &header_, 0, sizeof header_ ); - track_count_ = 1; - voice_count_ = 8; - mute_voices( 0 ); + set_voice_count( 8 ); + set_track_count( 1 ); + remute_voices(); return blargg_success; } @@ -135,7 +123,7 @@ { unload(); - if ( file_size < sizeof (header_t) ) + if ( file_size < (int) sizeof (header_t) ) return "Not a GYM file"; int data_offset = 0; @@ -144,29 +132,30 @@ return load_( file, data_offset, file_size ); } -blargg_err_t Gym_Emu::load( const header_t& h, Emu_Reader& in ) +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 ) ); - long file_size = sizeof h + in.remain(); - mem = new byte [file_size]; - if ( !mem ) - return "Out of memory"; - memcpy( mem, &h, sizeof h ); - BLARGG_RETURN_ERR( in.read( mem + sizeof h, file_size - sizeof h ) ); + 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, data_offset, file_size ); + return load_( mem.begin(), data_offset, mem.size() ); } -int Gym_Emu::track_length() const +long Gym_Emu::track_length() const { - if ( loop_offset || loop_begin ) - return 0; - - long time = 0; // 1/60 sec frames + long time = 0; const byte* p = data; while ( p < data_end ) { @@ -178,104 +167,172 @@ case 1: case 2: - ++p; - case 3: - ++p; + p += 2; break; - default: - dprintf( "Bad command: %02X\n", (int) p [-1] ); + case 3: + p += 1; break; } } - - return (time + 30 + 59) / 60; + return time; } -blargg_err_t Gym_Emu::start_track( int ) +void Gym_Emu::start_track( int track ) { require( data ); + Music_Emu::start_track( track ); + pos = &data [0]; - extra_pos = 0; - loop_remain = loop_offset; + loop_remain = get_le32( header_.loop_start ); prev_dac_count = 0; dac_enabled = false; - last_dac = -1; + dac_amp = -1; fm.reset(); apu.reset(); - blip_buf.clear( false ); - resampler.clear(); + 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++; + } - track_ended_ = false; + // 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; + } - return blargg_success; + // 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::play_frame( sample_t* out ) +void Gym_Emu::parse_frame() { - parse_frame(); + int dac_count = 0; + const byte* pos = this->pos; - // run SMS APU and buffer - blip_time_t clock_count = (blip_time_t) ((pairs_per_frame + 1 - blip_buf.samples_avail()) * - clocks_per_sample); - apu.end_frame( clock_count ); - blip_buf.end_frame( clock_count ); - assert( unsigned (blip_buf.samples_avail() - pairs_per_frame) <= 4 ); + if ( loop_remain && !--loop_remain ) + loop_begin = pos; // find loop on first time through sequence - // run fm - const int sample_count = oversamples_per_frame - resampler.written(); - sample_t* buf = resampler.buffer(); - memset( buf, 0, sample_count * sizeof *buf ); - fm.run( buf, sample_count ); - resampler.write( sample_count ); - int count = resampler.read( sample_buf, pairs_per_frame * 2 ); - assert( count <= sample_buf_size ); - assert( unsigned (count - pairs_per_frame * 2) < 32 ); + 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 + } + } - // mix outputs - mix_samples( out ); - blip_buf.remove_samples( pairs_per_frame ); + // 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; } -blargg_err_t Gym_Emu::play( long count, sample_t* out ) +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 ); - const int samples_per_frame = pairs_per_frame * 2; - - // empty extra buffer - if ( extra_pos ) { - int n = samples_per_frame - extra_pos; - if ( n > count ) - n = count; - memcpy( out, sample_buf + extra_pos, n * sizeof *out ); - out += n; - count -= n; - extra_pos = (extra_pos + n) % samples_per_frame; - } - - // entire frames - while ( count >= samples_per_frame ) { - play_frame( out ); - out += samples_per_frame; - count -= samples_per_frame; - } - - // extra - if ( count ) { - play_frame( sample_buf ); - extra_pos = count; - memcpy( out, sample_buf, count * sizeof *out ); - out += count; - } - - return blargg_success; + Dual_Resampler::play( count, out, blip_buf ); } -blargg_err_t Gym_Emu::skip( long count ) +void Gym_Emu::skip( long count ) { // to do: figure out why total muting generated access violation on MorphOS const int buf_size = 1024; @@ -287,173 +344,7 @@ if ( n > count ) n = count; count -= n; - BLARGG_RETURN_ERR( play( n, buf ) ); - } - - return blargg_success; -} - -void Gym_Emu::run_dac( int dac_count ) -{ - if ( !dac_disabled ) - { - // 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++; - } - - // adjust - 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_Buffer::resampled_time_t period = - blip_buf.resampled_duration( clock_rate / 60 ) / rate_count; - - Blip_Buffer::resampled_time_t time = blip_buf.resampled_time( 0 ) + - period * start + (period >> 1); - - int last_dac = this->last_dac; - if ( last_dac < 0 ) - last_dac = dac_buf [0]; - - for ( int i = 0; i < dac_count; i++ ) - { - int diff = dac_buf [i] - last_dac; - last_dac += diff; - dac_synth.offset_resampled( time, diff, &blip_buf ); - time += period; - } - this->last_dac = last_dac; - } - - int const step = (int) (6 * oversample); - int remain = (int) (pairs_per_frame * oversample); - while ( remain ) { - int n = step; - if ( n > remain ) - n = remain; - remain -= n; - fm.run_timer( n ); + play( n, buf ); } } -void Gym_Emu::parse_frame() -{ - if ( track_ended_ ) - return; - - 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 ( dac_count < sizeof dac_buf ) { - dac_buf [dac_count] = data2; - dac_count += dac_enabled; - } - } - else { - if ( data == 0x2B ) - dac_enabled = (data2 & 0x80) != 0; - - fm.write( 0, data ); - fm.write( 1, data2 ); - } - } - else if ( cmd == 2 ) { - fm.write( 2, data ); - fm.write( 3, *pos++ ); - } - else if ( cmd == 3 ) { - apu.write_data( 0, data ); - } - else { - dprintf( "Bad command: %02X\n", (int) cmd ); - --pos; // put data back - } - } - // loop - if ( pos >= data_end ) { - if ( loop_begin ) - pos = loop_begin; - else - track_ended_ = true; - } - this->pos = pos; - - // dac - if ( dac_count ) - run_dac( dac_count ); - prev_dac_count = dac_count; -} - -#include BLARGG_ENABLE_OPTIMIZER - -void Gym_Emu::mix_samples( sample_t* out ) -{ - // Mix one frame of Blip_Buffer (SMS APU and PCM) and resampled YM audio - Blip_Reader sn; - int bass = sn.begin( blip_buf ); - const sample_t* ym = sample_buf; - - for ( int n = pairs_per_frame; n--; ) - { - int s = sn.read(); - long l = ym [0] * 2 + s; - sn.next( bass ); - if ( (BOOST::int16_t) l != l ) - l = 0x7FFF - (l >> 24); - long r = ym [1] * 2 + s; - ym += 2; - out [0] = l; - out [1] = r; - out += 2; - if ( (BOOST::int16_t) r != r ) - out [-1] = 0x7FFF - (r >> 24); - } - - sn.end( blip_buf ); -} - - -Gym_Reader::Gym_Reader() : file( NULL ) { -} - -Gym_Reader::~Gym_Reader() { - close(); -} - -blargg_err_t Gym_Reader::read_head(Gym_Emu::header_t *header) { - vfs_fread(&header->tag, 1, 4,file); - vfs_fread(&header->song, 1, 32,file); - vfs_fread(&header->game, 1, 32,file); - vfs_fread(&header->emulator, 1, 32,file); - vfs_fread(&header->dumper, 1, 32,file); - vfs_fread(&header->comment, 1,256,file); - vfs_fread(&header->loop, 1, 4,file); - vfs_fread(&header->packed, 1, 4,file); -}
--- a/Plugins/Input/console/Gym_Emu.h Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Gym_Emu.h Tue Jan 24 19:10:07 2006 -0800 @@ -1,29 +1,22 @@ // Sega Genesis GYM music file emulator -// Game_Music_Emu 0.2.4. Copyright (C) 2004-2005 Shay Green. GNU LGPL license. +// Game_Music_Emu 0.3.0 #ifndef GYM_EMU_H #define GYM_EMU_H -#include "Fir_Resampler.h" -#include "Blip_Buffer.h" +#include "Dual_Resampler.h" +#include "Ym2612_Emu.h" #include "Music_Emu.h" #include "Sms_Apu.h" -#include "ym2612.h" -class Gym_Emu : public Music_Emu { +class Gym_Emu : public Music_Emu, private Dual_Resampler { public: - Gym_Emu(); - ~Gym_Emu(); - // Initialize emulator with given sample rate, gain, and oversample. A gain of 1.0 - // results in almost no clamping. Default gain roughly matches volume of other emulators. - // The FM chip is synthesized at an increased rate governed by the oversample factor, - // where 1.0 results in no oversampling and > 1.0 results in oversampling. - blargg_err_t init( long sample_rate, double gain = 1.5, double oversample = 5 / 3.0 ); - - struct header_t { + // GYM file header + struct header_t + { char tag [4]; char song [32]; char game [32]; @@ -31,7 +24,7 @@ char emulator [32]; char dumper [32]; char comment [256]; - byte loop [4]; + byte loop_start [4]; // in 1/60 seconds, 0 if not looped byte packed [4]; enum { track_count = 1 }; // one track per file @@ -39,72 +32,64 @@ }; BOOST_STATIC_ASSERT( sizeof (header_t) == 428 ); - // Load GYM, given its header and reader for remaining data - blargg_err_t load( const header_t&, Emu_Reader& ); + // Load GYM data + blargg_err_t load( Data_Reader& ); - // Load GYM, given pointer to complete file data. Keeps reference - // to data, but doesn't free it. - blargg_err_t load( const void*, long size ); + // 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 seconds (0 if looped) - int track_length() const; - + // 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 ); - blargg_err_t start_track( int ); - blargg_err_t play( long count, sample_t* ); + void start_track( int ); + void play( long count, sample_t* ); const char** voice_names() const; - blargg_err_t skip( long count ); - -// End of public interface + 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_offset; long loop_remain; // frames remaining until loop beginning has been located - byte* mem; + blargg_vector<byte> mem; + header_t header_; blargg_err_t load_( const void* file, long data_offset, long file_size ); - - // frames - double oversample; - double clocks_per_sample; - int pairs_per_frame; - int oversamples_per_frame; + void unload(); void parse_frame(); - void play_frame( sample_t* ); - void mix_samples( sample_t* ); // dac (pcm) - int last_dac; + int dac_amp; int prev_dac_count; bool dac_enabled; - bool dac_disabled; + bool dac_muted; void run_dac( int ); // sound - int extra_pos; // extra samples remaining from last read Blip_Buffer blip_buf; - YM2612_Emu fm; - Blip_Synth<blip_med_quality,256> dac_synth; + Ym2612_Emu fm; + Blip_Synth<blip_med_quality,1> dac_synth; Sms_Apu apu; - Fir_Resampler resampler; byte dac_buf [1024]; - enum { sample_buf_size = 4096 }; - sample_t sample_buf [sample_buf_size]; - - void unload(); -}; - -class Gym_Reader : public Std_File_Reader { - VFSFile* file; -public: - Gym_Reader(); - ~Gym_Reader(); - - // Custom reader for SPC headers [tempfix] - blargg_err_t read_head( Gym_Emu::header_t* ); }; #endif
--- a/Plugins/Input/console/Makefile.am Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Makefile.am Tue Jan 24 19:10:07 2006 -0800 @@ -3,35 +3,41 @@ libdir = $(plugindir)/$(INPUT_PLUGIN_DIR) libconsole_la_SOURCES = \ - Blip_Buffer.cpp \ - Classic_Emu.cpp \ - Effects_Buffer.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_Namco.cpp \ - Nes_Oscs.cpp \ - Nes_Vrc6.cpp \ - Nsf_Emu.cpp \ - Panning_Buffer.cpp \ - Sms_Apu.cpp \ - Snes_Spc.cpp \ - Spc_Cpu.cpp \ - Spc_Dsp.cpp \ - Spc_Emu.cpp \ - Vgm_Emu.cpp \ - abstract_file.cpp \ - ym2612.cpp \ + 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 = $(GTK_LIBS) $(top_builddir)/libaudacious/libaudacious.la -INCLUDES = $(GTK_CFLAGS) $(ARCH_DEFINES) -I$(top_srcdir)/intl -I$(top_srcdir) -Iboost/ +libconsole_la_LIBADD = -lz $(GTK_LIBS) $(top_builddir)/libaudacious/libaudacious.la +INCLUDES = $(GTK_CFLAGS) $(ARCH_DEFINES) -I$(top_srcdir)/intl -I$(top_srcdir)
--- a/Plugins/Input/console/Multi_Buffer.cpp Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Multi_Buffer.cpp Tue Jan 24 19:10:07 2006 -0800 @@ -1,9 +1,9 @@ -// Blip_Buffer 0.3.3. http://www.slack.net/~ant/libs/ +// Blip_Buffer 0.4.0. http://www.slack.net/~ant/ #include "Multi_Buffer.h" -/* Copyright (C) 2003-2005 Shay Green. This module is free software; you +/* 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 @@ -16,10 +16,11 @@ #include BLARGG_SOURCE_BEGIN -Multi_Buffer::Multi_Buffer() +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 ) @@ -27,7 +28,7 @@ return blargg_success; } -Mono_Buffer::Mono_Buffer() +Mono_Buffer::Mono_Buffer() : Multi_Buffer( 1 ) { } @@ -35,15 +36,24 @@ { } -blargg_err_t Mono_Buffer::sample_rate( long rate, int msec ) +blargg_err_t Mono_Buffer::set_sample_rate( long rate, int msec ) { - BLARGG_RETURN_ERR( buf.sample_rate( rate, msec ) ); - length_ = buf.length(); - sample_rate_ = buf.sample_rate(); - return blargg_success; + BLARGG_RETURN_ERR( buf.set_sample_rate( rate, msec ) ); + return Multi_Buffer::set_sample_rate( buf.sample_rate(), buf.length() ); } -Mono_Buffer::channel_t Mono_Buffer::channel( int index ) +// 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; @@ -57,7 +67,9 @@ buf.end_frame( t ); } -Stereo_Buffer::Stereo_Buffer() +// Stereo_Buffer + +Stereo_Buffer::Stereo_Buffer() : Multi_Buffer( 2 ) { chan.center = &bufs [0]; chan.left = &bufs [1]; @@ -68,13 +80,11 @@ { } -blargg_err_t Stereo_Buffer::sample_rate( long rate, int msec ) +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].sample_rate( rate, msec ) ); - length_ = msec; - sample_rate_ = rate; - return blargg_success; + 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 )
--- a/Plugins/Input/console/Multi_Buffer.h Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Multi_Buffer.h Tue Jan 24 19:10:07 2006 -0800 @@ -1,18 +1,19 @@ // Multi-channel sound buffer interface, and basic mono and stereo buffers -// Blip_Buffer 0.3.3. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. +// Blip_Buffer 0.4.0 #ifndef MULTI_BUFFER_H #define MULTI_BUFFER_H +#include "blargg_common.h" #include "Blip_Buffer.h" -// Multi_Buffer is an interface to one or more Blip_Buffers mapped to one or -// more channels consisting of left, center, and right buffers. +// 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(); + Multi_Buffer( int samples_per_frame ); virtual ~Multi_Buffer() { } // Set the number of channels available @@ -27,7 +28,7 @@ virtual channel_t channel( int index ) = 0; // See Blip_Buffer.h - //virtual blargg_err_t sample_rate( long rate, int msec = 0 ); + 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; @@ -41,21 +42,31 @@ // 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: - // Derived classes must set these to appropriate values, which are returned - // by the corresponding public function. - long sample_rate_; - int length_; + 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_; }; -// Mono_Buffer uses a single buffer and outputs mono samples. +// Uses a single buffer and outputs mono samples. class Mono_Buffer : public Multi_Buffer { Blip_Buffer buf; public: @@ -63,36 +74,39 @@ ~Mono_Buffer(); // Buffer used for all channels - Blip_Buffer* center(); + Blip_Buffer* center() { return &buf; } // See Multi_Buffer - blargg_err_t sample_rate( long rate, int msec = blip_default_length ); + 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 ); }; -// Stereo_Buffer uses three buffers (one for center) and outputs stereo sample pairs. +// 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(); - Blip_Buffer* left(); - Blip_Buffer* right(); + Blip_Buffer* center() { return &bufs [0]; } + Blip_Buffer* left() { return &bufs [1]; } + Blip_Buffer* right() { return &bufs [2]; } // See Multi_Buffer - blargg_err_t sample_rate( long, int msec = blip_default_length ); + 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: @@ -106,52 +120,56 @@ 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 Blip_Buffer* Stereo_Buffer::left() { - return &bufs [1]; -} - -inline Blip_Buffer* Stereo_Buffer::center() { - return &bufs [0]; +inline blargg_err_t Multi_Buffer::set_sample_rate( long rate, int msec ) +{ + sample_rate_ = rate; + length_ = msec; + return blargg_success; } -inline Blip_Buffer* Stereo_Buffer::right() { - return &bufs [2]; -} - -inline Stereo_Buffer::channel_t Stereo_Buffer::channel( int index ) { - return chan; -} - -inline long Multi_Buffer::sample_rate() const { - return sample_rate_; +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::length() const { - return length_; -} +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 Blip_Buffer* Mono_Buffer::center() { - return &buf; -} +inline Stereo_Buffer::channel_t Stereo_Buffer::channel( int ) { return chan; } -inline void Mono_Buffer::clock_rate( long rate ) { - buf.clock_rate( rate ); -} +inline long Multi_Buffer::sample_rate() const { return sample_rate_; } + +inline int Multi_Buffer::length() const { return length_; } -inline void Mono_Buffer::clear() { - buf.clear(); -} +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 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::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
--- a/Plugins/Input/console/Music_Emu.cpp Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Music_Emu.cpp Tue Jan 24 19:10:07 2006 -0800 @@ -1,11 +1,11 @@ -// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ #include "Music_Emu.h" #include <string.h> -/* Copyright (C) 2003-2005 Shay Green. This module is free software; you +/* 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 @@ -18,11 +18,17 @@ #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; - voice_count_ = 0; + error_count_ = 0; track_ended_ = false; } @@ -30,12 +36,14 @@ { } -void Music_Emu::mute_voices( int ) +blargg_err_t Music_Emu::load_file( const char* path ) { - // empty + Std_File_Reader in; + BLARGG_RETURN_ERR( in.open( path ) ); + return load( in ); } -blargg_err_t Music_Emu::skip( long count ) +void Music_Emu::skip( long count ) { const int buf_size = 1024; sample_t buf [buf_size]; @@ -46,8 +54,9 @@ int saved_mute = mute_mask_; mute_voices( ~0 ); - while ( count > threshold / 2 ) { - BLARGG_RETURN_ERR( play( buf_size, buf ) ); + while ( count > threshold / 2 ) + { + play( buf_size, buf ); count -= buf_size; } @@ -60,10 +69,8 @@ if ( n > count ) n = count; count -= n; - BLARGG_RETURN_ERR( play( n, buf ) ); + play( n, buf ); } - - return blargg_success; } const char** Music_Emu::voice_names() const
--- a/Plugins/Input/console/Music_Emu.h Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Music_Emu.h Tue Jan 24 19:10:07 2006 -0800 @@ -1,22 +1,44 @@ // Game music emulator interface base class -// Game_Music_Emu 0.2.4. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. +// Game_Music_Emu 0.3.0 #ifndef MUSIC_EMU_H #define MUSIC_EMU_H #include "blargg_common.h" - #include "abstract_file.h" -typedef Data_Reader Emu_Reader; // File reader base class -typedef Std_File_Reader Emu_Std_Reader; // Read from standard file -typedef Mem_File_Reader Emu_Mem_Reader; // Read from block of memory +class Multi_Buffer; class Music_Emu { public: - Music_Emu(); - virtual ~Music_Emu(); + + // 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; @@ -24,43 +46,92 @@ // Names of voices virtual const char** voice_names() const; - // Number of tracks. Zero if file hasn't been loaded yet. - int track_count() const; - - // Start a track, where 0 is the first track. Might un-mute any muted voices. - virtual blargg_err_t start_track( int ) = 0; - // Mute voice n if bit n (1 << n) of mask is set virtual void mute_voices( int mask ); - // Generate 'count' samples info 'buf' - typedef short sample_t; - virtual blargg_err_t play( long count, sample_t* buf ) = 0; + // 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 blargg_err_t skip( long count ); + virtual void skip( long count ); - // True if a track was started and has since ended. Currently only dumped + // 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; -// End of public interface + Music_Emu(); + virtual ~Music_Emu(); + protected: - typedef BOOST::uint8_t byte; // used often - int track_count_; - int voice_count_; - int mute_mask_; - bool track_ended_; + 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
--- a/Plugins/Input/console/Nes_Apu.cpp Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Nes_Apu.cpp Tue Jan 24 19:10:07 2006 -0800 @@ -1,9 +1,9 @@ -// Nes_Snd_Emu 0.1.6. http://www.slack.net/~ant/libs/ +// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/ #include "Nes_Apu.h" -/* Copyright (C) 2003-2005 Shay Green. This module is free software; you +/* 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 @@ -16,12 +16,14 @@ #include BLARGG_SOURCE_BEGIN -Nes_Apu::Nes_Apu() +int const amp_range = 15; + +Nes_Apu::Nes_Apu() : + square1( &square_synth ), + square2( &square_synth ) { dmc.apu = this; dmc.rom_reader = NULL; - square1.synth = &square_synth; - square2.synth = &square_synth; irq_notifier_ = NULL; oscs [0] = &square1; @@ -47,25 +49,30 @@ dmc.synth.treble_eq( eq ); } -void Nes_Apu::volume( double v, bool nonlinear ) +void Nes_Apu::enable_nonlinear( double v ) { - dmc.nonlinear = nonlinear; - if ( nonlinear ) - { - square_synth.volume( 0.25751258 * 0.25 * v ); - - const double tnd = 1.0 / 202 * 0.48; - triangle.synth.volume_unit( 3 * tnd ); - noise.synth.volume_unit( 2 * tnd ); - dmc.synth.volume_unit( tnd ); - } - else - { - square_synth.volume( 0.1128 * v ); - triangle.synth.volume( 0.12765 * v ); - noise.synth.volume( 0.0741 * v ); - dmc.synth.volume( 0.42545 * 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 ) @@ -87,6 +94,7 @@ dmc.reset(); last_time = 0; + last_dmc_time = 0; osc_enables = 0; irq_flag = false; earliest_irq_ = no_irq; @@ -98,8 +106,8 @@ write_register( 0, addr, (addr & 3) ? 0x00 : 0x10 ); dmc.dac = initial_dmc_dac; - if ( !dmc.nonlinear ) - dmc.last_amp = initial_dmc_dac; // prevent output transition + //if ( !dmc.nonlinear ) // to do: remove? + // dmc.last_amp = initial_dmc_dac; // prevent output transition } void Nes_Apu::irq_changed() @@ -123,11 +131,29 @@ 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 @@ -141,7 +167,6 @@ square2.run( last_time, time ); triangle.run( last_time, time ); noise.run( last_time, time ); - dmc.run( last_time, time ); last_time = time; if ( time == end_time ) @@ -152,10 +177,9 @@ switch ( frame++ ) { case 0: - // set interrupt in mode 0 if ( !(frame_mode & 0xc0) ) { + next_irq = time + frame_period * 4 + 1; irq_flag = true; - next_irq = time + frame_period * 4 + 1; } // fall through case 2: @@ -191,12 +215,42 @@ } } +// to do: remove +static long abs_time; + +template<class T> +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 ) { - run_until( 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 = 0; + 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 ); @@ -230,7 +284,7 @@ if ( addr < start_addr || end_addr < addr ) return; - run_until( time ); + run_until_( time ); if ( addr < 0x4014 ) { @@ -309,7 +363,7 @@ int Nes_Apu::read_status( nes_time_t time ) { - run_until( time - 1 ); + run_until_( time - 1 ); int result = (dmc.irq_flag << 7) | (irq_flag << 6); @@ -317,7 +371,7 @@ if ( oscs [i]->length_counter ) result |= 1 << i; - run_until( time ); + run_until_( time ); if ( irq_flag ) { irq_flag = false;
--- a/Plugins/Input/console/Nes_Apu.h Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Nes_Apu.h Tue Jan 24 19:10:07 2006 -0800 @@ -1,24 +1,51 @@ // NES 2A03 APU sound chip emulator -// Nes_Snd_Emu 0.1.6. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. +// Nes_Snd_Emu 0.1.7 #ifndef NES_APU_H #define NES_APU_H -typedef long nes_time_t; // CPU clock cycle count +typedef long nes_time_t; // CPU clock cycle count typedef unsigned nes_addr_t; // 16-bit memory address #include "Nes_Oscs.h" -class Tagged_Data; +struct apu_snapshot_t; +class Nonlinear_Buffer; class Nes_Apu { public: Nes_Apu(); ~Nes_Apu(); -// Initialization + // 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. @@ -26,32 +53,16 @@ // any audible click. void reset( bool pal_timing = false, int initial_dmc_dac = 0 ); - // 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 ); - - // 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 ); - - // Reflect complete state between tagged data container. - void reflect_state( Tagged_Data& ); + // Save/load snapshot of exact emulation state + void save_snapshot( apu_snapshot_t* out ) const; + void load_snapshot( apu_snapshot_t const& ); -// Sound output - - // Set overall volume (default is 1.0). Set nonlinear to true only when - // using special Nes_Nonlinearizer on output. - void volume( double, bool nonlinear = false ); + // Set overall volume (default is 1.0) + void volume( double ); - // Set treble equalization (see notes.txt). + // Set treble equalization (see notes.txt) void treble_eq( const blip_eq_t& ); - // Set sound output of all oscillators to buffer. If buffer is NULL, no - // sound is generated and emulation accuracy is reduced. - void output( Blip_Buffer* ); - // 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, @@ -59,45 +70,38 @@ enum { osc_count = 5 }; void osc_output( int index, Blip_Buffer* buffer ); -// Emulation - - // All time values are the number of CPU clock cycles relative to the - // beginning of the current time frame. - - // Emulate memory write at specified time. Address must be in the range - // start_addr to end_addr. - enum { start_addr = 0x4000 }; - enum { end_addr = 0x4017 }; - void write_register( nes_time_t, nes_addr_t, int data ); - - // Emulate memory read of the status register at specified time. - enum { status_addr = 0x4015 }; - int read_status( nes_time_t ); + // 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() const; - - // Run APU 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 ); + 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; - // 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 ); + // 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& ); @@ -110,6 +114,7 @@ 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; @@ -124,32 +129,46 @@ void irq_changed(); void state_restored(); - - friend struct Nes_Dmc; + void run_until_( nes_time_t ); }; - inline void Nes_Apu::osc_output( int osc, Blip_Buffer* buf ) { - assert(( "Nes_Apu::osc_output(): Index out of range", 0 <= osc && osc < osc_count )); - oscs [osc]->output = buf; - } +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() const { - return earliest_irq_; - } +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::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 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 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
--- a/Plugins/Input/console/Nes_Cpu.cpp Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Nes_Cpu.cpp Tue Jan 24 19:10:07 2006 -0800 @@ -1,13 +1,14 @@ -// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/nes-emu/ #include "Nes_Cpu.h" +#include <string.h> #include <limits.h> #include "blargg_endian.h" -/* Copyright (C) 2003-2005 Shay Green. This module is free software; you +/* 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 @@ -20,42 +21,24 @@ #include BLARGG_SOURCE_BEGIN -// Common instructions: -// 7.40% D0 BNE -// 6.05% F0 BEQ -// 5.46% BD LDA abs,X -// 4.44% C8 INY -// 4.32% 85 STA zp -// 4.29% C9 CMP imm -// 3.74% 20 JSR -// 3.66% 60 RTS -// 3.23% 8D STA abs -// 3.02% AD LDA abs - -#ifndef NES_CPU_FIXED_CYCLES - #define NES_CPU_FIXED_CYCLES 0 +#if BLARGG_NONPORTABLE + #define PAGE_OFFSET( addr ) (addr) +#else + #define PAGE_OFFSET( addr ) ((addr) & (page_size - 1)) #endif -static void write_unmapped( Nsf_Emu*, nes_addr_t, int ) -{ - check( false ); -} - -static int read_unmapped( Nsf_Emu*, nes_addr_t ) -{ - check( false ); - return 0; -} - Nes_Cpu::Nes_Cpu() { callback_data = NULL; - data_writer [page_count] = write_unmapped; - data_reader [page_count] = read_unmapped; reset(); } -void Nes_Cpu::reset( const void* unmapped_code_page ) +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; @@ -64,131 +47,131 @@ r.x = 0; r.y = 0; - cycle_count = 0; + clock_count = 0; base_time = 0; - #if NES_CPU_IRQ_SUPPORT - cycle_limit = 0; - #endif + clock_limit = 0; + irq_time_ = LONG_MAX / 2 + 1; + end_time_ = LONG_MAX / 2 + 1; for ( int i = 0; i < page_count + 1; i++ ) - code_map [i] = (uint8_t*) unmapped_code_page; - - map_memory( 0, 0x10000, read_unmapped, write_unmapped ); + { + 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 ) { - // start end end must fall on page bounadries - require( start % page_size == 0 && size % page_size == 0 ); + // 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--; ) - code_map [first_page + i] = (uint8_t*) data + i * page_size; + 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::map_memory( nes_addr_t start, unsigned long size, reader_t read, writer_t write ) +void Nes_Cpu::set_writer( nes_addr_t start, unsigned long size, writer_t func ) { - // start end end must fall on page bounadries - require( start % page_size == 0 && size % page_size == 0 ); + // 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 ); - if ( !read ) - read = read_unmapped; - if ( !write ) - write = write_unmapped; 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; - } + for ( unsigned i = size / page_size; i--; ) + data_writer [first_page + i] = func; } -// Note: 'addr' is evaulated more than once in some of the macros, so it +// Note: 'addr' is evaulated more than once in the following macros, so it // must not contain side-effects. -#define LOW_MEM( a ) (low_mem [int (a)]) - #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] [(addr) & (page_size - 1)]) +#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 PUSH( v ) (*--sp = (v)) -#define POP() (*sp++) +#define SET_SP( v ) (sp = ((v) + 1) | 0x100) +#define GET_SP() ((sp - 1) & 0xff) -int Nes_Cpu::read( nes_addr_t addr ) { +#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 ) { + +void Nes_Cpu::write( nes_addr_t addr, int value ) +{ WRITE( addr, value ); } #ifndef NES_CPU_GLUE_ONLY -static const unsigned char cycle_table [256] = { - 7,6,2,8,3,3,5,5,3,2,2,2,4,4,6,6, - 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, - 6,6,2,8,3,3,5,5,4,2,2,2,4,4,6,6, - 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, - 6,6,2,8,3,3,5,5,3,2,2,2,3,4,6,6, - 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, - 6,6,2,8,3,3,5,5,4,2,2,2,5,4,6,6, - 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, - 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4, - 2,6,2,6,4,4,4,4,2,5,2,5,5,5,5,5, - 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4, - 2,5,2,5,4,4,4,4,2,4,2,4,4,4,4,4, - 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6, - 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, - 2,6,3,8,3,3,5,5,2,2,2,2,4,4,6,6, - 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7 +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 -// on machines with relatively few registers: -// use local temporaries (if no function calls) over all other variables -// these are most likely to be in registers, so use them over others if possible: -// pc -// nz (so calculate with nz first, then assign to other variable if necessary) -// data -// this Nes_Cpu::result_t Nes_Cpu::run( nes_time_t end ) { - #if NES_CPU_IRQ_SUPPORT - end_time( end ); - #else - cycle_count -= (end - base_time); - base_time = end; - #endif + set_end_time( end ); - result_t result = result_cycles; + volatile result_t result = result_cycles; #if BLARGG_CPU_POWERPC // cache commonly-used values in registers - long cycle_count = this->cycle_count; + 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 - // stack pointer is kept one greater than usual 6502 stack pointer - #define SET_SP( v ) (sp = low_mem + 0x101 + (v)) - #define GET_SP() (sp - 0x101 - low_mem) - uint8_t* sp; - SET_SP( r.sp ); - // 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 - #define IS_NEG (int ((nz + 0x800) | (nz << (CHAR_BIT * sizeof (int) - 8))) < 0) - const int st_n = 0x80; const int st_v = 0x40; const int st_r = 0x20; @@ -198,103 +181,78 @@ const int st_z = 0x02; const int st_c = 0x01; - #define CALC_STATUS( out ) do { \ - out = status & ~(st_n | st_z | st_c); \ - out |= (c >> 8) & st_c; \ - if ( IS_NEG ) out |= st_n; \ - if ( (nz & 0xFF) == 0 ) out |= st_z; \ - } while ( false ) + #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_n | st_z | st_c | st_b | st_r); \ - c = in << 8; \ - nz = in << 4; \ - nz &= 0x820; \ - nz ^= ~0xDF; \ + #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 ) - uint8_t status; - int c; // store C as 'c' & 0x100. - int nz; // store Z as 'nz' & 0xFF == 0. see above for encoding of N. + 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; - - unsigned data; - -branch_taken: { - unsigned old_pc = pc; - pc += (BOOST::int8_t) data; - if ( !NES_CPU_FIXED_CYCLES ) - cycle_count += 1 + (((old_pc ^ (pc + 1)) >> 8) & 1); -} -inc_pc_loop: - pc++; +dec_clock_loop: + clock_count--; loop: - - check( (unsigned) pc < 0x10000 ); - check( (unsigned) GET_SP() < 0x100 ); - check( (unsigned) a < 0x100 ); - check( (unsigned) x < 0x100 ); - check( (unsigned) y < 0x100 ); - // Read opcode and first operand. Optimize if processor's byte order is known - // and non-portable constructs are allowed. -#if BLARGG_NONPORTABLE && BLARGG_BIG_ENDIAN - data = *(BOOST::uint16_t*) &READ_PROG( pc ); - pc++; - unsigned opcode = data >> 8; - data = (uint8_t) data; - -#elif BLARGG_NONPORTABLE && BLARGG_LITTLE_ENDIAN - data = *(BOOST::uint16_t*) &READ_PROG( pc ); + 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++; - unsigned opcode = (uint8_t) data; - data >>= 8; - -#else - unsigned opcode = READ_PROG( pc ); - pc++; - data = READ_PROG( pc ); -#endif - - if ( cycle_count >= cycle_limit ) + // page crossing + //check( opcode == 0x60 || &READ_PROG( pc ) == &page [PAGE_OFFSET( pc )] ); + + if ( clock_count >= clock_limit ) goto stop; - cycle_count += NES_CPU_FIXED_CYCLES ? NES_CPU_FIXED_CYCLES : cycle_table [opcode]; + clock_count += clock_table [opcode]; #if BLARGG_CPU_POWERPC - this->cycle_count = cycle_count; + this->clock_count = clock_count; #endif switch ( opcode ) { + +// Macros + #define ADD_PAGE (pc++, data += 0x100 * READ_PROG( pc )); #define GET_ADDR() READ_PROG16( pc ) -#if NES_CPU_FIXED_CYCLES - #define HANDLE_PAGE_CROSSING( lsb ) -#else - #define HANDLE_PAGE_CROSSING( lsb ) cycle_count += (lsb) >> 8; -#endif +#define HANDLE_PAGE_CROSSING( lsb ) clock_count += (lsb) >> 8; -#define INC_DEC_XY( reg, n ) \ - reg = uint8_t (nz = reg + n); \ - goto loop; +#define INC_DEC_XY( reg, n ) reg = uint8_t (nz = reg + n); goto loop; #define IND_Y { \ - int temp = LOW_MEM( data ) + y; \ - data = temp + 0x100 * LOW_MEM( uint8_t (data + 1) ); \ + 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 = LOW_MEM( uint8_t (temp) ) + 0x100 * LOW_MEM( uint8_t (temp + 1) ); \ + data = 0x100 * READ_LOW( uint8_t (temp + 1) ) + READ_LOW( uint8_t (temp) ); \ } #define ARITH_ADDR_MODES( op ) \ @@ -307,7 +265,7 @@ case op + 0x10: /* zp,X */ \ data = uint8_t (data + x); \ case op + 0x00: /* zp */ \ - data = LOW_MEM( data ); \ + data = READ_LOW( data ); \ goto imm##op; \ case op + 0x14: /* abs,Y */ \ data += y; \ @@ -323,17 +281,78 @@ 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 - if ( (uint8_t) nz ) - goto branch_taken; - goto inc_pc_loop; + 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 - if ( !(uint8_t) nz ) - goto branch_taken; - goto inc_pc_loop; + 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; @@ -342,41 +361,20 @@ case 0xAD: // LDA abs ADD_PAGE lda_ptr: - nz = a = READ( data ); - goto inc_pc_loop; - - case 0xC8: INC_DEC_XY( y, 1 ) // INY - - case 0x95: // STA zp,x - data = uint8_t (data + x); - case 0x85: // STA zp - LOW_MEM( data ) = a; - goto inc_pc_loop; - - ARITH_ADDR_MODES( 0xC5 ) // CMP - nz = a - data; - compare_common: - c = ~nz; - goto inc_pc_loop; - - case 0x4C: // JMP abs + a = nz = READ( data ); pc++; - goto jmp_common; - - case 0x20: // JSR - pc++; - PUSH( pc >> 8 ); - PUSH( pc ); - jmp_common: - pc = data + 0x100 * READ_PROG( pc ); goto loop; case 0x60: // RTS - // often-used instruction - pc = POP(); - pc += 0x100 * POP(); - goto inc_pc_loop; + 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: @@ -384,29 +382,17 @@ case 0x8D: // STA abs ADD_PAGE sta_ptr: + pc++; WRITE( data, a ); - goto inc_pc_loop; - - case 0xB5: // LDA zp,x - data = uint8_t (data + x); - case 0xA5: // LDA zp - data = LOW_MEM( data ); - case 0xA9: // LDA #imm - a = nz = data; - goto inc_pc_loop; - - case 0xA8: // TAY - y = a; - case 0x98: // TYA - a = nz = y; goto loop; -// Branch + case 0xA9: // LDA #imm + pc++; + a = data; + nz = data; + goto loop; -#define BRANCH( cond ) \ - if ( cond ) \ - goto branch_taken; \ - goto inc_pc_loop; +// Branch case 0x50: // BVC BRANCH( !(status & st_v) ) @@ -420,43 +406,41 @@ case 0x90: // BCC BRANCH( !(c & 0x100) ) - case 0x10: // BPL - BRANCH( !IS_NEG ) - - case 0x30: // BMI - BRANCH( IS_NEG ) - // Load/store case 0x94: // STY zp,x data = uint8_t (data + x); case 0x84: // STY zp - LOW_MEM( data ) = y; - goto inc_pc_loop; + pc++; + WRITE_LOW( data, y ); + goto loop; case 0x96: // STX zp,y data = uint8_t (data + y); case 0x86: // STX zp - LOW_MEM( data ) = x; - goto inc_pc_loop; + pc++; + WRITE_LOW( data, x ); + goto loop; case 0xB6: // LDX zp,y data = uint8_t (data + y); case 0xA6: // LDX zp - data = LOW_MEM( data ); - case 0xA2: // LDX imm + data = READ_LOW( data ); + case 0xA2: // LDX #imm + pc++; x = data; nz = data; - goto inc_pc_loop; + goto loop; case 0xB4: // LDY zp,x data = uint8_t (data + x); case 0xA4: // LDY zp - data = LOW_MEM( data ); - case 0xA0: // LDY imm + data = READ_LOW( data ); + case 0xA0: // LDY #imm + pc++; y = data; nz = data; - goto inc_pc_loop; + goto loop; case 0xB1: // LDA (ind),Y IND_Y @@ -466,10 +450,6 @@ IND_X goto lda_ptr; - case 0xB9: // LDA abs,Y - data += y; - goto lda_ind_common; - case 0x91: // STA (ind),Y IND_Y goto sta_ptr; @@ -478,18 +458,15 @@ IND_X goto sta_ptr; - case 0x99: // STA abs,Y - data += y; - goto sta_ind_common; - 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 inc_pc_loop; + goto loop; } case 0xBE: // LDX abs,y @@ -498,8 +475,9 @@ case 0xAE:{// LDX abs pc++; unsigned addr = data + 0x100 * READ_PROG( pc ); + pc++; x = nz = READ( addr ); - goto inc_pc_loop; + goto loop; } { @@ -511,7 +489,7 @@ case 0x8E: // STX abs temp = x; store_abs: - int addr = GET_ADDR(); + unsigned addr = GET_ADDR(); WRITE( addr, temp ); pc += 2; goto loop; @@ -527,11 +505,14 @@ } case 0xE4: // CPX zp - data = LOW_MEM( data ); - case 0xE0: // CPX imm + data = READ_LOW( data ); + case 0xE0: // CPX #imm cpx_data: nz = x - data; - goto compare_common; + pc++; + c = ~nz; + nz &= 0xff; + goto loop; case 0xCC:{// CPY abs unsigned addr = GET_ADDR(); @@ -541,42 +522,31 @@ } case 0xC4: // CPY zp - data = LOW_MEM( data ); - case 0xC0: // CPY imm + data = READ_LOW( data ); + case 0xC0: // CPY #imm cpy_data: nz = y - data; - goto compare_common; + pc++; + c = ~nz; + nz &= 0xff; + goto loop; // Logical ARITH_ADDR_MODES( 0x25 ) // AND nz = (a &= data); - goto inc_pc_loop; + pc++; + goto loop; ARITH_ADDR_MODES( 0x45 ) // EOR nz = (a ^= data); - goto inc_pc_loop; + pc++; + goto loop; ARITH_ADDR_MODES( 0x05 ) // ORA nz = (a |= data); - goto inc_pc_loop; - -// Add/Subtract - - ARITH_ADDR_MODES( 0x65 ) // ADC - adc_imm: { - int carry = (c >> 8) & 1; - int ov = (a ^ 0x80) + carry + (BOOST::int8_t) data; // sign-extend - c = nz = a + data + carry; - a = (uint8_t) nz; - status = (status & ~st_v) | ((ov >> 2) & 0x40); - goto inc_pc_loop; - } - - ARITH_ADDR_MODES( 0xE5 ) // SBC - case 0xEB: // unofficial equivalent - data ^= 0xFF; - goto adc_imm; + pc++; + goto loop; case 0x2C:{// BIT abs unsigned addr = GET_ADDR(); @@ -586,14 +556,36 @@ } case 0x24: // BIT zp - nz = LOW_MEM( data ); + nz = READ_LOW( data ); bit_common: - status = (status & ~st_v) | (nz & st_v); - if ( !(a & nz) ) // use special encoding since N and Z might both be set - nz = ((nz & 0x80) << 4) ^ ~0xFF; - goto inc_pc_loop; + 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; -// Shift/Rotate +// 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; @@ -634,8 +626,9 @@ nz = (c >> 8) & 1; nz |= (c = READ( data ) << 1); rotate_common: + pc++; WRITE( data, (uint8_t) nz ); - goto inc_pc_loop; + goto loop; case 0x7E: // ROR abs,X data += x; @@ -665,7 +658,7 @@ c = 0; case 0x66: // ROR zp ror_zp: { - int temp = LOW_MEM( data ); + int temp = READ_LOW( data ); nz = ((c >> 1) & 0x80) | (temp >> 1); c = temp << 8; goto write_nz_zp; @@ -682,13 +675,13 @@ case 0x26: // ROL zp rol_zp: nz = (c >> 8) & 1; - nz |= (c = LOW_MEM( data ) << 1); + nz |= (c = READ_LOW( data ) << 1); goto write_nz_zp; -// Increment/Decrement +// Increment/decrement - case 0xE8: INC_DEC_XY( x, 1 ) // INX case 0xCA: INC_DEC_XY( x, -1 ) // DEX + case 0x88: INC_DEC_XY( y, -1 ) // DEY case 0xF6: // INC zp,x @@ -702,10 +695,11 @@ case 0xC6: // DEC zp nz = -1; add_nz_zp: - nz += LOW_MEM( data ); + nz += READ_LOW( data ); write_nz_zp: - LOW_MEM( data ) = nz; - goto inc_pc_loop; + pc++; + WRITE_LOW( data, nz ); + goto loop; case 0xFE: // INC abs,x HANDLE_PAGE_CROSSING( data + x ); @@ -756,56 +750,69 @@ goto loop; case 0x68: // PLA - a = nz = POP(); + a = nz = READ_LOW( sp ); + sp = (sp - 0xff) | 0x100; goto loop; - { - int temp; case 0x40: // RTI - temp = POP(); - pc = POP(); - pc |= POP() << 8; - goto set_status; - case 0x28: // PLP - temp = POP(); - set_status: - #if !NES_CPU_IRQ_SUPPORT + { + 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; - #endif - data = status & st_i; + 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) ) - goto loop; - result = result_cli; // I flag was just cleared - goto end; + 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 ); - temp |= st_b | st_r; - PUSH( temp ); + PUSH( temp | st_b | st_r ); goto loop; } case 0x6C: // JMP (ind) data = GET_ADDR(); pc = READ( data ); - data++; - pc |= READ( data ) << 8; + pc |= READ( (data & 0xff00) | ((data + 1) & 0xff) ) << 8; goto loop; case 0x00: { // BRK - pc += 2; // verified - PUSH( pc >> 8 ); - PUSH( pc ); - status |= st_i; + pc++; + WRITE_LOW( 0x100 | (sp - 1), pc >> 8 ); + WRITE_LOW( 0x100 | (sp - 2), pc ); int temp; CALC_STATUS( temp ); - PUSH( temp | st_b | st_r ); + sp = (sp - 3) | 0x100; + WRITE_LOW( sp, temp | st_b | st_r ); pc = READ_PROG16( 0xFFFE ); - goto loop; + status |= st_i; + goto i_flag_changed; } // Flags @@ -831,33 +838,63 @@ goto loop; case 0x58: // CLI - #if !NES_CPU_IRQ_SUPPORT - status &= ~st_i; - goto loop; - #endif 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; - goto loop; + 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 +// 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: - goto inc_pc_loop; - + pc++; case 0xEA: case 0x1A: case 0x3A: case 0x5A: case 0x7A: case 0xDA: case 0xFA: // NOP goto loop; -// unimplemented +// 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 @@ -871,9 +908,6 @@ case 0x8B: // XAA case 0xAB: // OAL case 0xCB: // SAX - case 0x02: case 0x12: case 0x22: case 0x32: // ? - case 0x42: case 0x52: case 0x62: case 0x72: - case 0x92: case 0xB2: case 0xD2: case 0xF2: 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 @@ -885,6 +919,8 @@ result = result_badop; goto stop; } + + // If this fails then the case above is missing an opcode assert( false ); stop: @@ -897,16 +933,15 @@ r.status = temp; } - base_time += cycle_count; - #if NES_CPU_IRQ_SUPPORT - this->cycle_limit -= cycle_count; - #endif - this->cycle_count = 0; + 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; }
--- a/Plugins/Input/console/Nes_Cpu.h Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Nes_Cpu.h Tue Jan 24 19:10:07 2006 -0800 @@ -1,123 +1,174 @@ // Nintendo Entertainment System (NES) 6502 CPU emulator -// Game_Music_Emu 0.2.4. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. +// 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 long nes_time_t; // clock cycle count typedef unsigned nes_addr_t; // 16-bit address -class Nsf_Emu; - -#ifndef NES_CPU_IRQ_SUPPORT - #define NES_CPU_IRQ_SUPPORT 0 -#endif +class Nes_Emu; class Nes_Cpu { typedef BOOST::uint8_t uint8_t; enum { page_bits = 11 }; enum { page_count = 0x10000 >> page_bits }; - const uint8_t* code_map [page_count + 1]; + uint8_t const* code_map [page_count + 1]; public: Nes_Cpu(); - // Clear registers, unmap memory, and map code pages to unmapped_page. - void reset( const void* unmapped_page = NULL ); + // 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; } - // Memory read/write function types. Reader must return value from 0 to 255. - Nsf_Emu* callback_data; - typedef int (*reader_t)( Nsf_Emu* callback_data, nes_addr_t ); - typedef void (*writer_t)( Nsf_Emu* callback_data, nes_addr_t, int value ); + // 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) + // Map code memory (memory accessed via the program counter) void map_code( nes_addr_t start, unsigned long size, const void* code ); - // Map data memory to read and write functions + // 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 value ); - uint8_t* get_code( 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 { - BOOST::uint16_t pc; - uint8_t a; - uint8_t x; - uint8_t y; - uint8_t status; - uint8_t sp; - } r; + 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_cli, // I flag cleared + 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 }; - // Run CPU to or after end_time, or until a stop reason from above - // is encountered. Return the reason for stopping. - result_t run( nes_time_t end_time ); + result_t run( nes_time_t end_time_ ); - nes_time_t time() const { return base_time + cycle_count; } - void time( nes_time_t t ); - nes_time_t end_time() const { return base_time + cycle_limit; } + nes_time_t time() const { return base_time + clock_count; } + void set_time( nes_time_t t ); void end_frame( nes_time_t ); -#if NES_CPU_IRQ_SUPPORT - void end_time( nes_time_t t ) { cycle_limit = t - base_time; } -#endif + 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 cycle_count; + nes_time_t clock_limit; nes_time_t base_time; -#if NES_CPU_IRQ_SUPPORT - nes_time_t cycle_limit; -#else - enum { cycle_limit = 0 }; -#endif + nes_time_t clock_count; + nes_time_t irq_time_; + nes_time_t end_time_; + + Nes_Emu* callback_data; - reader_t data_reader [page_count + 1]; // extra entry to catch address overflow + 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 ) { - return (uint8_t*) &code_map [(addr) >> page_bits] [(addr) & (page_size - 1)]; - } +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 +} -#if NES_CPU_IRQ_SUPPORT - inline void Nes_Cpu::time( nes_time_t t ) { - t -= time(); - cycle_limit -= t; - base_time += t; - } -#else - inline void Nes_Cpu::time( nes_time_t t ) { - cycle_count = t - base_time; - } -#endif - - inline void Nes_Cpu::end_frame( nes_time_t end_time ) { - base_time -= end_time; - assert( time() >= 0 ); - } +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
--- a/Plugins/Input/console/Nes_Namco.cpp Tue Jan 24 13:57:22 2006 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,149 +0,0 @@ - -// Nes_Snd_Emu 0.1.6. http://www.slack.net/~ant/libs/ - -#include "Nes_Namco.h" - -#include "Tagged_Data.h" - -/* Copyright (C) 2003-2005 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::Nes_Namco() -{ - output( NULL ); - volume( 1.0 ); - reset(); -} - -Nes_Namco::~Nes_Namco() { -} - -void Nes_Namco::reset() -{ - 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::output( Blip_Buffer* buf ) -{ - for ( int i = 0; i < osc_count; i++ ) - osc_output( i, buf ); -} - -BOOST::uint8_t& Nes_Namco::access() -{ - int addr = addr_reg & 0x7f; - if ( addr_reg & 0x80 ) - addr_reg = (addr + 1) | 0x80; - return reg [addr]; -} - -#define chars_to_long(s) ( (unsigned long)(s[0] << 24) | (unsigned long)(s[1] << 16) | \ - (unsigned long)(s[2] << 8) | (unsigned long)(s[3]) ) - -void Nes_Namco::reflect_state( Tagged_Data& data ) -{ - reflect_int16( data, chars_to_long("ADDR"), &addr_reg ); - - static const char hex [17] = "0123456789ABCDEF"; - int i; - for ( i = 0; i < reg_count; i++ ) - reflect_int16( data, chars_to_long("RG\0\0") + hex [i >> 4] * 0x100 + hex [i & 15], ® [i] ); - - for ( i = 0; i < osc_count; i++ ) { - reflect_int32( data, chars_to_long("DLY0") + i, &oscs [i].delay ); - reflect_int16( data, chars_to_long("POS0") + i, &oscs [i].wave_pos ); - } -} - -#include BLARGG_ENABLE_OPTIMIZER - -void Nes_Namco::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_Buffer::resampled_time_t time = - output->resampled_time( last_time ) + osc.delay; - Blip_Buffer::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 ) - continue; - Blip_Buffer::resampled_time_t period = - output->resampled_duration( 983040 ) / freq * active_oscs; - - int wave_size = (8 - ((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]; - wave_pos++; - if ( addr & 1 ) - sample >>= 4; - 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; -} -
--- a/Plugins/Input/console/Nes_Namco.h Tue Jan 24 13:57:22 2006 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,99 +0,0 @@ - -// Namco 106 sound chip emulator - -// Nes_Snd_Emu 0.1.6. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. - -#ifndef NES_NAMCO_H -#define NES_NAMCO_H - -#include "Nes_Apu.h" - -class Tagged_Data; - -class Nes_Namco { -public: - Nes_Namco(); - ~Nes_Namco(); - - // 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(); - - // Read/write data register is at $4800 - enum { data_reg_addr = 0x4800 }; - void write_data( nes_time_t, int ); - int read_data(); - - // Write-only address register is at $F800 - enum { addr_reg_addr = 0xF800 }; - void write_addr( int ); - - void end_frame( nes_time_t ); - void reflect_state( Tagged_Data& ); - -// End of public interface - -private: - // noncopyable - Nes_Namco( const Nes_Namco& ); - Nes_Namco& operator = ( const Nes_Namco& ); - - 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<blip_good_quality,15> synth; - - BOOST::uint8_t& access(); - void run_until( nes_time_t ); -}; - - inline void Nes_Namco::volume( double v ) { - synth.volume( 0.10 / osc_count * v ); - } - - inline void Nes_Namco::treble_eq( const blip_eq_t& eq ) { - synth.treble_eq( eq ); - } - - inline void Nes_Namco::osc_output( int i, Blip_Buffer* buf ) { - assert( (unsigned) i < osc_count ); - oscs [i].output = buf; - } - - inline void Nes_Namco::write_addr( int v ) { - addr_reg = v; - } - - inline int Nes_Namco::read_data() { - return access(); - } - - inline void Nes_Namco::write_data( nes_time_t time, int data ) { - run_until( time ); - access() = data; - } - - inline void Nes_Namco::end_frame( nes_time_t time ) { - run_until( time ); - last_time -= time; - assert( last_time >= 0 ); - } - -#endif -
--- a/Plugins/Input/console/Nes_Oscs.cpp Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Nes_Oscs.cpp Tue Jan 24 19:10:07 2006 -0800 @@ -1,9 +1,9 @@ -// Nes_Snd_Emu 0.1.6. http://www.slack.net/~ant/libs/ +// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/ #include "Nes_Apu.h" -/* Copyright (C) 2003-2005 Shay Green. This module is free software; you +/* 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 @@ -94,7 +94,7 @@ if ( volume == 0 || period < 8 || (period + offset) >= 0x800 ) { if ( last_amp ) { - synth->offset( time, -last_amp, output ); + synth.offset( time, -last_amp, output ); last_amp = 0; } @@ -122,13 +122,13 @@ int delta = update_amp( amp ); if ( delta ) - synth->offset( time, delta, output ); + synth.offset( time, delta, output ); time += delay; if ( time < end_time ) { Blip_Buffer* const output = this->output; - const Synth* synth = this->synth; + const Synth& synth = this->synth; int delta = amp * 2 - volume; int phase = this->phase; @@ -136,7 +136,7 @@ phase = (phase + 1) & (phase_range - 1); if ( phase == 0 || phase == duty ) { delta = -delta; - synth->offset_inline( time, delta, output ); + synth.offset_inline( time, delta, output ); } time += timer_period; } @@ -163,6 +163,14 @@ 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 ) @@ -171,6 +179,10 @@ // 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 ) @@ -204,6 +216,7 @@ if ( volume < 0 ) phase += phase_range; this->phase = phase; + last_amp = calc_amp(); } delay = time - end_time; } @@ -217,21 +230,21 @@ buf = 0; bits_remain = 1; bits = 0; - buf_empty = true; + buf_full = false; silence = true; next_irq = Nes_Apu::no_irq; irq_flag = false; irq_enabled = false; Nes_Osc::reset(); - period = 0x036; + period = 0x1ac; } void Nes_Dmc::recalc_irq() { nes_time_t irq = Nes_Apu::no_irq; if ( irq_enabled && length_counter ) - irq = apu->last_time + delay + + 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; @@ -247,7 +260,7 @@ if ( length_counter == 0 ) return 0; // not reading - long first_read = apu->last_time + delay + long (bits_remain - 1) * period; + long first_read = next_read_time(); long avail = time - first_read; if ( avail <= 0 ) return 0; @@ -267,11 +280,11 @@ } 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, + {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 + {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() @@ -280,20 +293,22 @@ length_counter = regs [3] * 0x10 + 1; } -static const unsigned char dac_table [128] = { - 0, 0, 1, 2, 2, 3, 3, 4, 5, 5, 6, 7, 7, 8, 8, 9, - 10, 10, 11, 11, 12, 13, 13, 14, 14, 15, 15, 16, 17, 17, 18, 18, - 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, - 27, 27, 28, 28, 29, 29, 30, 30, 31, 31, 32, 32, 32, 33, 33, 34, - 34, 35, 35, 35, 36, 36, 37, 37, 38, 38, 38, 39, 39, 40, 40, 40, - 41, 41, 42, 42, 42, 43, 43, 44, 44, 44, 45, 45, 45, 46, 46, 47, - 47, 47, 48, 48, 48, 49, 49, 49, 50, 50, 50, 51, 51, 51, 52, 52, - 52, 53, 53, 53, 54, 54, 54, 55, 55, 55, 56, 56, 56, 57, 57, 57 +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 ) { + 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; @@ -301,17 +316,14 @@ } 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 ) - { - // adjust last_amp so that "pop" amplitude will be properly non-linear - // with respect to change in dac - int old_amp = dac_table [dac]; - dac = data & 0x7F; - int diff = dac_table [dac] - old_amp; - last_amp = dac - diff; - } - - dac = data & 0x7F; + last_amp = faked_nonlinear; } } @@ -324,12 +336,12 @@ void Nes_Dmc::fill_buffer() { - if ( buf_empty && length_counter ) + if ( !buf_full && length_counter ) { - require( rom_reader ); // dmc_reader must be set + require( rom_reader ); // rom_reader must be set buf = rom_reader( rom_reader_data, 0x8000u + address ); address = (address + 1) & 0x7FFF; - buf_empty = false; + buf_full = true; if ( --length_counter == 0 ) { if ( regs [0] & loop_flag ) { @@ -347,18 +359,17 @@ void Nes_Dmc::run( nes_time_t time, nes_time_t end_time ) { + int delta = update_amp( dac ); if ( !output ) - return; - - int delta = update_amp( dac ); - if ( delta ) + 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_empty ) + if ( silence && !buf_full ) { int count = (end_time - time + period - 1) / period; bits_remain = (bits_remain - 1 + 8 - (count % 8)) % 8 + 1; @@ -375,7 +386,7 @@ { if ( !silence ) { - const int step = (bits & 1) * 4 - 2; + int step = (bits & 1) * 4 - 2; bits >>= 1; if ( unsigned (dac + step) <= 0x7F ) { dac += step; @@ -388,13 +399,15 @@ if ( --bits_remain == 0 ) { bits_remain = 8; - if ( buf_empty ) { + if ( !buf_full ) { silence = true; } else { silence = false; bits = buf; - buf_empty = true; + buf_full = false; + if ( !output ) + silence = true; fill_buffer(); } } @@ -441,7 +454,7 @@ // round to next multiple of period time += (end_time - time + period - 1) / period * period; - // approximate noise cycling while muted, by shuffling up noise register a bit + // 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); @@ -453,8 +466,8 @@ Blip_Buffer* const output = this->output; // using resampled time avoids conversion in synth.offset() - Blip_Buffer::resampled_time_t rperiod = output->resampled_duration( period ); - Blip_Buffer::resampled_time_t rtime = output->resampled_time( time ); + 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;
--- a/Plugins/Input/console/Nes_Oscs.h Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Nes_Oscs.h Tue Jan 24 19:10:07 2006 -0800 @@ -1,11 +1,12 @@ // Private oscillators used by Nes_Apu -// Nes_Snd_Emu 0.1.6. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. +// 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; @@ -57,8 +58,10 @@ int phase; int sweep_delay; - typedef Blip_Synth<blip_good_quality,15> Synth; - const Synth* synth; // shared between squares + typedef Blip_Synth<blip_good_quality,1> 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 ); @@ -74,8 +77,9 @@ enum { phase_range = 16 }; int phase; int linear_counter; - Blip_Synth<blip_good_quality,15> synth; + Blip_Synth<blip_med_quality,1> synth; + int calc_amp() const; void run( nes_time_t, nes_time_t ); void clock_linear_counter(); void reset() { @@ -89,7 +93,7 @@ struct Nes_Noise : Nes_Envelope { int noise; - Blip_Synth<blip_med_quality,15> synth; + Blip_Synth<blip_med_quality,1> synth; void run( nes_time_t, nes_time_t ); void reset() { @@ -107,7 +111,7 @@ int buf; int bits_remain; int bits; - bool buf_empty; + bool buf_full; bool silence; enum { loop_flag = 0x40 }; @@ -125,7 +129,7 @@ Nes_Apu* apu; - Blip_Synth<blip_med_quality,127> synth; + Blip_Synth<blip_med_quality,1> synth; void start(); void write_register( int, int ); @@ -135,6 +139,7 @@ void reload_sample(); void reset(); int count_reads( nes_time_t, nes_time_t* ) const; + nes_time_t next_read_time() const; }; #endif
--- a/Plugins/Input/console/Nes_Vrc6.cpp Tue Jan 24 13:57:22 2006 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,213 +0,0 @@ - -// Nes_Snd_Emu 0.1.6. http://www.slack.net/~ant/libs/ - -#include "Nes_Vrc6.h" - -#include "Tagged_Data.h" - -/* Copyright (C) 2003-2005 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::Nes_Vrc6() -{ - output( NULL ); - volume( 1.0 ); - reset(); -} - -void Nes_Vrc6::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; - } -} - -Nes_Vrc6::~Nes_Vrc6() { -} - -void Nes_Vrc6::volume( double v ) -{ - v *= 0.0967 * 2; - saw_synth.volume( v ); - square_synth.volume( v * 0.5 ); -} - -void Nes_Vrc6::treble_eq( const blip_eq_t& eq ) -{ - saw_synth.treble_eq( eq ); - square_synth.treble_eq( eq ); -} - -void Nes_Vrc6::output( Blip_Buffer* buf ) -{ - for ( int i = 0; i < osc_count; i++ ) - osc_output( i, buf ); -} - -void Nes_Vrc6::run_until( nes_time_t time ) -{ - run_square( oscs [0], time ); - run_square( oscs [1], time ); - run_saw( time ); - last_time = time; -} - -void Nes_Vrc6::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; - - // to do: remove? this messed up volume envelope in Akumajou Densetsu track 22 - //if ( osc_index == 2 && reg == 2 ) - // oscs [2].amp = 0; -} - -void Nes_Vrc6::end_frame( nes_time_t time ) -{ - run_until( time ); - last_time -= time; - assert( last_time >= 0 ); -} - -#define chars_to_long(s) ( (long)(s[0] << 24) | (long)(s[1] << 16) | \ - (long)(s[2] << 8) | (long)(s[3]) ) - -void Nes_Vrc6::reflect_state( Tagged_Data& data ) -{ - for ( int i = 0; i < osc_count; i++ ) - { - Tagged_Data odata( data, chars_to_long("cCH0") + i ); - Vrc6_Osc& osc = oscs [i]; - for ( int r = 0; r < reg_count; r++ ) - reflect_int16( odata, chars_to_long("REG0") + r, &osc.regs [r] ); - reflect_int16( odata, chars_to_long("DELY"), &osc.delay ); - reflect_int16( odata, chars_to_long("PHAS"), &osc.phase ); - if ( i == 2 ) - reflect_int16( odata, chars_to_long("AMPL"), &osc.amp ); - } -} - -#include BLARGG_ENABLE_OPTIMIZER - -void Nes_Vrc6::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::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; -} -
--- a/Plugins/Input/console/Nes_Vrc6.h Tue Jan 24 13:57:22 2006 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,75 +0,0 @@ - -// Konami VRC6 sound chip emulator - -// Nes_Snd_Emu 0.1.6. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. - -#ifndef NES_VRC6_H -#define NES_VRC6_H - -#include "Nes_Apu.h" - -class Tagged_Data; - -class Nes_Vrc6 { -public: - Nes_Vrc6(); - ~Nes_Vrc6(); - - // See Nes_Apu.h for reference. - - void volume( double ); - void treble_eq( const blip_eq_t& ); - void output( Blip_Buffer* ); - enum { osc_count = 3 }; - void osc_output( int index, Blip_Buffer* ); - void reset(); - - // 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 ); - - void end_frame( nes_time_t ); - void reflect_state( Tagged_Data& ); - -// End of public interface - -private: - // noncopyable - Nes_Vrc6( const Nes_Vrc6& ); - Nes_Vrc6& operator = ( const Nes_Vrc6& ); - - 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<blip_med_quality,31> saw_synth; - Blip_Synth<blip_good_quality,15> square_synth; - - void run_until( nes_time_t ); - void run_square( Vrc6_Osc& osc, nes_time_t ); - void run_saw( nes_time_t ); -}; - - inline void Nes_Vrc6::osc_output( int i, Blip_Buffer* buf ) { - assert( (unsigned) i < osc_count ); - oscs [i].output = buf; - } - -#endif -
--- a/Plugins/Input/console/Nsf_Emu.cpp Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Nsf_Emu.cpp Tue Jan 24 19:10:07 2006 -0800 @@ -1,14 +1,20 @@ -// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ #include "Nsf_Emu.h" #include <string.h> -#include "libaudacious/vfs.h" +#include <stdio.h> + +#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-2005 Shay Green. This module is free software; you +/* 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 @@ -21,6 +27,14 @@ #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; @@ -31,6 +45,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 @@ -44,11 +64,13 @@ unsigned bank = addr - bank_select_addr; if ( bank < bank_count ) { - if ( data < emu->total_banks ) { + if ( data < emu->total_banks ) + { emu->cpu.map_code( (bank + 8) * page_size, page_size, &emu->rom [data * page_size] ); } - else { + else + { dprintf( "Bank %d out of range (%d banks total)\n", data, (int) emu->total_banks ); } @@ -79,12 +101,12 @@ int Nsf_Emu::read_low_mem( Nsf_Emu* emu, nes_addr_t addr ) { - return emu->cpu.low_mem [addr & (low_mem_size - 1)]; + 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 & (low_mem_size - 1)] = data; + emu->cpu.low_mem [addr] = data; } // SRAM @@ -104,30 +126,45 @@ // Namco int Nsf_Emu::read_namco( Nsf_Emu* emu, nes_addr_t addr ) { - if ( addr == Nes_Namco::data_reg_addr ) - return emu->namco.read_data(); + 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::data_reg_addr ) - emu->namco.write_data( emu->cpu.time(), 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::addr_reg_addr ) - emu->namco.write_addr( 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::addr_step - 1); - unsigned osc = unsigned (addr - Nes_Vrc6::base_addr) / Nes_Vrc6::addr_step; - if ( osc < Nes_Vrc6::osc_count && reg < Nes_Vrc6::reg_count ) - emu->vrc6.write_osc( emu->cpu.time(), osc, reg, 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 @@ -136,48 +173,65 @@ int Nsf_Emu::read_unmapped( Nsf_Emu*, nes_addr_t addr ) { dprintf( "Read unmapped $%.4X\n", (unsigned) addr ); - return addr >> 8; // high byte of address stays on bus + return (addr >> 8) & 0xff; // high byte of address stays on bus } -void Nsf_Emu::write_unmapped( Nsf_Emu*, nes_addr_t addr, int ) +void Nsf_Emu::write_unmapped( Nsf_Emu*, nes_addr_t addr, int data ) { - if (// some games write to $8000 and $8001 repeatedly - addr != 0x8000 && addr != 0x8001 && - - // probably namco sound mistakenly turned on in mck - addr != 0x4800 && addr != 0xF800 && - - // memory mapper? - addr != 0xFFF8 ) - { - dprintf( "Write unmapped $%.4X\n", (unsigned) addr ); - } + #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 ); } -static BOOST::uint8_t unmapped_code [Nes_Cpu::page_size]; - -Nsf_Emu::Nsf_Emu( double gain_ ) +Nes_Emu::Nes_Emu( double gain_ ) { - rom = NULL; + cpu.set_emu( this ); play_addr = 0; - clocks_per_msec = 0; gain = gain_; - cpu.callback_data = this; - set_equalizer( equalizer_t( -8.87, 8800, 110 ) ); 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 ); } -Nsf_Emu::~Nsf_Emu() +Nes_Emu::~Nes_Emu() { unload(); } void Nsf_Emu::unload() { - delete [] rom; - rom = NULL; + #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 @@ -193,58 +247,102 @@ "Square 1", "Square 2", "Triangle", "Noise", "DMC", "VRC6 Square 1", "VRC6 Square 2", "VRC6 Saw" }; - if ( exp_flags & namco_flag ) - return namco_names; - if ( exp_flags & vrc6_flag ) + 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) ) + if ( exp_flags & ~(namco_flag | vrc6_flag | fme7_flag) ) return "NSF requires unsupported expansion audio hardware"; // map memory - cpu.reset( unmapped_code ); - cpu.map_memory( 0, ram_size, read_unmapped, write_unmapped ); // unmapped - cpu.map_memory( 0, low_mem_size, read_low_mem, write_low_mem ); // low mem + 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 ); // apu - cpu.map_memory( exram_addr, Nes_Cpu::page_size, read_unmapped, write_exram ); // exram - cpu.map_memory( 0x6000, sram_size, read_sram, write_sram ); // sram + 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 ); // rom + cpu.map_memory( rom_begin, ram_size - rom_begin, read_code, write_unmapped ); - voice_count_ = Nes_Apu::osc_count; + set_voice_count( Nes_Apu::osc_count ); double adjusted_gain = gain; -#if NSF_EMU_APU_ONLY + #if NSF_EMU_APU_ONLY + if ( exp_flags ) + return "NSF requires expansion audio hardware"; + #else + if ( exp_flags ) - return "NSF requires expansion audio hardware"; -#else + set_voice_count( Nes_Apu::osc_count + 3 ); + // namco - if ( exp_flags & namco_flag ) { + if ( exp_flags & namco_flag ) + { + namco = BLARGG_NEW Nes_Namco_Apu; + BLARGG_CHECK_ALLOC( namco ); + adjusted_gain *= 0.75; - voice_count_ += 3; - cpu.map_memory( Nes_Namco::data_reg_addr, Nes_Cpu::page_size, + cpu.map_memory( Nes_Namco_Apu::data_reg_addr, Nes_Cpu::page_size, read_namco, write_namco ); - cpu.map_memory( Nes_Namco::addr_reg_addr, Nes_Cpu::page_size, + cpu.map_memory( Nes_Namco_Apu::addr_reg_addr, Nes_Cpu::page_size, read_code, write_namco_addr ); } // vrc6 - if ( exp_flags & vrc6_flag ) { + if ( exp_flags & vrc6_flag ) + { + vrc6 = BLARGG_NEW Nes_Vrc6_Apu; + BLARGG_CHECK_ALLOC( vrc6 ); + adjusted_gain *= 0.75; - voice_count_ += 3; - for ( int i = 0; i < Nes_Vrc6::osc_count; i++ ) - cpu.map_memory( Nes_Vrc6::base_addr + i * Nes_Vrc6::addr_step, + 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 ); } - namco.volume( adjusted_gain ); - vrc6.volume( adjusted_gain ); + // 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 ); @@ -254,33 +352,48 @@ void Nsf_Emu::update_eq( blip_eq_t const& eq ) { -#if !NSF_EMU_APU_ONLY - vrc6.treble_eq( eq ); - namco.treble_eq( eq ); -#endif 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( const header_t& h, Emu_Reader& in ) +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( h.tag, "NESM\x1A", 5 ) ) + if ( 0 != memcmp( header_.tag, "NESM\x1A", 5 ) ) return "Not an NSF file"; - if ( h.vers != 1 ) + if ( header_.vers != 1 ) return "Unsupported NSF format"; // sound and memory - exp_flags = h.chip_flags; + 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( h.load_addr ); - init_addr = get_le16( h.init_addr ); - play_addr = get_le16( h.play_addr ); + 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; @@ -289,13 +402,11 @@ // set up rom total_banks = (in.remain() + load_addr % page_size + page_size - 1) / page_size; - long rom_size = total_banks * page_size; - rom = new byte [rom_size]; - if ( !rom ) - return "Out of memory"; - memset( rom, 0, rom_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 ) { + if ( err ) + { unload(); return err; } @@ -305,76 +416,88 @@ for ( int i = 0; i < bank_count; i++ ) { unsigned bank = i - first_bank; - initial_banks [i] = (bank < total_banks) ? bank : 0; + initial_banks [i] = (bank < (unsigned) total_banks) ? bank : 0; - if ( h.banks [i] ) { + if ( header_.banks [i] ) + { // bank-switched - memcpy( initial_banks, h.banks, sizeof initial_banks ); + memcpy( initial_banks, header_.banks, sizeof initial_banks ); break; } } // playback rate - unsigned playback_rate = get_le16( h.ntsc_speed ); + 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 ( (h.speed_flags & 3) == 1 ) { + 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( h.pal_speed ); + playback_rate = get_le16( header_.pal_speed ); } - clocks_per_msec = clock_rate * (1.0 / 1000.0); - // 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 = h.speed_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; - track_count_ = h.track_count; + set_track_count( header_.track_count ); - return setup_buffer( ( int) (clock_rate + 0.5) ); + return setup_buffer( (long) (clock_rate + 0.5) ); } void Nsf_Emu::set_voice( int i, Blip_Buffer* buf, Blip_Buffer*, Blip_Buffer* ) { -#if !NSF_EMU_APU_ONLY - if ( i >= Nes_Apu::osc_count ) { - vrc6.osc_output( i - Nes_Apu::osc_count, buf ); - 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 ); - } + if ( i < Nes_Apu::osc_count ) + { + apu.osc_output( i, buf ); return; } -#endif - apu.osc_output( i, buf ); + + #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 } -blargg_err_t Nsf_Emu::start_track( int track ) +void Nsf_Emu::start_track( int track ) { - require( rom ); // file must be loaded + require( rom.size() ); // file must be loaded - starting_track(); + Classic_Emu::start_track( track ); // clear memory memset( cpu.low_mem, 0, sizeof cpu.low_mem ); @@ -389,12 +512,16 @@ apu.write_register( 0, 0x4015, 0x0F ); apu.write_register( 0, 0x4017, needs_long_frames ? 0x80 : 0 ); -#if !NSF_EMU_APU_ONLY - if ( exp_flags ) { - namco.reset(); - vrc6.reset(); - } -#endif + #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; @@ -408,49 +535,66 @@ cpu_jsr( init_addr, -1 ); next_play = 0; play_extra = 0; - - return blargg_success; } void Nsf_Emu::cpu_jsr( nes_addr_t pc, int adj ) { - unsigned ret_addr = cpu.r.pc + adj; + unsigned addr = cpu.r.pc + adj; cpu.r.pc = pc; - cpu.low_mem [cpu.r.sp-- + 0x100] = ret_addr >> 8; - cpu.low_mem [cpu.r.sp-- + 0x100] = ret_addr; + cpu.push_byte( addr >> 8 ); + cpu.push_byte( addr ); } -blip_time_t Nsf_Emu::run( int msec, bool* ) +void Nsf_Emu::call_play() +{ + cpu_jsr( play_addr, -1 ); +} + +blip_time_t Nsf_Emu::run_clocks( blip_time_t duration, bool* ) { // run cpu - blip_time_t duration = (blip_time_t) (clocks_per_msec * msec); - cpu.time( 0 ); + 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.time( duration ); + if ( next_play > duration ) + { + cpu.set_time( duration ); break; } if ( next_play > cpu.time() ) - cpu.time( next_play ); + 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; - cpu_jsr( play_addr, -1 ); + call_play(); } - Nes_Cpu::result_t result = cpu.run( duration ); + Nes_Cpu::result_t result = RUN_NES_CPU( cpu, duration ); if ( result == Nes_Cpu::result_badop && cpu.r.pc != exram_addr ) { - dprintf( "Bad opcode $%.2x at $%.4x\n", - (int) cpu.read( cpu.r.pc ), (int) cpu.r.pc ); - - return 0; // error + 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 ); + } + } } } @@ -461,38 +605,18 @@ next_play = 0; apu.end_frame( duration ); -#if !NSF_EMU_APU_ONLY - if ( exp_flags & namco_flag ) - namco.end_frame( duration ); - if ( exp_flags & vrc6_flag ) - vrc6.end_frame( duration ); -#endif + #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; } -Nsf_Reader::Nsf_Reader() : file( NULL ) { -} - -Nsf_Reader::~Nsf_Reader() { - close(); -} - -blargg_err_t Nsf_Reader::read_head(Nsf_Emu::header_t *header) { - vfs_fread(&header->tag, 1, 5,file); - vfs_fread(&header->vers, 1, 1,file); - vfs_fread(&header->track_count, 1, 1,file); - vfs_fread(&header->first_track, 1, 1,file); - vfs_fread(&header->load_addr, 1, 2,file); - vfs_fread(&header->init_addr, 1, 2,file); - vfs_fread(&header->play_addr, 1, 2,file); - vfs_fread(&header->game, 1,32,file); - vfs_fread(&header->author, 1,32,file); - vfs_fread(&header->copyright, 1,32,file); - vfs_fread(&header->ntsc_speed, 1, 2,file); - vfs_fread(&header->banks, 1, 3,file); - vfs_fread(&header->pal_speed, 1, 2,file); - vfs_fread(&header->speed_flags, 1, 1,file); - vfs_fread(&header->chip_flags, 1, 1,file); - vfs_fread(&header->unused, 1, 2,file); -}
--- a/Plugins/Input/console/Nsf_Emu.h Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Nsf_Emu.h Tue Jan 24 19:10:07 2006 -0800 @@ -1,7 +1,7 @@ -// Nintendo Entertainment System (NES) NSF-format game music file emulator +// Nintendo Entertainment System (NES) NSF music file emulator -// Game_Music_Emu 0.2.4. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. +// Game_Music_Emu 0.3.0 #ifndef NSF_EMU_H #define NSF_EMU_H @@ -10,20 +10,18 @@ #include "Nes_Apu.h" #include "Nes_Cpu.h" -// If NSF_EMU_APU_ONLY is non-zero, external sound chip support is disabled -#if !NSF_EMU_APU_ONLY - #include "Nes_Vrc6.h" - #include "Nes_Namco.h" -#endif +typedef Nes_Emu Nsf_Emu; -class Nsf_Emu : public Classic_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. - Nsf_Emu( double gain = 1.4 ); - ~Nsf_Emu(); + Nes_Emu( double gain = 1.4 ); - struct header_t { + // NSF file header + struct header_t + { char tag [5]; byte vers; byte track_count; @@ -45,20 +43,30 @@ }; BOOST_STATIC_ASSERT( sizeof (header_t) == 0x80 ); - // Load NSF, given its header and reader for remaining data - blargg_err_t load( const header_t&, Emu_Reader& ); + // 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& ); - blargg_err_t start_track( int ); + // 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; - - -// End of public interface protected: void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); void update_eq( blip_eq_t const& ); - blip_time_t run( int, bool* ); -private: + 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]; @@ -71,7 +79,6 @@ int exp_flags; // timing - double clocks_per_msec; nes_time_t next_play; long play_period; int play_extra; @@ -81,10 +88,29 @@ // rom int total_banks; - byte* rom; + blargg_vector<byte> 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 ); @@ -94,43 +120,18 @@ static void write_unmapped( Nsf_Emu*, nes_addr_t, int ); static void write_exram( Nsf_Emu*, nes_addr_t, int ); - blargg_err_t init_sound(); - // 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 ); -#if !NSF_EMU_APU_ONLY - // namco - Nes_Namco 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 ); - - // vrc6 - Nes_Vrc6 vrc6; - static void write_vrc6( Nsf_Emu*, nes_addr_t, int ); -#endif - // 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 ); - - friend class Nsf_Remote_Emu; // hack }; -class Nsf_Reader : public Std_File_Reader { - VFSFile* file; -public: - Nsf_Reader(); - ~Nsf_Reader(); - - // Custom reader for SPC headers [tempfix] - blargg_err_t read_head( Nsf_Emu::header_t* ); -}; #endif
--- a/Plugins/Input/console/Panning_Buffer.cpp Tue Jan 24 13:57:22 2006 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,180 +0,0 @@ - -// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ - -#include "Panning_Buffer.h" - -#include <string.h> - -/* Copyright (C) 2003-2005 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 - -typedef long fixed_t; - -#define TO_FIXED( f ) fixed_t ((f) * (1L << 15) + 0.5) -#define FMUL( x, y ) (((x) * (y)) >> 15) - -Panning_Buffer::Panning_Buffer() -{ - bufs = NULL; - buf_count = 0; - bass_freq_ = -1; - clock_rate_ = -1; -} - -Panning_Buffer::~Panning_Buffer() -{ - delete [] bufs; -} - -blargg_err_t Panning_Buffer::sample_rate( long rate, int msec ) -{ - for ( int i = 0; i < buf_count; i++ ) - BLARGG_RETURN_ERR( bufs [i].sample_rate( rate, msec ) ); - sample_rate_ = rate; - length_ = buf_count ? bufs [0].length() : msec; - return blargg_success; -} - -void Panning_Buffer::bass_freq( int freq ) -{ - bass_freq_ = freq; - for ( int i = 0; i < buf_count; i++ ) - bufs [i].bass_freq( freq ); -} - -void Panning_Buffer::clock_rate( long rate ) -{ - clock_rate_ = rate; - for ( int i = 0; i < buf_count; i++ ) - bufs [i].clock_rate( clock_rate_ ); -} - -void Panning_Buffer::clear() -{ - for ( int i = 0; i < buf_count; i++ ) - bufs [i].clear(); -} - -blargg_err_t Panning_Buffer::set_channel_count( int count ) -{ - count += 2; - if ( count != buf_count ) - { - delete [] bufs; - bufs = NULL; - - bufs = new buf_t [count]; - if ( !bufs ) - return "Out of memory"; - - buf_count = count; - - if ( sample_rate_ ) - BLARGG_RETURN_ERR( sample_rate( sample_rate_, length_ ) ); - - if ( clock_rate_ >= 0 ) - clock_rate( clock_rate_ ); - - if ( bass_freq_ >= 0 ) - bass_freq( bass_freq_ ); - - set_pan( left_chan, 1.0, 0.0 ); - set_pan( right_chan, 0.0, 1.0 ); - for ( int i = 0; i < buf_count - 2; i++ ) - set_pan( i, 1.0, 1.0 ); - } - return blargg_success; -} - -Panning_Buffer::channel_t Panning_Buffer::channel( int i ) -{ - i += 2; - require( i < buf_count ); - channel_t ch; - ch.center = &bufs [i]; - ch.left = &bufs [buf_count]; - ch.right = &bufs [buf_count]; - return ch; -} - - -void Panning_Buffer::set_pan( int i, double left, double right ) -{ - i += 2; - require( i < buf_count ); - bufs [i].left_gain = TO_FIXED( left ); - bufs [i].right_gain = TO_FIXED( right ); -} - - -void Panning_Buffer::end_frame( blip_time_t time, bool ) -{ - for ( int i = 0; i < buf_count; i++ ) - bufs [i].end_frame( time ); -} - -long Panning_Buffer::read_samples( blip_sample_t* out, long count ) -{ - require( count % 2 == 0 ); // count must be even - - long avail = bufs [0].samples_avail() * 2; - if ( count > avail ) - count = avail; - - if ( count ) - { - memset( out, 0, count * sizeof *out ); - - int pair_count = count >> 1; - - int i; - for ( i = 0; i < buf_count; i++ ) - add_panned( bufs [i], out, pair_count ); - - for ( i = 0; i < buf_count; i++ ) - bufs [i].remove_samples( pair_count ); - } - return count; -} - -#include BLARGG_ENABLE_OPTIMIZER - -void Panning_Buffer::add_panned( buf_t& buf, blip_sample_t* out, long count ) -{ - Blip_Reader in; - - fixed_t left_gain = buf.left_gain; - fixed_t right_gain = buf.right_gain; - int bass = in.begin( buf ); - - while ( count-- ) - { - long s = in.read(); - long l = out [0] + FMUL( s, left_gain ); - long r = out [1] + FMUL( s, right_gain ); - in.next(); - - if ( (BOOST::int16_t) l != l ) - l = 0x7FFF - (l >> 24); - - out [0] = l; - out [1] = r; - out += 2; - - if ( (BOOST::int16_t) r != r ) - out [-1] = 0x7FFF - (r >> 24); - } - - in.end( buf ); -} -
--- a/Plugins/Input/console/Panning_Buffer.h Tue Jan 24 13:57:22 2006 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ - -// Multi-channel buffer with pan control for each buffer - -// Game_Music_Emu 0.2.4. Copyright (C) 2004 Shay Green. GNU LGPL license. - -#ifndef PANNING_BUFFER_H -#define PANNING_BUFFER_H - -#include "Multi_Buffer.h" - -// Panning_Buffer uses several buffers and outputs stereo sample pairs. -class Panning_Buffer : public Multi_Buffer { -public: - Panning_Buffer(); - ~Panning_Buffer(); - - // Set pan of a channel, using left and right gain values (1.0 = normal). - // Use left_chan and right_chan for the common left and right buffers used - // by all channels. - enum { left_chan = -2 }; - enum { right_chan = -1 }; - void set_pan( int channel, double left, double right ); - - // See Multi_Buffer.h - blargg_err_t sample_rate( long rate, int msec ); - void clock_rate( long ); - void bass_freq( int ); - void clear(); - blargg_err_t set_channel_count( int ); - channel_t channel( int ); - void end_frame( blip_time_t, bool unused = true ); - long read_samples( blip_sample_t*, long ); - -private: - typedef long fixed_t; - - struct buf_t : Blip_Buffer { - fixed_t left_gain; - fixed_t right_gain; - }; - buf_t* bufs; - int buf_count; - long clock_rate_; - int bass_freq_; - - void add_panned( buf_t&, blip_sample_t*, long ); -}; - -#endif -
--- a/Plugins/Input/console/Sms_Apu.cpp Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Sms_Apu.cpp Tue Jan 24 19:10:07 2006 -0800 @@ -1,9 +1,9 @@ -// Sms_Snd_Emu 0.1.3. http://www.slack.net/~ant/libs/ +// Sms_Snd_Emu 0.1.3. http://www.slack.net/~ant/ #include "Sms_Apu.h" -/* Copyright (C) 2003-2005 Shay Green. This module is free software; you +/* 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 @@ -38,10 +38,7 @@ // Sms_Square -Sms_Square::Sms_Square() { -} - -void Sms_Square::reset() +inline void Sms_Square::reset() { period = 0; phase = 0; @@ -53,15 +50,18 @@ if ( !volume || period <= 128 ) { // ignore 16kHz and higher - if ( last_amp ) { + if ( last_amp ) + { synth->offset( time, -last_amp, output ); last_amp = 0; } time += delay; - if ( !period ) { + if ( !period ) + { time = end_time; } - else if ( time < end_time ) { + else if ( time < end_time ) + { // keep calculating phase int count = (end_time - time + period - 1) / period; phase = (phase + count) & 1; @@ -71,19 +71,22 @@ else { int amp = phase ? volume : -volume; - if ( amp != last_amp ) { - synth->offset( time, amp - last_amp, output ); + 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; - amp *= 2; - do { - amp = -amp; // amp always alternates - synth->offset_inline( time, amp, output ); + int delta = amp * 2; + do + { + delta = -delta; + synth->offset_inline( time, delta, output ); time += period; phase ^= 1; } @@ -98,9 +101,6 @@ static const int noise_periods [3] = { 0x100, 0x200, 0x400 }; -inline Sms_Noise::Sms_Noise() { -} - inline void Sms_Noise::reset() { period = &noise_periods [0]; @@ -111,55 +111,55 @@ void Sms_Noise::run( sms_time_t time, sms_time_t end_time ) { - int cur_amp = 0; - int period = *this->period * 2; - if ( !volume ) { - if ( last_amp ) { - synth.offset( time, -last_amp, output ); - last_amp = 0; - } - delay = 0; + int amp = volume; + if ( shifter & 1 ) + amp = -amp; + + int delta = amp - last_amp; + if ( delta ) + { + last_amp = amp; + synth.offset( time, delta, output ); } - else + + time += delay; + if ( !volume ) + time = end_time; + + if ( time < end_time ) { - int amp = (shifter & 1) ? -volume : volume; + Blip_Buffer* const output = this->output; + unsigned shifter = this->shifter; + int delta = amp * 2; + int period = *this->period * 2; if ( !period ) period = 16; - if ( amp != last_amp ) { - synth.offset( time, amp - last_amp, output ); - last_amp = amp; - } - time += delay; - if ( time < end_time ) + do { - Blip_Buffer* const output = this->output; - unsigned shifter = this->shifter; - amp *= 2; - - do { - int changed = (shifter + 1) & 2; - shifter = (((shifter << 15) ^ (shifter << tap)) & 0x8000) | (shifter >> 1); - if ( changed ) { // prev and next bits differ - amp = -amp; - synth.offset_inline( time, amp, output ); - } - time += period; + 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 ); } - while ( time < end_time ); - - this->shifter = shifter; - this->last_amp = amp >> 1; + time += period; } - delay = time - end_time; + 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++ ) { + for ( int i = 0; i < 3; i++ ) + { squares [i].synth = &square_synth; oscs [i] = &squares [i]; } @@ -169,7 +169,15 @@ reset(); } -Sms_Apu::~Sms_Apu() { +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 ) @@ -178,11 +186,15 @@ noise.synth.treble_eq( eq ); } -void Sms_Apu::volume( double vol ) +void Sms_Apu::osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) { - vol *= 0.85 / osc_count; - square_synth.volume( vol ); - noise.synth.volume( vol ); + 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 ) @@ -191,29 +203,6 @@ osc_output( i, center, left, right ); } -void Sms_Apu::osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, - Blip_Buffer* right ) -{ - require( (unsigned) index < osc_count ); - - Sms_Osc& osc = *oscs [index]; - if ( center && !left && !right ) - { - // mono - left = center; - right = center; - } - else - { - // must be silenced or stereo - require( (!left && !right) || (left && right) ); - } - osc.outputs [1] = right; - osc.outputs [2] = left; - osc.outputs [3] = center; - osc.output = osc.outputs [osc.output_select]; -} - void Sms_Apu::reset() { stereo_found = false; @@ -233,12 +222,18 @@ if ( end_time > last_time ) { // run oscillators - for ( int i = 0; i < osc_count; ++i ) { + for ( int i = 0; i < osc_count; ++i ) + { Sms_Osc& osc = *oscs [i]; - if ( osc.output ) { + if ( osc.output ) + { if ( osc.output != osc.outputs [3] ) stereo_found = true; // playing on side output - osc.run( last_time, end_time ); + + if ( i < 3 ) + squares [i].run( last_time, end_time ); + else + noise.run( last_time, end_time ); } } @@ -248,8 +243,11 @@ bool Sms_Apu::end_frame( sms_time_t end_time ) { - run_until( end_time ); - last_time = 0; + 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; @@ -258,19 +256,19 @@ void Sms_Apu::write_ggstereo( sms_time_t time, int data ) { - require( (unsigned) data <= 0xff ); + require( (unsigned) data <= 0xFF ); run_until( time ); - // left/right assignments 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_select = (flags >> 3 & 2) | (flags & 1); osc.output = osc.outputs [osc.output_select]; - if ( osc.output != old_output && osc.last_amp ) { + if ( osc.output != old_output && osc.last_amp ) + { if ( old_output ) square_synth.offset( time, -osc.last_amp, old_output ); osc.last_amp = 0; @@ -278,14 +276,14 @@ } } -static const char volumes [16] = { +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 ); + require( (unsigned) data <= 0xFF ); run_until( time ); @@ -295,28 +293,26 @@ int index = (latch >> 5) & 3; if ( latch & 0x10 ) { - // volume oscs [index]->volume = volumes [data & 15]; } else if ( index < 3 ) { - // square period Sms_Square& sq = squares [index]; if ( data & 0x80 ) - sq.period = (sq.period & ~0xff) | ((data << 4) & 0xff); + sq.period = (sq.period & 0xFF00) | (data << 4 & 0x00FF); else - sq.period = (sq.period & 0xff) | ((data << 8) & 0x3f00); + sq.period = (sq.period & 0x00FF) | (data << 8 & 0x3F00); } else { - // noise period/mode int select = data & 3; if ( select < 3 ) noise.period = &noise_periods [select]; else noise.period = &squares [2].period; - noise.tap = (data & 0x04) ? 12 : 16; // 16 disables tap + int const tap_disabled = 16; + noise.tap = (data & 0x04) ? 12 : tap_disabled; noise.shifter = 0x8000; } }
--- a/Plugins/Input/console/Sms_Apu.h Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Sms_Apu.h Tue Jan 24 19:10:07 2006 -0800 @@ -1,7 +1,7 @@ // Sega Master System SN76489 PSG sound chip emulator -// Sms_Snd_Emu 0.1.3. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. +// Sms_Snd_Emu 0.1.3 #ifndef SMS_APU_H #define SMS_APU_H @@ -12,28 +12,28 @@ class Sms_Apu { public: - Sms_Apu(); - ~Sms_Apu(); - - // Overall volume of all oscillators, where 1.0 is full volume. + // Set overall volume of all oscillators, where 1.0 is full volume void volume( double ); - // Treble equalization (see notes.txt). + // 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, silence all oscillators. + // is NULL, silences all oscillators. void output( Blip_Buffer* mono ); void output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ); - // Assign oscillator output to buffer(s). Valid indicies are 0 to - // osc_count - 1, which refer to Square 1, Square 2, Square 3, and - // Noise, respectively. If buffer is NULL, silence oscillator. + // 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 + // Reset oscillators and internal state void reset(); // Write GameGear left/right assignment byte @@ -43,13 +43,14 @@ 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. Return true if any oscillators added + // 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 ); - - - // End of public interface + +public: + Sms_Apu(); + ~Sms_Apu(); private: // noncopyable Sms_Apu( const Sms_Apu& ); @@ -57,22 +58,24 @@ Sms_Osc* oscs [osc_count]; Sms_Square squares [3]; - Sms_Noise noise; - Sms_Square::Synth square_synth; // shared between squares + 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 ); }; -inline void Sms_Apu::output( Blip_Buffer* mono ) { - output( mono, NULL, NULL ); -} +struct sms_apu_state_t +{ + unsigned char regs [8] [2]; + unsigned char latch; +}; -inline void Sms_Apu::osc_output( int index, Blip_Buffer* mono ) { - osc_output( index, mono, NULL, NULL ); -} +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
--- a/Plugins/Input/console/Sms_Oscs.h Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Sms_Oscs.h Tue Jan 24 19:10:07 2006 -0800 @@ -1,11 +1,12 @@ // Private oscillators used by Sms_Apu -// Sms_Snd_Emu 0.1.3. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. +// 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 @@ -20,7 +21,6 @@ Sms_Osc(); void reset(); - virtual void run( sms_time_t start, sms_time_t end ) = 0; }; struct Sms_Square : Sms_Osc @@ -28,10 +28,9 @@ int period; int phase; - typedef Blip_Synth<blip_good_quality,64 * 2> Synth; + typedef Blip_Synth<blip_good_quality,1> Synth; const Synth* synth; - Sms_Square(); void reset(); void run( sms_time_t, sms_time_t ); }; @@ -42,10 +41,9 @@ unsigned shifter; unsigned tap; - typedef Blip_Synth<blip_med_quality,64 * 2> Synth; + typedef Blip_Synth<blip_med_quality,1> Synth; Synth synth; - Sms_Noise(); void reset(); void run( sms_time_t, sms_time_t ); };
--- a/Plugins/Input/console/Snes_Spc.cpp Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Snes_Spc.cpp Tue Jan 24 19:10:07 2006 -0800 @@ -1,13 +1,12 @@ -// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ #include "Snes_Spc.h" #include <assert.h> #include <string.h> -#include <cstdio> -/* Copyright (C) 2004-2005 Shay Green. This module is free software; you +/* 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 @@ -20,7 +19,10 @@ #include BLARGG_SOURCE_BEGIN -Snes_Spc::Snes_Spc() : cpu( ram, this ), dsp( ram ) +// 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 @@ -32,7 +34,7 @@ // Load -const char* Snes_Spc::load_spc( const void* data, long size, int clear_echo_ ) +blargg_err_t Snes_Spc::load_spc( const void* data, long size, bool clear_echo_ ) { struct spc_file_t { char signature [27]; @@ -88,7 +90,7 @@ // Handle other file formats (emulator save states) in user code, not here. -const char* Snes_Spc::load_state( const registers_t& cpu_state, const void* new_ram, +blargg_err_t Snes_Spc::load_state( const registers_t& cpu_state, const void* new_ram, const void* dsp_state ) { // cpu @@ -120,9 +122,11 @@ { 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.next_tick = 0; t.counter = ram [0xfd + i] & 15; int p = ram [0xfa + i]; @@ -158,6 +162,8 @@ 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; @@ -264,9 +270,10 @@ 0x5D, 0xD0, 0xDB, 0x1F, 0x00, 0x00, 0xC0, 0xFF }; -void Snes_Spc::enable_rom( int enable ) +void Snes_Spc::enable_rom( bool enable ) { - if ( rom_enabled != enable ) { + if ( rom_enabled != enable ) + { rom_enabled = enable; memcpy( ram + rom_addr, (enable ? boot_rom : extra_ram), rom_size ); } @@ -333,7 +340,7 @@ Timer& t = timer [i]; if ( !(data & (1 << i)) ) { t.enabled = 0; - t.next_tick = 0; + t.next_tick = timer_disabled_time; } else if ( !t.enabled ) { // just enabled @@ -424,7 +431,7 @@ 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; @@ -435,9 +442,11 @@ // 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++ ) { + for ( int i = 0; i < timer_count; i++ ) + { Timer& t = timer [i]; - if ( t.enabled ) { + if ( t.enabled ) + { t.next_tick -= duration; t.run_until( -duration ); }
--- a/Plugins/Input/console/Snes_Spc.h Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Snes_Spc.h Tue Jan 24 19:10:07 2006 -0800 @@ -1,7 +1,7 @@ // Super Nintendo (SNES) SPC-700 APU Emulator -// Game_Music_Emu 0.2.4. Copyright (C) 2004-2005 Shay Green. GNU LGPL license. +// Game_Music_Emu 0.3.0 #ifndef SNES_SPC_H #define SNES_SPC_H @@ -12,11 +12,13 @@ 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, int clear_echo = 1 ); + 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; @@ -42,13 +44,14 @@ // 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: - typedef BOOST::uint8_t uint8_t; - // timers - struct Timer { + struct Timer + { spc_time_t next_tick; int period; int count; @@ -57,7 +60,8 @@ int enabled; void run_until_( spc_time_t ); - void run_until( spc_time_t time ) { + void run_until( spc_time_t time ) + { if ( time >= next_tick ) run_until_( time ); } @@ -66,21 +70,12 @@ Timer timer [timer_count]; // hardware - Spc_Cpu cpu; int extra_cycles; spc_time_t time() const; int read( spc_addr_t ); void write( spc_addr_t, int ); friend class Spc_Cpu; - // boot rom - enum { rom_size = 64 }; - enum { rom_addr = 0xffc0 }; - int rom_enabled; - uint8_t extra_ram [rom_size]; - static const uint8_t boot_rom [rom_size]; - void enable_rom( int ); - // dsp sample_t* sample_buf; sample_t* buf_end; // to do: remove this once possible bug resolved @@ -92,20 +87,27 @@ void run_dsp( spc_time_t ); void run_dsp_( spc_time_t ); bool echo_accessed; - void check_for_echo_access( spc_addr_t addr ); + void check_for_echo_access( spc_addr_t ); - // 64KB RAM + padding filled with STOP instruction to catch PC overflow. + // 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]; + uint8_t ram [ram_size + 0x100]; // padding for catching jumps past end }; -inline void Snes_Spc::mute_voices( int mask ) { - dsp.mute_voices( mask ); -} +inline void Snes_Spc::disable_surround( bool disable ) { dsp.disable_surround( disable ); } -inline void Snes_Spc::set_gain( double v ) { - dsp.set_gain( v ); -} +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
--- a/Plugins/Input/console/Spc_Cpu.cpp Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Spc_Cpu.cpp Tue Jan 24 19:10:07 2006 -0800 @@ -1,5 +1,5 @@ -// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ #include "Spc_Cpu.h" @@ -8,7 +8,7 @@ #include "blargg_endian.h" #include "Snes_Spc.h" -/* Copyright (C) 2004-2005 Shay Green. This module is free software; you +/* 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 @@ -39,9 +39,7 @@ // 2% 0xF6 MOV A,abs+Y // (1% and below not shown) -Spc_Cpu::Spc_Cpu( uint8_t* ram_, Snes_Spc* e ) : - ram( ram_ ), - emu( *e ) +Spc_Cpu::Spc_Cpu( Snes_Spc* e, uint8_t* ram_in ) : ram( ram_in ), emu( *e ) { remain_ = 0; BOOST_STATIC_ASSERT( sizeof (int) >= 4 ); @@ -68,22 +66,23 @@ // Cycle table derived from text copy of SPC-700 manual (using regular expressions) static const unsigned char cycle_table [0x100] = { - 2,8,4,5,3,4,3,6,2,6,5,4,5,4,6,8, - 2,8,4,5,4,5,5,6,5,5,6,5,2,2,4,6, - 2,8,4,5,3,4,3,6,2,6,5,4,5,4,5,4, - 2,8,4,5,4,5,5,6,5,5,6,5,2,2,3,8, - 2,8,4,5,3,4,3,6,2,6,4,4,5,4,6,6, - 2,8,4,5,4,5,5,6,5,5,4,5,2,2,4,3, - 2,8,4,5,3,4,3,6,2,6,4,4,5,4,5,5, - 2,8,4,5,4,5,5,6,5,5,5,5,2,2,3,6, - 2,8,4,5,3,4,3,6,2,6,5,4,5,2,4,5, - 2,8,4,5,4,5,5,6,5,5,5,5,2,2,12,5, - 3,8,4,5,3,4,3,6,2,6,4,4,5,2,4,4, - 2,8,4,5,4,5,5,6,5,5,5,5,2,2,3,4, - 3,8,4,5,4,5,4,7,2,5,6,4,5,2,4,9, - 2,8,4,5,5,6,6,7,4,5,4,5,2,2,6,3, - 2,8,4,5,3,4,3,6,2,4,5,3,4,3,4,3, - 2,8,4,5,4,5,5,6,3,4,5,4,2,2,4,3 +// 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 @@ -101,9 +100,7 @@ { remain_ = cycle_count; -#if BLARGG_CPU_POWERPC uint8_t* const ram = this->ram; // cache -#endif // Stack pointer is kept one greater than usual SPC stack pointer to allow // common pre-decrement and post-increment memory instructions that some @@ -134,30 +131,27 @@ const int st_z = 0x02; const int st_c = 0x01; - // Special encoding for negative and zero being set simultaneously (by POP PSW). - // To do: be sure this works properly (I copied it from my NES 6502 emulator). - #define IS_NEG (int ((nz + 0x800) | (nz << (CHAR_BIT * sizeof (int) - 8))) < 0) + #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 ( !(uint8_t) nz ) out |= st_z; \ + 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; \ - nz &= 0x820; \ - nz ^= ~0xDF; \ + 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; // store Z as 'nz' & 0xFF == 0 (see above for encoding of N) + int nz; // Z set if (nz & 0xff) == 0, N set if (nz & 0x880) != 0 unsigned dp; // direct page base { int temp = r.status; @@ -170,43 +164,24 @@ // Common endings for instructions cbranch_taken_loop: // compare and branch - data = READ_PROG( pc ); -branch_taken_loop: // taken branch (displacement already in 'data') - pc += (BOOST::int8_t) data; // sign-extend + pc += (BOOST::int8_t) READ_PROG( pc ); remain_ -= 2; inc_pc_loop: // end of instruction with an operand pc++; loop: - // Be sure all registers are in range. PC and SP wrap-around isn't handled so - // those checks might fail, but a, x, and y should always be in range. check( (unsigned) pc < 0x10000 ); check( (unsigned) GET_SP() < 0x100 ); - check( (unsigned) a < 0x100 ); - check( (unsigned) x < 0x100 ); - check( (unsigned) y < 0x100 ); - // Read opcode and first operand. Optimize if processor's byte order is known - // and non-portable constructs are allowed. -#if BLARGG_NONPORTABLE && BLARGG_BIG_ENDIAN - data = *(BOOST::uint16_t*) &READ_PROG( pc ); - pc++; - unsigned opcode = data >> 8; - data = (uint8_t) data; - -#elif BLARGG_NONPORTABLE && BLARGG_LITTLE_ENDIAN - data = *(BOOST::uint16_t*) &READ_PROG( pc ); - pc++; - unsigned opcode = (uint8_t) data; - data >>= 8; - -#else + 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 ); -#endif - if ( remain_ <= 0 ) goto stop; @@ -217,11 +192,16 @@ switch ( opcode ) { - #define BRANCH( cond ) \ - if ( cond ) \ - goto branch_taken_loop; \ - goto inc_pc_loop; - + #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) @@ -319,7 +299,7 @@ nz = data; goto inc_pc_loop; -// 2. 8-BIT DATA TRANSMISSION COMMANDS. GROUP 2. +// 2. 8-BIT DATA TRANSMISSION COMMANDS, GROUP 2 ADDR_MODES( 0xC8 ) // MOV addr,A WRITE( data, a ); @@ -394,7 +374,7 @@ x++; goto loop; -// 5. 8-BIT LOGIC OPERATION COMMANDS. +// 5. 8-BIT LOGIC OPERATION COMMANDS #define LOGICAL_OP( op, func ) \ ADDR_MODES( op ) /* addr */ \ @@ -425,19 +405,21 @@ LOGICAL_OP( 0x48, ^ ); // EOR -// 4. 8-BIT ARITHMETIC OPERATION COMMANDS. +// 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) @@ -446,6 +428,7 @@ pc++; nz = READ_DP( READ_PROG( pc ) ) - data; c = ~nz; + nz &= 0xff; goto inc_pc_loop; case 0x3E: // CMP X,dp @@ -459,6 +442,7 @@ case 0xC8: // CMP X,imm nz = x - data; c = ~nz; + nz &= 0xff; goto inc_pc_loop; case 0x7E: // CMP Y,dp @@ -472,6 +456,7 @@ case 0xAD: // CMP Y,imm nz = y - data; c = ~nz; + nz &= 0xff; goto inc_pc_loop; { @@ -521,7 +506,7 @@ } -// 6. ADDITION & SUBTRACTION COMMANDS. +// 6. ADDITION & SUBTRACTION COMMANDS #define INC_DEC_REG( reg, n ) \ nz = reg + n; \ @@ -638,7 +623,7 @@ WRITE_DP( uint8_t (data + 1), y ); goto inc_pc_loop; -// 9. 16-BIT OPERATION COMMANDS. +// 9. 16-BIT OPERATION COMMANDS case 0x3A: // INCW dp case 0x1A:{// DECW dp @@ -685,7 +670,7 @@ // add high byte (Y) temp >>= 8; c = y + temp; - nz |= c; + nz = (nz | c) & 0xff; // half-carry (temporary avoids CodeWarrior optimizer bug) unsigned hc = (c & 15) - (y & 15); @@ -706,10 +691,11 @@ temp -= READ_DP( uint8_t (data + 1) ); nz |= temp; c = ~temp; + nz &= 0xff; goto inc_pc_loop; } -// 10. MULTIPLICATION & DIVISON COMMANDS. +// 10. MULTIPLICATION & DIVISON COMMANDS case 0xCF: { // MUL YA unsigned temp = y * a; @@ -726,22 +712,22 @@ status &= ~(st_h | st_v); - if ( y >= x ) - status |= st_v; - if ( (y & 15) >= (x & 15) ) status |= st_h; - unsigned temp = y * 0x100 + a; - if ( y < x * 2 ) { - a = temp / x; - y = temp - a * x; + if ( y >= x ) + status |= st_v; + + unsigned ya = y * 0x100 + a; + if ( y < x * 2 ) + { + a = ya / x; + y = ya - a * x; } - else { - temp -= x * 0x200; - a = temp / (256 - x); - y = temp - a * (256 - x) + x; - a = 255 - a; + else + { + a = 255 - (ya - x * 0x200) / (256 - x); + y = x + (ya - x * 0x200) % (256 - x); } nz = (uint8_t) a; @@ -750,16 +736,17 @@ goto loop; } -// 11. DECIMAL COMPENSATION COMMANDS. +// 11. DECIMAL COMPENSATION COMMANDS // seem unused // case 0xDF: // DAA // case 0xBE: // DAS -// 12. BRANCHING COMMANDS. +// 12. BRANCHING COMMANDS case 0x2F: // BRA rel - goto branch_taken_loop; + pc += (BOOST::int8_t) data; + goto inc_pc_loop; case 0x30: // BMI BRANCH( IS_NEG ) @@ -816,9 +803,7 @@ case 0xFE: // DBNZ Y,rel y = uint8_t (y - 1); - if ( y ) - goto branch_taken_loop; - goto inc_pc_loop; + BRANCH( y ) case 0x6E: { // DBNZ dp,rel pc++; @@ -836,11 +821,10 @@ pc = READ_PROG16( pc ); goto loop; -// 13. SUB-ROUTINE CALL RETURN COMMANDS. +// 13. SUB-ROUTINE CALL RETURN COMMANDS - /* - // seems unused case 0x0F: // BRK + check( false ); // untested PUSH16( pc + 1 ); pc = READ_PROG16( 0xffde ); // vector address verified int temp; @@ -848,7 +832,6 @@ PUSH( temp ); status = (status | st_b) & ~st_i; goto loop; - */ case 0x4F: // PCALL offset pc++; @@ -876,7 +859,7 @@ pc = READ_PROG16( 0xffde - (opcode >> 3) ); goto loop; -// 14. STACK OPERATION COMMANDS. +// 14. STACK OPERATION COMMANDS { int temp; @@ -923,7 +906,7 @@ y = POP(); goto loop; -// 15. BIT OPERATION COMMANDS. +// 15. BIT OPERATION COMMANDS case 0x02: // SET1 case 0x22: @@ -967,30 +950,31 @@ c &= mem_bit( pc ); pc += 2; goto loop; - /* - // seem unused + 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 C,mem.bit + case 0xEA: { // NOT1 mem.bit data = READ_PROG16( pc ); pc += 2; unsigned temp = READ( data & 0x1fff ); @@ -1004,7 +988,7 @@ pc += 2; unsigned temp = READ( data & 0x1fff ); unsigned bit = data >> 13; - temp = (temp & ~(1 << bit)) | ((c >> (8 - bit)) & 1); + temp = (temp & ~(1 << bit)) | (((c >> 8) & 1) << bit); WRITE( data & 0x1fff, temp ); goto loop; } @@ -1014,7 +998,7 @@ pc += 2; goto loop; -// 16. PROGRAM STATUS FLAG OPERATION COMMANDS. +// 16. PROGRAM STATUS FLAG OPERATION COMMANDS case 0x60: // CLRC c = 0; @@ -1040,17 +1024,17 @@ dp = 0x100; goto loop; - /* // seem unused 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. +// 17. OTHER COMMANDS case 0x00: // NOP goto loop;
--- a/Plugins/Input/console/Spc_Cpu.h Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Spc_Cpu.h Tue Jan 24 19:10:07 2006 -0800 @@ -1,7 +1,7 @@ // Super Nintendo (SNES) SPC-700 CPU emulator -// Game_Music_Emu 0.2.4. Copyright (C) 2004 Shay Green. GNU LGPL license. +// Game_Music_Emu 0.3.0 #ifndef SPC_CPU_H #define SPC_CPU_H @@ -9,22 +9,20 @@ #include "blargg_common.h" typedef unsigned spc_addr_t; -typedef long spc_time_t; +typedef long spc_time_t; class Snes_Spc; class Spc_Cpu { typedef BOOST::uint8_t uint8_t; uint8_t* const ram; - spc_time_t remain_; - Snes_Spc& emu; public: - // Keeps pointer to ram and spc - Spc_Cpu( uint8_t ram [0x10000], Snes_Spc* spc ); + // 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 { - unsigned short pc; + long pc; // more than 16 bits to allow overflow detection uint8_t a; uint8_t x; uint8_t y; @@ -51,11 +49,12 @@ 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_; -} +inline spc_time_t Spc_Cpu::remain() const { return remain_; } #endif
--- a/Plugins/Input/console/Spc_Dsp.cpp Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Spc_Dsp.cpp Tue Jan 24 19:10:07 2006 -0800 @@ -1,7 +1,7 @@ -// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ -// Based on Brad Martin's OpenSPC DSP emulator. +// Based on Brad Martin's OpenSPC DSP emulator #include "Spc_Dsp.h" @@ -10,7 +10,7 @@ #include "blargg_endian.h" /* Copyright (C) 2002 Brad Martin */ -/* Copyright (C) 2004-2005 Shay Green. This module is free software; you +/* 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 @@ -25,12 +25,19 @@ Spc_Dsp::Spc_Dsp( uint8_t* ram_ ) : ram( ram_ ) { - voices_muted = 0; 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; @@ -42,13 +49,16 @@ g.flags = 0xE0; // reset, mute, echo off g.key_ons = 0; - for ( int i = 0; i < voice_count; i++ ) { - voice_state [i].on_cnt = 0; - voice_state [i].envstate = state_release; + 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 ); - memset( voice_vol, 0, sizeof voice_vol ); } void Spc_Dsp::write( int i, int data ) @@ -62,17 +72,18 @@ // voice volume case 0: case 1: { - int left = (int8_t) reg [i & ~1]; + short* volume = voice_state [high].volume; + int left = (int8_t) reg [i & ~1]; int right = (int8_t) reg [i | 1]; - voice_vol [high] [0] = left; - voice_vol [high] [1] = right; + volume [0] = left; + volume [1] = right; // kill surround only if signs of volumes differ - if ( left * right < 0 ) + if ( left * right < surround_threshold ) { if ( left < 0 ) - voice_vol [high] [0] = -left; + volume [0] = -left; else - voice_vol [high] [1] = -right; + volume [1] = -right; } break; } @@ -89,7 +100,8 @@ // 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] = { +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, @@ -98,7 +110,7 @@ const int env_range = 0x800; -int Spc_Dsp::clock_envelope( int v ) +inline int Spc_Dsp::clock_envelope( int v ) { /* Return value is current * ENVX */ raw_voice_t& raw_voice = this->voice [v]; @@ -116,7 +128,8 @@ * no need for a count because it always happens every update. */ envx -= env_range / 256; - if ( envx <= 0 ) { + if ( envx <= 0 ) + { envx = 0; keys &= ~(1 << v); return -1; @@ -135,17 +148,20 @@ case state_attack: { // increase envelope by 1/64 each step int t = adsr1 & 15; - if ( t == 15 ) { + if ( t == 15 ) + { envx += env_range / 2; } - else { + else + { cnt -= env_rates [t * 2 + 1]; if ( cnt > 0 ) break; envx += env_range / 64; cnt = env_rate_init; } - if ( envx >= env_range ) { + if ( envx >= env_range ) + { envx = env_range - 1; voice.envstate = state_decay; } @@ -159,7 +175,8 @@ // Multiplying ENVX by 255/256 every time DECAY is // updated. cnt -= env_rates [((adsr1 >> 3) & 0xE) + 0x10]; - if ( cnt <= 0 ) { + if ( cnt <= 0 ) + { cnt = env_rate_init; envx -= ((envx - 1) >> 8) + 1; voice.envx = envx; @@ -176,12 +193,17 @@ // Multiplying ENVX by 255/256 every time SUSTAIN is // updated. cnt -= env_rates [raw_voice.adsr [1] & 0x1f]; - if ( cnt <= 0 ) { + if ( cnt <= 0 ) + { cnt = env_rate_init; envx -= ((envx - 1) >> 8) + 1; voice.envx = envx; } break; + + case state_release: + // handled above + break; } } else @@ -210,7 +232,8 @@ break; cnt = env_rate_init; envx -= env_range / 64; - if ( envx < 0 ) { + if ( envx < 0 ) + { envx = 0; if ( voice.envstate == state_attack ) voice.envstate = state_decay; @@ -225,7 +248,8 @@ break; cnt = env_rate_init; envx -= ((envx - 1) >> 8) + 1; - if ( envx < 0 ) { + if ( envx < 0 ) + { envx = 0; if ( voice.envstate == state_attack ) voice.envstate = state_decay; @@ -275,7 +299,7 @@ void Spc_Dsp::run( long count, short* out_buf ) { - // to do: make clock_envelope() inline to avoid out-of-line calls? + // 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. @@ -289,10 +313,12 @@ const src_dir* const sd = (src_dir*) &ram [g.wave_page * 0x100]; - int const left_volume = g.left_volume * emu_gain; - int right_volume = g.right_volume * emu_gain; - if ( left_volume * right_volume < 0 ) + 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 ) { @@ -305,15 +331,14 @@ g.wave_ended &= ~g.key_ons; // Keying on a voice resets that bit in ENDX. - if ( g.noise_enables ) { + if ( g.noise_enables ) + { noise_count -= env_rates [g.flags & 0x1F]; - if ( noise_count <= 0 ) { + if ( noise_count <= 0 ) + { noise_count = env_rate_init; - if ( noise & 0x4000 ) - noise_amp += (noise & 1) ? noise : -noise; - else - noise_amp >>= 1; + noise_amp = BOOST::int16_t (noise * 2); int feedback = (noise << 13) ^ (noise << 14); noise = (feedback & 0x4000) | (noise >> 1); @@ -356,20 +381,23 @@ voice.envstate = state_attack; } - if ( g.key_ons & vbit & ~g.key_offs ) { + 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 ) { + 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 ) { + if ( !(keys & vbit) || (envx = clock_envelope( vidx )) < 0 ) + { raw_voice.envx = 0; raw_voice.outx = 0; prev_outx = 0; @@ -385,11 +413,13 @@ { g.wave_ended |= vbit; - if ( voice.block_header & 2 ) { + if ( voice.block_header & 2 ) + { // verified (played endless looping sample and ENDX was set) voice.addr = GET_LE16( sd [raw_voice.waveform].loop ); } - else { + else + { // first block was end block; don't play anything (verified) goto sample_ended; // to do: find alternative to goto } @@ -409,7 +439,8 @@ raw_voice.envx = 0; voice.envx = 0; // add silence samples to interpolation buffer - do { + do + { voice.interp3 = voice.interp2; voice.interp2 = voice.interp1; voice.interp1 = voice.interp0; @@ -420,7 +451,8 @@ } int delta = ram [voice.addr]; - if ( voice.block_remain & 1 ) { + if ( voice.block_remain & 1 ) + { delta <<= 4; // use lower nybble voice.addr++; } @@ -474,50 +506,45 @@ } // rate (with possible modulation) - int rate = GET_LE16( raw_voice.rate ) & 0x3fff; + int rate = GET_LE16( raw_voice.rate ) & 0x3FFF; if ( g.pitch_mods & vbit ) rate = (rate * (prev_outx + 32768)) >> 15; - // fraction - int fraction = voice.fraction & 0xfff; - voice.fraction = fraction + rate; - fraction >>= 4; - // Gaussian interpolation using most recent 4 samples - const short* table = gauss [fraction]; - const short* table2 = gauss [255 - fraction]; + 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) + - ((table2 [1] * voice.interp1) >> 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 almost never used + int output = noise_amp; // noise is rarely used if ( !(g.noise_enables & vbit) ) output = clamp_16( s * 2 ); - int muted = vbit & voices_muted; + // scale output and set outx values + output = (output * envx) >> 11 & ~1; - // 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; - - // apply muting if voice is externally disabled (not a SNES feature) - if ( muted ) - output = 0; - - // output - int l = (voice_vol [vidx] [0] * output) >> 7; - int r = (voice_vol [vidx] [1] * output) >> 7; - if ( g.echo_ons & vbit ) { + if ( g.echo_ons & vbit ) + { echol += l; echor += r; } left += l; right += r; - } // end of channel loop + } + // end of channel loop // main volume control - left = (left * left_volume) >> (7 + emu_gain_bits); + left = (left * left_volume ) >> (7 + emu_gain_bits); right = (right * right_volume) >> (7 + emu_gain_bits); // Echo FIR filter @@ -528,7 +555,7 @@ 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_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; @@ -560,24 +587,23 @@ fir_pos [6] [1] * fir_coeff [1] + fir_pos [7] [1] * fir_coeff [0]; - // overlap calculations with tests - left += (fb_left * g.left_echo_volume) >> 14; + 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; + 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 , clamp_16( echol ) ); SET_LE16( echo_buf + 2, clamp_16( echor ) ); } - right += (fb_right * g.right_echo_volume) >> 14; - if ( out_buf ) { // write final samples - left = clamp_16( left ); + left = clamp_16( left ); right = clamp_16( right ); int mute = g.flags & 0x40; @@ -587,7 +613,8 @@ out_buf += 2; // muting - if ( mute ) { + if ( mute ) + { out_buf [-2] = 0; out_buf [-1] = 0; } @@ -595,19 +622,14 @@ } } -// Base normal_gauss table is very close to the following: -#if 0 -double e = 2.718281828; -for ( int i = 0; i < 512; i++ ) { - double x = i / 511.0 * 2.31 - 0.05; - double y = pow( e, -x * x ) * 1305.64; - normal_gauss [i] = y - 16.54; -} -#endif +// 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] [j] = normal_gauss [(1 - j) * 256 + i] -const short Spc_Dsp::gauss [256] [2] = { +// 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,
--- a/Plugins/Input/console/Spc_Dsp.h Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Spc_Dsp.h Tue Jan 24 19:10:07 2006 -0800 @@ -1,24 +1,20 @@ // Super Nintendo (SNES) SPC DSP emulator -// Game_Music_Emu 0.2.4. Copyright (C) 2004-2005 Shay Green. GNU LGPL license. -// Copyright (C) 2002 Brad Martin +// Game_Music_Emu 0.3.0 #ifndef SPC_DSP_H #define SPC_DSP_H #include "blargg_common.h" -// Surround effects using opposite volumes for left and right are currently removed -// by making left and right volume positive if their signs differ. - class Spc_Dsp { typedef BOOST::int8_t int8_t; typedef BOOST::uint8_t uint8_t; public: - // Keeps pointer to ram - Spc_Dsp( uint8_t ram [0x10000] ); + // 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 }; @@ -31,6 +27,9 @@ // 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 ); @@ -97,8 +96,6 @@ short fir_buf [16] [2]; int fir_offset; // (0 to 7) - short voice_vol [voice_count] [2]; - enum { emu_gain_bits = 8 }; int emu_gain; @@ -109,11 +106,10 @@ int noise_amp; int noise; int noise_count; - - int voices_muted; - int disable_surround_; // set to sign bit (0x80) when disabled - static const short gauss [] [2]; + int surround_threshold; + + static const BOOST::int16_t gauss []; enum state_t { state_attack, @@ -123,18 +119,21 @@ }; struct voice_t { + short volume [2]; short fraction;// 12-bit fractional position - short interp0; // most recent four decoded samples + short interp3; // most recent four decoded samples + short interp2; short interp1; - short interp2; - short interp3; + 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; - state_t envstate; + short enabled; // 7 if enabled, 31 if disabled + short envstate; + short unused; // pad to power of 2 }; voice_t voice_state [voice_count]; @@ -142,18 +141,15 @@ int clock_envelope( int ); }; -inline int Spc_Dsp::read( int i ) { +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]; } -inline void Spc_Dsp::mute_voices( int mask ) { - voices_muted = mask; -} - -inline void Spc_Dsp::set_gain( double v ) { - emu_gain = (int) (v * (1 << emu_gain_bits)); -} - #endif
--- a/Plugins/Input/console/Spc_Emu.cpp Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Spc_Emu.cpp Tue Jan 24 19:10:07 2006 -0800 @@ -1,12 +1,13 @@ -// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ #include "Spc_Emu.h" +#include <stdlib.h> #include <string.h> -#include "abstract_file.h" +#include "blargg_endian.h" -/* Copyright (C) 2004-2005 Shay Green. This module is free software; you +/* 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 @@ -19,11 +20,9 @@ #include BLARGG_SOURCE_BEGIN -Spc_Emu::Spc_Emu() +Spc_Emu::Spc_Emu( double gain ) { - resample_ratio = 1.0; - use_resampler = false; - track_count_ = 0; + apu.set_gain( gain ); } Spc_Emu::~Spc_Emu() @@ -38,62 +37,83 @@ return names; } -blargg_err_t Spc_Emu::init( long sample_rate, double gain ) +void Spc_Emu::mute_voices( int m ) { - apu.set_gain( gain ); - use_resampler = false; - resample_ratio = (double) native_sample_rate / sample_rate; + 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( resample_ratio, 0.9965 ); - use_resampler = true; + resampler.time_ratio( (double) native_sample_rate / sample_rate, 0.9965 ); } - - return blargg_success; + return Music_Emu::set_sample_rate( sample_rate ); } -blargg_err_t Spc_Emu::load( const header_t& h, Emu_Reader& in ) +blargg_err_t Spc_Emu::load( Data_Reader& in ) { - if ( in.remain() < sizeof file.data ) + 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"; - track_count_ = 1; - voice_count_ = Snes_Spc::voice_count; + long remain = in.remain(); + long size = remain + sizeof h; + if ( size < trailer_offset ) + size = trailer_offset; + BLARGG_RETURN_ERR( spc_data.resize( size ) ); - memcpy( &file.header, &h, sizeof file.header ); - return in.read( file.data, sizeof file.data ); + 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 ); } -blargg_err_t Spc_Emu::start_track( int ) +void Spc_Emu::start_track( int track ) { + Music_Emu::start_track( track ); + resampler.clear(); - return apu.load_spc( &file, sizeof file ); + if ( apu.load_spc( spc_data.begin(), spc_data.size() ) ) + check( false ); } -blargg_err_t Spc_Emu::skip( long count ) +void Spc_Emu::skip( long count ) { - count = long (count * resample_ratio) & ~1; + count = long (count * resampler.ratio()) & ~1; count -= resampler.skip_input( count ); if ( count > 0 ) - BLARGG_RETURN_ERR( apu.skip( count ) ); + apu.skip( count ); // eliminate pop due to resampler const int resampler_latency = 64; sample_t buf [resampler_latency]; - return play( resampler_latency, buf ); + play( resampler_latency, buf ); } -blargg_err_t Spc_Emu::play( long count, sample_t* out ) +void Spc_Emu::play( long count, sample_t* out ) { - require( track_count_ ); // file must be loaded + require( track_count() ); // file must be loaded - if ( !use_resampler ) - return apu.play( count, out ); + if ( sample_rate() == native_sample_rate ) + { + if ( apu.play( count, out ) ) + log_error(); + return; + } long remain = count; while ( remain > 0 ) @@ -102,43 +122,12 @@ if ( remain > 0 ) { long n = resampler.max_write(); - BLARGG_RETURN_ERR( apu.play( n, resampler.buffer() ) ); + if ( apu.play( n, resampler.buffer() ) ) + log_error(); resampler.write( n ); } } assert( remain == 0 ); - - return blargg_success; -} - -Spc_Reader::Spc_Reader() : file( NULL ) { -} - -Spc_Reader::~Spc_Reader() { - close(); } -blargg_err_t Spc_Reader::read_head(Spc_Emu::header_t *header) { - vfs_fread(&header->tag, 1,35,file); - vfs_fread(&header->format, 1, 1,file); - vfs_fread(&header->version, 1, 1,file); - vfs_fread(&header->pc, 1, 2,file); - vfs_fread(&header->a, 1, 1,file); - vfs_fread(&header->x, 1, 1,file); - vfs_fread(&header->y, 1, 1,file); - vfs_fread(&header->psw, 1, 1,file); - vfs_fread(&header->sp, 1, 1,file); - vfs_fread(&header->unused, 1, 2,file); - vfs_fread(&header->song, 1,32,file); - vfs_fread(&header->game, 1,32,file); - vfs_fread(&header->dumper, 1,16,file); - vfs_fread(&header->comment, 1,32,file); - vfs_fread(&header->date, 1,11,file); - vfs_fread(&header->len_secs,1, 3,file); - vfs_fread(&header->fade_msec,1,5,file); - vfs_fread(&header->author, 1,32,file); - vfs_fread(&header->mute_mask,1,1,file); - vfs_fread(&header->emulator,1, 1,file); - vfs_fread(&header->unused2, 1,45,file); -}
--- a/Plugins/Input/console/Spc_Emu.h Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Spc_Emu.h Tue Jan 24 19:10:07 2006 -0800 @@ -1,7 +1,7 @@ // Super Nintendo (SNES) SPC music file emulator -// Game_Music_Emu 0.2.4. Copyright (C) 2004 Shay Green. GNU LGPL license. +// Game_Music_Emu 0.3.0 #ifndef SPC_EMU_H #define SPC_EMU_H @@ -11,19 +11,19 @@ #include "Snes_Spc.h" class Spc_Emu : public Music_Emu { + enum { trailer_offset = 0x10200 }; public: - Spc_Emu(); - ~Spc_Emu(); + // 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 + // 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 }; - // Initialize emulator with given sample rate and gain. A sample rate different than - // the native 32kHz results in internal resampling to the desired rate. A gain of 1.0 - // results in almost no clamping. Default gain roughly matches volume of other emulators. - blargg_err_t init( long sample_rate, double gain = 1.4 ); - - struct header_t { + // SPC file header + struct header_t + { char tag [35]; byte format; byte version; @@ -42,50 +42,45 @@ byte emulator; byte unused2 [45]; + enum { track_count = 1 }; enum { copyright = 0 }; // no copyright field }; - - int length; + BOOST_STATIC_ASSERT( sizeof (header_t) == 0x100 ); - // Load SPC, given its header and reader for remaining data - blargg_err_t load( const header_t&, Emu_Reader& ); + // Load SPC data + blargg_err_t load( Data_Reader& ); - void mute_voices( int ); - blargg_err_t start_track( int ); - blargg_err_t play( long count, sample_t* ); - blargg_err_t skip( long ); - const char** voice_names() const; - + // 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; } -// End of public interface -private: - Snes_Spc apu; - Fir_Resampler resampler; - double resample_ratio; - bool use_resampler; + // If true, prevents channels and global volumes from being phase-negated + void disable_surround( bool disable = true ); - struct spc_file_t { - header_t header; - char data [0x10080]; - }; - BOOST_STATIC_ASSERT( sizeof (spc_file_t) == 0x10180 ); - - spc_file_t file; +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<byte> spc_data; + Fir_Resampler<24> resampler; + Snes_Spc apu; }; -inline void Spc_Emu::mute_voices( int m ) { - apu.mute_voices( m ); -} - -class Spc_Reader : public Std_File_Reader { - VFSFile* file; -public: - Spc_Reader(); - ~Spc_Reader(); - - // Custom reader for SPC headers [tempfix] - blargg_err_t read_head( Spc_Emu::header_t* ); -}; +inline void Spc_Emu::disable_surround( bool b ) { apu.disable_surround( b ); } #endif
--- a/Plugins/Input/console/Tagged_Data.h Tue Jan 24 13:57:22 2006 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ - -// Stubs to disable tagged data reflection functions - -// Game_Music_Emu 0.2.4. Copyright (C) 2005 Shay Green. GNU LGPL license. - -#ifndef TAGGED_DATA_H -#define TAGGED_DATA_H - -typedef long data_tag_t; - -class Tagged_Data { -public: - Tagged_Data( Tagged_Data& parent, data_tag_t ) { } - int reading() const { return false; } - int not_found() const { return true; } - int reflect_int8( data_tag_t, int n ) { return n; } - int reflect_int16( data_tag_t, int n ) { return n; } - long reflect_int32( data_tag_t, long n ) { return n; } -}; - -template<class T> -inline void reflect_int8( Tagged_Data&, data_tag_t, T* ) { } - -template<class T> -inline void reflect_int16( Tagged_Data&, data_tag_t, T* ) { } - -template<class T> -inline void reflect_int32( Tagged_Data&, data_tag_t, T* ) { } - -#endif -
--- a/Plugins/Input/console/Vgm_Emu.cpp Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Vgm_Emu.cpp Tue Jan 24 19:10:07 2006 -0800 @@ -1,12 +1,13 @@ -// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ #include "Vgm_Emu.h" +#include <math.h> #include <string.h> -#include <math.h> +#include "blargg_endian.h" -/* Copyright (C) 2003-2005 Shay Green. This module is free software; you +/* 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 @@ -19,16 +20,21 @@ #include BLARGG_SOURCE_BEGIN -const long vgm_sample_rate = 44100; +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( double gain ) +Vgm_Emu::Vgm_Emu( bool os, double tempo ) { + oversample = os; + pos = NULL; data = NULL; - pos = NULL; - apu.volume( gain ); + uses_fm = false; + vgm_rate = (long) (header_t::time_rate * tempo + 0.5); - // to do: decide on equalization parameters - set_equalizer( equalizer_t( -32, 8000, 66 ) ); + static equalizer_t const eq = { -14.0, 80 }; + set_equalizer( eq ); + psg.volume( 1.0 ); } Vgm_Emu::~Vgm_Emu() @@ -38,269 +44,255 @@ void Vgm_Emu::unload() { - delete [] data; data = NULL; pos = NULL; - track_ended_ = false; + 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 ); } -const char** Vgm_Emu::voice_names() const +BOOST::uint8_t const* Vgm_Emu::gd3_data( int* size ) const { - static const char* names [] = { "Square 1", "Square 2", "Square 3", "Noise" }; - return names; -} - -void Vgm_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) -{ - apu.osc_output( i, c, l, r ); + 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 ) { - apu.treble_eq( 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 ); + } + } } -const int time_bits = 12; - -inline sms_time_t Vgm_Emu::clocks_from_samples( int samples ) const +blargg_err_t Vgm_Emu::load_( const header_t& h, void const* new_data, long new_size ) { - const long round_up = 1L << (time_bits - 1); - return (samples * time_factor + round_up) >> time_bits; + 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; } -static long get_le32( const BOOST::uint8_t b [4] ) +blargg_err_t Vgm_Emu::setup_fm() { - return b [3] * 0x1000000L + b [2] * 0x10000L + b [1] * 0x100L + b [0]; + 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( const header_t& h, Emu_Reader& in ) +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(); - // compatibility - if ( 0 != memcmp( h.tag, "Vgm ", 4 ) ) - return "Not a VGM file"; - if ( get_le32( h.vers ) > 0x0101 ) - return "Unsupported VGM format"; - - // clock rate - long clock_rate = get_le32( h.psg_rate ); - if ( !clock_rate ) - return "Only PSG sound chip is supported"; - time_factor = (long) floor( clock_rate * ((1L << time_bits) / - (double) vgm_sample_rate) + 0.5 ); - - // data - long data_size = in.remain(); - data = new byte [data_size + 3]; // allow pointer to go past end - if ( !data ) - return "Out of memory"; - end = data + data_size; - data [data_size] = 0; - data [data_size + 1] = 0; - data [data_size + 2] = 0; - blargg_err_t err = in.read( data, data_size ); + // 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; } - long loop_offset = get_le32( h.loop_offset ); - loop_begin = end; - loop_duration = 0; - if ( loop_offset ) - { - loop_duration = get_le32( h.loop_duration ); - if ( loop_duration ) - loop_begin = &data [loop_offset + 0x1c - sizeof (header_t)]; - } + memset( &mem [data_size], 0x66, padding ); // pad with end command - voice_count_ = Sms_Apu::osc_count; - track_count_ = 1; - - return setup_buffer( clock_rate ); + return load_( h, mem.begin(), data_size ); } -int Vgm_Emu::track_length( const byte** end_out, int* remain_out ) const +void Vgm_Emu::start_track( int track ) { require( data ); // file must have been loaded - long time = 0; + Classic_Emu::start_track( track ); + psg.reset(); - if ( end_out || !loop_duration ) + dac_disabled = -1; + pcm_data = data; + pcm_pos = data; + dac_amp = -1; + vgm_time = 0; + pos = data; + if ( get_le32( header_.version ) >= 0x150 ) { - const byte* p = data; - while ( p < end ) - { - int cmd = *p++; - switch ( cmd ) - { - case 0x4f: - case 0x50: - p += 1; - break; - - case 0x61: - if ( p + 1 < end ) { - time += p [1] * 0x100L + p [0]; - p += 2; - } - break; - - case 0x62: - time += 735; // ntsc frame - break; - - case 0x63: - time += 882; // pal frame - break; - - default: - if ( (p [-1] & 0xf0) == 0x50 ) { - p += 2; - break; - } - dprintf( "Bad command in VGM stream: %02X\n", (int) cmd ); - break; - - case 0x66: - if ( end_out ) - *end_out = p; - if ( remain_out ) - *remain_out = end - p; - p = end; - break; - } - } + long data_offset = get_le32( header_.data_offset ); + check( data_offset ); + if ( data_offset ) + pos += data_offset + offsetof (header_t,data_offset) - 0x40; } - // i.e. ceil( exact_length + 0.5 ) - return loop_duration ? 0 : - (time + (vgm_sample_rate >> 1) + vgm_sample_rate - 1) / vgm_sample_rate; + if ( uses_fm ) + { + if ( ym2413.enabled() ) + ym2413.reset(); + + if ( ym2612.enabled() ) + ym2612.reset(); + + fm_time_offset = 0; + blip_buf.clear(); + Dual_Resampler::clear(); + } } -blargg_err_t Vgm_Emu::start_track( int ) +long Vgm_Emu::run( int msec, bool* added_stereo ) { - require( data ); // file must have been loaded - - pos = data; - loop_remain = 0; - delay = 0; - track_ended_ = false; - apu.reset(); - starting_track(); - return blargg_success; + blip_time_t psg_end = run_commands( msec * vgm_rate / 1000 ); + *added_stereo = psg.end_frame( psg_end ); + return psg_end; } -blip_time_t Vgm_Emu::run( int msec, bool* added_stereo ) +void Vgm_Emu::play( long count, sample_t* out ) { require( pos ); // track must have been started - const int duration = vgm_sample_rate / 100 * msec / 10; - int time = delay; - while ( time < duration && pos < end ) - { - if ( !loop_remain && pos >= loop_begin ) - loop_remain = loop_duration; - - int cmd = *pos++; - int delay = 0; - switch ( cmd ) - { - case 0x66: - pos = end; // end - if ( loop_duration ) { - pos = loop_begin; - loop_remain = loop_duration; - } - break; - - case 0x62: - delay = 735; // ntsc frame - break; - - case 0x63: - delay = 882; // pal frame - break; - - case 0x4f: - if ( pos == end ) { - check( false ); // missing data - break; - } - apu.write_ggstereo( clocks_from_samples( time ), *pos++ ); - break; - - case 0x50: - if ( pos == end ) { - check( false ); // missing data - break; - } - apu.write_data( clocks_from_samples( time ), *pos++ ); - break; - - case 0x61: - if ( end - pos < 1 ) { - check( false ); // missing data - break; - } - delay = pos [1] * 0x100L + pos [0]; - pos += 2; - break; - - default: - if ( (cmd & 0xf0) == 0x50 ) - { - if ( end - pos < 1 ) { - check( false ); // missing data - break; - } - pos += 2; - break; - } - dprintf( "Bad command in VGM stream: %02X\n", (int) cmd ); - break; - - } - time += delay; - if ( loop_remain && (loop_remain -= delay) <= 0 ) - { - pos = loop_begin; - loop_remain = 0; - } - } - - blip_time_t end_time = clocks_from_samples( duration ); - if ( pos < end ) - { - delay = time - duration; - if ( apu.end_frame( end_time ) && added_stereo ) - *added_stereo = true; - } - else { - delay = 0; - track_ended_ = true; - } - - return end_time; + if ( uses_fm ) + Dual_Resampler::play( count, out, blip_buf ); + else + Classic_Emu::play( count, out ); } -Vgm_Reader::Vgm_Reader() : file( NULL ) { -} - -Vgm_Reader::~Vgm_Reader() { - close(); -} - -blargg_err_t Vgm_Reader::read_head(Vgm_Emu::header_t *header) { - vfs_fread(&header->tag, 1, 4,file); - vfs_fread(&header->data_size, 1, 4,file); - vfs_fread(&header->vers, 1, 4,file); - vfs_fread(&header->psg_rate, 1, 4,file); - vfs_fread(&header->fm_rate, 1, 4,file); - vfs_fread(&header->g3d_offset, 1, 4,file); - vfs_fread(&header->sample_count,1, 4,file); - vfs_fread(&header->loop_offset, 1, 4,file); - vfs_fread(&header->loop_duration,1,4,file); - vfs_fread(&header->frame_rate, 1, 4,file); - vfs_fread(&header->unused, 1,0x18,file); -}
--- a/Plugins/Input/console/Vgm_Emu.h Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/Vgm_Emu.h Tue Jan 24 19:10:07 2006 -0800 @@ -1,85 +1,118 @@ -// Sega Master System VGM-format game music file emulator (PSG chip only) +// Multi-format VGM music emulator with support for SMS PSG and Mega Drive FM -// Game_Music_Emu 0.2.4. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. +// Game_Music_Emu 0.3.0 #ifndef VGM_EMU_H #define VGM_EMU_H -#include "Classic_Emu.h" -#include "Sms_Apu.h" +#include "abstract_file.h" +#include "Vgm_Emu_Impl.h" -class Vgm_Emu : public Classic_Emu { +// 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: - // Set internal gain, where 1.0 results in almost no clamping. Default gain - // roughly matches volume of other emulators. - Vgm_Emu( double gain = 1.0 ); - ~Vgm_Emu(); - struct header_t { + // 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 vers [4]; + byte version [4]; byte psg_rate [4]; - byte fm_rate [4]; - byte g3d_offset [4]; - byte sample_count [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]; - char unused [0x18]; + 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 - // no text fields + // 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, given its header and reader for remaining data - blargg_err_t load( const header_t&, Emu_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; - // Determine length of track, in seconds (0 if track is endless). - // Optionally returns pointer and size of data past end of sequence data - // (i.e. any tagging information). - int track_length( const byte** end_out = NULL, int* remain_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; } - blargg_err_t start_track( int ); +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; - - -// End of public interface + 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: - byte* data; - const byte* pos; - const byte* end; - const byte* loop_begin; - long loop_duration; - long loop_remain; - long time_factor; - int delay; - Sms_Apu apu; + header_t header_; + blargg_vector<byte> mem; + long vgm_rate; + bool oversample; + bool uses_fm; - sms_time_t clocks_from_samples( int ) const; + 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(); }; -class Vgm_Reader : public Std_File_Reader { - VFSFile* file; -public: - Vgm_Reader(); - ~Vgm_Reader(); - - // Custom reader for SPC headers [tempfix] - blargg_err_t read_head( Vgm_Emu::header_t* ); -}; +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
--- a/Plugins/Input/console/abstract_file.cpp Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/abstract_file.cpp Tue Jan 24 19:10:07 2006 -0800 @@ -4,8 +4,9 @@ #include <assert.h> #include <string.h> #include <stddef.h> +#include <stdlib.h> -/* Copyright (C) 2005 by Shay Green. Permission is hereby granted, free of +/* 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, @@ -20,12 +21,17 @@ 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 ); @@ -40,11 +46,6 @@ return NULL; } -long File_Reader::remain() const -{ - return size() - tell(); -} - error_t Data_Reader::skip( long count ) { char buf [512]; @@ -59,6 +60,11 @@ return NULL; } +long File_Reader::remain() const +{ + return size() - tell(); +} + error_t File_Reader::skip( long n ) { assert( n >= 0 ); @@ -128,7 +134,7 @@ // Std_File_Reader -Std_File_Reader::Std_File_Reader() : file( NULL ) { +Std_File_Reader::Std_File_Reader() : file_( NULL ) { } Std_File_Reader::~Std_File_Reader() { @@ -137,8 +143,8 @@ error_t Std_File_Reader::open( const char* path ) { - file = vfs_fopen( path, "rb" ); - if ( !file ) + file_ = fopen( path, "rb" ); + if ( !file_ ) RAISE_ERROR( "Couldn't open file" ); return NULL; } @@ -146,38 +152,38 @@ long Std_File_Reader::size() const { long pos = tell(); - vfs_fseek( file, 0, SEEK_END ); + fseek( file_, 0, SEEK_END ); long result = tell(); - vfs_fseek( file, pos, SEEK_SET ); + fseek( file_, pos, SEEK_SET ); return result; } long Std_File_Reader::read_avail( void* p, long s ) { - return vfs_fread( p, 1, s, file ); + return (long) fread( p, 1, s, file_ ); } long Std_File_Reader::tell() const { - return vfs_ftell( file ); + return ftell( file_ ); } error_t Std_File_Reader::seek( long n ) { - if ( vfs_fseek( file, n, SEEK_SET ) != 0 ) + if ( fseek( file_, n, SEEK_SET ) != 0 ) RAISE_ERROR( "Error seeking in file" ); return NULL; } void Std_File_Reader::close() { - if ( file ) { - vfs_fclose( file ); - file = NULL; + if ( file_ ) { + fclose( file_ ); + file_ = NULL; } } // Std_File_Writer -Std_File_Writer::Std_File_Writer() : file( NULL ) { +Std_File_Writer::Std_File_Writer() : file_( NULL ) { } Std_File_Writer::~Std_File_Writer() { @@ -186,19 +192,19 @@ error_t Std_File_Writer::open( const char* path ) { - file = vfs_fopen( path, "wb" ); - if ( !file ) + 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 ); + //setvbuf( file_, NULL, _IOFBF, 32 * 1024L ); return NULL; } error_t Std_File_Writer::write( const void* p, long s ) { - long result = vfs_fwrite( p, 1, s, file ); + long result = (long) fwrite( p, 1, s, file_ ); if ( result != s ) RAISE_ERROR( "Couldn't write to file" ); return NULL; @@ -206,42 +212,71 @@ void Std_File_Writer::close() { - if ( file ) { - vfs_fclose( file ); - file = NULL; + if ( file_ ) { + fclose( file_ ); + file_ = NULL; } } // Mem_Writer -Mem_Writer::Mem_Writer( void* p, long s, int b ) : - out( p ), - remain_( s ), - ignore_excess( b ) +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 ) { - if ( s > remain_ ) + long remain = allocated - size_; + if ( s > remain ) { - if ( !ignore_excess ) + if ( mode == fixed ) RAISE_ERROR( "Tried to write more data than expected" ); - s = remain_; + + 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; + } } - remain_ -= s; - memcpy( out, p, s ); - out = (char*) out + s; + + assert( size_ + s <= allocated ); + memcpy( data_ + size_, p, s ); + size_ += s; + return NULL; } -long Mem_Writer::remain() const { - return remain_; -} - // Null_Writer -error_t Null_Writer::write( const void*, long ) { +error_t Null_Writer::write( const void*, long ) +{ return NULL; }
--- a/Plugins/Input/console/abstract_file.h Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/abstract_file.h Tue Jan 24 19:10:07 2006 -0800 @@ -1,19 +1,13 @@ // Abstract file access interfaces -// Copyright (C) 2005 Shay Green. MIT license. - #ifndef ABSTRACT_FILE_H #define ABSTRACT_FILE_H -#include "libaudacious/vfs.h" - -// to do: built-in buffering? +#include <stdio.h> +// Supports reading and finding out how many bytes are remaining class Data_Reader { - // noncopyable - Data_Reader( const Data_Reader& ); - Data_Reader& operator = ( const Data_Reader& ); public: Data_Reader() { } virtual ~Data_Reader() { } @@ -21,12 +15,11 @@ // NULL on success, otherwise error string typedef const char* error_t; - // Read at most 'n' bytes. Return number of bytes read, negative - // value if error. + // 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). NULL on success, - // otherwise error string. + // Read exactly 'n' bytes (error if fewer are available). virtual error_t read( void*, long ); // Number of bytes remaining @@ -36,8 +29,14 @@ 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 @@ -54,6 +53,7 @@ error_t skip( long n ); }; +// Limit access to a subset of data class Subset_Reader : public Data_Reader { Data_Reader* in; long remain_; @@ -63,6 +63,7 @@ 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; @@ -77,14 +78,23 @@ error_t seek( long ); }; +// File reader based on C FILE class Std_File_Reader : public File_Reader { - VFSFile* file; + 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 ); @@ -94,10 +104,8 @@ void close(); }; +// Supports writing class Data_Writer { - // noncopyable - Data_Writer( const Data_Writer& ); - Data_Writer& operator = ( const Data_Writer& ); public: Data_Writer() { } virtual ~Data_Writer() { } @@ -106,21 +114,26 @@ // Write 'n' bytes. NULL on success, otherwise error string. virtual error_t write( const void*, long n ) = 0; -}; - -class Null_Writer : public Data_Writer { -public: - error_t write( const void*, long ); + + void satisfy_lame_linker_(); +private: + // noncopyable + Data_Writer( const Data_Writer& ); + Data_Writer& operator = ( const Data_Writer& ); }; class Std_File_Writer : public Data_Writer { - VFSFile* file; + 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* ); @@ -129,18 +142,35 @@ void close(); }; -// to do: mem file writer - -// Write to block of memory +// Write data to memory class Mem_Writer : public Data_Writer { - void* out; - long remain_; - int ignore_excess; + char* data_; + long size_; + long allocated; + enum { expanding, fixed, ignore_excess } mode; public: - // to do: automatic allocation and expansion of memory? - Mem_Writer( void*, long size, int ignore_excess = 1 ); + // 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 ); - long remain() const; + + // 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
--- a/Plugins/Input/console/blargg_common.h Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/blargg_common.h Tue Jan 24 19:10:07 2006 -0800 @@ -1,167 +1,241 @@ -// Common headers used by Shay Green's libraries - -// Copyright (C) 2004-2005 Shay Green. +// 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 -// Allow prefix configuration file *which can re-include blargg_common.h* -// (probably indirectly). +// 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 -#ifdef WORDS_BIGENDIAN -# define BLARGG_BIG_ENDIAN 1 -#else -# define BLARGG_LITTLE_ENDIAN 1 +// 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 -// Source files use #include BLARGG_ENABLE_OPTIMIZER before performance-critical code -#ifndef BLARGG_ENABLE_OPTIMIZER - #define BLARGG_ENABLE_OPTIMIZER "blargg_common.h" -#endif - -// Source files have #include BLARGG_SOURCE_BEGIN at the beginning -#ifndef BLARGG_SOURCE_BEGIN - #define BLARGG_SOURCE_BEGIN "blargg_source.h" +// 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) + #define BLARGG_BIG_ENDIAN 1 + #else + #define BLARGG_LITTLE_ENDIAN 1 + #endif #endif // Determine compiler's language support +// Metrowerks CodeWarrior #if defined (__MWERKS__) - // Metrowerks CodeWarrior #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) - // Microsoft Visual C++ #if _MSC_VER < 1100 #define BLARGG_COMPILER_HAS_BOOL 0 #endif +// GNU C++ #elif defined (__GNUC__) - // GNU C++ - #define BLARGG_COMPILER_HAS_BOOL 1 + #if __GNUC__ > 2 + #define BLARGG_COMPILER_HAS_NAMESPACE 1 + #endif +// Mingw #elif defined (__MINGW32__) - // Mingw? - #define BLARGG_COMPILER_HAS_BOOL 1 + // empty +// Pre-ISO C++ compiler #elif __cplusplus < 199711 - // Pre-ISO C++ compiler - #define BLARGG_COMPILER_HAS_BOOL 0 + #ifndef BLARGG_COMPILER_HAS_BOOL + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif #endif -// Set up boost -#include "boost/config.hpp" -#ifndef BOOST_MINIMAL - #define BOOST boost - #ifndef BLARGG_COMPILER_HAS_NAMESPACE - #define BLARGG_COMPILER_HAS_NAMESPACE 1 - #endif - #ifndef BLARGG_COMPILER_HAS_BOOL - #define BLARGG_COMPILER_HAS_BOOL 1 - #endif -#endif - -// Bool support -#ifndef BLARGG_COMPILER_HAS_BOOL - #define BLARGG_COMPILER_HAS_BOOL 1 -#elif !BLARGG_COMPILER_HAS_BOOL +/* 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 -// Set up namespace support - -#ifndef BLARGG_COMPILER_HAS_NAMESPACE - #define BLARGG_COMPILER_HAS_NAMESPACE 0 -#endif - -#ifndef BLARGG_USE_NAMESPACE - #define BLARGG_USE_NAMESPACE BLARGG_COMPILER_HAS_NAMESPACE -#endif - -#ifndef BOOST - #if BLARGG_USE_NAMESPACE - #define BOOST boost - #else - #define BOOST - #endif -#endif - -#undef BLARGG_BEGIN_NAMESPACE -#undef BLARGG_END_NAMESPACE -#if BLARGG_USE_NAMESPACE - #define BLARGG_BEGIN_NAMESPACE( name ) namespace name { - #define BLARGG_END_NAMESPACE } -#else - #define BLARGG_BEGIN_NAMESPACE( name ) - #define BLARGG_END_NAMESPACE -#endif - -#if BLARGG_USE_NAMESPACE +// BLARGG_USE_NAMESPACE: If 1, use <cxxx> headers rather than <xxxx.h> +#if BLARGG_USE_NAMESPACE || (!defined (BLARGG_USE_NAMESPACE) && BLARGG_COMPILER_HAS_NAMESPACE) + #include <cstddef> + #include <cstdlib> + #include <cassert> + #include <climits> #define STD std #else + #include <stddef.h> + #include <stdlib.h> + #include <assert.h> + #include <limits.h> #define STD #endif -// BOOST::uint8_t, BOOST::int16_t, etc. -#include "boost/cstdint.hpp" +// 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. -// BOOST_STATIC_ASSERT( expr ) -#include "boost/static_assert.hpp" +// HAVE_STDINT_H: If defined, use <stdint.h> for int8_t etc. +#if defined (HAVE_STDINT_H) + #include <stdint.h> + #define BOOST + +// HAVE_INTTYPES_H: If defined, use <stdint.h> for int8_t etc. +#elif defined (HAVE_INTTYPES_H) + #include <inttypes.h> + #define BOOST -// Common standard headers -#if BLARGG_COMPILER_HAS_NAMESPACE - #include <cstddef> - #include <cassert> #else - #include <stddef.h> - #include <assert.h> + 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<T> (expr) +#ifndef STATIC_CAST + #define STATIC_CAST(T,expr) ((T) (expr)) #endif // blargg_err_t (NULL on success, otherwise error string) -typedef const char* blargg_err_t; -const blargg_err_t blargg_success = 0; - -// BLARGG_BIG_ENDIAN and BLARGG_LITTLE_ENDIAN -#if !defined (BLARGG_BIG_ENDIAN) && !defined (BLARGG_LITTLE_ENDIAN) - #if defined (__powerc) || defined (macintosh) - #define BLARGG_BIG_ENDIAN 1 - - #elif defined (_MSC_VER) && defined (_M_IX86) - #define BLARGG_LITTLE_ENDIAN 1 - - #endif +#ifndef blargg_err_t + typedef const char* blargg_err_t; #endif +const char* const blargg_success = 0; -// BLARGG_NONPORTABLE (allow use of nonportable optimizations/features) -#ifndef BLARGG_NONPORTABLE - #define BLARGG_NONPORTABLE 0 -#endif -#ifdef BLARGG_MOST_PORTABLE - #error "BLARGG_MOST_PORTABLE has been removed; see BLARGG_NONPORTABLE." -#endif - -// BLARGG_CPU_* -#if !defined (BLARGG_CPU_POWERPC) && !defined (BLARGG_CPU_X86) - #if defined (__powerc) - #define BLARGG_CPU_POWERPC 1 +// blargg_vector: Simple array that does *not* work for types with a constructor (non-POD). +template<class T> +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; + } - #elif defined (_MSC_VER) && defined (_M_IX86) - #define BLARGG_CPU_X86 1 + 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_; } - #endif -#endif + 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
--- a/Plugins/Input/console/blargg_endian.h Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/blargg_endian.h Tue Jan 24 19:10:07 2006 -0800 @@ -1,47 +1,156 @@ // CPU Byte Order Utilities -// Game_Music_Emu 0.2.4. Copyright (C) 2005 Shay Green. BSD license. +// Game_Music_Emu 0.3.0 #ifndef BLARGG_ENDIAN #define BLARGG_ENDIAN -inline unsigned get_le16( const void* p ) { - return *((unsigned char*) p + 1) * 0x100u + *(unsigned char*) p; +#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 void set_le16( void* p, unsigned n ) { - *(unsigned char*) p = n; - *((unsigned char*) p + 1) = n >> 8; +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 - - #if 0 - // Read 16-bit little-endian unsigned integer from memory - unsigned GET_LE16( const void* ); - - // Write 16-bit little-endian integer to memory - void SET_LE16( void*, unsigned ); - #endif - // Optimized implementation if byte order is known #if BLARGG_NONPORTABLE && BLARGG_LITTLE_ENDIAN - #define GET_LE16( addr ) (*(unsigned short*) (addr)) - #define SET_LE16( addr, data ) (void (*(unsigned short*) (addr) = (data))) + #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 - #define GET_LE16( addr ) ((unsigned) __lhbrx( (addr), 0 )) + // 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 - #else - #define GET_LE16( addr ) get_le16( addr ) - #define SET_LE16( addr, data ) set_le16( addr, data ) +#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 - #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 -#endif -
--- a/Plugins/Input/console/blargg_source.h Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/blargg_source.h Tue Jan 24 19:10:07 2006 -0800 @@ -1,5 +1,6 @@ -// By default, #included at beginning of library source files +// 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. @@ -16,25 +17,60 @@ // module. A failed requirement indicates a bug outside the module. // void require( bool expr ); #undef require -#define require( expr ) assert(( "unmet requirement", expr )) +#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. +// nothing (not even evaluate its arguments). // void dprintf( const char* format, ... ); #undef dprintf -#define dprintf (1) ? ((void) 0) : (void) +#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 -#define check( expr ) ((void) 0) +#ifdef BLARGG_CHECK + #define check( expr ) BLARGG_CHECK( expr ) +#else + #define check( expr ) ((void) 0) +#endif -// If expr returns error string, return it from current function, otherwise continue. +// 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<class T> +inline T min( T x, T y ) +{ + if ( x < y ) + return x; + return y; +} + +template<class T> +inline T max( T x, T y ) +{ + if ( x < y ) + return y; + return x; +} + #endif
--- a/Plugins/Input/console/boost/config.hpp Tue Jan 24 13:57:22 2006 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ - -// Boost substitute. For full boost library see http://boost.org - -#ifndef BOOST_CONFIG_HPP -#define BOOST_CONFIG_HPP - -#define BOOST_MINIMAL 1 - -#define BLARGG_BEGIN_NAMESPACE( name ) -#define BLARGG_END_NAMESPACE - -#endif -
--- a/Plugins/Input/console/boost/cstdint.hpp Tue Jan 24 13:57:22 2006 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ - -// Boost substitute. For full boost library see http://boost.org - -#ifndef BOOST_CSTDINT_HPP -#define BOOST_CSTDINT_HPP - -#if BLARGG_USE_NAMESPACE -# include <climits> -#else -# include <limits.h> -#endif - -BLARGG_BEGIN_NAMESPACE (boost) - -#include <inttypes.h> - -BLARGG_END_NAMESPACE - -#endif -
--- a/Plugins/Input/console/boost/static_assert.hpp Tue Jan 24 13:57:22 2006 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ - -// Boost substitute. For full boost library see http://boost.org - -#ifndef BOOST_STATIC_ASSERT_HPP -#define BOOST_STATIC_ASSERT_HPP - -#if defined (_MSC_VER) && _MSC_VER <= 1200 - // MSVC6 can't handle the ##line concatenation - #define BOOST_STATIC_ASSERT( expr ) struct { int n [1 / ((expr) ? 1 : 0)]; } - -#else - #define BOOST_STATIC_ASSERT3( expr, line ) \ - typedef int boost_static_assert_##line [1 / ((expr) ? 1 : 0)] - - #define BOOST_STATIC_ASSERT2( expr, line ) BOOST_STATIC_ASSERT3( expr, line ) - - #define BOOST_STATIC_ASSERT( expr ) BOOST_STATIC_ASSERT2( expr, __LINE__ ) - -#endif - -#endif -
--- a/Plugins/Input/console/changes.txt Tue Jan 24 13:57:22 2006 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +0,0 @@ -Game_Music_Emu Change Log - - -Game_Music_Emu 0.2.4 --------------------- - -- Created a discussion forum for problems and feedback: -http://groups-beta.google.com/group/blargg-sound-libs - -- Added to-do list and design notes - -- Added Music_Emu::skip( long sample_count ) to skip ahead in current track - -- Added Gym_Emu::track_length() and Vgm_Emu::track_length() for determining the -length of non-looped GYM and VGM files - -- Fixed Fir_Resampler, used for SPC and GYM playback (was incorrectly using -abs() instead of fabs()...argh) - -- Fixed SPC emulation bugs: eliminated clicks in Plok! soundtrack and now stops -sample slightly earlier than the end, as the SNES does. Fixed a totally broken -CPU addressing mode. - -- Fixed Konami VRC6 saw wave (was very broken before). Now VRC6 music sounds -decent - -- Fixed a minor GBS emulation bug - -- Fixed GYM loop point bug when track was restarted before loop point had been -reached - -- Made default GBS frequency equalization less muffled - -- Added pseudo-surround effect removal for SPC files - -- Added Music_Emu::voice_names() which returns names for each voice. - -- Added BLARGG_SOURCE_BEGIN which allows custom compiler options to be easily -set for library sources - -- Changed assignment of expansion sound chips in Nsf_Emu to be spread more -evenly when using Effects_Buffer - - -Game_Music_Emu 0.2.0 --------------------- - -- Redid framework and rewrote/cleaned up emulators - -- Changed licensing to GNU Lesser General Public License (LGPL) - -- Added Sega Genesis GYM and Super Nintendo SPC emulators - -- Added Namco-106 and Konami VRC6 sound chip support to NSF emulator - -- Eliminated use of static mutable data in emulators, allowing multi-instance -safety - - -Game_Music_Emu 0.1.0 --------------------- - -- First release
--- a/Plugins/Input/console/design.txt Tue Jan 24 13:57:22 2006 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,177 +0,0 @@ -Game_Music_Emu Design Notes - - -Managing Complexity -------------------- -Complexity has been a factor in most library decisions. Many features -have been passed by due to the complexity they would add. Once past a -certain level, complexity prevents mentally grasping the library in its -entirety, at which point more defects will occur and be hard to find. - -I chose 16-bit signed samples because it seems to be the most common -format. Supporting multiple formats would add too much complexity to be -worth it. Other formats can be obtained via conversion. - -I've kept interfaces fairly lean, leaving many possible features -untapped but easy to add if necessary. For example the classic emulators -could have volume and frequency equalization adjusted separately for -each channel, since they each have an associated Blip_Synth. - -Source files of 400 lines or less seem to be the best size to limit -complexity. In a few cases there is no reasonable way to split longer -files, or there is benefit from having the source together in one file. - - -Library Configuration ---------------------- -Library optimizations can be configured through macros defined in -config.h. By default, the library is configured to be most likely to -compile and work on any platform, rather than be most optimal with -increased chance of problems. It's easier to track down optimiztation -problems if the library can first be shown to work correctly without -them. - - -Flexibility through indirection -------------------------------- -I've tried to allow the most flexibility of modules by using indirection -to allow extension by the user. This keeps each module simple and more -focused on its unique task. - -The classic emulators use Multi_Buffer, which potentially allows a -separate Blip_Buffer for each channel. This keeps emulators free of -typical code to allow output in mono, stereo, panning, etc. - -All emulators use a reader object to access file data, allowing it to be -stored in a regular file, compressed archive, memory, or generated -on-the-fly. Again, the library can be kept free of the particulars of -file access and changes required to support new formats. - - -Choice of pre-ISO (ARM) C++ ---------------------------- -The library started out as an unreleased NSF player written using ISO -C++. The code was clean enough that I decided to release it as a player -library. Before release I evaluated its use of C++ features to determine -the important ones. - -Namespaces and exceptions weren't essential, so I compared a version of -the library with and without them. I decided that I could do without and -get better compatibility with older compilers or newer ones with buggy -namespace and exception implementations. - -Templates are the worst area for most compilers, due to their inherent -complexity, but they are too useful to avoid entirely. I've used them -sparingly and in ways that compilers are more likely to work with. - -Sticking to ARM C++ has helped keep the library simpler to understand. - - -Platform-specific optimization ------------------------------- -Performance profiling doesn't shown any big bottlenecks that warrant -heavy platform-specific optimization. The main bottlenecks are CPU -emulation, Blip_Buffer synthesis and sample reading, Super NES DSP, Sega -Genesis FM, and Fir_Resampler. - -Further optimization of the CPU emulators can probably only be achieved -by writing them in assembly or using dynamic recompilation. Blip_Buffer -might benefit somewhat from vector instructions. Sega Genesis FM -synthesis can probably be made twice as fast by someone who fully -understands its operation (I am almost clueless about its internals). -The Super NES DSP might have some room for optimization. Fir_Resampler -would benefit greatly from vector operations. - -Most of the above optimizations add more complexity than they are worth. -All that seems worthwhile is optimization of Sega Genesis FM emulation -and Fir_Resampler. - - -Preventing Bugs ---------------- -I've done many things to reduce the opportunity for defects. A general -principle is to write code so that defects will be as visible as -possible. I've used several techniques to achieve this. - -I put assertions at key points where defects seem likely or where -corruption due to a defect is likely to be visible. I've also put -assetions where violations of the interface are likely. In emulators -where I am unsure of exact hardware operation in a particular case, I -output a debug-only message noting that this has occurred; many times I -haven't implemented a hardware feature because nothing uses it. I've -made code brittle where there is no clear reason flexibility; code -written to handle every possibility sacrifices quality and reliability -to handle vaguely defined situations. - - -Miscellaneous -------------- -I don't like naming header files with a ".hpp" suffix for some reason. -They aren't as visually distinct from ".cpp" source files. The ".cpp" -suffix is useful to inform the compiler that it's a C++ file rather than -a C file, but I don't know of significant practical benefits the ".hpp" -suffix gives over ".h" (I used to use *no* suffix for header files, but -this does cause problems). - -When implementation has to be put in a header file, it's as far near the -end as possible to prevent distraction from the public interface. - - -CPU Cores ---------- -I've spent lots of time coming up with techniques to optimize the CPU -cores. Some of the most important: execute multiple instructions during -an emulation call, keep state in local variables to allow register -assignment, optimize state representation for most common instructions, -defer status flag calculation until actually needed, read program code -directly without a call to the memory read function, always pre-fetch -the operand byte before decoding instruction, and emulate instructions -using common blocks of code. - -I've successfully used Nes_Cpu in a simple NES emulator, and I'd like to -make all the CPU emulators suitable for use in emulators. It seems a -waste for them to be used only for the small amount of emulation -necessary for game music files. - -I debugged the CPU cores by writing a test shell that ran them in -parallel with other CPU cores and compared all memory accesses and -processor states at each step. This provided good value at little cost. - -The CPU mapping page size is adjustable to allow the best tradeoff -between memory/cache usage and handler granularity. The interface allows -code to be somewhat independent of the page size. - -I optimize program memory accesses to to direct reads rather than calls -to the memory read function. My assumption is that it would be difficult -to get useful code out of hardware I/O addresses, so no software will -intentionally execute out of I/O space. Since the page size can be -changed easily, most program memory mapping schemes can be accommodated. -This greatly reduces memory access function calls. - - -Sub-Libraries -------------- -I've also released the sound cores as individual libraries to reduce -complexity for emulator authors who just want a single sound core. These -authors will be using the sound cores directly, while users of this -music emulator library won't even see them, so documentation and demos -can be specific to each library. - - -Documentation -------------- -I started out with separate documentation in HTML and found that it -wasn't going to be easy to maintain. I switched to putting descriptions -of function behavior in header files before the function declarations. -This has worked well so far. - -I think the concrete executable demo code helps the most when someone is -using the library for the first time, since it shows a complete program -and provides a framework for using the library. By recording to a sound -file, I can keep the code portable, and the result can be listened to or -examined closely, which is important for something real-time like sound. - - -I want to write some tutorials to complement the demo code, desribing -the basic framework and operation of the modules. -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/gme_notes.txt Tue Jan 24 19:10:07 2006 -0800 @@ -0,0 +1,181 @@ +Game_Music_Emu 0.3.0 Notes +-------------------------- +Author : Shay Green <hotpop.com@blargg> +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. +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/gme_readme.txt Tue Jan 24 19:10:07 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 <hotpop.com@blargg> +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.
--- a/Plugins/Input/console/notes.txt Tue Jan 24 13:57:22 2006 -0800 +++ b/Plugins/Input/console/notes.txt Tue Jan 24 19:10:07 2006 -0800 @@ -1,220 +1,77 @@ -Game_Music_Emu Notes - - -Architecture ------------- - -This library has several emulator classes derived from common interface -classes. Music_Emu specifies the main interface, and Classic_Emu adds -features available only for "classic" systems (frequency equalization -and customizable multi-channel sound buffer). - -To play a given game music file, do the following: - -- Determine its file type -- Create and set up an appropriate emulator -- Load file header and file data into emulator -- Start desired track -- When samples are needed, call play() -- When done, delete emulator - -Each emulator type defines a nested header_t structure type with members -for the file header. When loading a file, the emulators expect the file -header to have already been loaded; this allows the caller to use header -fields like the game name and music author. +Audacious Console Game Music Driver +----------------------------------- +Contact: Shay Green <hotpop.com@blargg> -See Music_Emu.h and Classic_Emu.h for reference. - - -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. +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). -To allow compatibility with older C++ compilers, no exceptions are -thrown by any of the modules. The library is exception-safe, and any -exceptions which occur are not intercepted. +- 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. -Due to the different ways compiler runtime libraries handle -out-of-memory errors, if the library encounters one it will be reported -in one of two ways: if the compiler is configured to throw an exception -when operator new can't satisfy a request (which is the case in ISO -C++), it is allowed to propagate normally, otherwise the error string -"Out of memory" is returned. - -Significant violations of the documented interface are flagged with -debug-only assertions. Failure of these usually indicates a caller error -rather than a defect in the library. - +- Seeking should be tested carefully. It might be too slow for some +formats. -Configuration -------------- - -The header "blargg_common.h" is used to establish a common environment. -It attempts to automatically determine the features of the environment, -but might need help. - -If HAVE_CONFIG_H is defined, the file "config.h" is included at the -beginning of each library header file, allowing configuration options -for the library to be set. It's fine if other libraries also use this -scheme, as they won't conflict. +- 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. -Some libraries depend on the order of bytes in multibyte types. These -will cause a compilation error if the order can't be determined. If this -occurs, define the appropriate symbol. For big-endian (most significant -byte first, i.e. Motorola 68000, PowerPC), #define BLARGG_BIG_ENDIAN to -1. For little-endian (least significant byte first, i.e. Intel x86), -#define BLARGG_LITTLE_ENDIAN to 1. - -Pre-ISO C++ compilers might not support bool. Support is provided where -bool is not available, but the compiler's support of bool might not be -properly determined. If errors occur in "blargg_common.h" in the bool -section, #define BLARGG_COMPILER_HAS_BOOL to 1 if your compiler supports -bool, otherwise 0. +- 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. -If your compiler supports namespaces, blargg_common.h uses standard -headers with the "c" prefix to avoid bringing names from std into the -global namespace. If your compiler supports namespaces but this isn't -being detected by blargg_common.h, #define BLARGG_COMPILER_HAS_NAMESPACE -to 1 in your config.h file. - -If you have any problems with "blargg_common.h", contact me. - - -Game Music File Handling ------------------------- +- 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. -Game music files include text fields with information about the game and -track. Each emulator's header_t defines the basic fields (game, song, -author, copyright, track_count) supported by that music type, or has an -enum { field = 0 } if unsupported. This allows the same code to be used -for parsing each header if it checks that the field is non-zero. - -Text fields in most game music formats won't have a nul terminator if -the string completely fills the field. The demos show one way to handle -this. - -This library is focused on playback only and the emulators don't parse -extended text fields, since this can be complex for some formats and can -be done by the caller. To load the NSFE format, it must be parsed first -and an NSF header must be made in memory and passed Nsf_Emu. See the -Game Music Box source code for an example of this: - - http://www.slack.net/~ant/game-music-box/dev.html +- 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. -Frequency equalization ----------------------- +Change Log +---------- +- Marked things to be addressed with "// to do:" comments -The classic emulators allow frequency equalization to be adjusted; -Classic_Emu::equalizer_t( treble, cutoff, bass ) specifies low-pass and -high-pass filtering. - -Low-pass is an exponential rolloff beginning at 'cutoff' Hz and -attenuating by 'treble' dB at 22kHz. For example, with cutoff = 8000 Hz -and treble = -6 dB, the following results: +- Updated to Game_Music_Emu 0.3.0 and eliminated unnecessary source +files - cutoff = 8kHz - 0dB -------*__ - ~~--__ treble = -6dB - ~~-*__ - ~~--__ - ~~--__ - ~~--__ --18dB - - - - - - - - - - - - - - - - - - - - - - - 0 8kHz 22kHz 44kHz ... +- Eliminated Audacious_Driver.h since it served no purpose +- Added support for NSFE files -High-pass is a steep rolloff which passes -3dB attenuation at -'breakpoint' Hz, where useful frequencies range from 0 to 6000 Hz. For -example, with breakpoint = 1000 Hz, the following results: +- Added Vfs_File, a wrapper for the vfs_* file functions. This allows +all the emulators to access files in this manner. - breakpoint = 1000 Hz - 0dB ___________ --3dB ,_*---~~~~~ - _~ - / - / - | - | --21dB - - - - - - - - - - - - - - 0 1000 Hz 4000 Hz +- Updated Makefile.am but didn't add line to link with zlib + +- Added fading at end of tracks -Each emulator defaults to a profile that approximates its particular -console's sound quality; this default can be determined by getting the -current equalization just after creating the emulator. See Classic_Emu.h -for reference. - +- 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. -Emulator gain control ---------------------- - -Each emulator allows its gain to be adjusted. The default gains are -selected to give consistent relative volumes between emulators without -resulting in excessive clamping of samples that would otherwise go -beyond the 16-bit range. A gain of 1.0 results in a conservative volume -that rarely requires any clamping to stay within the 16-bit sample -range. Clamping samples to 16 bits is handled by the library. +- 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). -Output sample rate ------------------- - -Each emulator has a way of specifying the output sample rate during -initialization. All the emulators use internal band-limiting, so there -is no reason to use a sample rate above 48kHz unless the sound hardware -demands it; 44-48kHz will yield the best results. You could use the -following code to choose a rate that is both near this range and an -integral division of the native rate: - - long adjust_rate( double native ) - { - for ( double divider = 1; divider <= 4; divider++ ) - { - long adjusted = native / divider + 0.5; - if ( adjusted <= 56000 ) - return adjusted; - } - return 44100; // give up; CD rate probably works well enough - } - - -Interface conventions ----------------------- - -If a function will keep a pointer to an object passed, to make this -clear in source code it takes a pointer rather than a reference. +To Do +----- +- Separate track info handling from Audacious_Driver.cpp, since the +current complexity is a good source of bugs -Multi-word names have an underscore '_' separator between individual -words. - -Functions are named with lowercase words. Functions which perform an -action with side-effects are named with a verb phrase (i.e. load, move, -run). Functions which set or return the value of a piece of state are -named using a noun phrase (i.e. loaded, moved, running). - -Classes are named with capitalized words. Only the first letter of an -acronym is capitalized. Class names are nouns, sometimes suggestive of -what they do (i.e. File_Scanner). - -Structure, enumeration, and typedefs to these and built-in types are -named using lowercase words with a _t suffix. - -Macros are named with all-uppercase words. - -Internal names which can't be hidden due to technical reasons have an -underscore '_' suffix. - - -Misc ----- - -Special thanks to Chris Moeller (kode54) for help with library testing -and feedback. His openspc++ library in C++ was an essential starting -point and framework for developing the SPC emulator. Brad Martin's -excellent SNES DSP emulator (also part of openspc++) provided an -essential foundation for the DSP core. -
--- a/Plugins/Input/console/readme.txt Tue Jan 24 13:57:22 2006 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,122 +0,0 @@ -Game_Music_Emu 0.2.4: Multi-Format Game Music Emulation Library - - -Game_Music_Emu is a collection of portable video game music emulators for the -following file formats: Nintendo NSF, Game Boy GBS, Sega Master System VGM, -Sega Gensesis GYM, and Super Nintendo SPC. - -Licensed under the GNU Lesser General Public License (LGPL); see LGPL.txt. -Copyright (C) 2003-2005 Shay Green. SNES SPC DSP emulator based on OpenSPC, -Copyright (C) 2002 Brad Martin. Sega Genesis YM2612 emulator from Gens project, -Copyright (C) 2002 Stephane Dallongeville. - -Website: http://www.slack.net/~ant/libs/ -Forum : http://groups-beta.google.com/group/blargg-sound-libs -Contact: hotpop.com@blargg (swap to e-mail) - - -Getting Started ---------------- - -This library is written in somewhat conservative C++ that should compile with -current and older compilers (ANSI/ISO and ARM). - -If the Boost library is installed in your environment, delete the included -"boost" compatibility directory, otherwise add the included "boost" directory -to your compiler's search paths. - -Build a program consisting of the included source files except demo_effects.cpp -and demo_panning.cpp, and any necessary system libraries. Be sure "test.nsf" is -in the same directory. The program should generate a WAVE sound file "out.wav" -of music. - -For a full example of using Game_Music_Emu in a music player, see the Game -Music Box source code: http://www.slack.net/~ant/game-music-box/dev.html - -See notes.txt for more information, and respective header (.h) files for -reference. Visit the discussion forum to get assistance. - - -Files ------ - -notes.txt Collection of notes about the library -changes.txt Changes made since previous releases -todo.txt Planned improvements and fixes -design.txt Library design notes -LGPL.TXT GNU Lesser General Public License - -demo.cpp Record NSF to WAVE sound file using emulator -demo_effects.cpp Use Effects_Buffer while recording GBS file -demo_panning.cpp Use Panning_Buffer while recording VGM file -test.nsf Test file for NSF emulator - -Music_Emu.h Game music emulator interface -Spc_Emu.h Super NES SPC emulator -Gym_Emu.h Sega Genesis GYM emulator - -Classic_Emu.h "Classic" game music emulator interface -Nsf_Emu.h Nintendo NSF emulator -Gbs_Emu.h Game Boy GBS emulator -Vgm_Emu.h Sega Master System VGM emulator - -Multi_Buffer.h Mono and stereo buffers for classic emulators -Effects_Buffer.h Effects buffer for classic emulators -Panning_Buffer.h Panning buffer for classic emulators - -blargg_common.h Common library source -blargg_endian.h -blargg_source.h -Blip_Buffer.cpp -Blip_Buffer.h -Blip_Synth.h -Music_Emu.cpp -Classic_Emu.cpp -Multi_Buffer.cpp -Effects_Buffer.cpp -Panning_Buffer.cpp -Fir_Resampler.cpp -Fir_Resampler.h -abstract_file.cpp -abstract_file.h -Nes_Apu.cpp NSF emulator source -Nes_Apu.h -Nes_Cpu.cpp -Nes_Cpu.h -Nes_Oscs.cpp -Nes_Oscs.h -Nsf_Emu.cpp -Nes_Namco.cpp -Nes_Namco.h -Nes_Vrc6.cpp -Nes_Vrc6.h -Tagged_Data.h -Gbs_Emu.cpp GBS emulator source -Gb_Apu.cpp -Gb_Apu.h -Gb_Cpu.cpp -Gb_Cpu.h -Gb_Oscs.cpp -Gb_Oscs.h -Sms_Apu.cpp VGM emulator source -Sms_Apu.h -Sms_Oscs.h -Vgm_Emu.cpp -Gym_Emu.cpp GYM emulator source -ym2612.cpp -ym2612.h -Spc_Emu.cpp SPC emulator source -Snes_Spc.cpp -Snes_Spc.h -Spc_Cpu.cpp -Spc_Cpu.h -Spc_Dsp.cpp -Spc_Dsp.h - -boost/ Substitute for boost library if it's unavailable - -Wave_Writer.hpp WAVE sound file writer used for demo output -Wave_Writer.cpp - --- -Shay Green <hotpop.com@blargg> (swap to e-mail)
--- a/Plugins/Input/console/todo.txt Tue Jan 24 13:57:22 2006 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ -Game_Music_Emu 0.2.4: Problems and planned improvements - - -Interface ---------- - -- Refer to sound chip oscillators as channels, since Nes_Channel was -renamed a long time ago. - -- Rename setters with set_ prefix. I decided the terseness wasn't worth -it. - -- Consider putting buffer pointer before count in play() etc. Only -reason I put count first is to allow making buffer pointer default to -NULL for skip. But it seems better to have skip() a separate function. -One other reason to put count first is to follow the left-to-right -principle in organizing arguments: play( count, buf ) generates 'count' -samples and writes them into 'buf', and count is a more significant -parameter than the buffer to write to. - -- VGM, GYM: change track_length() to return length and start of loop? - - -Misc ----- - -- NSF: Partial implementation of DAC non-linearity causes possible -drift. Messes up saw wave demo at high frequencies. - -- GYM: See if ignoring PCM when it's disabled eliminates pop in some -tracks. - -- All CPU emulators: what if instruction straddles page boundary? Might -need to completely avoid reading words from code memory. - -- Test reloading a new file into emulator after already loading one - -- Optimize resampling in Sega Genesis GYM emulator - -- Improve GBS emulation - -- Improve GYM PCM channel emulation - -- SPC: Finish KON and KOFF reverse-engineering and incorporate into -Spc_Dsp - -- GBS: Crystalis track 18 plays really fast. Does same in gbsplay 0.7. - -- GBS: Ultima 3 overflows stack. Messes up in gbsplay 0.7. - -- Set track_ended flag when emulation error occurs (log emulation error -only in debug mode). - -- Change wave and noise Gb_Oscs to blip_good_quality? - -- VGM: Support Sega Genesis - -- VGM: Parse extended header - -- VGM: Support new version of VGM file format that might be released -soon - -- GYM: Check Altered Beast tracks with samples. They cut off seemingly -too soon. - -- Include example of how to use NSF non-linear handling (with note about -limitations) - -- Keep SP and PC as 32-bit in CPU registers structure so emulator can -detect overflow/underflow and halt emulation, rather than having them -masked when written back? -
--- a/Plugins/Input/console/ym2612.cpp Tue Jan 24 13:57:22 2006 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1328 +0,0 @@ - -// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ - -#include "ym2612.h" - -#include <string.h> -#include "libaudacious/vfs.h" -#include <math.h> - -/* Copyright (C) 2002 Stéphane Dallongeville (gens@consolemul.com) */ -/* Copyright (C) 2004-2005 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 - -// 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 max_length = 3072; // A little over 4 frames - -const int output_bits = 14; - -typedef struct YM2612_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 -} slot_t; - -typedef struct YM2612_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 -} channel_t; - -typedef struct YM2612_state_t { - int Clock; // Horloge YM2612 - int Rate; // Sample Rate (11025/22050/44100) - int TimerBase; // TimerBase calculation - int Status; // YM2612 Status (timer overflow) - int OPNAadr; // addresse pour l'ecriture dans l'OPN A (propre à l'emulateur) - int OPNBadr; // addresse pour l'ecriture dans l'OPN B (propre à l'emulateur) - int LFOcnt; // LFO counter = compteur-frequence pour le LFO - 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 - double Frequence; // Frequence de base, se calcul par rapport à l'horlage et au sample rate - 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 -} state_t; - -#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; - } -} - -typedef struct YM2612_tables_t -{ - short SIN_TAB [SIN_LENGHT]; // SINUS TABLE (offset into TL TABLE) - 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 - - int LFO_ENV_FREQ_UP [max_length * 2]; // Temporary calculated LFO FMS, AMS (adjusted for 11.8 dB) (interleved) - unsigned int FINC_TAB [2048]; // Frequency step table - - short ENV_TAB [2 * ENV_LENGHT + 8]; // ENV CURVE TABLE (attack & decay) - - unsigned int DECAY_TO_ATTACK [ENV_LENGHT]; // Conversion from decay to attack phase - 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) -} tables_t; - -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 int 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 ); - - blargg_err_t set_rate( long sample_rate, long clock_rate ); - void reset(); - void write( int addr, int data ); - void run_timer( int ); - void run( YM2612_Emu::sample_t*, int count ); -}; - -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 = YM2612.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; -} - -YM2612_Emu::YM2612_Emu() { - impl = NULL; -} - -YM2612_Emu::~YM2612_Emu() { - delete impl; -} - -blargg_err_t YM2612_Emu::set_rate( long rate, long clock ) -{ - if ( !impl ) { - impl = new YM2612_Impl; - if ( !impl ) - return "Out of memory"; - impl->mute_mask = 0; - } - return impl->set_rate( rate, clock ); -} - -blargg_err_t YM2612_Impl::set_rate( long Rate, long Clock ) -{ - require( Rate ); - require( Clock ); - - int i, j; - double x; - - memset(&YM2612, 0, sizeof(YM2612)); - - YM2612.Clock = Clock; - YM2612.Rate = Rate; - - // 144 = 12 * (prescale * 2) = 12 * 6 * 2 - // prescale set to 6 by default - - YM2612.Frequence = ((double) YM2612.Clock / (double) YM2612.Rate) / 144.0; - YM2612.TimerBase = (int) (YM2612.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 - { - 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++) - { - x = sin(2.0 * PI * (double) (i) / (double) (SIN_LENGHT)); // Sinus - x = 20 * log10(1 / x); // convert to dB - - 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++) - { - 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) - 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; - } - - g.ENV_TAB [ENV_END >> ENV_LBITS] = ENV_LENGHT - 1; // for the stopped state - - // Tableau pour la conversion Attack -> Decay and Decay -> Attack - - for(i = 0, j = ENV_LENGHT - 1; i < ENV_LENGHT; i++) - { - while (j && (g.ENV_TAB [j] < (unsigned) i)) j--; - - g.DECAY_TO_ATTACK [i] = j << ENV_LBITS; - } - - // Tableau pour le Substain Level - - for(i = 0; i < 15; i++) - { - x = i * 3; // 3 and not 6 (Mickey Mania first music for test) - x /= ENV_STEP; - - j = (int) x; - j <<= ENV_LBITS; - - g.SL_TAB [i] = j + ENV_DECAY; - } - - j = ENV_LENGHT - 1; // special case : volume off - j <<= ENV_LBITS; - g.SL_TAB [15] = j + ENV_DECAY; - - // Tableau Frequency Step - - for(i = 0; i < 2048; i++) - { - x = (double) (i) * YM2612.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++) - { - x = YM2612.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; - } - - // Tableau Detune - - for(i = 0; i < 4; i++) - { - for (j = 0; j < 32; j++) - { -#if ((SIN_LBITS + SIN_HBITS - 21) < 0) - x = (double) DT_DEF_TAB [(i << 5) + j] * YM2612.Frequence / (double) (1 << (21 - SIN_LBITS - SIN_HBITS)); -#else - x = (double) DT_DEF_TAB [(i << 5) + j] * YM2612.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 - - j = YM2612.Rate; - - g.LFO_INC_TAB [0] = (unsigned int) (3.98 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / j); - g.LFO_INC_TAB [1] = (unsigned int) (5.56 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / j); - g.LFO_INC_TAB [2] = (unsigned int) (6.02 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / j); - g.LFO_INC_TAB [3] = (unsigned int) (6.37 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / j); - g.LFO_INC_TAB [4] = (unsigned int) (6.88 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / j); - g.LFO_INC_TAB [5] = (unsigned int) (9.63 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / j); - g.LFO_INC_TAB [6] = (unsigned int) (48.1 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / j); - g.LFO_INC_TAB [7] = (unsigned int) (72.2 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / j); - - reset(); - - return blargg_success; -} - -void YM2612_Emu::reset() { - impl->reset(); -} - -void YM2612_Impl::reset() -{ - YM2612.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; - - YM2612.OPNAadr = 0; - YM2612.OPNBadr = 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-- ) { - write( 0, i ); - write( 2, i ); - write( 1, 0xC0 ); - write( 3, 0xC0 ); - } - - for ( i = 0xB2; i >= 0x22; i-- ) { - write( 0, i ); - write( 2, i ); - write( 1, 0 ); - write( 3, 0 ); - } - - write( 0, 0x2A ); - write( 1, 0x80 ); -} - -inline void YM2612_Impl::write( int addr, int data ) -{ - require( (unsigned) addr < 4 ); - require( (unsigned) data <= 0xFF ); - - switch( addr ) - { - case 0: - YM2612.OPNAadr = data; - break; - - case 2: - YM2612.OPNBadr = data; - break; - - case 1: { - int opn_addr = YM2612.OPNAadr; - 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 ); - } - break; - } - - case 3: { - int opn_addr = YM2612.OPNBadr; - 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 ); - } - break; - } - } -} - -void YM2612_Emu::write( int addr, int data ) { - impl->write( addr, data ); -} - -inline void YM2612_Impl::run_timer( int length ) -{ - int i = YM2612.TimerBase * length; - - 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; - } - } -} - -void YM2612_Emu::run_timer( int length ) { - impl->run_timer( length ); -} - -void YM2612_Emu::mute_voices( int mask ) { - impl->mute_mask = mask; -} - -#include BLARGG_ENABLE_OPTIMIZER - -static const int no_lfo [2] = { 0, 0 }; - -static void update_env( channel_t& ch ) -{ - slot_t* sl = ch.SLOT; - for ( int n = 4; n--; sl++ ) - { - if ( (sl->Ecnt += sl->Einc) < sl->Ecmp ) - continue; - - 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 - } - } -} - -template<int algo> -struct ym2612_update_chan { - static void func( const tables_t& g, channel_t&, YM2612_Emu::sample_t*, int ); -}; - -typedef void (*ym2612_update_chan_t)( const tables_t& g, channel_t&, YM2612_Emu::sample_t*, int ); - -template<int algo> -void ym2612_update_chan<algo>::func( const 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; - - const int CH_LEFT = ch.LEFT; - const int CH_RIGHT = ch.RIGHT; - - int CH_S0_OUT_1 = ch.S0_OUT [1]; - - if ( !not_end ) - return; - - int in0 = ch.SLOT [S0].Fcnt; - int in1 = ch.SLOT [S1].Fcnt; - int in2 = ch.SLOT [S2].Fcnt; - int in3 = ch.SLOT [S3].Fcnt; - - const int* lfo_freq_env = g.LFO_ENV_FREQ_UP; - int lfo_step = 2; - if ( !g.LFOinc ) { - lfo_step = 0; - lfo_freq_env = no_lfo; - } - - const int* const TL_TAB = g.TL_TAB; // cache - -#define SINT( i, o ) (TL_TAB [g.SIN_TAB [(i)] + (o)]) - - const short* const ENV_TAB = g.ENV_TAB; // cache - - goto first_iter; - - while ( --length ) - { - // update envelope - update_env( ch ); - first_iter: - - // calc envelope - int const env_LFO = lfo_freq_env [0]; - - #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 ) - - // 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 - const unsigned freq_LFO = ((ch.FMS * lfo_freq_env [1]) >> (LFO_HBITS - 1 + 1)) + (1L << (LFO_FMS_LBITS - 1)); - 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); - - ch.S0_OUT [0] = CH_S0_OUT_0; - buf [0] = t0; - buf [1] = t1; - buf += 2; - - lfo_freq_env += lfo_step; - } - - 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; - - update_env( ch ); -} - -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( YM2612_Emu::sample_t* buf, int length ) -{ - require( length % 2 == 0 ); // generates pairs of samples - require( length <= max_length * 2 ); - - length >>= 1; - - // 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); - } - } - - if ( g.LFOinc ) - { - // Precalcul LFO wav - for(int i = 0; i < length; i++) - { - int j = ((YM2612.LFOcnt += g.LFOinc) >> LFO_LBITS) & LFO_MASK; - - g.LFO_ENV_FREQ_UP [i * 2] = g.LFO_ENV_TAB [j]; - g.LFO_ENV_FREQ_UP [i * 2 + 1] = g.LFO_FREQ_TAB [j]; - } - } - - 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], buf, length ); -} - -void YM2612_Emu::run( sample_t* buf, int length ) { - impl->run( buf, length ); -} -
--- a/Plugins/Input/console/ym2612.h Tue Jan 24 13:57:22 2006 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ - -// Sega Genesis YM2612 FM Sound Chip Emulator - -// Game_Music_Emu 0.2.4. Copyright (C) 2004-2005 Shay Green. GNU LGPL license. -// Copyright (C) 2002 Stéphane Dallongeville - -#ifndef YM2612_H -#define YM2612_H - -#include "blargg_common.h" - -struct YM2612_Impl; - -class YM2612_Emu { -public: - YM2612_Emu(); - ~YM2612_Emu(); - - blargg_err_t set_rate( long sample_rate, long clock_rate ); - - void reset(); - - enum { channel_count = 6 }; - void mute_voices( int mask ); - - void write( int addr, int data ); - - void run_timer( int ); - - typedef BOOST::int16_t sample_t; - void run( sample_t*, int count ); - -private: - YM2612_Impl* impl; -}; - -#endif -