269
|
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 }
|