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