Mercurial > audlegacy
comparison Plugins/Input/sid/xmms-sid.c @ 269:1b82a9932b60 trunk
[svn] Import sid plugin. Ported from XMMS by giacomo.
author | chainsaw |
---|---|
date | Thu, 08 Dec 2005 15:12:12 -0800 |
parents | |
children | d0e9693d2115 |
comparison
equal
deleted
inserted
replaced
268:1368faba73c9 | 269:1b82a9932b60 |
---|---|
1 /* | |
2 XMMS-SID - SIDPlay input plugin for X MultiMedia System (XMMS) | |
3 | |
4 Main source file | |
5 | |
6 Programmed and designed by Matti 'ccr' Hamalainen <ccr@tnsp.org> | |
7 (C) Copyright 1999-2005 Tecnic Software productions (TNSP) | |
8 | |
9 This program is free software; you can redistribute it and/or modify | |
10 it under the terms of the GNU General Public License as published by | |
11 the Free Software Foundation; either version 2 of the License, or | |
12 (at your option) any later version. | |
13 | |
14 This program is distributed in the hope that it will be useful, | |
15 but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 GNU General Public License for more details. | |
18 | |
19 You should have received a copy of the GNU General Public License | |
20 along with this program; if not, write to the Free Software | |
21 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
22 */ | |
23 #include "xmms-sid.h" | |
24 #include "xs_support.h" | |
25 | |
26 #ifdef HAVE_STDLIB_H | |
27 #include <stdlib.h> | |
28 #endif | |
29 | |
30 #include <stdarg.h> | |
31 | |
32 #include <audacious/plugin.h> | |
33 #include <libaudacious/util.h> | |
34 | |
35 #include <gdk/gdkkeysyms.h> | |
36 #include <gtk/gtk.h> | |
37 | |
38 #include "xs_config.h" | |
39 #include "xs_length.h" | |
40 #include "xs_stil.h" | |
41 #include "xs_filter.h" | |
42 #include "xs_fileinfo.h" | |
43 #include "xs_interface.h" | |
44 #include "xs_glade.h" | |
45 | |
46 /* | |
47 * Include player engines | |
48 */ | |
49 #ifdef HAVE_SIDPLAY1 | |
50 #include "xs_sidplay1.h" | |
51 #endif | |
52 #ifdef HAVE_SIDPLAY2 | |
53 #include "xs_sidplay2.h" | |
54 #endif | |
55 | |
56 | |
57 /* | |
58 * List of players and links to their functions | |
59 */ | |
60 t_xs_player xs_playerlist[] = { | |
61 #ifdef HAVE_SIDPLAY1 | |
62 {XS_ENG_SIDPLAY1, | |
63 xs_sidplay1_isourfile, | |
64 xs_sidplay1_init, xs_sidplay1_close, | |
65 xs_sidplay1_initsong, xs_sidplay1_fillbuffer, | |
66 xs_sidplay1_loadsid, xs_sidplay1_deletesid, | |
67 xs_sidplay1_getsidinfo | |
68 }, | |
69 #endif | |
70 #ifdef HAVE_SIDPLAY2 | |
71 {XS_ENG_SIDPLAY2, | |
72 xs_sidplay2_isourfile, | |
73 xs_sidplay2_init, xs_sidplay2_close, | |
74 xs_sidplay2_initsong, xs_sidplay2_fillbuffer, | |
75 xs_sidplay2_loadsid, xs_sidplay2_deletesid, | |
76 xs_sidplay2_getsidinfo | |
77 }, | |
78 #endif | |
79 }; | |
80 | |
81 const gint xs_nplayerlist = (sizeof(xs_playerlist) / sizeof(t_xs_player)); | |
82 | |
83 | |
84 /* | |
85 * Global variables | |
86 */ | |
87 t_xs_status xs_status; | |
88 XS_MUTEX(xs_status); | |
89 static pthread_t xs_decode_thread; | |
90 | |
91 static GtkWidget *xs_subctrl = NULL; | |
92 static GtkObject *xs_subctrl_adj = NULL; | |
93 XS_MUTEX(xs_subctrl); | |
94 | |
95 void xs_subctrl_close(void); | |
96 void xs_subctrl_update(void); | |
97 | |
98 | |
99 /* | |
100 * Error messages | |
101 */ | |
102 void XSERR(const char *fmt, ...) | |
103 { | |
104 va_list ap; | |
105 fprintf(stderr, "XMMS-SID: "); | |
106 va_start(ap, fmt); | |
107 vfprintf(stderr, fmt, ap); | |
108 va_end(ap); | |
109 } | |
110 | |
111 #ifndef DEBUG_NP | |
112 void XSDEBUG(const char *fmt, ...) | |
113 { | |
114 #ifdef DEBUG | |
115 va_list ap; | |
116 fprintf(stderr, "XSDEBUG: "); | |
117 va_start(ap, fmt); | |
118 vfprintf(stderr, fmt, ap); | |
119 va_end(ap); | |
120 #endif | |
121 } | |
122 #endif | |
123 | |
124 /* | |
125 * Initialization functions | |
126 */ | |
127 void xs_reinit(void) | |
128 { | |
129 gint iPlayer; | |
130 gboolean isInitialized; | |
131 | |
132 /* Stop playing, if we are */ | |
133 XS_MUTEX_LOCK(xs_status); | |
134 if (xs_status.isPlaying) { | |
135 XS_MUTEX_UNLOCK(xs_status); | |
136 xs_stop(); | |
137 } else { | |
138 XS_MUTEX_UNLOCK(xs_status); | |
139 } | |
140 | |
141 /* Initialize status and sanitize configuration */ | |
142 xs_memset(&xs_status, 0, sizeof(xs_status)); | |
143 | |
144 if (xs_cfg.audioFrequency < 8000) | |
145 xs_cfg.audioFrequency = 8000; | |
146 | |
147 if (xs_cfg.oversampleFactor < XS_MIN_OVERSAMPLE) | |
148 xs_cfg.oversampleFactor = XS_MIN_OVERSAMPLE; | |
149 else if (xs_cfg.oversampleFactor > XS_MAX_OVERSAMPLE) | |
150 xs_cfg.oversampleFactor = XS_MAX_OVERSAMPLE; | |
151 | |
152 if (xs_cfg.audioChannels != XS_CHN_MONO) | |
153 xs_cfg.oversampleEnable = FALSE; | |
154 | |
155 xs_status.audioFrequency = xs_cfg.audioFrequency; | |
156 xs_status.audioBitsPerSample = xs_cfg.audioBitsPerSample; | |
157 xs_status.audioChannels = xs_cfg.audioChannels; | |
158 xs_status.audioFormat = -1; | |
159 xs_status.oversampleEnable = xs_cfg.oversampleEnable; | |
160 xs_status.oversampleFactor = xs_cfg.oversampleFactor; | |
161 | |
162 /* Try to initialize emulator engine */ | |
163 XSDEBUG("initializing emulator engine #%i...\n", xs_cfg.playerEngine); | |
164 | |
165 iPlayer = 0; | |
166 isInitialized = FALSE; | |
167 while ((iPlayer < xs_nplayerlist) && !isInitialized) { | |
168 if (xs_playerlist[iPlayer].plrIdent == xs_cfg.playerEngine) { | |
169 if (xs_playerlist[iPlayer].plrInit(&xs_status)) { | |
170 isInitialized = TRUE; | |
171 xs_status.sidPlayer = (t_xs_player *) & xs_playerlist[iPlayer]; | |
172 } | |
173 } | |
174 iPlayer++; | |
175 } | |
176 | |
177 XSDEBUG("init#1: %s, %i\n", (isInitialized) ? "OK" : "FAILED", iPlayer); | |
178 | |
179 iPlayer = 0; | |
180 while ((iPlayer < xs_nplayerlist) && !isInitialized) { | |
181 if (xs_playerlist[iPlayer].plrInit(&xs_status)) { | |
182 isInitialized = TRUE; | |
183 xs_status.sidPlayer = (t_xs_player *) & xs_playerlist[iPlayer]; | |
184 xs_cfg.playerEngine = xs_playerlist[iPlayer].plrIdent; | |
185 } else | |
186 iPlayer++; | |
187 } | |
188 | |
189 XSDEBUG("init#2: %s, %i\n", (isInitialized) ? "OK" : "FAILED", iPlayer); | |
190 | |
191 /* Get settings back, in case the chosen emulator backend changed them */ | |
192 xs_cfg.audioFrequency = xs_status.audioFrequency; | |
193 xs_cfg.audioBitsPerSample = xs_status.audioBitsPerSample; | |
194 xs_cfg.audioChannels = xs_status.audioChannels; | |
195 xs_cfg.oversampleEnable = xs_status.oversampleEnable; | |
196 | |
197 /* Initialize song-length database */ | |
198 xs_songlen_close(); | |
199 if (xs_cfg.songlenDBEnable && (xs_songlen_init() != 0)) { | |
200 XSERR("Error initializing song-length database!\n"); | |
201 } | |
202 | |
203 /* Initialize STIL database */ | |
204 xs_stil_close(); | |
205 if (xs_cfg.stilDBEnable && (xs_stil_init() != 0)) { | |
206 XSERR("Error initializing STIL database!\n"); | |
207 } | |
208 } | |
209 | |
210 | |
211 /* | |
212 * Initialize XMMS-SID | |
213 */ | |
214 void xs_init(void) | |
215 { | |
216 XSDEBUG("xs_init()\n"); | |
217 | |
218 /* Initialize and get configuration */ | |
219 xs_memset(&xs_cfg, 0, sizeof(xs_cfg)); | |
220 xs_init_configuration(); | |
221 xs_read_configuration(); | |
222 | |
223 /* Initialize subsystems */ | |
224 xs_reinit(); | |
225 | |
226 XSDEBUG("OK\n"); | |
227 } | |
228 | |
229 | |
230 /* | |
231 * Shut down XMMS-SID | |
232 */ | |
233 void xs_close(void) | |
234 { | |
235 XSDEBUG("xs_close(): shutting down...\n"); | |
236 | |
237 /* Stop playing, free structures */ | |
238 xs_stop(); | |
239 | |
240 xs_tuneinfo_free(xs_status.tuneInfo); | |
241 xs_status.tuneInfo = NULL; | |
242 xs_status.sidPlayer->plrDeleteSID(&xs_status); | |
243 xs_status.sidPlayer->plrClose(&xs_status); | |
244 | |
245 xs_songlen_close(); | |
246 xs_stil_close(); | |
247 | |
248 XSDEBUG("shutdown finished.\n"); | |
249 } | |
250 | |
251 | |
252 /* | |
253 * Check whether the given file is handled by this plugin | |
254 */ | |
255 gint xs_is_our_file(gchar * pcFilename) | |
256 { | |
257 gchar *pcExt; | |
258 assert(xs_status.sidPlayer); | |
259 | |
260 /* Check the filename */ | |
261 if (pcFilename == NULL) | |
262 return FALSE; | |
263 | |
264 /* Try to detect via detection routine, if required */ | |
265 if (xs_cfg.detectMagic && xs_status.sidPlayer->plrIsOurFile(pcFilename)) | |
266 return TRUE; | |
267 | |
268 /* Detect just by checking filename extension */ | |
269 pcExt = xs_strrchr(pcFilename, '.'); | |
270 if (pcExt) { | |
271 pcExt++; | |
272 switch (xs_cfg.playerEngine) { | |
273 case XS_ENG_SIDPLAY1: | |
274 case XS_ENG_SIDPLAY2: | |
275 if (!g_strcasecmp(pcExt, "psid")) | |
276 return TRUE; | |
277 if (!g_strcasecmp(pcExt, "sid")) | |
278 return TRUE; | |
279 if (!g_strcasecmp(pcExt, "dat")) | |
280 return TRUE; | |
281 if (!g_strcasecmp(pcExt, "inf")) | |
282 return TRUE; | |
283 if (!g_strcasecmp(pcExt, "info")) | |
284 return TRUE; | |
285 break; | |
286 } | |
287 } | |
288 | |
289 return FALSE; | |
290 } | |
291 | |
292 | |
293 /* | |
294 * Main playing thread loop | |
295 */ | |
296 void *xs_playthread(void *argPointer) | |
297 { | |
298 t_xs_status myStatus; | |
299 t_xs_tuneinfo *myTune; | |
300 gboolean audioOpen = FALSE, doPlay = FALSE, isFound = FALSE; | |
301 gboolean playedTune[XS_STIL_MAXENTRY + 1]; | |
302 gint audioGot, songLength, i; | |
303 gchar *audioBuffer = NULL, *oversampleBuffer = NULL; | |
304 | |
305 (void) argPointer; | |
306 | |
307 /* Initialize */ | |
308 XSDEBUG("entering player thread\n"); | |
309 XS_MUTEX_LOCK(xs_status); | |
310 memcpy(&myStatus, &xs_status, sizeof(t_xs_status)); | |
311 myTune = xs_status.tuneInfo; | |
312 XS_MUTEX_UNLOCK(xs_status); | |
313 | |
314 xs_memset(&playedTune, 0, sizeof(playedTune)); | |
315 | |
316 /* Allocate audio buffer */ | |
317 audioBuffer = (gchar *) g_malloc(XS_AUDIOBUF_SIZE); | |
318 if (audioBuffer == NULL) { | |
319 XSERR("Couldn't allocate memory for audio data buffer!\n"); | |
320 goto xs_err_exit; | |
321 } | |
322 | |
323 if (myStatus.oversampleEnable) { | |
324 oversampleBuffer = (gchar *) g_malloc(XS_AUDIOBUF_SIZE * myStatus.oversampleFactor); | |
325 if (oversampleBuffer == NULL) { | |
326 XSERR("Couldn't allocate memory for audio oversampling buffer!\n"); | |
327 goto xs_err_exit; | |
328 } | |
329 } | |
330 | |
331 /* | |
332 * Main player loop: while not stopped, loop here - play subtunes | |
333 */ | |
334 audioOpen = FALSE; | |
335 doPlay = TRUE; | |
336 while (xs_status.isPlaying && doPlay) { | |
337 /* Automatic sub-tune change logic */ | |
338 XS_MUTEX_LOCK(xs_cfg); | |
339 XS_MUTEX_LOCK(xs_status); | |
340 assert(xs_status.currSong >= 1); | |
341 assert(xs_status.currSong <= XS_STIL_MAXENTRY); | |
342 myStatus.isPlaying = TRUE; | |
343 | |
344 if (xs_cfg.subAutoEnable && (myStatus.currSong == xs_status.currSong)) { | |
345 /* Check if currently selected sub-tune has been played already */ | |
346 if (playedTune[myStatus.currSong]) { | |
347 /* Find a tune that has not been played */ | |
348 XSDEBUG("tune #%i already played, finding next match ...\n", myStatus.currSong); | |
349 isFound = FALSE; | |
350 i = 0; | |
351 while (!isFound && (++i <= myTune->nsubTunes)) { | |
352 if (xs_cfg.subAutoMinOnly) { | |
353 /* A tune with minimum length must be found */ | |
354 if (!playedTune[i] | |
355 && myTune->subTunes[i].tuneLength >= xs_cfg.subAutoMinTime) | |
356 isFound = TRUE; | |
357 } else { | |
358 /* Any unplayed tune is okay */ | |
359 if (!playedTune[i]) | |
360 isFound = TRUE; | |
361 } | |
362 } | |
363 | |
364 if (isFound) { | |
365 /* Set the new sub-tune */ | |
366 XSDEBUG("found #%i\n", i); | |
367 xs_status.currSong = i; | |
368 } else | |
369 /* This is the end */ | |
370 doPlay = FALSE; | |
371 | |
372 XS_MUTEX_UNLOCK(xs_status); | |
373 XS_MUTEX_UNLOCK(xs_cfg); | |
374 continue; /* This is ugly, but ... */ | |
375 } | |
376 } | |
377 | |
378 /* Tell that we are initializing, update sub-tune controls */ | |
379 myStatus.currSong = xs_status.currSong; | |
380 playedTune[myStatus.currSong] = TRUE; | |
381 XS_MUTEX_UNLOCK(xs_status); | |
382 XS_MUTEX_UNLOCK(xs_cfg); | |
383 | |
384 XSDEBUG("subtune #%i selected, initializing...\n", myStatus.currSong); | |
385 | |
386 GDK_THREADS_ENTER(); | |
387 xs_subctrl_update(); | |
388 GDK_THREADS_LEAVE(); | |
389 | |
390 /* Check minimum playtime */ | |
391 songLength = myTune->subTunes[myStatus.currSong - 1].tuneLength; | |
392 if (xs_cfg.playMinTimeEnable && (songLength >= 0)) { | |
393 if (songLength < xs_cfg.playMinTime) | |
394 songLength = xs_cfg.playMinTime; | |
395 } | |
396 | |
397 /* Initialize song */ | |
398 if (!myStatus.sidPlayer->plrInitSong(&myStatus)) { | |
399 XSERR("Couldn't initialize SID-tune '%s' (sub-tune #%i)!\n", | |
400 myTune->sidFilename, myStatus.currSong); | |
401 goto xs_err_exit; | |
402 } | |
403 | |
404 | |
405 /* Open the audio output */ | |
406 if (!xs_plugin_ip.output-> | |
407 open_audio(myStatus.audioFormat, myStatus.audioFrequency, myStatus.audioChannels)) { | |
408 XSERR("Couldn't open XMMS audio output (fmt=%x, freq=%i, nchan=%i)!\n", myStatus.audioFormat, | |
409 myStatus.audioFrequency, myStatus.audioChannels); | |
410 | |
411 XS_MUTEX_LOCK(xs_status); | |
412 xs_status.isError = TRUE; | |
413 XS_MUTEX_UNLOCK(xs_status); | |
414 goto xs_err_exit; | |
415 } | |
416 | |
417 audioOpen = TRUE; | |
418 | |
419 /* Set song information for current subtune */ | |
420 xs_plugin_ip.set_info(myTune->subTunes[myStatus.currSong - 1].tuneTitle, | |
421 (songLength > 0) ? (songLength * 1000) : -1, | |
422 (myTune->subTunes[myStatus.currSong - 1].tuneSpeed > | |
423 0) ? (myTune->subTunes[myStatus.currSong - 1].tuneSpeed * 1000) : -1, | |
424 myStatus.audioFrequency, myStatus.audioChannels); | |
425 | |
426 | |
427 XSDEBUG("playing\n"); | |
428 | |
429 /* | |
430 * Play the subtune | |
431 */ | |
432 while (xs_status.isPlaying && myStatus.isPlaying && (xs_status.currSong == myStatus.currSong)) { | |
433 /* Render audio data */ | |
434 if (myStatus.oversampleEnable) { | |
435 /* Perform oversampled rendering */ | |
436 audioGot = myStatus.sidPlayer->plrFillBuffer(&myStatus, | |
437 oversampleBuffer, | |
438 (XS_AUDIOBUF_SIZE * | |
439 myStatus.oversampleFactor)); | |
440 | |
441 audioGot /= myStatus.oversampleFactor; | |
442 | |
443 /* Execute rate-conversion with filtering */ | |
444 if (xs_filter_rateconv(audioBuffer, oversampleBuffer, | |
445 myStatus.audioFormat, myStatus.oversampleFactor, audioGot) < 0) { | |
446 XSERR("Oversampling rate-conversion pass failed.\n"); | |
447 XS_MUTEX_LOCK(xs_status); | |
448 xs_status.isError = TRUE; | |
449 XS_MUTEX_UNLOCK(xs_status); | |
450 goto xs_err_exit; | |
451 } | |
452 } else | |
453 audioGot = myStatus.sidPlayer->plrFillBuffer(&myStatus, audioBuffer, XS_AUDIOBUF_SIZE); | |
454 | |
455 /* I <3 visualice/haujobb */ | |
456 xs_plugin_ip.add_vis_pcm(xs_plugin_ip.output->written_time(), | |
457 myStatus.audioFormat, myStatus.audioChannels, audioGot, audioBuffer); | |
458 | |
459 /* Wait a little */ | |
460 while (xs_status.isPlaying && | |
461 (xs_status.currSong == myStatus.currSong) && | |
462 (xs_plugin_ip.output->buffer_free() < audioGot)) | |
463 xmms_usleep(500); | |
464 | |
465 /* Output audio */ | |
466 if (xs_status.isPlaying && (xs_status.currSong == myStatus.currSong)) | |
467 xs_plugin_ip.output->write_audio(audioBuffer, audioGot); | |
468 | |
469 /* Check if we have played enough */ | |
470 if (xs_cfg.playMaxTimeEnable) { | |
471 if (xs_cfg.playMaxTimeUnknown) { | |
472 if ((songLength < 0) && | |
473 (xs_plugin_ip.output->output_time() >= (xs_cfg.playMaxTime * 1000))) | |
474 myStatus.isPlaying = FALSE; | |
475 } else { | |
476 if (xs_plugin_ip.output->output_time() >= (xs_cfg.playMaxTime * 1000)) | |
477 myStatus.isPlaying = FALSE; | |
478 } | |
479 } | |
480 | |
481 if (songLength >= 0) { | |
482 if (xs_plugin_ip.output->output_time() >= (songLength * 1000)) | |
483 myStatus.isPlaying = FALSE; | |
484 } | |
485 } | |
486 | |
487 XSDEBUG("subtune ended/stopped\n"); | |
488 | |
489 /* Close audio output plugin */ | |
490 if (audioOpen) { | |
491 XSDEBUG("close audio #1\n"); | |
492 xs_plugin_ip.output->close_audio(); | |
493 audioOpen = FALSE; | |
494 } | |
495 | |
496 /* Now determine if we continue by selecting other subtune or something */ | |
497 if (!myStatus.isPlaying && !xs_cfg.subAutoEnable) | |
498 doPlay = FALSE; | |
499 } | |
500 | |
501 xs_err_exit: | |
502 /* Close audio output plugin */ | |
503 if (audioOpen) { | |
504 XSDEBUG("close audio #2\n"); | |
505 xs_plugin_ip.output->close_audio(); | |
506 } | |
507 | |
508 g_free(audioBuffer); | |
509 g_free(oversampleBuffer); | |
510 | |
511 /* Set playing status to false (stopped), thus when | |
512 * XMMS next calls xs_get_time(), it can return appropriate | |
513 * value "not playing" status and XMMS knows to move to | |
514 * next entry in the playlist .. or whatever it wishes. | |
515 */ | |
516 XS_MUTEX_LOCK(xs_status); | |
517 xs_status.isPlaying = FALSE; | |
518 XS_MUTEX_UNLOCK(xs_status); | |
519 | |
520 /* Exit the playing thread */ | |
521 XSDEBUG("exiting thread, bye.\n"); | |
522 pthread_exit(NULL); | |
523 } | |
524 | |
525 | |
526 /* | |
527 * Start playing the given file | |
528 * Here we load the tune and initialize the playing thread. | |
529 * Usually you would also initialize the output-plugin, but | |
530 * this is XMMS-SID and we do it on the player thread instead. | |
531 */ | |
532 void xs_play_file(gchar * pcFilename) | |
533 { | |
534 assert(xs_status.sidPlayer); | |
535 | |
536 XSDEBUG("play '%s'\n", pcFilename); | |
537 | |
538 /* Get tune information */ | |
539 if ((xs_status.tuneInfo = xs_status.sidPlayer->plrGetSIDInfo(pcFilename)) == NULL) | |
540 return; | |
541 | |
542 /* Initialize the tune */ | |
543 if (!xs_status.sidPlayer->plrLoadSID(&xs_status, pcFilename)) { | |
544 xs_tuneinfo_free(xs_status.tuneInfo); | |
545 xs_status.tuneInfo = NULL; | |
546 return; | |
547 } | |
548 | |
549 XSDEBUG("load ok\n"); | |
550 | |
551 /* Set general status information */ | |
552 xs_status.isPlaying = TRUE; | |
553 xs_status.isError = FALSE; | |
554 xs_status.currSong = xs_status.tuneInfo->startTune; | |
555 | |
556 /* Start the playing thread! */ | |
557 if (pthread_create(&xs_decode_thread, NULL, xs_playthread, NULL) < 0) { | |
558 XSERR("Couldn't start playing thread!\n"); | |
559 xs_tuneinfo_free(xs_status.tuneInfo); | |
560 xs_status.tuneInfo = NULL; | |
561 xs_status.sidPlayer->plrDeleteSID(&xs_status); | |
562 } | |
563 | |
564 /* Okay, here the playing thread has started up and we | |
565 * return from here to XMMS. Rest is up to XMMS's GUI | |
566 * and playing thread. | |
567 */ | |
568 XSDEBUG("systems should be up?\n"); | |
569 } | |
570 | |
571 | |
572 /* | |
573 * Stop playing | |
574 * Here we set the playing status to stop and wait for playing | |
575 * thread to shut down. In any "correctly" done plugin, this is | |
576 * also the function where you close the output-plugin, but since | |
577 * XMMS-SID has special behaviour (audio opened/closed in the | |
578 * playing thread), we don't do that here. | |
579 * | |
580 * Finally tune and other memory allocations are free'd. | |
581 */ | |
582 void xs_stop(void) | |
583 { | |
584 XSDEBUG("STOP_REQ\n"); | |
585 | |
586 /* Close the sub-tune control window, if any */ | |
587 xs_subctrl_close(); | |
588 | |
589 /* Lock xs_status and stop playing thread */ | |
590 XS_MUTEX_LOCK(xs_status); | |
591 if (xs_status.isPlaying) { | |
592 /* Stop playing */ | |
593 XSDEBUG("stopping...\n"); | |
594 xs_status.isPlaying = FALSE; | |
595 XS_MUTEX_UNLOCK(xs_status); | |
596 pthread_join(xs_decode_thread, NULL); | |
597 } else { | |
598 XS_MUTEX_UNLOCK(xs_status); | |
599 } | |
600 | |
601 /* Status is now stopped, update the sub-tune | |
602 * controller in fileinfo window (if open) | |
603 */ | |
604 xs_fileinfo_update(); | |
605 | |
606 /* Free tune information */ | |
607 xs_status.sidPlayer->plrDeleteSID(&xs_status); | |
608 xs_tuneinfo_free(xs_status.tuneInfo); | |
609 xs_status.tuneInfo = NULL; | |
610 } | |
611 | |
612 | |
613 /* | |
614 * Pause/unpause the playing | |
615 */ | |
616 void xs_pause(short pauseState) | |
617 { | |
618 XS_MUTEX_LOCK(xs_status); | |
619 /* FIXME FIX ME todo: pause should disable sub-tune controls */ | |
620 XS_MUTEX_UNLOCK(xs_status); | |
621 | |
622 xs_subctrl_close(); | |
623 xs_fileinfo_update(); | |
624 xs_plugin_ip.output->pause(pauseState); | |
625 } | |
626 | |
627 | |
628 /* | |
629 * Pop-up subtune selector | |
630 */ | |
631 void xs_subctrl_setsong(void) | |
632 { | |
633 gint n; | |
634 | |
635 XS_MUTEX_LOCK(xs_status); | |
636 XS_MUTEX_LOCK(xs_subctrl); | |
637 | |
638 if (xs_status.tuneInfo && xs_status.isPlaying) { | |
639 n = (gint) GTK_ADJUSTMENT(xs_subctrl_adj)->value; | |
640 if ((n >= 1) && (n <= xs_status.tuneInfo->nsubTunes)) | |
641 xs_status.currSong = n; | |
642 } | |
643 | |
644 XS_MUTEX_UNLOCK(xs_subctrl); | |
645 XS_MUTEX_UNLOCK(xs_status); | |
646 } | |
647 | |
648 | |
649 void xs_subctrl_prevsong(void) | |
650 { | |
651 XS_MUTEX_LOCK(xs_status); | |
652 | |
653 if (xs_status.tuneInfo && xs_status.isPlaying) { | |
654 if (xs_status.currSong > 1) | |
655 xs_status.currSong--; | |
656 } | |
657 | |
658 XS_MUTEX_UNLOCK(xs_status); | |
659 | |
660 xs_subctrl_update(); | |
661 } | |
662 | |
663 | |
664 void xs_subctrl_nextsong(void) | |
665 { | |
666 XS_MUTEX_LOCK(xs_status); | |
667 | |
668 if (xs_status.tuneInfo && xs_status.isPlaying) { | |
669 if (xs_status.currSong < xs_status.tuneInfo->nsubTunes) | |
670 xs_status.currSong++; | |
671 } | |
672 | |
673 XS_MUTEX_UNLOCK(xs_status); | |
674 | |
675 xs_subctrl_update(); | |
676 } | |
677 | |
678 | |
679 void xs_subctrl_update(void) | |
680 { | |
681 GtkAdjustment *tmpAdj; | |
682 | |
683 XS_MUTEX_LOCK(xs_status); | |
684 XS_MUTEX_LOCK(xs_subctrl); | |
685 | |
686 /* Check if control window exists, we are currently playing and have a tune */ | |
687 if (xs_subctrl) { | |
688 if (xs_status.tuneInfo && xs_status.isPlaying) { | |
689 tmpAdj = GTK_ADJUSTMENT(xs_subctrl_adj); | |
690 | |
691 tmpAdj->value = xs_status.currSong; | |
692 tmpAdj->lower = 1; | |
693 tmpAdj->upper = xs_status.tuneInfo->nsubTunes; | |
694 XS_MUTEX_UNLOCK(xs_status); | |
695 XS_MUTEX_UNLOCK(xs_subctrl); | |
696 gtk_adjustment_value_changed(tmpAdj); | |
697 } else { | |
698 XS_MUTEX_UNLOCK(xs_status); | |
699 XS_MUTEX_UNLOCK(xs_subctrl); | |
700 xs_subctrl_close(); | |
701 } | |
702 } else { | |
703 XS_MUTEX_UNLOCK(xs_subctrl); | |
704 XS_MUTEX_UNLOCK(xs_status); | |
705 } | |
706 | |
707 xs_fileinfo_update(); | |
708 } | |
709 | |
710 | |
711 void xs_subctrl_close(void) | |
712 { | |
713 XS_MUTEX_LOCK(xs_subctrl); | |
714 | |
715 if (xs_subctrl) { | |
716 gtk_widget_destroy(xs_subctrl); | |
717 xs_subctrl = NULL; | |
718 } | |
719 | |
720 XS_MUTEX_UNLOCK(xs_subctrl); | |
721 } | |
722 | |
723 | |
724 gboolean xs_subctrl_keypress(GtkWidget * win, GdkEventKey * ev) | |
725 { | |
726 (void) win; | |
727 | |
728 if (ev->keyval == GDK_Escape) | |
729 xs_subctrl_close(); | |
730 | |
731 return FALSE; | |
732 } | |
733 | |
734 | |
735 void xs_subctrl_open(void) | |
736 { | |
737 GtkWidget *frame25, *hbox15, *subctrl_prev, *subctrl_current, *subctrl_next; | |
738 | |
739 XS_MUTEX_LOCK(xs_subctrl); | |
740 if (!xs_status.tuneInfo || !xs_status.isPlaying || xs_subctrl || (xs_status.tuneInfo->nsubTunes <= 1)) { | |
741 XS_MUTEX_UNLOCK(xs_subctrl); | |
742 return; | |
743 } | |
744 | |
745 /* Create the pop-up window */ | |
746 xs_subctrl = gtk_window_new (GTK_WINDOW_TOPLEVEL); | |
747 gtk_window_set_type_hint (GTK_WINDOW(xs_subctrl), GDK_WINDOW_TYPE_HINT_DIALOG); | |
748 gtk_widget_set_name(xs_subctrl, "xs_subctrl"); | |
749 gtk_object_set_data(GTK_OBJECT(xs_subctrl), "xs_subctrl", xs_subctrl); | |
750 | |
751 gtk_window_set_title(GTK_WINDOW(xs_subctrl), "Subtune Control"); | |
752 gtk_window_set_position(GTK_WINDOW(xs_subctrl), GTK_WIN_POS_MOUSE); | |
753 gtk_container_set_border_width(GTK_CONTAINER(xs_subctrl), 0); | |
754 gtk_window_set_policy(GTK_WINDOW(xs_subctrl), FALSE, FALSE, FALSE); | |
755 | |
756 gtk_signal_connect(GTK_OBJECT(xs_subctrl), "destroy", GTK_SIGNAL_FUNC(gtk_widget_destroyed), &xs_subctrl); | |
757 | |
758 gtk_signal_connect(GTK_OBJECT(xs_subctrl), "focus_out_event", GTK_SIGNAL_FUNC(xs_subctrl_close), NULL); | |
759 | |
760 gtk_widget_realize(xs_subctrl); | |
761 gdk_window_set_decorations(xs_subctrl->window, (GdkWMDecoration) 0); | |
762 | |
763 | |
764 /* Create the control widgets */ | |
765 frame25 = gtk_frame_new(NULL); | |
766 gtk_container_add(GTK_CONTAINER(xs_subctrl), frame25); | |
767 gtk_container_set_border_width(GTK_CONTAINER(frame25), 2); | |
768 gtk_frame_set_shadow_type(GTK_FRAME(frame25), GTK_SHADOW_OUT); | |
769 | |
770 hbox15 = gtk_hbox_new(FALSE, 4); | |
771 gtk_container_add(GTK_CONTAINER(frame25), hbox15); | |
772 | |
773 subctrl_prev = gtk_button_new_with_label(" < "); | |
774 gtk_widget_set_name(subctrl_prev, "subctrl_prev"); | |
775 gtk_box_pack_start(GTK_BOX(hbox15), subctrl_prev, FALSE, FALSE, 0); | |
776 | |
777 xs_subctrl_adj = gtk_adjustment_new(xs_status.currSong, 1, xs_status.tuneInfo->nsubTunes, 1, 1, 0); | |
778 gtk_signal_connect(GTK_OBJECT(xs_subctrl_adj), "value_changed", GTK_SIGNAL_FUNC(xs_subctrl_setsong), NULL); | |
779 | |
780 subctrl_current = gtk_hscale_new(GTK_ADJUSTMENT(xs_subctrl_adj)); | |
781 gtk_widget_set_name(subctrl_current, "subctrl_current"); | |
782 gtk_box_pack_start(GTK_BOX(hbox15), subctrl_current, FALSE, TRUE, 0); | |
783 gtk_scale_set_digits(GTK_SCALE(subctrl_current), 0); | |
784 gtk_range_set_update_policy(GTK_RANGE(subctrl_current), GTK_UPDATE_DELAYED); | |
785 gtk_widget_grab_focus(subctrl_current); | |
786 | |
787 subctrl_next = gtk_button_new_with_label(" > "); | |
788 gtk_widget_set_name(subctrl_next, "subctrl_next"); | |
789 gtk_box_pack_start(GTK_BOX(hbox15), subctrl_next, FALSE, FALSE, 0); | |
790 | |
791 gtk_signal_connect(GTK_OBJECT(subctrl_prev), "clicked", GTK_SIGNAL_FUNC(xs_subctrl_prevsong), NULL); | |
792 | |
793 gtk_signal_connect(GTK_OBJECT(subctrl_next), "clicked", GTK_SIGNAL_FUNC(xs_subctrl_nextsong), NULL); | |
794 | |
795 gtk_signal_connect(GTK_OBJECT(xs_subctrl), "key_press_event", GTK_SIGNAL_FUNC(xs_subctrl_keypress), NULL); | |
796 | |
797 gtk_widget_show_all(xs_subctrl); | |
798 | |
799 XS_MUTEX_UNLOCK(xs_subctrl); | |
800 } | |
801 | |
802 | |
803 /* | |
804 * Set the time-seek position | |
805 * The playing thread will do the "seeking", which means sub-tune | |
806 * changing in XMMS-SID's case. iTime argument is time in seconds, | |
807 * in contrast to milliseconds used in other occasions. | |
808 * | |
809 * This function is called whenever position slider is clicked or | |
810 * other method of seeking is used (keyboard, etc.) | |
811 */ | |
812 void xs_seek(gint iTime) | |
813 { | |
814 /* Check status */ | |
815 XS_MUTEX_LOCK(xs_status); | |
816 if (!xs_status.tuneInfo || !xs_status.isPlaying) { | |
817 XS_MUTEX_UNLOCK(xs_status); | |
818 return; | |
819 } | |
820 | |
821 /* Act according to settings */ | |
822 switch (xs_cfg.subsongControl) { | |
823 case XS_SSC_SEEK: | |
824 if (iTime < xs_status.lastTime) { | |
825 if (xs_status.currSong > 1) | |
826 xs_status.currSong--; | |
827 } else if (iTime > xs_status.lastTime) { | |
828 if (xs_status.currSong < xs_status.tuneInfo->nsubTunes) | |
829 xs_status.currSong++; | |
830 } | |
831 break; | |
832 | |
833 case XS_SSC_POPUP: | |
834 xs_subctrl_open(); | |
835 break; | |
836 | |
837 /* If we have song-position patch, check settings */ | |
838 #ifdef HAVE_SONG_POSITION | |
839 case XS_SSC_PATCH: | |
840 if ((iTime > 0) && (iTime <= xs_status.tuneInfo->nsubTunes)) | |
841 xs_status.currSong = iTime; | |
842 break; | |
843 #endif | |
844 } | |
845 | |
846 XS_MUTEX_UNLOCK(xs_status); | |
847 } | |
848 | |
849 | |
850 /* | |
851 * Return the playing "position/time" | |
852 * Determine current position/time in song. Used by XMMS to update | |
853 * the song clock and position slider and MOST importantly to determine | |
854 * END OF SONG! Return value of -2 means error, XMMS opens an audio | |
855 * error dialog. -1 means end of song (if one was playing currently). | |
856 */ | |
857 gint xs_get_time(void) | |
858 { | |
859 /* If errorflag is set, return -2 to signal it to XMMS's idle callback */ | |
860 XS_MUTEX_LOCK(xs_status); | |
861 if (xs_status.isError) { | |
862 XS_MUTEX_UNLOCK(xs_status); | |
863 return -2; | |
864 } | |
865 | |
866 /* If there is no tune, return -1 */ | |
867 if (!xs_status.tuneInfo) { | |
868 XS_MUTEX_UNLOCK(xs_status); | |
869 return -1; | |
870 } | |
871 | |
872 /* If tune has ended, return -1 */ | |
873 if (!xs_status.isPlaying) { | |
874 XS_MUTEX_UNLOCK(xs_status); | |
875 return -1; | |
876 } | |
877 | |
878 /* Let's see what we do */ | |
879 switch (xs_cfg.subsongControl) { | |
880 case XS_SSC_SEEK: | |
881 xs_status.lastTime = (xs_plugin_ip.output->output_time() / 1000); | |
882 break; | |
883 | |
884 #ifdef HAVE_SONG_POSITION | |
885 case XS_SSC_PATCH: | |
886 set_song_position(xs_status.currSong, 1, xs_status.tuneInfo->nsubTunes); | |
887 break; | |
888 #endif | |
889 } | |
890 | |
891 XS_MUTEX_UNLOCK(xs_status); | |
892 | |
893 /* Return output time reported by audio output plugin */ | |
894 return xs_plugin_ip.output->output_time(); | |
895 } | |
896 | |
897 | |
898 /* | |
899 * Return song information | |
900 * This function is called by XMMS when initially loading the playlist. | |
901 * Subsequent changes to information are made by the player thread, | |
902 * which uses xs_plugin_ip.set_info(); | |
903 */ | |
904 void xs_get_song_info(gchar * songFilename, gchar ** songTitle, gint * songLength) | |
905 { | |
906 t_xs_tuneinfo *pInfo; | |
907 gint tmpInt; | |
908 | |
909 /* Get tune information from emulation engine */ | |
910 pInfo = xs_status.sidPlayer->plrGetSIDInfo(songFilename); | |
911 if (!pInfo) | |
912 return; | |
913 | |
914 /* Get sub-tune information, if available */ | |
915 if ((pInfo->startTune > 0) && (pInfo->startTune <= pInfo->nsubTunes)) { | |
916 (*songTitle) = g_strdup(pInfo->subTunes[pInfo->startTune - 1].tuneTitle); | |
917 | |
918 tmpInt = pInfo->subTunes[pInfo->startTune - 1].tuneLength; | |
919 if (tmpInt < 0) | |
920 (*songLength) = -1; | |
921 else | |
922 (*songLength) = (tmpInt * 1000); | |
923 } | |
924 | |
925 /* Free tune information */ | |
926 xs_tuneinfo_free(pInfo); | |
927 } | |
928 | |
929 | |
930 /* Allocate a new tune information structure | |
931 */ | |
932 t_xs_tuneinfo *xs_tuneinfo_new(gchar * pcFilename, gint nsubTunes, gint startTune, | |
933 gchar * sidName, gchar * sidComposer, gchar * sidCopyright, | |
934 gint loadAddr, gint initAddr, gint playAddr, gint dataFileLen) | |
935 { | |
936 t_xs_tuneinfo *pResult; | |
937 | |
938 /* Allocate structure */ | |
939 pResult = (t_xs_tuneinfo *) g_malloc0(sizeof(t_xs_tuneinfo)); | |
940 if (!pResult) { | |
941 XSERR("Could not allocate memory for t_xs_tuneinfo ('%s')\n", pcFilename); | |
942 return NULL; | |
943 } | |
944 | |
945 pResult->sidFilename = g_strdup(pcFilename); | |
946 if (!pResult->sidFilename) { | |
947 XSERR("Could not allocate sidFilename ('%s')\n", pcFilename); | |
948 g_free(pResult); | |
949 return NULL; | |
950 } | |
951 | |
952 /* Allocate space for subtune information */ | |
953 if (nsubTunes > 0) { | |
954 pResult->subTunes = g_malloc0(sizeof(t_xs_subtuneinfo) * nsubTunes); | |
955 if (!pResult->subTunes) { | |
956 XSERR("Could not allocate memory for t_xs_subtuneinfo ('%s', %i)\n", pcFilename, nsubTunes); | |
957 | |
958 g_free(pResult->sidFilename); | |
959 g_free(pResult); | |
960 return NULL; | |
961 } | |
962 } | |
963 | |
964 /* The following allocations don't matter if they fail */ | |
965 pResult->sidName = g_strdup(sidName); | |
966 pResult->sidComposer = g_strdup(sidComposer); | |
967 pResult->sidCopyright = g_strdup(sidCopyright); | |
968 | |
969 pResult->nsubTunes = nsubTunes; | |
970 pResult->startTune = startTune; | |
971 | |
972 pResult->loadAddr = loadAddr; | |
973 pResult->initAddr = initAddr; | |
974 pResult->playAddr = playAddr; | |
975 pResult->dataFileLen = dataFileLen; | |
976 | |
977 return pResult; | |
978 } | |
979 | |
980 | |
981 /* Free given tune information structure | |
982 */ | |
983 void xs_tuneinfo_free(t_xs_tuneinfo * pTune) | |
984 { | |
985 gint i; | |
986 if (!pTune) | |
987 return; | |
988 | |
989 for (i = 0; i < pTune->nsubTunes; i++) { | |
990 g_free(pTune->subTunes[i].tuneTitle); | |
991 pTune->subTunes[i].tuneTitle = NULL; | |
992 } | |
993 | |
994 g_free(pTune->subTunes); | |
995 pTune->nsubTunes = 0; | |
996 g_free(pTune->sidFilename); | |
997 pTune->sidFilename = NULL; | |
998 g_free(pTune->sidName); | |
999 pTune->sidName = NULL; | |
1000 g_free(pTune->sidComposer); | |
1001 pTune->sidComposer = NULL; | |
1002 g_free(pTune->sidCopyright); | |
1003 pTune->sidCopyright = NULL; | |
1004 g_free(pTune); | |
1005 } |