changeset 2951:6abe60a81301

reimplemented cuesheet plugin using subtune.
author Yoshiki Yazawa <yaz@honeyplanet.jp>
date Sat, 11 Oct 2008 20:03:01 +0900
parents dcd8d93ba781
children 63bf9d97ce65
files src/cue/Makefile src/cue/cuesheet.c src/cue/cuesheet.h src/cue/watchdog.c
diffstat 4 files changed, 608 insertions(+), 534 deletions(-) [+]
line wrap: on
line diff
--- a/src/cue/Makefile	Sat Oct 11 02:02:41 2008 +0900
+++ b/src/cue/Makefile	Sat Oct 11 20:03:01 2008 +0900
@@ -1,6 +1,6 @@
-PLUGIN = cuesheet${PLUGIN_SUFFIX}
+PLUGIN = cuesheet_ng${PLUGIN_SUFFIX}
 
-SRCS = cuesheet.c
+SRCS = cuesheet.c watchdog.c
 
 include ../../buildsys.mk
 include ../../extra.mk
--- a/src/cue/cuesheet.c	Sat Oct 11 02:02:41 2008 +0900
+++ b/src/cue/cuesheet.c	Sat Oct 11 20:03:01 2008 +0900
@@ -15,51 +15,21 @@
  * details.
  */
 
-#include "config.h"
-
-/* #define AUD_DEBUG 1 */
-
-#include <string.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <ctype.h>
-#include <audacious/plugin.h>
-#include <audacious/output.h>
-
-#define MAX_CUE_LINE_LENGTH 1000
-#define MAX_CUE_TRACKS 1000
+#include "cuesheet.h"
 
-static void cache_cue_file(gchar *f);
-static void free_cue_info(void);
-static void fix_cue_argument(char *line);
-static gboolean is_our_file(gchar *filespec);
-static void play(InputPlayback *data);
-static void play_cue_uri(InputPlayback *data, gchar *uri);
-static void seek(InputPlayback *data, gint time);
-static void stop(InputPlayback *data);
-static void cue_pause(InputPlayback *data, short);
-static Tuple *get_tuple(gchar *uri);
-static Tuple *get_aud_tuple_uri(gchar *uri);
-static void cue_init(void);
-static void cue_cleanup(void);
-static gpointer watchdog_func(gpointer data);
+#define USE_WATCHDOG 1
 
 static GThread *watchdog_thread = NULL;
 static GThread *play_thread = NULL;
 static GThread *real_play_thread = NULL;
 
-static GMutex *cue_mutex;
-static GCond *cue_cond;
-static enum {
-    STOP,
-    RUN,
-    EXIT,
-} watchdog_state;
+GMutex *cue_mutex;
+GCond *cue_cond;
 
 static GMutex *cue_block_mutex;
 static GCond *cue_block_cond;
 
-static InputPlayback *caller_ip = NULL;
+InputPlayback *caller_ip = NULL;
 
 static gchar *cue_file = NULL;
 static gchar *cue_title = NULL;
@@ -68,42 +38,45 @@
 static gchar *cue_year = NULL;
 static gchar *cue_track = NULL;
 
-static gint last_cue_track = 0;
-static gint cur_cue_track = 0;
-static gint target_time = 0;
-static GMutex *cue_target_time_mutex;
+gint last_cue_track = 0;
+gint cur_cue_track = 0;
+gulong target_time = 0;
+GMutex *cue_target_time_mutex;
+static gint full_length = 0;
 
-static struct {
-	gchar *title;
-	gchar *performer;
-	gint index;
-} cue_tracks[MAX_CUE_TRACKS];
+cue_tracks_t cue_tracks[MAX_CUE_TRACKS];
+
+watchdog_state_t watchdog_state;
 
-static gint finetune_seek = 0;
+gint finetune_seek = 0;
 
-static InputPlayback *real_ip = NULL;
-
-static gchar *cue_fmts[] = { "cue", NULL };
+InputPlayback *real_ip = NULL;
+static gchar *cue_fmts[] = {"cue", NULL};
 
 InputPlugin cue_ip =
 {
-	.description = "Cuesheet Plugin",	/* description */
-	.init = cue_init,	       	/* init */
-	.is_our_file = is_our_file,
-	.play_file = play,
-	.stop = stop,
-	.pause = cue_pause,
-	.seek = seek,
-	.cleanup = cue_cleanup,		/* cleanup */
-	.get_song_tuple = get_tuple,
-	.vfs_extensions = cue_fmts,
+    .description = "Cuesheet2 Plugin",    /* description */
+    .init = cue_init,               /* init */
+    .play_file = play,
+    .stop = stop,
+    .pause = cue_pause,
+    .seek = seek,
+    .mseek = mseek,
+    .get_time = get_time,
+    .cleanup = cue_cleanup,        /* cleanup */
+    .get_song_tuple = get_song_tuple,
+    .vfs_extensions = cue_fmts,
+    .is_our_file = is_our_file,
+    .probe_for_tuple = probe_for_tuple,
+    .have_subtune = TRUE
 };
 
 InputPlugin *cue_iplist[] = { &cue_ip, NULL };
 
 DECLARE_PLUGIN(cue, NULL, NULL, cue_iplist, NULL, NULL, NULL, NULL, NULL);
 
-static void cue_init(void)
+void
+cue_init(void)
 {
     cue_mutex = g_mutex_new();
     cue_cond = g_cond_new();
@@ -111,24 +84,27 @@
     cue_block_cond = g_cond_new();
     cue_target_time_mutex = g_mutex_new();
 
+#if USE_WATCHDOG
     /* create watchdog thread */
     g_mutex_lock(cue_mutex);
     watchdog_state = STOP;
     g_mutex_unlock(cue_mutex);
     watchdog_thread = g_thread_create(watchdog_func, NULL, TRUE, NULL);
     AUDDBG("watchdog_thread = %p\n", watchdog_thread);
-    aud_uri_set_plugin("cue://", &cue_ip);
+#endif
 }
 
-static void cue_cleanup(void)
+void
+cue_cleanup(void)
 {
+#if USE_WATCHDOG
     g_mutex_lock(cue_mutex);
     watchdog_state = EXIT;
     g_mutex_unlock(cue_mutex);
     g_cond_broadcast(cue_cond);
 
     g_thread_join(watchdog_thread);
-
+#endif
     g_cond_free(cue_cond);
     g_mutex_free(cue_mutex);
     g_cond_free(cue_block_cond);
@@ -136,42 +112,52 @@
     g_mutex_free(cue_target_time_mutex);
 }
 
-static int is_our_file(gchar *filename)
+Tuple *
+probe_for_tuple(gchar *uri, VFSFile *fd)
+{
+    Tuple *tuple = NULL;
+
+    AUDDBG("uri=%s\n",uri);
+
+    if(!is_our_file(uri))
+        return NULL;
+
+	/* Get subtune information */
+	tuple = get_song_tuple(uri);
+	return tuple;
+}
+
+
+int
+is_our_file(gchar *filename)
 {
 	gchar *ext;
 
-	/* is it a cue:// URI? */
-	if (!strncasecmp(filename, "cue://", 6))
-		return TRUE;
-
 	ext = strrchr(filename, '.');
-
 	if(!ext)
 		return FALSE;
-	
+
 	if (!strncasecmp(ext, ".cue", 4))
-	{
-		gint i;
-
-		/* add the files, build cue urls, etc. */
-		cache_cue_file(filename); //filename should be uri.
-
-		for (i = 0; i < last_cue_track; i++)
-		{
-			gchar *tmp = g_strdup_printf("cue://%s?%d", filename, i);
-			aud_playlist_add_url(aud_playlist_get_active(), tmp);
-			g_free(tmp);
-		}
-
-		free_cue_info();
-
-		return -1;
-	}
+		return TRUE;
 
 	return FALSE;
 }
 
-static void play(InputPlayback *data)
+gint
+get_time(InputPlayback *playback)
+{
+    gint raw_time = playback->output->output_time();
+    gint cooked_time;
+
+    /* translate actual time into subtune time */
+    cooked_time = raw_time - cue_tracks[cur_cue_track].index;
+
+    /* return raw_time; */
+    return cooked_time; /* visualization will be missing. --yaz */
+}
+
+void
+play(InputPlayback *data)
 {
     gchar *uri = g_strdup(data->filename);
 
@@ -179,121 +165,140 @@
     AUDDBG("play: uri = %s\n", uri);
 
     caller_ip = data;
-	/* this isn't a cue:// uri? */
-	if (strncasecmp("cue://", uri, 6))
-	{
-		gchar *tmp = g_strdup_printf("cue://%s?0", uri);
-		g_free(uri);
-		uri = tmp;
-	}
-	play_thread = g_thread_self();
-	data->set_pb_ready(data); // it should be done in real input plugin? --yaz
-	play_cue_uri(data, uri);
-	g_free(uri);
+
+    play_thread = g_thread_self();
+    data->set_pb_ready(data); /* it should be done in real
+                                 input plugin? --yaz */
+    play_cue_uri(data, uri);
+    g_free(uri); uri = NULL;
 }
 
-static Tuple *get_tuple(gchar *uri)
-{
-	Tuple *ret;
-
-	/* this isn't a cue:// uri? */
-	if (strncasecmp("cue://", uri, 6))
-	{
-		gchar *tmp = g_strdup_printf("cue://%s?0", uri);
-		ret = get_aud_tuple_uri(tmp);
-		g_free(tmp);
-		return ret;
-	}
-
-	return get_aud_tuple_uri(uri);
-}
-
-static void _aud_tuple_copy_field(Tuple *tuple, Tuple *tuple2, const gint nfield, const gchar *field)
+void
+_aud_tuple_copy_field(Tuple *tuple, Tuple *tuple2, const gint nfield, const gchar *field)
 {
     const gchar *str = aud_tuple_get_string(tuple, nfield, field);
     aud_tuple_disassociate(tuple2, nfield, field);
     aud_tuple_associate_string(tuple2, nfield, field, str);
 }
 
-static Tuple *get_aud_tuple_uri(gchar *uri)
+
+/* this function will be called back for subtune-info in adding to
+ * playlist. */
+Tuple *
+get_song_tuple(gchar *uri) /* *.cue or *.cue?1- */
 {
-    gchar *path2 = g_strdup(uri + 6);
-    gchar *_path = strchr(path2, '?');
+    Tuple *phys_tuple, *out;
+    ProbeResult *pr = NULL;
+    InputPlugin *dec = NULL;
     gint track = 0;
 
-	ProbeResult *pr;
-	InputPlugin *dec;
-
-	Tuple *phys_tuple=NULL, *out;
+    /* check subtune */
+    gchar *path2 = g_strdup(uri);
+    gchar *_path = strchr(path2, '?');
 
-        if (_path != NULL && *_path == '?')
-        {
-                *_path = '\0';
-                _path++;
-                track = atoi(_path);
-        }	
-
-	cache_cue_file(path2); //path2 should be uri.
+    /* subtune specifed */
+    if (_path != NULL && *_path == '?') {
+        *_path = '\0';
+        _path++;
+        track = atoi(_path) - 1; /* subtune number */
+    }
 
-	if (cue_file == NULL)
-		return NULL;
-
-	AUDDBG("cue_file = %s\n", cue_file);
+    /* parse file of uri and find actual file to play */
+    cache_cue_file(path2);
 
-	pr = aud_input_check_file(cue_file, FALSE);
-	if (pr == NULL)
-		return NULL;
-	dec = pr->ip;
-	if (dec == NULL)
-		return NULL;
+    /* obtain probe result for actual file */
+    pr = aud_input_check_file(cue_file, FALSE);
+    if (pr == NULL)
+        return NULL;
+    dec = pr->ip;
+    if (dec == NULL)
+        return NULL;
 
-	if (dec->get_song_tuple)
-		phys_tuple = dec->get_song_tuple(cue_file);
+    /* get tuple for actual file */
+    if (dec->get_song_tuple)
+        phys_tuple = dec->get_song_tuple(cue_file);
 
     if(!phys_tuple)
         return NULL;
 
-    out = aud_tuple_new();
+
+    /* build tuple to be returned */
+    gchar *realfn = g_filename_from_uri(cue_file, NULL, NULL);
+    if(!realfn)
+        return NULL;
 
-    _aud_tuple_copy_field(phys_tuple, out, FIELD_FILE_PATH, NULL);
-    _aud_tuple_copy_field(phys_tuple, out, FIELD_FILE_NAME, NULL);
-    _aud_tuple_copy_field(phys_tuple, out, FIELD_FILE_EXT, NULL);
+    gchar *ext = strrchr(realfn, '.');
+    ext++;
+
+    /* copy physical tuple */
+    out = aud_tuple_new();
     _aud_tuple_copy_field(phys_tuple, out, FIELD_CODEC, NULL);
     _aud_tuple_copy_field(phys_tuple, out, FIELD_QUALITY, NULL);
     _aud_tuple_copy_field(phys_tuple, out, FIELD_COPYRIGHT, NULL);
     _aud_tuple_copy_field(phys_tuple, out, FIELD_COMMENT, NULL);
 
-    aud_tuple_associate_int(out, FIELD_LENGTH, NULL, aud_tuple_get_int(phys_tuple, FIELD_LENGTH, NULL));
+    full_length = aud_tuple_get_int(phys_tuple, FIELD_LENGTH, NULL);
 
     aud_tuple_free(phys_tuple);
 
-    aud_tuple_associate_string(out, FIELD_TITLE, NULL, cue_tracks[track].title);
-    aud_tuple_associate_string(out, FIELD_ARTIST, NULL, cue_tracks[track].performer ?
-				  cue_tracks[track].performer : cue_performer);
-    aud_tuple_associate_string(out, FIELD_ALBUM, NULL, cue_title);
-    aud_tuple_associate_string(out, FIELD_GENRE, NULL, cue_genre);
-    if(cue_year)
-        aud_tuple_associate_int(out, FIELD_YEAR, NULL, atoi(cue_year));
-    aud_tuple_associate_int(out, FIELD_TRACK_NUMBER, NULL, track + 1);
+    /* make path related parts */
+    aud_tuple_associate_string(out, FIELD_FILE_PATH, NULL,
+                               g_path_get_dirname(realfn));
+    aud_tuple_associate_string(out, FIELD_FILE_NAME, NULL,
+                               g_path_get_basename(realfn));
+    aud_tuple_associate_string(out, FIELD_FILE_EXT, NULL, ext);
+
+    /* set subtune information */
+    out->nsubtunes = last_cue_track;
+    out->subtunes = NULL; /* ok? */
 
+    /* subtune specified */
+    if(_path) {
+        aud_tuple_associate_string(out, FIELD_TITLE, NULL,
+                                   cue_tracks[track].title);
+        aud_tuple_associate_string(out, FIELD_ARTIST, NULL,
+                                   cue_tracks[track].performer ?
+                                   cue_tracks[track].performer : cue_performer);
+        aud_tuple_associate_string(out, FIELD_ALBUM, NULL, cue_title);
+        aud_tuple_associate_string(out, FIELD_GENRE, NULL, cue_genre);
+        if(cue_year)
+            aud_tuple_associate_int(out, FIELD_YEAR, NULL, atoi(cue_year));
+        aud_tuple_associate_int(out, FIELD_TRACK_NUMBER, NULL, track+1);
+        aud_tuple_associate_int(out, FIELD_LENGTH, NULL,
+                                cue_tracks[track].duration);
+    }
     return out;
 }
 
-static void seek(InputPlayback * data, gint time)
+
+void
+mseek(InputPlayback *input, gulong time)
 {
+    AUDDBG("cur_cue_track=%d\n",cur_cue_track);
+
     g_mutex_lock(cue_target_time_mutex);
-    target_time = time * 1000;
+    target_time = time + cue_tracks[cur_cue_track].index;
     g_mutex_unlock(cue_target_time_mutex);
 
-    AUDDBG("seek: playback = %p\n", data);
-    AUDDBG("cue: seek: target_time = %d\n", target_time);
+    AUDDBG("cue: mseek: target_time = %lu\n", target_time);
 
-	if (real_ip != NULL) {
-		real_ip->plugin->seek(real_ip, time);
+    if (real_ip != NULL) {
+        if(real_ip->plugin->mseek)
+            real_ip->plugin->mseek(real_ip, target_time);
+        else
+            real_ip->plugin->seek(real_ip, target_time/1000);
     }
 }
 
-static void stop(InputPlayback * data)
+void
+seek(InputPlayback *input, gint time)
+{
+    gulong millisecond = time * 1000;
+    mseek(input, millisecond);
+}
+
+void
+stop(InputPlayback * data)
 {
     AUDDBG("f: stop: playback = %p\n", data);
 
@@ -313,11 +318,12 @@
             if (caller_ip != NULL)
                 caller_ip->playing = 0;
 
+#if USE_WATCHDOG
             g_mutex_lock(cue_mutex);
             watchdog_state = STOP;
             g_mutex_unlock(cue_mutex);
             g_cond_signal(cue_cond);
-
+#endif
             free_cue_info();
 
             if (real_ip != NULL) {
@@ -332,71 +338,47 @@
 
     } /*play_thread*/
 
+
     AUDDBG("e: stop\n");
 }
 
-static gboolean do_stop(gpointer data)
-{
-    AUDDBG("f: do_stop\n");
-    audacious_drct_stop();
-    AUDDBG("e: do_stop\n");
-
-    return FALSE; //only once
-}
-
-static gboolean do_setpos(gpointer data)
+void
+cue_pause(InputPlayback * data, short p)
 {
-    Playlist *playlist = aud_playlist_get_active();
-    gint pos = aud_playlist_get_position_nolock(playlist);
-    gint incr = *(gint *)data;
-
-    pos = pos + incr;
-    if(pos < 0)
-        pos = 0;
-
-    AUDDBG("do_setpos: pos = %d\n\n", pos);
-
-    if (!playlist)
-        return FALSE;
-
-    /* being done from the main loop thread, does not require locks */
-    aud_playlist_set_position(playlist, (guint)pos);
-
-    return FALSE; //one-shot
+    if (real_ip != NULL)
+        real_ip->plugin->pause(real_ip, p);
 }
 
-static void cue_pause(InputPlayback * data, short p)
+void
+set_info_override(gchar * unused, gint length, gint rate, gint freq, gint nch)
 {
-	if (real_ip != NULL)
-		real_ip->plugin->pause(real_ip, p);
+    gchar *title;
+    Playlist *playlist = aud_playlist_get_active();
+    gint cur_len;
+
+    g_return_if_fail(playlist != NULL);
+
+    /* annoying.. */
+    if (playlist->position->tuple == NULL)
+    {
+        gint pos = aud_playlist_get_position(playlist);
+        aud_playlist_get_tuple(playlist, pos);
+    }
+
+    title = g_strdup(playlist->position->title);
+    cur_len = cue_tracks[cur_cue_track].duration;
+    cue_ip.set_info(title, cur_len, rate, freq, nch);
 }
 
-static void set_info_override(gchar * unused, gint length, gint rate, gint freq, gint nch)
+
+void
+play_cue_uri(InputPlayback * data, gchar *uri)
 {
-	gchar *title;
-	Playlist *playlist = aud_playlist_get_active();
-
-	g_return_if_fail(playlist != NULL);
-
-	/* annoying. */
-	if (playlist->position->tuple == NULL)
-	{
-		gint pos = aud_playlist_get_position(playlist);
-		aud_playlist_get_tuple(playlist, pos);
-	}
-
-	title = g_strdup(playlist->position->title);
-
-	cue_ip.set_info(title, length, rate, freq, nch);
-}
-
-static void play_cue_uri(InputPlayback * data, gchar *uri)
-{
-    gchar *path2 = g_strdup(uri + 6); // "cue://" is stripped.
+    gchar *path2 = g_strdup(uri); /* file:// */
     gchar *_path = strchr(path2, '?');
-	gint track = 0;
-	ProbeResult *pr;
-	InputPlugin *real_ip_plugin;
+    gint track = 0;
+    ProbeResult *pr;
+    InputPlugin *real_ip_plugin;
     Tuple *tuple = NULL;
 
     AUDDBG("f: play_cue_uri\n");
@@ -404,389 +386,251 @@
     AUDDBG("play_cue_uri: path2 = %s\n", path2);
 
     /* stop watchdog thread */
+#if USE_WATCHDOG
     g_mutex_lock(cue_mutex);
     watchdog_state = STOP;
     g_mutex_unlock(cue_mutex);
     g_cond_signal(cue_cond);
+#endif
 
     if (_path != NULL && *_path == '?')
     {
         *_path = '\0';
         _path++;
-        track = atoi(_path);
-    }	
-	cur_cue_track = track;
-	cache_cue_file(path2); //path2 should be uri.
+        track = atoi(_path) - 1;
+        AUDDBG("track=%d\n",track);
+    }
+    cur_cue_track = track;
+    cache_cue_file(path2); /* path2 should be uri. */
 
     if (cue_file == NULL || !aud_vfs_file_test(cue_file, G_FILE_TEST_EXISTS))
         return;
 
-	pr = aud_input_check_file(cue_file, FALSE);
-	if (pr == NULL)
-		return;
+    pr = aud_input_check_file(cue_file, FALSE); /* find actual input plugin */
+    if (pr == NULL)
+        return;
+
+    real_ip_plugin = pr->ip;
 
-	real_ip_plugin = pr->ip;
+    if (real_ip_plugin != NULL)
+    {
+        if (real_ip)
+            g_free(real_ip);
 
-	if (real_ip_plugin != NULL)
-	{
-		if (real_ip)
-			g_free(real_ip);
+        /* duplicate original playback and modify */
+        real_ip = (InputPlayback *)g_memdup(data, sizeof(InputPlayback));
+        real_ip->plugin = real_ip_plugin;
+        real_ip->plugin->set_info = set_info_override;
+        real_ip->filename = cue_file;
 
-		/* duplicate original playback and modify */
-		real_ip = (InputPlayback *)g_memdup(data, sizeof(InputPlayback));
-		real_ip->plugin = real_ip_plugin;
-		real_ip->plugin->set_info = set_info_override;
-		real_ip->filename = cue_file;
+        data->playing = 1;
 
-		data->playing = 1;
+        real_play_thread =
+            g_thread_create((GThreadFunc)(real_ip->plugin->play_file),
+                            (gpointer)real_ip, TRUE, NULL);
+        g_usleep(50000); /* wait for 50msec while real input plugin
+                            is initializing. */
 
-		real_play_thread = g_thread_create((GThreadFunc)(real_ip->plugin->play_file), (gpointer)real_ip, TRUE, NULL);
-		g_usleep(50000); // wait for 50msec while real input plugin is initializing.
-
-		if(real_ip->plugin->mseek) {
+        if(real_ip->plugin->mseek) {
             AUDDBG("mseek\n");
-			real_ip->plugin->mseek(real_ip, finetune_seek ? finetune_seek : cue_tracks[track].index);
-		}
-		else
-			real_ip->plugin->seek(real_ip, finetune_seek ? finetune_seek / 1000 : cue_tracks[track].index / 1000 + 1);
+            real_ip->plugin->mseek(real_ip, finetune_seek ?
+                                   finetune_seek : cue_tracks[track].index);
+        }
+        else
+            real_ip->plugin->seek(real_ip, finetune_seek ?
+                                  finetune_seek / 1000 :
+                                  cue_tracks[track].index / 1000 + 1);
 
         g_mutex_lock(cue_target_time_mutex);
         target_time = finetune_seek ? finetune_seek : cue_tracks[track].index;
         g_mutex_unlock(cue_target_time_mutex);
 
-        AUDDBG("cue: play_cue_uri: target_time = %d\n", target_time);
+        AUDDBG("cue: play_cue_uri: target_time = %lu\n", target_time);
 
         tuple = real_ip->plugin->get_song_tuple(cue_file);
         if(tuple) {
-            cue_tracks[last_cue_track].index = aud_tuple_get_int(tuple, FIELD_LENGTH, NULL);
+            cue_tracks[last_cue_track].index =
+                aud_tuple_get_int(tuple, FIELD_LENGTH, NULL);
             aud_tuple_free(tuple); tuple = NULL;
         }
 
         /* kick watchdog thread */
+#if USE_WATCHDOG
         g_mutex_lock(cue_mutex);
         watchdog_state = RUN;
         g_mutex_unlock(cue_mutex);
         g_cond_signal(cue_cond);
 
         AUDDBG("watchdog activated\n");
-
+#endif
         finetune_seek = 0;
         if(real_play_thread) {
             g_mutex_lock(cue_block_mutex);
-            g_cond_wait(cue_block_cond, cue_block_mutex); // block until stop() is called.
-            g_mutex_unlock(cue_block_mutex);            
+            /* block until stop() is called. */
+            g_cond_wait(cue_block_cond, cue_block_mutex);
+            g_mutex_unlock(cue_block_mutex);
         }
-	}
+    }
 
     AUDDBG("e: play_cue_uri\n");
 }
 
-/******************************************************* watchdog */
+
+/******************************************************** cuefile */
 
-/*
- * This is fairly hard to explain.
- *
- * Basically we loop until we have reached the correct track.
- * Then we set a finetune adjustment to make sure we stay in the
- * right place.
- *
- * I used to recurse here (it was prettier), but that didn't work
- * as well as I was hoping.
- *
- * Anyhow, yeah. The logic here isn't great, but it works, so I'm
- * cool with it.
- *
- *     - nenolod
- */
-static gpointer watchdog_func(gpointer data)
+void
+free_cue_info(void)
 {
-    gint time = 0;
-    Playlist *playlist = NULL;
-    GTimeVal sleep_time;
-
-    AUDDBG("f: watchdog\n");
-
-    while(1) {
-#if 0
-        AUDDBG("time = %d cur = %d cidx = %d nidx = %d last = %d\n",
-                time, cur_cue_track,
-                cue_tracks[cur_cue_track].index,
-                cue_tracks[cur_cue_track+1].index, last_cue_track);
-#endif
-        g_get_current_time(&sleep_time);
-        g_time_val_add(&sleep_time, 10000); // interval is 10msec.
+    g_free(cue_file);    cue_file = NULL;
+    g_free(cue_title);    cue_title = NULL;
+    g_free(cue_performer);    cue_performer = NULL;
+    g_free(cue_genre);    cue_genre = NULL;
+    g_free(cue_year);     cue_year = NULL;
+    g_free(cue_track);    cue_track = NULL;
 
-        g_mutex_lock(cue_mutex);
-        switch(watchdog_state) {
-        case EXIT:
-            AUDDBG("e: watchdog exit\n");
-            g_mutex_unlock(cue_mutex); // stop() will lock cue_mutex.
-            stop(real_ip); // need not to care about real_ip != NULL here.
-            g_thread_exit(NULL);
-            break;
-        case RUN:
-            if(!playlist)
-                playlist = aud_playlist_get_active();
-            g_cond_timed_wait(cue_cond, cue_mutex, &sleep_time);
-            break;
-        case STOP:
-            AUDDBG("watchdog deactivated\n");
-            g_cond_wait(cue_cond, cue_mutex);
-            playlist = aud_playlist_get_active();
-            break;
-        }
-        g_mutex_unlock(cue_mutex);
-
-        if(watchdog_state != RUN)
-            continue;
+    for (; last_cue_track > 0; last_cue_track--) {
+        g_free(cue_tracks[last_cue_track-1].performer);
+        cue_tracks[last_cue_track-1].performer = NULL;
+        g_free(cue_tracks[last_cue_track-1].title);
+        cue_tracks[last_cue_track-1].title = NULL;
+    }
+    AUDDBG("free_cue_info: last_cue_track = %d\n", last_cue_track);
+    last_cue_track = 0;
+}
 
-        time = audacious_drct_get_output_time();
-#if 0
-        AUDDBG("time = %d target_time = %d\n", time, target_time);
-#endif
-        if(time == 0 || time <= target_time)
-            continue;
+void
+cache_cue_file(char *f)
+{
+    VFSFile *file = aud_vfs_fopen(f, "rb");
+    gchar line[MAX_CUE_LINE_LENGTH+1];
 
-        // prev track
-        if (time < cue_tracks[cur_cue_track].index)
-        {
-            static gint incr = 0;
-            gint oldpos = cur_cue_track;
-            AUDDBG("i: watchdog prev\n");
-            AUDDBG("time = %d cur = %d cidx = %d nidx = %d\n", time, cur_cue_track,
-                   cue_tracks[cur_cue_track].index,
-                   cue_tracks[cur_cue_track+1].index);
+    if(!file)
+        return;
 
-            while(time < cue_tracks[cur_cue_track].index) {
-                cur_cue_track--;
-                incr = cur_cue_track - oldpos; // relative position
-                if (time >= cue_tracks[cur_cue_track].index)
-                    finetune_seek = time;
+    while (TRUE) {
+        gint p, q;
 
-                AUDDBG("cue: prev_track: time = %d cue_tracks[cur_cue_track].index = %d\n",
-                       time, cue_tracks[cur_cue_track].index);
-                AUDDBG("cue: prev_track: finetune_seek = %d\n", finetune_seek);
-            }
-
-            g_mutex_lock(cue_target_time_mutex);
-            target_time = finetune_seek ? finetune_seek : cue_tracks[cur_cue_track].index;
-            g_mutex_unlock(cue_target_time_mutex);
-            AUDDBG("cue: prev_track: target_time = %d\n", target_time);
-            g_idle_add_full(G_PRIORITY_HIGH , do_setpos, &incr, NULL);
-            continue;
+        if (aud_vfs_fgets(line, MAX_CUE_LINE_LENGTH+1, file) == NULL) {
+            aud_vfs_fclose(file);
+            cue_tracks[last_cue_track-1].duration =
+                full_length - cue_tracks[last_cue_track-1].index;
+            return;
         }
 
-        // next track
-        if (cur_cue_track + 1 < last_cue_track && time > cue_tracks[cur_cue_track + 1].index)
-        {
-            static gint incr = 0;
-            gint oldpos = cur_cue_track;
-            AUDDBG("i: watchdog next\n");
-            AUDDBG("time = %d cur = %d cidx = %d nidx = %d last = %d lidx = %d\n", time, cur_cue_track,
-                    cue_tracks[cur_cue_track].index,
-                    cue_tracks[cur_cue_track+1].index,
-                    last_cue_track, cue_tracks[last_cue_track].index);
-            while(time > cue_tracks[cur_cue_track + 1].index) {
-                cur_cue_track++;
-                incr = cur_cue_track - oldpos; // relative position
-                if (time >= cue_tracks[cur_cue_track].index)
-                    finetune_seek = time;
-                AUDDBG("cue: next_track: time = %d cue_tracks[cur_cue_track].index = %d\n",
-                       time, cue_tracks[cur_cue_track].index);
-                AUDDBG("cue: next_track: finetune_seek = %d\n", finetune_seek);
+        for (p = 0; line[p] && isspace((int) line[p]); p++);
+        if (!line[p])
+            continue;
+        for (q = p; line[q] && !isspace((int) line[q]); q++);
+        if (!line[q])
+            continue;
+        line[q] = '\0';
+        for (q++; line[q] && isspace((int) line[q]); q++);
+        if (strcasecmp(line+p, "REM") == 0) {
+            for (p = q; line[p] && !isspace((int) line[p]); p++);
+            if (!line[p])
+                continue;
+            line[p] = '\0';
+            for (p++; line[p] && isspace((int) line[p]); p++);
+            if(strcasecmp(line+q, "GENRE") == 0) {
+                fix_cue_argument(line+p);
+                if (last_cue_track == 0)
+                    cue_genre = aud_str_to_utf8(line + p);
             }
-
-            g_mutex_lock(cue_target_time_mutex);
-            target_time = finetune_seek ? finetune_seek : cue_tracks[cur_cue_track].index;
-            g_mutex_unlock(cue_target_time_mutex);
-            AUDDBG("cue: next_track: target_time = %d\n", target_time);
-            if(aud_cfg->stopaftersong) {
-                g_idle_add_full(G_PRIORITY_HIGH, do_stop, (void *)real_ip, NULL);
-                continue;
-            }
-            else {
-                g_idle_add_full(G_PRIORITY_HIGH , do_setpos, &incr, NULL);
-                continue;
+            if(strcasecmp(line+q, "DATE") == 0) {
+                gchar *tmp;
+                fix_cue_argument(line+p);
+                if (last_cue_track == 0) {
+                    tmp = g_strdup(line + p);
+                    if (tmp) {
+                        cue_year = strtok(tmp, "/");
+                    }
+                }
             }
         }
+        else if (strcasecmp(line+p, "PERFORMER") == 0) {
+            fix_cue_argument(line+q);
 
-        // last track
-        if (cur_cue_track + 1 == last_cue_track &&
-            (cue_tracks[last_cue_track].index - time < 500 ||
-             time > cue_tracks[last_cue_track].index) ){ // may not happen. for safety.
-            if(!real_ip->output->buffer_playing()) {
-                gint pos = aud_playlist_get_position(playlist);
-                if (pos + 1 == aud_playlist_get_length(playlist)) {
-                    AUDDBG("i: watchdog eof reached\n\n");
-                    if(aud_cfg->repeat) {
-                        static gint incr = 0;
-                        incr = -pos;
-                        g_idle_add_full(G_PRIORITY_HIGH , do_setpos, &incr, NULL);
-                        continue;
-                    }
-                    else {
-                        g_idle_add_full(G_PRIORITY_HIGH, do_stop, (void *)real_ip, NULL);
-                        continue;
-                    }
+            if (last_cue_track == 0)
+                cue_performer = aud_str_to_utf8(line + q);
+            else
+                cue_tracks[last_cue_track - 1].performer = aud_str_to_utf8(line + q);
+        }
+        else if (strcasecmp(line+p, "FILE") == 0) {
+            gchar *tmp = g_path_get_dirname(f);
+            fix_cue_argument(line+q);
+            cue_file = g_strdup_printf("%s/%s", tmp, line+q);
+            g_free(tmp);
+        }
+        else if (strcasecmp(line+p, "TITLE") == 0) {
+            fix_cue_argument(line+q);
+            if (last_cue_track == 0)
+                cue_title = aud_str_to_utf8(line + q);
+            else
+                cue_tracks[last_cue_track-1].title = aud_str_to_utf8(line + q);
+        }
+        else if (strcasecmp(line+p, "TRACK") == 0) {
+            gint track;
+
+            fix_cue_argument(line+q);
+            for (p = q; line[p] && isdigit((int) line[p]); p++);
+            line[p] = '\0';
+            for (; line[q] && line[q] == '0'; q++);
+            if (!line[q])
+                continue;
+            track = atoi(line+q);
+            if (track >= MAX_CUE_TRACKS)
+                continue;
+            last_cue_track = track;
+            cue_tracks[last_cue_track-1].index = 0;
+            cue_tracks[last_cue_track-1].performer = NULL;
+            cue_tracks[last_cue_track-1].title = NULL;
+        }
+        else if (strcasecmp(line+p, "INDEX") == 0) {
+            gint min, sec, frac;
+            for (p = q; line[p] && !isspace((int) line[p]); p++);
+            if (!line[p])
+                continue;
+            line[p] = '\0';
+            for (p++; line[p] && isspace((int) line[p]); p++);
+            if(strcasecmp(line+q, "01") == 0) {
+                fix_cue_argument(line+p);
+                if(sscanf(line+p, "%d:%d:%d", &min, &sec, &frac) == 3) {
+                    cue_tracks[last_cue_track-1].index =
+                        min * 60000 + sec * 1000 + frac * 1000 / 75;
                 }
-                else {
-                    if(aud_cfg->stopaftersong) {
-                        g_idle_add_full(G_PRIORITY_HIGH, do_stop, (void *)real_ip, NULL);
-                        continue;
-                    }
-                    AUDDBG("i: watchdog end of cue, advance in playlist\n\n");
-                    static gint incr = 1;
-                    g_idle_add_full(G_PRIORITY_HIGH , do_setpos, &incr, NULL);
-                    continue;
+            }
+            else if(strcasecmp(line+q, "00") == 0) {
+                if(sscanf(line+p, "%d:%d:%d", &min, &sec, &frac) == 3) {
+                    gint index0 = min * 60000 + sec * 1000 + frac * 10;
+                    cue_tracks[last_cue_track-2].duration =
+                        index0 - cue_tracks[last_cue_track-2].index;
                 }
             }
         }
     }
-    AUDDBG("e: watchdog\n");
-    return NULL; // dummy.
-}
 
-/******************************************************** cuefile */
-
-static void free_cue_info(void)
-{
-	g_free(cue_file);	cue_file = NULL;
-	g_free(cue_title);	cue_title = NULL;
-	g_free(cue_performer);	cue_performer = NULL;
-	g_free(cue_genre);	cue_genre = NULL;
-	g_free(cue_year); 	cue_year = NULL;
-	g_free(cue_track);	cue_track = NULL;
-
-	for (; last_cue_track > 0; last_cue_track--) {
-		g_free(cue_tracks[last_cue_track-1].performer);
-		cue_tracks[last_cue_track-1].performer = NULL;
-		g_free(cue_tracks[last_cue_track-1].title);
-		cue_tracks[last_cue_track-1].title = NULL;
-	}
-	AUDDBG("free_cue_info: last_cue_track = %d\n", last_cue_track);
-	last_cue_track = 0;
+    aud_vfs_fclose(file);
 }
 
-static void cache_cue_file(char *f)
+void
+fix_cue_argument(char *line)
 {
-	VFSFile *file = aud_vfs_fopen(f, "rb");
-	gchar line[MAX_CUE_LINE_LENGTH+1];
-
-	if(!file)
-		return;
-	
-	while (TRUE) {
-		gint p;
-		gint q;
-
-		if (aud_vfs_fgets(line, MAX_CUE_LINE_LENGTH+1, file) == NULL) {
-			aud_vfs_fclose(file);
-			return;
-        }
-
-		for (p = 0; line[p] && isspace((int) line[p]); p++);
-		if (!line[p])
-			continue;
-		for (q = p; line[q] && !isspace((int) line[q]); q++);
-		if (!line[q])
-			continue;
-		line[q] = '\0';
-		for (q++; line[q] && isspace((int) line[q]); q++);
-		if (strcasecmp(line+p, "REM") == 0) {
-			for (p = q; line[p] && !isspace((int) line[p]); p++);
-			if (!line[p])
-				continue;
-			line[p] = '\0';
-			for (p++; line[p] && isspace((int) line[p]); p++);
-			if(strcasecmp(line+q, "GENRE") == 0) {
-				fix_cue_argument(line+p);
-				if (last_cue_track == 0)
-					cue_genre = aud_str_to_utf8(line + p);
-			}
-			if(strcasecmp(line+q, "DATE") == 0) {
-				gchar *tmp;
-				fix_cue_argument(line+p);
-				if (last_cue_track == 0) {
-					tmp = g_strdup(line + p);
-					if (tmp) {
-						cue_year = strtok(tmp, "/"); // XXX: always in the same format?
-					}
-				}
-			}
-		}
-		else if (strcasecmp(line+p, "PERFORMER") == 0) {
-			fix_cue_argument(line+q);
-
-			if (last_cue_track == 0)
-				cue_performer = aud_str_to_utf8(line + q);
-			else
-				cue_tracks[last_cue_track - 1].performer = aud_str_to_utf8(line + q);
-		}
-		else if (strcasecmp(line+p, "FILE") == 0) {
-			gchar *tmp = g_path_get_dirname(f);
-			fix_cue_argument(line+q);
-			cue_file = g_strdup_printf("%s/%s", tmp, line+q); //XXX need to check encoding?
-			g_free(tmp);
-		}
-		else if (strcasecmp(line+p, "TITLE") == 0) {
-			fix_cue_argument(line+q);
-			if (last_cue_track == 0)
-				cue_title = aud_str_to_utf8(line + q);
-			else
-				cue_tracks[last_cue_track-1].title = aud_str_to_utf8(line + q);
-		}
-		else if (strcasecmp(line+p, "TRACK") == 0) {
-			gint track;
-
-			fix_cue_argument(line+q);
-			for (p = q; line[p] && isdigit((int) line[p]); p++);
-			line[p] = '\0';
-			for (; line[q] && line[q] == '0'; q++);
-			if (!line[q])
-				continue;
-			track = atoi(line+q);
-			if (track >= MAX_CUE_TRACKS)
-				continue;
-			last_cue_track = track;
-			cue_tracks[last_cue_track-1].index = 0;
-			cue_tracks[last_cue_track-1].performer = NULL;
-			cue_tracks[last_cue_track-1].title = NULL;
-		}
-		else if (strcasecmp(line+p, "INDEX") == 0) {
-            gint min, sec, frac;
-			for (p = q; line[p] && !isspace((int) line[p]); p++);
-			if (!line[p])
-				continue;
-			for (p++; line[p] && isspace((int) line[p]); p++);
-			for (q = p; line[q] && !isspace((int) line[q]); q++);
-
-            if(sscanf(line+p, "%d:%d:%d", &min, &sec, &frac) == 3) {
-                cue_tracks[last_cue_track-1].index = min * 60000 + sec * 1000 + frac * 1000 / 75;
+    if (line[0] == '"') {
+        gchar *l2;
+        for (l2 = line+1; *l2 && *l2 != '"'; l2++)
+            *(l2-1) = *l2;
+        *(l2-1) = *l2;
+        for (; *line && *line != '"'; line++) {
+            if (*line == '\\' && *(line+1)) {
+                for (l2 = line+1; *l2 && *l2 != '"'; l2++)
+                    *(l2-1) = *l2;
+                *(l2-1) = *l2;
             }
         }
+        *line = '\0';
     }
-
-	aud_vfs_fclose(file);
+    else {
+        for (; *line && *line != '\r' && *line != '\n'; line++);
+        *line = '\0';
+    }
 }
-
-static void fix_cue_argument(char *line)
-{
-	if (line[0] == '"') {
-		gchar *l2;
-		for (l2 = line+1; *l2 && *l2 != '"'; l2++)
-				*(l2-1) = *l2;
-			*(l2-1) = *l2;
-		for (; *line && *line != '"'; line++) {
-			if (*line == '\\' && *(line+1)) {
-				for (l2 = line+1; *l2 && *l2 != '"'; l2++)
-					*(l2-1) = *l2;
-				*(l2-1) = *l2;
-			}
-		}
-		*line = '\0';
-	}
-	else {
-		for (; *line && *line != '\r' && *line != '\n'; line++);
-		*line = '\0';
-	}
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cue/cuesheet.h	Sat Oct 11 20:03:01 2008 +0900
@@ -0,0 +1,64 @@
+#ifndef _CUE_H_
+#define _CUE_H_
+
+#include "config.h"
+
+#define AUD_DEBUG 1
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <audacious/plugin.h>
+#include <audacious/output.h>
+
+#define MAX_CUE_LINE_LENGTH 1000
+#define MAX_CUE_TRACKS 1000
+
+extern GMutex *cue_mutex;
+extern GCond *cue_cond;
+
+typedef enum {
+    STOP = 0,
+    RUN,
+    EXIT
+} watchdog_state_t;
+
+extern watchdog_state_t watchdog_state;
+
+extern gint last_cue_track;
+extern gint cur_cue_track;
+extern gulong target_time;
+extern GMutex *cue_target_time_mutex;
+
+typedef struct cue_tracks {
+    gchar *title;
+    gchar *performer;
+    gint index;
+    gint duration;
+} cue_tracks_t;
+
+extern cue_tracks_t cue_tracks[];
+extern gint finetune_seek;
+extern InputPlayback *real_ip;
+extern InputPlayback *caller_ip;
+
+/* prototypes */
+void cache_cue_file(gchar *f);
+void free_cue_info(void);
+void fix_cue_argument(char *line);
+int is_our_file(gchar *filename);
+void play(InputPlayback *data);
+void play_cue_uri(InputPlayback *data, gchar *uri);
+void mseek(InputPlayback *data, gulong time);
+void seek(InputPlayback *data, gint time);
+void stop(InputPlayback *data);
+void cue_pause(InputPlayback *data, short);
+Tuple *get_song_tuple(gchar *uri);
+void cue_init(void);
+gint get_time(InputPlayback *playback);
+void cue_cleanup(void);
+gpointer watchdog_func(gpointer data);
+Tuple *probe_for_tuple(gchar *songFilename, VFSFile *fd);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cue/watchdog.c	Sat Oct 11 20:03:01 2008 +0900
@@ -0,0 +1,166 @@
+#include "cuesheet.h"
+
+gboolean
+do_stop(gpointer data)
+{
+    AUDDBG("f: do_stop\n");
+    audacious_drct_stop();
+    AUDDBG("e: do_stop\n");
+
+    return FALSE; /* only once */
+}
+
+gboolean
+do_setpos(gpointer data)
+{
+    Playlist *playlist = aud_playlist_get_active();
+    gint pos = aud_playlist_get_position_nolock(playlist);
+    gint incr = *(gint *)data;
+
+    /* mpris needs state change */
+    audacious_drct_stop();
+
+    pos = pos + incr;
+    if(pos < 0)
+        pos = 0;
+
+    AUDDBG("do_setpos: pos = %d\n\n", pos);
+
+    if (!playlist)
+        return FALSE;
+
+    /* being done from the main loop thread, does not require locks */
+    aud_playlist_set_position(playlist, (guint)pos);
+
+    /* mpris needs state change */
+    audacious_drct_play();
+
+    return FALSE; /* only once */
+}
+
+gpointer
+watchdog_func(gpointer data)
+{
+    gint time = 0;
+    Playlist *playlist = NULL;
+    GTimeVal sleep_time;
+
+    AUDDBG("enter\n");
+
+    while(1) {
+#if 0
+        AUDDBG("time = %d cur = %d cidx = %d nidx = %d last = %d\n",
+               time,
+               cur_cue_track,
+               cue_tracks[cur_cue_track].index,
+               cue_tracks[cur_cue_track+1].index,
+               last_cue_track);
+#endif
+        g_get_current_time(&sleep_time);
+        g_time_val_add(&sleep_time, 500000); /* interval is 0.5sec. */
+
+        g_mutex_lock(cue_mutex);
+        switch(watchdog_state) {
+        case EXIT:
+            AUDDBG("e: watchdog exit\n");
+            g_mutex_unlock(cue_mutex); /* stop() will lock cue_mutex */
+            stop(real_ip); /* no need to care real_ip != NULL here. */
+            g_thread_exit(NULL);
+            break;
+        case RUN:
+            if(!playlist)
+                playlist = aud_playlist_get_active();
+            g_cond_timed_wait(cue_cond, cue_mutex, &sleep_time);
+            break;
+        case STOP:
+            AUDDBG("watchdog deactivated\n");
+            g_cond_wait(cue_cond, cue_mutex);
+            playlist = aud_playlist_get_active();
+            break;
+        }
+        g_mutex_unlock(cue_mutex);
+
+        if(watchdog_state != RUN)
+            continue;
+
+        /* get raw time */
+        time = real_ip->output->output_time();
+
+#if 0
+        AUDDBG("time = %d target_time = %lu durattion = %d\n",
+               time,
+               target_time,
+               cue_tracks[cur_cue_track].duration);
+#endif
+
+        if(time == 0 || time <= target_time)
+            continue;
+
+        /* next track */
+        if(time >= cue_tracks[cur_cue_track].index +
+           cue_tracks[cur_cue_track].duration)
+        {
+            static gint incr = 0;
+            AUDDBG("i: watchdog next\n");
+            AUDDBG("time = %d cur = %d cidx = %d nidx = %d last = %d lidx = %d\n",
+                   time,
+                   cur_cue_track,
+                   cue_tracks[cur_cue_track].index,
+                   cue_tracks[cur_cue_track+1].index,
+                   last_cue_track,
+                   cue_tracks[last_cue_track].index);
+
+            incr = 1; /* is this ok? */
+
+            if(aud_cfg->stopaftersong) {
+                g_idle_add_full(G_PRIORITY_HIGH, do_stop, (void *)real_ip, NULL);
+                continue;
+            }
+            else {
+                g_idle_add_full(G_PRIORITY_HIGH , do_setpos, &incr, NULL);
+                continue;
+            }
+        }
+
+        /* last track */
+        if (cur_cue_track + 1 == last_cue_track &&
+            (cue_tracks[last_cue_track].index - time < 500 ||
+             time > cue_tracks[last_cue_track].index) ){
+            AUDDBG("last track\n");
+            gint pos = aud_playlist_get_position(playlist);
+            if (pos + 1 == aud_playlist_get_length(playlist)) {
+
+                AUDDBG("i: watchdog eof reached\n\n");
+
+                if(aud_cfg->repeat) {
+                    static gint incr = 0;
+                    incr = -pos;
+                    g_idle_add_full(G_PRIORITY_HIGH , do_setpos, &incr, NULL);
+                    continue;
+                }
+                else {
+                    g_idle_add_full(G_PRIORITY_HIGH, do_stop,
+                                    (void *)real_ip, NULL);
+                    continue;
+                }
+            }
+            else {
+                if(aud_cfg->stopaftersong) {
+                    g_idle_add_full(G_PRIORITY_HIGH, do_stop,
+                                    (void *)real_ip, NULL);
+                    continue;
+                }
+
+                AUDDBG("i: watchdog end of cue, advance in playlist\n\n");
+
+                static gint incr = 1;
+                g_idle_add_full(G_PRIORITY_HIGH , do_setpos, &incr, NULL);
+                continue;
+            }
+        }
+    }
+
+    AUDDBG("e: watchdog\n");
+
+    return NULL; /* dummy */
+}