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 |