Mercurial > audlegacy
comparison Plugins/Input/console/Audacious_Driver.cpp @ 493:c04dff121e1d trunk
[svn] hostile merge, phase 2: reimport based on new plugin code
| author | nenolod |
|---|---|
| date | Tue, 24 Jan 2006 20:19:01 -0800 |
| parents | |
| children | 0868188271e0 |
comparison
equal
deleted
inserted
replaced
| 492:ccb68bad47b2 | 493:c04dff121e1d |
|---|---|
| 1 /* | |
| 2 * Audacious: Cross platform multimedia player | |
| 3 * Copyright (c) 2005 Audacious Team | |
| 4 * | |
| 5 * Driver for Game_Music_Emu library. See details at: | |
| 6 * http://www.slack.net/~ant/libs/ | |
| 7 */ | |
| 8 | |
| 9 #include <glib.h> | |
| 10 #include <glib/gi18n.h> | |
| 11 #include <gtk/gtk.h> | |
| 12 #include "libaudacious/configdb.h" | |
| 13 #include "libaudacious/util.h" | |
| 14 #include "libaudacious/titlestring.h" | |
| 15 extern "C" { | |
| 16 #include "audacious/output.h" | |
| 17 } | |
| 18 #include <string.h> | |
| 19 #include <stdlib.h> | |
| 20 #include <ctype.h> | |
| 21 #include <math.h> | |
| 22 | |
| 23 // Game_Music_Emu | |
| 24 #include "Nsf_Emu.h" | |
| 25 #include "Nsfe_Emu.h" | |
| 26 #include "Gbs_Emu.h" | |
| 27 #include "Vgm_Emu.h" | |
| 28 #include "Gym_Emu.h" | |
| 29 #include "Spc_Emu.h" | |
| 30 | |
| 31 #include "Track_Emu.h" | |
| 32 #include "Vfs_File.h" | |
| 33 #include "Gzip_File.h" | |
| 34 #include "blargg_endian.h" | |
| 35 | |
| 36 //typedef Vfs_File_Reader Audacious_Reader; // will use VFS once it handles gzip transparently | |
| 37 typedef Gzip_File_Reader Audacious_Reader; | |
| 38 | |
| 39 struct AudaciousConsoleConfig { | |
| 40 gint loop_length; // length of tracks that lack timing information | |
| 41 gboolean resample; // whether or not to resample | |
| 42 gint resample_rate; // rate to resample at | |
| 43 gboolean nsfe_playlist; // if true, use optional NSFE playlist | |
| 44 }; | |
| 45 static AudaciousConsoleConfig audcfg = { 180, FALSE, 32000, TRUE }; | |
| 46 static GThread* decode_thread; | |
| 47 static GStaticMutex playback_mutex = G_STATIC_MUTEX_INIT; | |
| 48 static volatile gboolean console_ip_is_going; | |
| 49 static volatile long pending_seek; | |
| 50 extern InputPlugin console_ip; | |
| 51 static Music_Emu* emu = 0; | |
| 52 static Track_Emu track_emu; | |
| 53 | |
| 54 static void unload_file() | |
| 55 { | |
| 56 delete emu; | |
| 57 emu = NULL; | |
| 58 } | |
| 59 | |
| 60 // Information | |
| 61 | |
| 62 typedef unsigned char byte; | |
| 63 | |
| 64 #define DUPE_FIELD( field ) g_strndup( field, sizeof (field) ); | |
| 65 | |
| 66 struct track_info_t | |
| 67 { | |
| 68 int track; // track to get info for | |
| 69 int length; // in msec, -1 = unknown | |
| 70 int loop; // in msec, -1 = unknown, 0 = not looped | |
| 71 int intro; // in msec, -1 = unknown | |
| 72 | |
| 73 TitleInput* ti; | |
| 74 }; | |
| 75 | |
| 76 // NSFE | |
| 77 | |
| 78 void get_nsfe_info( Nsfe_Info const& nsfe, track_info_t* out ) | |
| 79 { | |
| 80 Nsfe_Info::info_t const& h = nsfe.info(); | |
| 81 out->ti->performer = DUPE_FIELD( h.author ); | |
| 82 out->ti->album_name = DUPE_FIELD( h.game ); | |
| 83 out->ti->comment = DUPE_FIELD( h.copyright ); | |
| 84 out->ti->track_name = g_strdup( nsfe.track_name( out->track ) ); | |
| 85 int time = nsfe.track_time( out->track ); | |
| 86 if ( time > 0 ) | |
| 87 out->length = time; | |
| 88 if ( nsfe.info().track_count > 1 ) | |
| 89 out->ti->track_number = out->track + 1; | |
| 90 } | |
| 91 | |
| 92 inline void get_info_emu( Nsfe_Emu& emu, track_info_t* out ) | |
| 93 { | |
| 94 emu.enable_playlist( audcfg.nsfe_playlist ); // to do: kind of hacky | |
| 95 get_nsfe_info( emu, out ); | |
| 96 } | |
| 97 | |
| 98 inline void get_file_info( Nsfe_Emu::header_t const& h, Data_Reader& in, track_info_t* out ) | |
| 99 { | |
| 100 Nsfe_Info nsfe; | |
| 101 if ( !nsfe.load( h, in ) ) | |
| 102 { | |
| 103 nsfe.enable_playlist( audcfg.nsfe_playlist ); | |
| 104 get_nsfe_info( nsfe, out ); | |
| 105 } | |
| 106 } | |
| 107 | |
| 108 // NSF | |
| 109 | |
| 110 static void get_nsf_info_( Nsf_Emu::header_t const& h, track_info_t* out ) | |
| 111 { | |
| 112 out->ti->performer = DUPE_FIELD( h.author ); | |
| 113 out->ti->album_name = DUPE_FIELD( h.game ); | |
| 114 out->ti->comment = DUPE_FIELD( h.copyright ); | |
| 115 if ( h.track_count > 1 ) | |
| 116 out->ti->track_number = out->track + 1; | |
| 117 } | |
| 118 | |
| 119 inline void get_info_emu( Nsf_Emu& emu, track_info_t* out ) | |
| 120 { | |
| 121 get_nsf_info_( emu.header(), out ); | |
| 122 } | |
| 123 | |
| 124 inline void get_file_info( Nsf_Emu::header_t const& h, Data_Reader& in, track_info_t* out ) | |
| 125 { | |
| 126 get_nsf_info_( h, out ); | |
| 127 } | |
| 128 | |
| 129 // GBS | |
| 130 | |
| 131 static void get_gbs_info_( Gbs_Emu::header_t const& h, track_info_t* out ) | |
| 132 { | |
| 133 out->ti->performer = DUPE_FIELD( h.author ); | |
| 134 out->ti->album_name = DUPE_FIELD( h.game ); | |
| 135 out->ti->comment = DUPE_FIELD( h.copyright ); | |
| 136 if ( h.track_count > 1 ) | |
| 137 out->ti->track_number = out->track + 1; | |
| 138 } | |
| 139 | |
| 140 inline void get_info_emu( Gbs_Emu& emu, track_info_t* out ) | |
| 141 { | |
| 142 get_gbs_info_( emu.header(), out ); | |
| 143 } | |
| 144 | |
| 145 inline void get_file_info( Gbs_Emu::header_t const& h, Data_Reader& in, track_info_t* out ) | |
| 146 { | |
| 147 get_gbs_info_( h, out ); | |
| 148 } | |
| 149 | |
| 150 // GYM | |
| 151 | |
| 152 static void get_gym_info_( Gym_Emu::header_t const& h, track_info_t* out ) | |
| 153 { | |
| 154 if ( !memcmp( h.tag, "GYMX", 4 ) ) | |
| 155 { | |
| 156 out->ti->performer = DUPE_FIELD( h.copyright ); | |
| 157 out->ti->album_name = DUPE_FIELD( h.game ); | |
| 158 out->ti->track_name = DUPE_FIELD( h.song ); | |
| 159 out->ti->comment = DUPE_FIELD( h.comment ); | |
| 160 } | |
| 161 } | |
| 162 | |
| 163 static void get_gym_timing_( Gym_Emu const& emu, track_info_t* out ) | |
| 164 { | |
| 165 out->length = emu.track_length() * 50 / 3; // 1000 / 60 | |
| 166 out->loop = 0; | |
| 167 | |
| 168 long loop = get_le32( emu.header().loop_start ); | |
| 169 if ( loop ) | |
| 170 { | |
| 171 out->intro = loop * 50 / 3; | |
| 172 out->loop = out->length - out->intro; | |
| 173 out->length = -1; | |
| 174 } | |
| 175 } | |
| 176 | |
| 177 inline void get_info_emu( Gym_Emu& emu, track_info_t* out ) | |
| 178 { | |
| 179 get_gym_info_( emu.header(), out ); | |
| 180 get_gym_timing_( emu, out ); | |
| 181 } | |
| 182 | |
| 183 inline void get_file_info( Gym_Emu::header_t const& h, Data_Reader& in, track_info_t* out ) | |
| 184 { | |
| 185 get_gym_info_( h, out ); | |
| 186 | |
| 187 // have to load and parse entire GYM file to determine length | |
| 188 // to do: could make more efficient by manually parsing data (format is simple) | |
| 189 // rather than loading into emulator with its FM chips and resampler | |
| 190 Gym_Emu* emu = new Gym_Emu; | |
| 191 if ( emu && !emu->set_sample_rate( 44100 ) && !emu->load( h, in ) ) | |
| 192 get_gym_timing_( *emu, out ); | |
| 193 delete emu; | |
| 194 } | |
| 195 | |
| 196 // SPC | |
| 197 | |
| 198 static void get_spc_xid6( byte const* begin, long size, track_info_t* out ) | |
| 199 { | |
| 200 // header | |
| 201 byte const* end = begin + size; | |
| 202 if ( size < 8 || memcmp( begin, "xid6", 4 ) ) | |
| 203 return; | |
| 204 long info_size = get_le32( begin + 4 ); | |
| 205 byte const* in = begin + 8; | |
| 206 if ( end - in > info_size ) | |
| 207 end = in + info_size; | |
| 208 | |
| 209 while ( end - in >= 4 ) | |
| 210 { | |
| 211 // header | |
| 212 int id = in [0]; | |
| 213 int data = in [3] * 0x100 + in [2]; | |
| 214 int type = in [1]; | |
| 215 int len = type ? data : 0; | |
| 216 in += 4; | |
| 217 if ( len > end - in ) | |
| 218 break; // block goes past end of data | |
| 219 | |
| 220 // handle specific block types | |
| 221 switch ( id ) | |
| 222 { | |
| 223 case 0x01: out->ti->track_name = g_strndup( (char*) in, len ); break; | |
| 224 case 0x02: out->ti->album_name = g_strndup( (char*) in, len ); break; | |
| 225 case 0x03: out->ti->performer = g_strndup( (char*) in, len ); break; | |
| 226 case 0x07: out->ti->comment = g_strndup( (char*) in, len ); break; | |
| 227 //case 0x31: // loop length, but I haven't found any SPC files that use this | |
| 228 } | |
| 229 | |
| 230 // skip to next block | |
| 231 in += len; | |
| 232 | |
| 233 // blocks are supposed to be 4-byte aligned with zero-padding... | |
| 234 byte const* unaligned = in; | |
| 235 while ( (in - begin) & 3 && in < end ) | |
| 236 { | |
| 237 if ( *in++ != 0 ) | |
| 238 { | |
| 239 // ...but some files have no padding | |
| 240 in = unaligned; | |
| 241 break; | |
| 242 } | |
| 243 } | |
| 244 } | |
| 245 } | |
| 246 | |
| 247 static void get_spc_info_( Spc_Emu::header_t const& h, byte const* xid6, long xid6_size, | |
| 248 track_info_t* out ) | |
| 249 { | |
| 250 // decode length (can be in text or binary format) | |
| 251 char s [4] = { h.len_secs [0], h.len_secs [1], h.len_secs [2], 0 }; | |
| 252 int len_secs = (unsigned char) s [1] * 0x100 + s [0]; | |
| 253 if ( s [1] >= ' ' || (!s [1] && isdigit( s [0] )) ) | |
| 254 len_secs = atoi( s ); | |
| 255 if ( len_secs ) | |
| 256 out->length = len_secs * 1000; | |
| 257 | |
| 258 if ( xid6_size ) | |
| 259 get_spc_xid6( xid6, xid6_size, out ); | |
| 260 | |
| 261 // use header to fill any remaining fields | |
| 262 if ( !out->ti->performer ) out->ti->performer = DUPE_FIELD( h.author ); | |
| 263 if ( !out->ti->album_name ) out->ti->album_name = DUPE_FIELD( h.game ); | |
| 264 if ( !out->ti->track_name ) out->ti->track_name = DUPE_FIELD( h.song ); | |
| 265 } | |
| 266 | |
| 267 inline void get_info_emu( Spc_Emu& emu, track_info_t* out ) | |
| 268 { | |
| 269 get_spc_info_( emu.header(), emu.trailer(), emu.trailer_size(), out ); | |
| 270 } | |
| 271 | |
| 272 inline void get_file_info( Spc_Emu::header_t const& h, Data_Reader& in, track_info_t* out ) | |
| 273 { | |
| 274 // handle xid6 data at end of file | |
| 275 long const xid6_skip = 0x10200 - sizeof (Spc_Emu::header_t); | |
| 276 long xid6_size = in.remain() - xid6_skip; | |
| 277 blargg_vector<byte> xid6; | |
| 278 if ( xid6_size <= 0 || xid6.resize( xid6_size ) || in.skip( xid6_skip ) || | |
| 279 in.read( xid6.begin(), xid6.size() ) ) | |
| 280 xid6_size = 0; | |
| 281 | |
| 282 get_spc_info_( h, xid6.begin(), xid6_size, out ); | |
| 283 } | |
| 284 | |
| 285 // VGM | |
| 286 | |
| 287 static void get_gd3_str( byte const* in, byte const* end, gchar** out ) | |
| 288 { | |
| 289 int len = (end - in) / 2 - 1; | |
| 290 if ( len > 0 ) | |
| 291 { | |
| 292 *out = g_strndup( "", len ); | |
| 293 if ( !*out ) | |
| 294 return; | |
| 295 for ( int i = 0; i < len; i++ ) | |
| 296 (*out) [i] = in [i * 2]; // to do: convert to utf-8 | |
| 297 } | |
| 298 } | |
| 299 | |
| 300 static byte const* skip_gd3_str( byte const* in, byte const* end ) | |
| 301 { | |
| 302 while ( end - in >= 2 ) | |
| 303 { | |
| 304 in += 2; | |
| 305 if ( !(in [-2] | in [-1]) ) | |
| 306 break; | |
| 307 } | |
| 308 return in; | |
| 309 } | |
| 310 | |
| 311 static byte const* get_gd3_pair( byte const* in, byte const* end, gchar** out ) | |
| 312 { | |
| 313 byte const* mid = skip_gd3_str( in, end ); | |
| 314 if ( out ) | |
| 315 get_gd3_str( in, mid, out ); | |
| 316 return skip_gd3_str( mid, end ); | |
| 317 } | |
| 318 | |
| 319 static void get_vgm_gd3( byte const* in, long size, track_info_t* out ) | |
| 320 { | |
| 321 byte const* end = in + size; | |
| 322 in = get_gd3_pair( in, end, &out->ti->track_name ); | |
| 323 in = get_gd3_pair( in, end, &out->ti->album_name ); | |
| 324 in = get_gd3_pair( in, end, 0 ); // system | |
| 325 in = get_gd3_pair( in, end, &out->ti->performer ); | |
| 326 in = get_gd3_pair( in, end, 0 ); // copyright | |
| 327 // ... other fields (release date, dumper, notes) | |
| 328 } | |
| 329 | |
| 330 static void get_vgm_length( Vgm_Emu::header_t const& h, track_info_t* out ) | |
| 331 { | |
| 332 long length = get_le32( h.track_duration ); | |
| 333 if ( length > 0 ) | |
| 334 { | |
| 335 out->length = length * 10 / 441; // 1000 / 44100 (VGM files used 44100 as timebase) | |
| 336 out->loop = 0; | |
| 337 | |
| 338 long loop = get_le32( h.loop_duration ); | |
| 339 if ( loop > 0 && get_le32( h.loop_offset ) ) | |
| 340 { | |
| 341 out->loop = loop * 10 / 441; | |
| 342 out->intro = out->length - out->loop; | |
| 343 out->length = -1; | |
| 344 } | |
| 345 } | |
| 346 } | |
| 347 | |
| 348 inline void get_info_emu( Vgm_Emu& emu, track_info_t* out ) | |
| 349 { | |
| 350 get_vgm_length( emu.header(), out ); | |
| 351 | |
| 352 int size; | |
| 353 byte const* data = emu.gd3_data( &size ); | |
| 354 if ( data ) | |
| 355 get_vgm_gd3( data + 12, size, out ); | |
| 356 } | |
| 357 | |
| 358 inline void get_file_info( Vgm_Emu::header_t const& vgm_h, Data_Reader& in, track_info_t* out ) | |
| 359 { | |
| 360 get_vgm_length( vgm_h, out ); | |
| 361 | |
| 362 // find gd3 header | |
| 363 long gd3_offset = get_le32( vgm_h.gd3_offset ) + offsetof(Vgm_Emu::header_t,gd3_offset) - | |
| 364 sizeof vgm_h; | |
| 365 long gd3_max_size = in.remain() - gd3_offset; | |
| 366 byte gd3_h [12]; | |
| 367 if ( gd3_offset <= 0 || gd3_max_size < (int) sizeof gd3_h ) | |
| 368 return; | |
| 369 | |
| 370 // read gd3 header | |
| 371 if ( in.skip( gd3_offset ) || in.read( gd3_h, sizeof gd3_h ) ) | |
| 372 return; | |
| 373 | |
| 374 // check header signature and version | |
| 375 if ( memcmp( gd3_h, "Gd3 ", 4 ) || get_le32( gd3_h + 4 ) >= 0x200 ) | |
| 376 return; | |
| 377 | |
| 378 // get and check size | |
| 379 long gd3_size = get_le32( gd3_h + 8 ); | |
| 380 if ( gd3_size > gd3_max_size - 12 ) | |
| 381 return; | |
| 382 | |
| 383 // read and parse gd3 data | |
| 384 blargg_vector<byte> gd3; | |
| 385 if ( gd3.resize( gd3_size ) || in.read( gd3.begin(), gd3.size() ) ) | |
| 386 return; | |
| 387 | |
| 388 get_vgm_gd3( gd3.begin(), gd3.size(), out ); | |
| 389 } | |
| 390 | |
| 391 // File identification | |
| 392 | |
| 393 enum { type_none = 0, type_spc, type_nsf, type_nsfe, type_vgm, type_gbs, type_gym }; | |
| 394 | |
| 395 int const tag_size = 4; | |
| 396 typedef char tag_t [tag_size]; | |
| 397 | |
| 398 static int identify_file( gchar* path, tag_t tag ) | |
| 399 { | |
| 400 // GYM file format doesn't require *any* header, just the ".gym" extension | |
| 401 if ( g_str_has_suffix( path, ".gym" ) ) // to do: is pathname in unicode? | |
| 402 return type_gym; | |
| 403 // to do: trust suffix for all file types, avoiding having to look inside files? | |
| 404 | |
| 405 int result = type_none; | |
| 406 if ( !memcmp( tag, "SNES", 4 ) ) result = type_spc; | |
| 407 if ( !memcmp( tag, "NESM", 4 ) ) result = type_nsf; | |
| 408 if ( !memcmp( tag, "NSFE", 4 ) ) result = type_nsfe; | |
| 409 if ( !memcmp( tag, "GYMX", 4 ) ) result = type_gym; | |
| 410 if ( !memcmp( tag, "GBS" , 3 ) ) result = type_gbs; | |
| 411 if ( !memcmp( tag, "Vgm ", 4 ) ) result = type_vgm; | |
| 412 return result; | |
| 413 } | |
| 414 | |
| 415 static gint is_our_file( gchar* path ) | |
| 416 { | |
| 417 Audacious_Reader in; | |
| 418 tag_t tag; | |
| 419 return !in.open( path ) && !in.read( tag, sizeof tag ) && identify_file( path, tag ); | |
| 420 } | |
| 421 | |
| 422 // Get info | |
| 423 | |
| 424 static int begin_get_info( const char* path, track_info_t* out ) | |
| 425 { | |
| 426 out->track = 0; | |
| 427 out->length = -1; | |
| 428 out->loop = -1; | |
| 429 out->intro = -1; | |
| 430 TitleInput* fields = bmp_title_input_new(); | |
| 431 out->ti = fields; | |
| 432 if ( !fields ) | |
| 433 return true; | |
| 434 | |
| 435 fields->file_name = g_path_get_basename( path ); | |
| 436 fields->file_path = g_path_get_dirname( path ); | |
| 437 return false; | |
| 438 } | |
| 439 | |
| 440 static char* end_get_info( track_info_t const& info, int* length, bool* has_length ) | |
| 441 { | |
| 442 *length = info.length; | |
| 443 if ( has_length ) | |
| 444 *has_length = (*length > 0); | |
| 445 | |
| 446 if ( *length <= 0 ) | |
| 447 *length = audcfg.loop_length * 1000; | |
| 448 | |
| 449 // use filename for formats that don't have field for name of game | |
| 450 // to do: strip off file extension | |
| 451 if ( !info.ti->track_name ) | |
| 452 info.ti->track_name = g_strdup( info.ti->file_name ); | |
| 453 | |
| 454 char* result = xmms_get_titlestring( xmms_get_gentitle_format(), info.ti ); | |
| 455 g_free( info.ti ); | |
| 456 return result; | |
| 457 } | |
| 458 | |
| 459 template<class Header> | |
| 460 inline void get_info_t( tag_t tag, Data_Reader& in, track_info_t* out, Header* ) | |
| 461 { | |
| 462 Header h; | |
| 463 memcpy( &h, tag, tag_size ); | |
| 464 if ( !in.read( (char*) &h + tag_size, sizeof h - tag_size ) ) | |
| 465 get_file_info( h, in, out ); | |
| 466 } | |
| 467 | |
| 468 static void get_song_info( char* path, char** title, int* length ) | |
| 469 { | |
| 470 int track = 0; // to do: way to select other tracks | |
| 471 | |
| 472 *length = -1; | |
| 473 *title = NULL; | |
| 474 Audacious_Reader in; | |
| 475 tag_t tag; | |
| 476 if ( in.open( path ) || in.read( tag, sizeof tag ) ) | |
| 477 return; | |
| 478 | |
| 479 int type = identify_file( path, tag ); | |
| 480 if ( !type ) | |
| 481 return; | |
| 482 | |
| 483 track_info_t info; | |
| 484 if ( begin_get_info( path, &info ) ) | |
| 485 return; | |
| 486 info.track = track; | |
| 487 | |
| 488 switch ( type ) | |
| 489 { | |
| 490 case type_nsf: get_info_t( tag, in, &info, (Nsf_Emu::header_t*) 0 ); break; | |
| 491 case type_gbs: get_info_t( tag, in, &info, (Gbs_Emu::header_t*) 0 ); break; | |
| 492 case type_gym: get_info_t( tag, in, &info, (Gym_Emu::header_t*) 0 ); break; | |
| 493 case type_vgm: get_info_t( tag, in, &info, (Vgm_Emu::header_t*) 0 ); break; | |
| 494 case type_spc: get_info_t( tag, in, &info, (Spc_Emu::header_t*) 0 ); break; | |
| 495 case type_nsfe:get_info_t( tag, in, &info, (Nsfe_Emu::header_t*)0 ); break; | |
| 496 } | |
| 497 *title = end_get_info( info, length, 0 ); | |
| 498 } | |
| 499 | |
| 500 // Playback | |
| 501 | |
| 502 static int silence_pending; | |
| 503 | |
| 504 static void* play_loop_track( gpointer ) | |
| 505 { | |
| 506 g_static_mutex_lock( &playback_mutex ); | |
| 507 | |
| 508 while ( console_ip_is_going ) | |
| 509 { | |
| 510 int const buf_size = 1024; | |
| 511 Music_Emu::sample_t buf [buf_size]; | |
| 512 | |
| 513 // wait for free space | |
| 514 while ( console_ip.output->buffer_free() < (int) sizeof buf ) | |
| 515 xmms_usleep( 10000 ); | |
| 516 | |
| 517 // handle pending seek | |
| 518 long s = pending_seek; | |
| 519 pending_seek = -1; // to do: use atomic swap | |
| 520 if ( s >= 0 ) | |
| 521 track_emu.seek( s ); | |
| 522 | |
| 523 // fill buffer | |
| 524 if ( track_emu.play( buf_size, buf ) ) | |
| 525 console_ip_is_going = false; | |
| 526 produce_audio( console_ip.output->written_time(), FMT_S16_NE, 1, sizeof buf, buf, NULL ); | |
| 527 } | |
| 528 | |
| 529 // stop playing | |
| 530 unload_file(); | |
| 531 console_ip.output->close_audio(); | |
| 532 console_ip_is_going = FALSE; | |
| 533 g_static_mutex_unlock( &playback_mutex ); | |
| 534 // to do: should decode_thread be cleared here? | |
| 535 g_thread_exit( NULL ); | |
| 536 return NULL; | |
| 537 } | |
| 538 | |
| 539 template<class Emu> | |
| 540 void load_file( tag_t tag, Data_Reader& in, long rate, track_info_t* out, Emu* dummy ) | |
| 541 { | |
| 542 typename Emu::header_t h; | |
| 543 memcpy( &h, tag, tag_size ); | |
| 544 if ( in.read( (char*) &h + tag_size, sizeof h - tag_size ) ) | |
| 545 return; | |
| 546 | |
| 547 Emu* local_emu = new Emu; | |
| 548 if ( !local_emu || local_emu->set_sample_rate( rate ) || local_emu->load( h, in ) ) | |
| 549 { | |
| 550 delete local_emu; // delete NULL is safe | |
| 551 return; | |
| 552 } | |
| 553 | |
| 554 emu = local_emu; | |
| 555 get_info_emu( *local_emu, out ); | |
| 556 } | |
| 557 | |
| 558 static void play_file( char* path ) | |
| 559 { | |
| 560 int track = 0; // to do: some way to select other tracks | |
| 561 | |
| 562 // open and identify file | |
| 563 unload_file(); | |
| 564 Audacious_Reader in; | |
| 565 tag_t tag; | |
| 566 if ( in.open( path ) || in.read( tag, sizeof tag ) ) | |
| 567 return; | |
| 568 int type = identify_file( path, tag ); | |
| 569 | |
| 570 // setup info | |
| 571 long sample_rate = 44100; | |
| 572 if ( type == type_spc ) | |
| 573 sample_rate = Spc_Emu::native_sample_rate; | |
| 574 if ( audcfg.resample ) | |
| 575 sample_rate = audcfg.resample_rate; | |
| 576 track_info_t info; | |
| 577 info.track = track; | |
| 578 if ( begin_get_info( path, &info ) ) | |
| 579 return; | |
| 580 | |
| 581 // load in emulator and get info | |
| 582 switch ( type ) | |
| 583 { | |
| 584 case type_nsf: load_file( tag, in, sample_rate, &info, (Nsf_Emu*) 0 ); break; | |
| 585 case type_nsfe:load_file( tag, in, sample_rate, &info, (Nsfe_Emu*)0 ); break; | |
| 586 case type_gbs: load_file( tag, in, sample_rate, &info, (Gbs_Emu*) 0 ); break; | |
| 587 case type_gym: load_file( tag, in, sample_rate, &info, (Gym_Emu*) 0 ); break; | |
| 588 case type_vgm: load_file( tag, in, sample_rate, &info, (Vgm_Emu*) 0 ); break; | |
| 589 case type_spc: load_file( tag, in, sample_rate, &info, (Spc_Emu*) 0 ); break; | |
| 590 } | |
| 591 in.close(); | |
| 592 if ( !emu ) | |
| 593 return; | |
| 594 | |
| 595 // set info | |
| 596 int length = -1; | |
| 597 bool has_length = false; | |
| 598 char* title = end_get_info( info, &length, &has_length ); | |
| 599 if ( title ) | |
| 600 { | |
| 601 console_ip.set_info( title, length, emu->voice_count() * 1000, sample_rate, 2 ); | |
| 602 g_free( title ); | |
| 603 } | |
| 604 | |
| 605 // start | |
| 606 if ( !console_ip.output->open_audio( FMT_S16_NE, sample_rate, 2 ) ) | |
| 607 return; | |
| 608 pending_seek = -1; | |
| 609 track_emu.start_track( emu, track, length, !has_length ); | |
| 610 console_ip_is_going = TRUE; | |
| 611 decode_thread = g_thread_create( play_loop_track, NULL, TRUE, NULL ); | |
| 612 } | |
| 613 | |
| 614 static void seek( gint time ) | |
| 615 { | |
| 616 // to do: be sure seek works at all | |
| 617 // to do: disallow seek on slow formats (SPC, GYM, VGM using FM)? | |
| 618 pending_seek = time; | |
| 619 } | |
| 620 | |
| 621 static void console_stop(void) | |
| 622 { | |
| 623 console_ip_is_going = FALSE; | |
| 624 if ( decode_thread ) | |
| 625 { | |
| 626 g_thread_join( decode_thread ); | |
| 627 decode_thread = NULL; | |
| 628 } | |
| 629 console_ip.output->close_audio(); | |
| 630 unload_file(); | |
| 631 } | |
| 632 | |
| 633 static void console_pause(gshort p) | |
| 634 { | |
| 635 console_ip.output->pause(p); | |
| 636 } | |
| 637 | |
| 638 static int get_time(void) | |
| 639 { | |
| 640 return console_ip_is_going ? console_ip.output->output_time() : -1; | |
| 641 } | |
| 642 | |
| 643 // Setup | |
| 644 | |
| 645 static void console_init(void) | |
| 646 { | |
| 647 ConfigDb *db; | |
| 648 | |
| 649 db = bmp_cfg_db_open(); | |
| 650 | |
| 651 bmp_cfg_db_get_int(db, "console", "loop_length", &audcfg.loop_length); | |
| 652 bmp_cfg_db_get_bool(db, "console", "resample", &audcfg.resample); | |
| 653 bmp_cfg_db_get_int(db, "console", "resample_rate", &audcfg.resample_rate); | |
| 654 bmp_cfg_db_get_bool(db, "console", "nsfe_playlist", &audcfg.nsfe_playlist); | |
| 655 | |
| 656 bmp_cfg_db_close(db); | |
| 657 } | |
| 658 | |
| 659 extern "C" void console_aboutbox(void) | |
| 660 { | |
| 661 xmms_show_message(_("About the Console Music Decoder"), | |
| 662 _("Console music decoder engine based on Game_Music_Emu 0.3.0.\n" | |
| 663 "Audacious implementation by: William Pitcock <nenolod@nenolod.net>, " | |
| 664 // Please do not put my hotpop.com address in the clear (I hate spam) | |
| 665 "Shay Green <hotpop.com@blargg>"), | |
| 666 _("Ok"), | |
| 667 FALSE, NULL, NULL); | |
| 668 } | |
| 669 | |
| 670 InputPlugin console_ip = | |
| 671 { | |
| 672 NULL, | |
| 673 NULL, | |
| 674 NULL, | |
| 675 console_init, | |
| 676 console_aboutbox, | |
| 677 NULL, | |
| 678 is_our_file, | |
| 679 NULL, | |
| 680 play_file, | |
| 681 console_stop, | |
| 682 console_pause, | |
| 683 seek, | |
| 684 NULL, | |
| 685 get_time, | |
| 686 NULL, | |
| 687 NULL, | |
| 688 NULL, | |
| 689 NULL, | |
| 690 NULL, | |
| 691 NULL, | |
| 692 NULL, | |
| 693 get_song_info, | |
| 694 NULL, | |
| 695 NULL | |
| 696 }; | |
| 697 | |
| 698 extern "C" InputPlugin *get_iplugin_info(void) | |
| 699 { | |
| 700 console_ip.description = g_strdup_printf(_("SPC, VGM, NSF/NSFE, GBS, and GYM module decoder")); | |
| 701 return &console_ip; | |
| 702 } | |
| 703 |
