Mercurial > audlegacy-plugins
diff src/console/Audacious_Driver.cxx @ 316:fb513e10174e trunk
[svn] - merge libconsole-blargg into mainline libconsole:
+ obsoletes plugins-ugly:sapplug
| author | nenolod |
|---|---|
| date | Thu, 30 Nov 2006 19:54:33 -0800 |
| parents | 3da1b8942b8b |
| children | 687f74f92f6d |
line wrap: on
line diff
--- a/src/console/Audacious_Driver.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Audacious_Driver.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,6 +1,6 @@ /* * Audacious: Cross platform multimedia player - * Copyright (c) 2005 Audacious Team + * Copyright (c) 2005-2006 Audacious Team * * Driver for Game_Music_Emu library. See details at: * http://www.slack.net/~ant/libs/ @@ -17,537 +17,189 @@ } #include <string.h> #include <stdlib.h> -#include <ctype.h> #include <math.h> // configdb and prefs ui #include "Audacious_Config.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 "Music_Emu.h" +#include "Vfs_File.h" -#include "Track_Emu.h" -#include "Vfs_File.h" -#include "Gzip_File.h" -#include "blargg_endian.h" +int const fade_threshold = 10 * 1000; +int const fade_length = 8 * 1000; -//typedef Vfs_File_Reader Audacious_Reader; // will use VFS once it handles gzip transparently -typedef Gzip_File_Reader Audacious_Reader; - -AudaciousConsoleConfig audcfg = { 180, FALSE, 32000, TRUE, 0, 0, FALSE }; +AudaciousConsoleConfig audcfg = { 180, FALSE, 32000, TRUE, 0, 0, FALSE, 0 }; static GThread* decode_thread; static GStaticMutex playback_mutex = G_STATIC_MUTEX_INIT; static int console_ip_is_going; static volatile long pending_seek; extern InputPlugin console_ip; static Music_Emu* emu = 0; -static Track_Emu track_emu; static int track_ended; +static blargg_err_t log_err( blargg_err_t err ) +{ + if ( err ) + printf( "console error: %s\n", err ); + return err; +} + +static void log_warning( Music_Emu* emu ) +{ + const char* w = emu->warning(); + if ( w ) + printf( "console warning: %s\n", w ); +} + static void unload_file() { - delete emu; + if ( emu ) + log_warning( emu ); + gme_delete( emu ); emu = NULL; } -// Information - -typedef unsigned char byte; - -#define DUPE_FIELD( field ) g_strndup( field, sizeof (field) ); +// Extracts track number from file path, also frees memory at end of block -struct track_info_t +struct Url_Parser { - 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; + gchar* path; // path without track number specification + int track; // track number (0 = first track) + bool track_specified; // false if no track number was specified in path + Url_Parser( gchar* path ); + ~Url_Parser() { g_free( path ); } }; -// NSFE - -void get_nsfe_info( Nsfe_Info const& nsfe, track_info_t* out ) -{ - Nsfe_Info::info_t const& h = nsfe.info(); - out->ti->performer = DUPE_FIELD( h.author ); - out->ti->album_name = DUPE_FIELD( h.game ); - out->ti->comment = DUPE_FIELD( h.copyright ); - out->ti->track_name = g_strdup( nsfe.track_name( out->track ) ); - int time = nsfe.track_time( out->track ); - if ( time > 0 ) - out->length = time; - if ( nsfe.info().track_count > 1 ) - out->ti->track_number = out->track + 1; -} - -inline void get_info_emu( Nsfe_Emu& emu, track_info_t* out ) -{ - emu.enable_playlist( audcfg.nsfe_playlist ); // to do: kind of hacky - get_nsfe_info( emu, out ); -} - -inline void get_file_info( Nsfe_Emu::header_t const& h, Data_Reader& in, track_info_t* out ) -{ - Nsfe_Info nsfe; - if ( !nsfe.load( h, in ) ) - { - nsfe.enable_playlist( audcfg.nsfe_playlist ); - get_nsfe_info( nsfe, out ); - } -} - -// NSF - -static void get_nsf_info_( Nsf_Emu::header_t const& h, track_info_t* out ) +Url_Parser::Url_Parser( gchar* path_in ) { - out->ti->performer = DUPE_FIELD( h.author ); - out->ti->album_name = DUPE_FIELD( h.game ); - out->ti->comment = DUPE_FIELD( h.copyright ); - if ( h.track_count > 1 ) - out->ti->track_number = out->track + 1; -} - -inline void get_info_emu( Nsf_Emu& emu, track_info_t* out ) -{ - get_nsf_info_( emu.header(), out ); -} - -inline void get_file_info( Nsf_Emu::header_t const& h, Data_Reader& in, track_info_t* out ) -{ - get_nsf_info_( h, out ); -} - -// GBS - -static void get_gbs_info_( Gbs_Emu::header_t const& h, track_info_t* out ) -{ - out->ti->performer = DUPE_FIELD( h.author ); - out->ti->album_name = DUPE_FIELD( h.game ); - out->ti->comment = DUPE_FIELD( h.copyright ); - if ( h.track_count > 1 ) - out->ti->track_number = out->track + 1; -} - -inline void get_info_emu( Gbs_Emu& emu, track_info_t* out ) -{ - get_gbs_info_( emu.header(), out ); -} - -inline void get_file_info( Gbs_Emu::header_t const& h, Data_Reader& in, track_info_t* out ) -{ - get_gbs_info_( h, out ); -} - -// GYM - -static void get_gym_info_( Gym_Emu::header_t const& h, track_info_t* out ) -{ - if ( !memcmp( h.tag, "GYMX", 4 ) ) + track = 0; + track_specified = false; + + path = g_strdup( path_in ); + if ( path ) { - out->ti->performer = DUPE_FIELD( h.copyright ); - out->ti->album_name = DUPE_FIELD( h.game ); - out->ti->track_name = DUPE_FIELD( h.song ); - out->ti->comment = DUPE_FIELD( h.comment ); - } -} - -static void get_gym_timing_( Gym_Emu const& emu, track_info_t* out ) -{ - out->length = emu.track_length() * 50 / 3; // 1000 / 60 - out->loop = 0; - - long loop = get_le32( emu.header().loop_start ); - if ( loop ) - { - out->intro = loop * 50 / 3; - out->loop = out->length - out->intro; - out->length = -1; - } -} - -inline void get_info_emu( Gym_Emu& emu, track_info_t* out ) -{ - get_gym_info_( emu.header(), out ); - get_gym_timing_( emu, out ); -} - -inline void get_file_info( Gym_Emu::header_t const& h, Data_Reader& in, track_info_t* out ) -{ - get_gym_info_( h, out ); - - // have to load and parse entire GYM file to determine length - // to do: could make more efficient by manually parsing data (format is simple) - // rather than loading into emulator with its FM chips and resampler - Gym_Emu* emu = new Gym_Emu; - if ( emu && !emu->set_sample_rate( 44100 ) && !emu->load( h, in ) ) - get_gym_timing_( *emu, out ); - delete emu; -} - -// SPC - -static void get_spc_xid6( byte const* begin, long size, track_info_t* out ) -{ - // header - byte const* end = begin + size; - if ( size < 8 || memcmp( begin, "xid6", 4 ) ) - return; - long info_size = get_le32( begin + 4 ); - byte const* in = begin + 8; - if ( end - in > info_size ) - end = in + info_size; - - while ( end - in >= 4 ) - { - // header - int id = in [0]; - int data = in [3] * 0x100 + in [2]; - int type = in [1]; - int len = type ? data : 0; - in += 4; - if ( len > end - in ) - break; // block goes past end of data - - // handle specific block types - switch ( id ) + gchar* args = strchr( path, '?' ); + if ( args ) { - case 0x01: out->ti->track_name = g_strndup( (char*) in, len ); break; - case 0x02: out->ti->album_name = g_strndup( (char*) in, len ); break; - case 0x03: out->ti->performer = g_strndup( (char*) in, len ); break; - case 0x07: out->ti->comment = g_strndup( (char*) in, len ); break; - //case 0x31: // loop length, but I haven't found any SPC files that use this - } - - // skip to next block - in += len; - - // blocks are supposed to be 4-byte aligned with zero-padding... - byte const* unaligned = in; - while ( (in - begin) & 3 && in < end ) - { - if ( *in++ != 0 ) - { - // ...but some files have no padding - in = unaligned; - break; - } + *args = '\0'; + track = atoi( args + 1 ); + if ( track ) + track_specified = true; } } } -static void get_spc_info_( Spc_Emu::header_t const& h, byte const* xid6, long xid6_size, - track_info_t* out ) -{ - // decode length (can be in text or binary format) - char s [4] = { h.len_secs [0], h.len_secs [1], h.len_secs [2], 0 }; - int len_secs = (unsigned char) s [1] * 0x100 + s [0]; - if ( s [1] >= ' ' || (!s [1] && isdigit( s [0] )) ) - len_secs = atoi( s ); - if ( len_secs ) - out->length = len_secs * 1000; - - if ( xid6_size ) - get_spc_xid6( xid6, xid6_size, out ); - - // use header to fill any remaining fields - if ( !out->ti->performer ) out->ti->performer = DUPE_FIELD( h.author ); - if ( !out->ti->album_name ) out->ti->album_name = DUPE_FIELD( h.game ); - if ( !out->ti->track_name ) out->ti->track_name = DUPE_FIELD( h.song ); -} - -inline void get_info_emu( Spc_Emu& emu, track_info_t* out ) -{ - get_spc_info_( emu.header(), emu.trailer(), emu.trailer_size(), out ); -} - -inline void get_file_info( Spc_Emu::header_t const& h, Data_Reader& in, track_info_t* out ) +// Determine file type based on header contents. Returns 0 if unrecognized or path is NULL. +static gme_type_t identify_file( gchar* path ) { - // 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 ) + if ( path ) { - *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 + char header [4] = { }; + GME_FILE_READER in; + if ( !log_err( in.open( path ) ) && !log_err( in.read( header, sizeof header ) ) ) + return gme_identify_extension( gme_identify_header( header ), gme_type_list() ); } -} - -static byte const* skip_gd3_str( byte const* in, byte const* end ) -{ - while ( end - in >= 2 ) - { - in += 2; - if ( !(in [-2] | in [-1]) ) - break; - } - return in; -} - -static byte const* get_gd3_pair( byte const* in, byte const* end, gchar** out ) -{ - byte const* mid = skip_gd3_str( in, end ); - if ( out ) - get_gd3_str( in, mid, out ); - return skip_gd3_str( mid, end ); -} - -static void get_vgm_gd3( byte const* in, long size, track_info_t* out ) -{ - byte const* end = in + size; - in = get_gd3_pair( in, end, &out->ti->track_name ); - in = get_gd3_pair( in, end, &out->ti->album_name ); - in = get_gd3_pair( in, end, 0 ); // system - in = get_gd3_pair( in, end, &out->ti->performer ); - in = get_gd3_pair( in, end, 0 ); // copyright - // ... other fields (release date, dumper, notes) + return 0; } -static void get_vgm_length( Vgm_Emu::header_t const& h, track_info_t* out ) -{ - long length = get_le32( h.track_duration ); - if ( length > 0 ) - { - out->length = length * 10 / 441; // 1000 / 44100 (VGM files used 44100 as timebase) - out->loop = 0; - - long loop = get_le32( h.loop_duration ); - if ( loop > 0 && get_le32( h.loop_offset ) ) - { - out->loop = loop * 10 / 441; - out->intro = out->length - out->loop; - out->length = -1; - } - } -} - -inline void get_info_emu( Vgm_Emu& emu, track_info_t* out ) +// Load file into emulator/info reader and load m3u in same directory, if present. +// If emu is NULL, returns out of memory error. +static blargg_err_t load_in_emu( Music_Emu* emu, const char* path, VFSFile* fd = 0 ) { - get_vgm_length( emu.header(), out ); + if ( !emu ) + return "Out of memory"; - int size; - byte const* data = emu.gd3_data( &size ); - if ( data ) - get_vgm_gd3( data + 12, size, out ); -} - -inline void get_file_info( Vgm_Emu::header_t const& vgm_h, Data_Reader& in, track_info_t* out ) -{ - get_vgm_length( vgm_h, out ); + Vfs_File_Reader in; + blargg_err_t err = 0; + if ( fd ) + in.reset( fd ); // use fd and let caller close it + else + err = in.open( path ); - // 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; + if ( !err ) + emu->load( in ); + in.close(); - // 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; + if ( !err ) + { + log_warning( emu ); + + // load .m3u in same directory + int const path_max = 4096; + char m3u_path [path_max + 5]; + strncpy( m3u_path, path, path_max ); + m3u_path [path_max] = 0; + char* p = strrchr( m3u_path, '.' ); + if ( !p ) + p = m3u_path + strlen( m3u_path ); + strcpy( p, ".m3u" ); + + if ( emu->load_m3u( m3u_path ) ) { } // TODO: log error if m3u file exists + } - get_vgm_gd3( gd3.begin(), gd3.size(), out ); -} - -// File identification - -enum { type_none = 0, type_spc, type_nsf, type_nsfe, type_vgm, type_gbs, type_gym }; - -int const tag_size = 4; -typedef char tag_t [tag_size]; - -static int identify_file( gchar* path, tag_t tag ) -{ - // GYM file format doesn't require *any* header, just the ".gym" extension - if ( g_str_has_suffix( path, ".gym" ) ) // to do: is pathname in unicode? - return type_gym; - // to do: trust suffix for all file types, avoiding having to look inside files? - - int result = type_none; - if ( !memcmp( tag, "SNES", 4 ) ) result = type_spc; - if ( !memcmp( tag, "NESM", 4 ) ) result = type_nsf; - if ( !memcmp( tag, "NSFE", 4 ) ) result = type_nsfe; - if ( !memcmp( tag, "GYMX", 4 ) ) result = type_gym; - if ( !memcmp( tag, "GBS" , 3 ) ) result = type_gbs; - if ( !memcmp( tag, "Vgm ", 4 ) ) result = type_vgm; - return result; + return err; } // Get info -static int begin_get_info( const char* path, track_info_t* out ) +static TitleInput* get_track_ti( const char* path, track_info_t const& info, int track ) { - 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; + TitleInput* ti = bmp_title_input_new(); + if ( ti ) + { + ti->file_name = g_path_get_basename( path ); + ti->file_path = g_path_get_dirname ( path ); + ti->performer = g_strdup( info.author ); + ti->album_name = g_strdup( info.game ); + ti->track_name = g_strdup( info.song ? info.song : ti->file_name ); + if ( info.track_count > 1 ) + ti->track_number = track + 1; + ti->comment = g_strdup( info.copyright ); + + int length = info.length; + if ( length <= 0 ) + length = info.intro_length + 2 * info.loop_length; + if ( length <= 0 ) + length = audcfg.loop_length * 1000; + else if ( length >= fade_threshold ) + length += fade_length; + ti->length = length; + } + return ti; } -static char* end_get_info( track_info_t const& info, int* length, bool* has_length ) +static char* format_and_free_ti( TitleInput* ti, int* length ) { - *length = info.length; - if ( has_length ) - *has_length = (*length > 0); + char* result = xmms_get_titlestring( xmms_get_gentitle_format(), ti ); + if ( result ) + *length = ti->length; + bmp_title_input_free( ti ); - if ( *length <= 0 ) - *length = audcfg.loop_length * 1000; - - // use filename for formats that don't have field for name of game - // to do: strip off file extension - if ( !info.ti->track_name ) - info.ti->track_name = g_strdup( info.ti->file_name ); - - char* result = xmms_get_titlestring( xmms_get_gentitle_format(), info.ti ); - g_free( info.ti ); return result; } -template<class Header> -inline void get_info_t( tag_t tag, Data_Reader& in, track_info_t* out, Header* ) +static TitleInput *get_song_tuple( gchar *path ) { - Header h; - memcpy( &h, tag, tag_size ); - if ( !in.read( (char*) &h + tag_size, sizeof h - tag_size ) ) - get_file_info( h, in, out ); + TitleInput* result = 0; + + Url_Parser url( path ); + Music_Emu* emu = gme_new_info( identify_file( url.path ) ); + track_info_t info; + if ( !log_err( load_in_emu( emu, url.path ) ) && + !log_err( emu->track_info( &info, url.track ) ) ) + result = get_track_ti( url.path, info, url.track ); + delete emu; + return result; } static void get_song_info( char* path, char** title, int* length ) { - int track = 0; // to do: way to select other tracks - - // extract the subsong id from the virtual path - gchar *path2 = g_strdup(path); - gchar *_path = strchr(path2, '?'); - - if (_path != NULL && *_path == '?') - { - *_path = '\0'; - _path++; - track = atoi(_path); - } - *length = -1; *title = NULL; - Audacious_Reader in; - tag_t tag; - if ( in.open( path2 ) || in.read( tag, sizeof tag ) ) - return; - int type = identify_file( path2, tag ); - if ( !type ) - return; - - track_info_t info; - if ( begin_get_info( path2, &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 ); - - g_free(path2); -} - -// Get tuple - -static TitleInput *get_song_tuple( char *path ) -{ - int track = 0; // to do: way to select other tracks - - // extract the subsong id from the virtual path - gchar *path2 = g_strdup(path); - gchar *_path = strchr(path2, '?'); - - if (_path != NULL && *_path == '?') - { - *_path = '\0'; - _path++; - track = atoi(_path); - } - - Audacious_Reader in; - tag_t tag; - if ( in.open( path2 ) || in.read( tag, sizeof tag ) ) - return NULL; - - int type = identify_file( path2, tag ); - if ( !type ) - return NULL; - - track_info_t info; - if ( begin_get_info( path2, &info ) ) - return NULL; - 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; - } - - info.ti->length = info.length; - - if ( info.ti->length <= 0 ) - info.ti->length = audcfg.loop_length * 1000; - - return info.ti; + TitleInput* ti = get_song_tuple( path ); + if ( ti ) + *title = format_and_free_ti( ti, length ); } // Playback @@ -563,23 +215,26 @@ // handle pending seek long s = pending_seek; - pending_seek = -1; // to do: use atomic swap + pending_seek = -1; // TODO: use atomic swap if ( s >= 0 ) { console_ip.output->flush( s * 1000 ); - track_emu.seek( s * 1000 ); + emu->seek( s * 1000 ); } // fill buffer if ( track_ended ) { - if ( track_ended++ > emu->sample_rate() * 3 / (buf_size / 2) ) + // TODO: remove delay once host doesn't cut the end of track off + int const delay = 0; // seconds + if ( track_ended++ > delay * emu->sample_rate() / (buf_size / 2) ) console_ip_is_going = false; memset( buf, 0, sizeof buf ); } - else if ( track_emu.play( buf_size, buf ) ) + else { - track_ended = 1; + emu->play( buf_size, buf ); + track_ended = emu->track_ended(); } produce_audio( console_ip.output->written_time(), FMT_S16_NE, 1, sizeof buf, buf, @@ -591,105 +246,48 @@ console_ip.output->close_audio(); console_ip_is_going = 0; g_static_mutex_unlock( &playback_mutex ); - // to do: should decode_thread be cleared here? + // TODO: should decode_thread be cleared here? g_thread_exit( NULL ); return NULL; } -template<class Emu> -void load_file( tag_t tag, Data_Reader& in, long rate, track_info_t* out, Emu* dummy ) -{ - typename Emu::header_t h; - memcpy( &h, tag, tag_size ); - if ( in.read( (char*) &h + tag_size, sizeof h - tag_size ) ) - return; - - if ( rate == 0 ) - rate = 44100; - - 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; - if (out != NULL) - get_info_emu( *local_emu, out ); -} - static void play_file( char* path ) { - int track = 0; // to do: some way to select other tracks - - // open and identify file unload_file(); - Audacious_Reader in; - tag_t tag; - - // extract the subsong id from the virtual path - gchar *path2 = g_strdup(path); - gchar *_path = strchr(path2, '?'); - - if (_path != NULL && *_path == '?') - { - *_path = '\0'; - _path++; - track = atoi(_path); - } - - if ( in.open( path2 ) || in.read( tag, sizeof tag ) ) - return; - int type = identify_file( path2, tag ); - // setup info - long sample_rate = 44100; - if ( type == type_spc ) - sample_rate = Spc_Emu::native_sample_rate; + // identify file + Url_Parser url( path ); + gme_type_t type = identify_file( url.path ); + if ( !type ) return; + + // sample rate + long sample_rate = 0; + if ( type == gme_spc_type ) + sample_rate = 32000; if ( audcfg.resample ) sample_rate = audcfg.resample_rate; - track_info_t info; - if ( begin_get_info( path2, &info ) ) - return; - info.track = track; - - // load in emulator and get info - switch ( type ) + if ( !sample_rate ) + sample_rate = 44100; + + // create emulator and load + emu = gme_new_emu( type, sample_rate ); + if ( load_in_emu( emu, url.path ) ) { - 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 ) + unload_file(); return; - - // set info - int length = -1; - bool has_length = false; - - if (( type == type_spc ) && ( audcfg.ignore_spc_length == TRUE )) - info.length = -1; - - 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 ); } - // set frequency equalization + // stereo echo depth + gme_set_stereo_depth( emu, 1.0 / 100 * audcfg.echo ); + + // set equalizer if ( audcfg.treble || audcfg.bass ) { - Music_Emu::equalizer_t eq = emu->equalizer(); + Music_Emu::equalizer_t eq; // bass - logarithmic, 2 to 8194 Hz double bass = 1.0 - (audcfg.bass / 200.0 + 0.5); - eq.bass = (long int) pow(2.0, bass * 13.0) + (long int) 2.0; + eq.bass = (long) (2.0 + pow( 2.0, bass * 13 )); // treble - -50 to 0 to +5 dB double treble = audcfg.treble / 100.0; @@ -698,21 +296,50 @@ emu->set_equalizer(eq); } - // start + // get info + int length = -1; + track_info_t info; + if ( !log_err( emu->track_info( &info, url.track ) ) ) + { + if ( type == gme_spc_type && audcfg.ignore_spc_length ) + info.length = -1; + TitleInput* ti = get_track_ti( url.path, info, url.track ); + if ( ti ) + { + char* title = format_and_free_ti( ti, &length ); + if ( title ) + { + console_ip.set_info( title, length, emu->voice_count() * 1000, sample_rate, 2 ); + g_free( title ); + } + } + } + if ( length <= 0 ) + length = audcfg.loop_length * 1000; + + if ( log_err( emu->start_track( url.track ) ) ) + { + unload_file(); + return; + } + log_warning( emu ); + + // start track if ( !console_ip.output->open_audio( FMT_S16_NE, sample_rate, 2 ) ) return; pending_seek = -1; track_ended = 0; - track_emu.start_track( emu, track, length, !has_length ); + if ( length >= fade_threshold + fade_length ) + length -= fade_length; + emu->set_fade( length, fade_length ); console_ip_is_going = 1; decode_thread = g_thread_create( play_loop_track, NULL, TRUE, NULL ); - g_free(path2); } static void seek( gint time ) { - // to do: be sure seek works at all - // to do: disallow seek on slow formats (SPC, GYM, VGM using FM)? + // TODO: be sure seek works at all + // TODO: disallow seek on slow formats (SPC, GYM, VGM using FM)? pending_seek = time; } @@ -738,63 +365,67 @@ return console_ip_is_going ? console_ip.output->output_time() : -1; } -static gint is_our_file( gchar* path ) +static gint is_our_file_from_vfs( gchar* filename, VFSFile* fd ) { - Audacious_Reader in; - tag_t tag; - - // extract the subsong id from the virtual path - gchar *path2 = g_strdup(path); - gchar *_path = strchr(path2, '?'); - gboolean is_subsong = FALSE; - gint type; - - if (_path != NULL && *_path == '?') + Url_Parser url( filename ); + if ( !url.path ) return false; + + // open file if not already open + Vfs_File_Reader in; + if ( !fd ) + { + if ( log_err( in.open( url.path ) ) ) return false; + fd = in.file(); + // in will be closed when function ends + } + + // read header and identify type + gchar header [4] = { }; + vfs_fread( header, sizeof header, 1, fd ); + gme_type_t type = gme_identify_extension( gme_identify_header( header ), gme_type_list() ); + + gint result = 0; + if ( type ) { - *_path = '\0'; - _path++; - is_subsong = TRUE; - } - - gint ret = !in.open( path2 ) && !in.read( tag, sizeof tag ) && (type = identify_file( path2, tag )); - - if (ret == TRUE && is_subsong == FALSE) - { - if (type == type_spc || type == type_gym || type == type_vgm) + if ( url.track_specified || type->track_count == 1 ) + { + // don't even need to read file if track is specified or + // that file format can't have more than one track per file + result = 1; + } + else { - g_free(path2); - return ret; - } + // format requires reading file info to get track count + Music_Emu* emu = gme_new_info( type ); + vfs_rewind( fd ); + if ( !log_err( load_in_emu( emu, url.path, fd ) ) ) + { + if ( emu->track_count() == 1 ) + { + result = 1; + } + else + { + // for multi-track types, add each track to playlist + for (int i = 0; i < emu->track_count(); i++) + { + gchar _buf[4096]; + g_snprintf(_buf, 4096, "%s?%d", url.path, i); - switch ( type ) - { - case type_nsf: load_file( tag, in, 0, NULL, (Nsf_Emu*) 0 ); break; - case type_nsfe:load_file( tag, in, 0, NULL, (Nsfe_Emu*)0 ); break; - case type_gbs: load_file( tag, in, 0, NULL, (Gbs_Emu*) 0 ); break; - case type_gym: load_file( tag, in, 0, NULL, (Gym_Emu*) 0 ); break; - case type_vgm: load_file( tag, in, 0, NULL, (Vgm_Emu*) 0 ); break; - case type_spc: load_file( tag, in, 0, NULL, (Spc_Emu*) 0 ); break; - default: return FALSE; + playlist_add_url(_buf); + } + result = -1; + } + } + delete emu; } - - if (emu == NULL) - return FALSE; + } + return result; +} - for (int i = 0; i < emu->track_count(); i++) - { - gchar _buf[65535]; - g_snprintf(_buf, 65535, "%s?%d", path2, i); - - playlist_add_url(_buf); - } - - ret = -1; - - unload_file(); - } - - g_free(path2); - return ret; +static gint is_our_file( gchar* filename ) +{ + return is_our_file_from_vfs( filename, 0 ); } // Setup @@ -811,10 +442,9 @@ if (!aboutbox) { aboutbox = xmms_show_message(_("About the Console Music Decoder"), - _("Console music decoder engine based on Game_Music_Emu 0.3.0.\n" + _("Console music decoder engine based on Game_Music_Emu 0.5.1.\n" "Audacious implementation by: William Pitcock <nenolod@nenolod.net>, \n" - // Please do not put my hotpop.com address in the clear (I hate spam) - " Shay Green <hotpop.com@blargg>"), + " Shay Green <gblargg@gmail.com>"), _("Ok"), FALSE, NULL, NULL); gtk_signal_connect(GTK_OBJECT(aboutbox), "destroy", @@ -849,11 +479,13 @@ NULL, NULL, get_song_tuple, + NULL, + NULL, + is_our_file_from_vfs }; extern "C" InputPlugin *get_iplugin_info(void) { - console_ip.description = g_strdup_printf(_("SPC, VGM, NSF/NSFE, GBS, and GYM module decoder")); + console_ip.description = g_strdup_printf(_("AY, GBS, GYM, HES, KSS, NSF, NSFE, SAP, SPC, VGM, VGZ module decoder")); return &console_ip; } -
