diff src/crossfade/cfgutil.c @ 3059:2e241e90494a

Import work in progress xmms-crossfade rewrite.
author William Pitcock <nenolod@atheme.org>
date Fri, 24 Apr 2009 05:57:35 -0500
parents
children 43a336a7791b
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/crossfade/cfgutil.c	Fri Apr 24 05:57:35 2009 -0500
@@ -0,0 +1,752 @@
+/* 
+ *  XMMS Crossfade Plugin
+ *  Copyright (C) 2000-2007  Peter Eisenlohr <peter@eisenlohr.org>
+ *
+ *  based on the original OSS Output Plugin
+ *  Copyright (C) 1998-2000  Peter Alm, Mikael Alm, Olle Hallnas, Thomas Nilsson and 4Front Technologies
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ *  USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#undef PRESET_SUPPORT
+
+#include "crossfade.h"
+#include "configure.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#  define ConfigFile ConfigDb
+#  define xmms_cfg_read_int           aud_cfg_db_get_int
+#  define xmms_cfg_read_string        aud_cfg_db_get_string
+#  define xmms_cfg_read_boolean       aud_cfg_db_get_bool
+#  define xmms_cfg_write_int          aud_cfg_db_set_int
+#  define xmms_cfg_write_string       aud_cfg_db_set_string
+#  define xmms_cfg_write_boolean      aud_cfg_db_set_bool
+#  define xmms_cfg_remove_key         aud_cfg_db_unset_key
+#  define xmms_cfg_open_default_file  aud_cfg_db_open
+#  define xmms_cfg_write_default_file aud_cfg_db_close
+void xmms_cfg_dummy(ConfigDb *dummy) { }
+#  define xmms_cfg_free xmms_cfg_dummy
+
+/* init with DEFAULT_CFG to make sure all string pointers are set to NULL */
+config_t _xfg = CONFIG_DEFAULT;  /* also used in configure.c */
+static config_t *xfg = &_xfg;
+
+/*****************************************************************************/
+
+static void
+update_plugin_config(gchar **config_string, gchar *name, plugin_config_t *pc, gboolean save);
+
+static void
+g_free_f(gpointer data, gpointer user_data)
+{
+	g_free(data);
+}
+        
+/*****************************************************************************/
+
+static gchar *strip(gchar *s)
+{
+	gchar *p;
+	if (!s)
+		return NULL;
+
+	for (; *s == ' '; s++);
+	if (!*s)
+		return s;
+
+	for (p = s + strlen(s) - 1; *p == ' '; p--);
+	*++p = 0;
+	return s;
+}
+
+static void
+update_plugin_config(gchar **config_string, gchar *name, plugin_config_t *pc, gboolean save)
+{
+	plugin_config_t default_pc = DEFAULT_OP_CONFIG;
+
+	gchar *buffer = NULL;
+	gchar out[1024];
+
+	gboolean plugin_found = FALSE;
+	gchar *plugin, *next_plugin;
+	gchar *args;
+
+	if (pc && !save)
+		*pc = default_pc;
+
+	if (!config_string || !*config_string || !name || !pc)
+	{
+		DEBUG(("[crossfade] update_plugin_config: missing arg!\n"));
+		return;
+	}
+
+	buffer = g_strdup(*config_string);
+	out[0] = 0;
+
+	for (plugin = buffer; plugin; plugin = next_plugin)
+	{
+		if ((next_plugin = strchr(plugin, ';')))
+			*next_plugin++ = 0;
+
+		if ((args = strchr(plugin, '=')))
+			*args++ = 0;
+
+		plugin = strip(plugin);
+		if (!*plugin || !args || !*args)
+			continue;
+
+		if (save)
+		{
+			if (strcmp(plugin, name) == 0)
+				continue;
+				
+			if (*out)
+				strcat(out, "; ");
+				
+			strcat(out, plugin);
+			strcat(out, "=");
+			strcat(out, args);
+			continue;
+		}
+		else if (strcmp(plugin, name))
+			continue;
+
+		args = strip(args);
+		sscanf(args, "%d,%d,%d,%d", &pc->throttle_enable, &pc->max_write_enable, &pc->max_write_len, &pc->force_reopen);
+		pc->max_write_len &= -4;
+		plugin_found = TRUE;
+	}
+
+	if (save)
+	{
+		/* only save if settings differ from defaults */
+		if ((pc->throttle_enable  != default_pc.throttle_enable)
+		 || (pc->max_write_enable != default_pc.max_write_enable)
+		 || (pc->max_write_len    != default_pc.max_write_len)
+		 || (pc->force_reopen     != default_pc.force_reopen))
+		{
+			if (*out)
+				strcat(out, "; ");
+
+			sprintf(out + strlen(out), "%s=%d,%d,%d,%d", name,
+				pc->throttle_enable ? 1 : 0, pc->max_write_enable ? 1 : 0, pc->max_write_len, pc->force_reopen);
+		}
+		if (*config_string)
+			g_free(*config_string);
+
+		*config_string = g_strdup(out);
+	}
+
+	g_free(buffer);
+}
+
+/*****************************************************************************/
+
+#ifdef PRESET_SUPPORT
+static void
+scan_presets(gchar *filename)
+{
+	struct stat stats;
+	FILE *fh;
+	gchar *data, **lines, *tmp, *name;
+	int i;
+
+	if (lstat(filename, &stats))
+	{
+		DEBUG(("[crossfade] scan_presets: \"%s\":\n", filename));
+		PERROR("[crossfade] scan_presets: lstat");
+		return;
+	}
+	if (stats.st_size <= 0)
+		return;
+
+	if (!(data = g_malloc(stats.st_size + 1)))
+	{
+		DEBUG(("[crossfade] scan_presets: g_malloc(%ld) failed!\n", stats.st_size));
+		return;
+	}
+
+	if (!(fh = fopen(filename, "r")))
+	{
+		PERROR("[crossfade] scan_presets: fopen");
+		g_free(data);
+		return;
+	}
+
+	if (fread(data, stats.st_size, 1, fh) != 1)
+	{
+		DEBUG(("[crossfade] scan_presets: fread() failed!\n"));
+		g_free(data);
+		fclose(fh);
+		return;
+	}
+	fclose(fh);
+	data[stats.st_size] = 0;
+
+	lines = g_strsplit(data, "\n", 0);
+	g_free(data);
+
+	if (!lines)
+	{
+		DEBUG(("[crossfade] scan_presets: g_strsplit() failed!\n"));
+		return;
+	}
+
+	g_list_foreach(config->presets, g_free_f, NULL);
+	g_list_free(config->presets);
+	config->presets = NULL;
+
+	for (i = 0; lines[i]; i++)
+	{
+		if (lines[i][0] == '[')
+		{
+			if ((tmp = strchr(lines[i], ']')))
+			{
+				*tmp = 0;
+				if ((name = g_strdup(lines[i] + 1)))
+					config->presets = g_list_append(config->presets, name);
+			}
+		}
+	}
+
+	g_strfreev(lines);
+}
+#endif
+
+static void
+read_fade_config(ConfigFile *cfgfile, gchar *section, gchar *key, fade_config_t *fc)
+{
+	gchar *s = NULL;
+	gint n;
+
+	if (!cfgfile || !section || !key || !fc)
+		return;
+
+	xmms_cfg_read_string(cfgfile, section, key, &s);
+	if (!s)
+		return;
+
+	n = sscanf(s,
+		   "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
+		   &fc->type,
+		   &fc->pause_len_ms,
+		   &fc->simple_len_ms,
+		   &fc->out_enable,
+		   &fc->out_len_ms,
+		   &fc->out_volume,
+		   &fc->ofs_type,
+		   &fc->ofs_type_wanted,
+		   &fc->ofs_custom_ms,
+		   &fc->in_locked,
+		   &fc->in_enable,
+		   &fc->in_len_ms,
+		   &fc->in_volume,
+		   &fc->flush_pause_enable,
+		   &fc->flush_pause_len_ms, &fc->flush_in_enable, &fc->flush_in_len_ms, &fc->flush_in_volume);
+
+	g_free(s);
+}
+
+static void
+write_fade_config(ConfigFile *cfgfile, gchar *section, gchar *key, fade_config_t *fc)
+{
+	gchar *s;
+
+	if (!cfgfile || !section || !key || !fc)
+		return;
+
+	s = g_strdup_printf("%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
+			    fc->type,
+			    fc->pause_len_ms,
+			    fc->simple_len_ms,
+			    fc->out_enable,
+			    fc->out_len_ms,
+			    fc->out_volume,
+			    fc->ofs_type,
+			    fc->ofs_type_wanted,
+			    fc->ofs_custom_ms,
+			    fc->in_locked,
+			    fc->in_enable,
+			    fc->in_len_ms,
+			    fc->in_volume,
+			    fc->flush_pause_enable,
+			    fc->flush_pause_len_ms, fc->flush_in_enable, fc->flush_in_len_ms, fc->flush_in_volume);
+
+	if (!s)
+		return;
+
+	xmms_cfg_write_string(cfgfile, section, key, s);
+	g_free(s);
+}
+
+void
+xfade_load_config()
+{
+#ifdef PRESET_SUPPORT
+	gchar *filename;
+#endif
+	gchar *section = "Crossfade";
+	ConfigFile *cfgfile;
+
+	if ((cfgfile = xmms_cfg_open_default_file()))
+	{
+		/* *INDENT-OFF* */
+		/* config items used in v0.1 */
+		xmms_cfg_read_int    (cfgfile, section, "output_method",        &config->output_method);
+		xmms_cfg_read_int    (cfgfile, section, "audio_device",         &config->oss_audio_device);
+		xmms_cfg_read_boolean(cfgfile, section, "use_alt_audio_device", &config->oss_use_alt_audio_device);
+		xmms_cfg_read_string (cfgfile, section, "alt_audio_device",     &config->oss_alt_audio_device);
+		xmms_cfg_read_int    (cfgfile, section, "mixer_device",         &config->oss_mixer_device);
+		xmms_cfg_read_string (cfgfile, section, "output_plugin",        &config->op_name);
+		xmms_cfg_read_string (cfgfile, section, "op_config_string",     &config->op_config_string);
+		xmms_cfg_read_int    (cfgfile, section, "buffer_size",          &config->mix_size_ms);
+		xmms_cfg_read_int    (cfgfile, section, "sync_size",            &config->sync_size_ms);
+		xmms_cfg_read_int    (cfgfile, section, "preload_size",         &config->preload_size_ms);
+		xmms_cfg_read_int    (cfgfile, section, "songchange_timeout",   &config->songchange_timeout);
+		xmms_cfg_read_boolean(cfgfile, section, "enable_mixer",         &config->enable_mixer);
+		xmms_cfg_read_boolean(cfgfile, section, "mixer_reverse",        &config->mixer_reverse);
+		xmms_cfg_read_boolean(cfgfile, section, "enable_debug",         &config->enable_debug);
+		xmms_cfg_read_boolean(cfgfile, section, "enable_monitor",       &config->enable_monitor);
+
+		/* config items introduced by v0.2 */
+		xmms_cfg_read_int    (cfgfile, section, "oss_buffer_size",      &config->oss_buffer_size_ms);
+		xmms_cfg_read_int    (cfgfile, section, "oss_preload_size",     &config->oss_preload_size_ms);
+		xmms_cfg_read_boolean(cfgfile, section, "oss_mixer_use_master", &config->oss_mixer_use_master);
+		xmms_cfg_read_boolean(cfgfile, section, "gap_lead_enable",      &config->gap_lead_enable);
+		xmms_cfg_read_int    (cfgfile, section, "gap_lead_len_ms",      &config->gap_lead_len_ms);
+		xmms_cfg_read_int    (cfgfile, section, "gap_lead_level",       &config->gap_lead_level);
+		xmms_cfg_read_boolean(cfgfile, section, "gap_trail_enable",     &config->gap_trail_enable);
+		xmms_cfg_read_int    (cfgfile, section, "gap_trail_len_ms",     &config->gap_trail_len_ms);
+		xmms_cfg_read_int    (cfgfile, section, "gap_trail_level",      &config->gap_trail_level);
+		xmms_cfg_read_int    (cfgfile, section, "gap_trail_locked",     &config->gap_trail_locked);
+
+		/* config items introduced by v0.2.1 */
+		xmms_cfg_read_boolean(cfgfile, section, "buffer_size_auto",     &config->mix_size_auto);
+
+		/* config items introduced by v0.2.3 */
+		xmms_cfg_read_boolean(cfgfile, section, "album_detection",      &config->album_detection);
+
+		/* config items introduced by v0.2.4 */
+		xmms_cfg_read_boolean(cfgfile, section, "http_workaround",      &config->enable_http_workaround);
+		xmms_cfg_read_boolean(cfgfile, section, "enable_op_max_used",   &config->enable_op_max_used);
+		xmms_cfg_read_int    (cfgfile, section, "op_max_used_ms",       &config->op_max_used_ms);
+
+		/* config items introduced by v0.2.6 */
+		xmms_cfg_read_string (cfgfile, section, "effect_plugin",        &config->ep_name);
+		xmms_cfg_read_boolean(cfgfile, section, "effect_enable",        &config->ep_enable);
+		xmms_cfg_read_int    (cfgfile, section, "output_rate",          &config->output_rate);
+
+		/* config items introduced by v0.2.7 */
+		xmms_cfg_read_boolean(cfgfile, section, "oss_maxbuf_enable",    &config->oss_maxbuf_enable);
+
+		/* config items introduced by v0.3.0 */
+		xmms_cfg_read_boolean(cfgfile, section, "use_alt_mixer_device", &config->oss_use_alt_mixer_device);
+		xmms_cfg_read_int    (cfgfile, section, "oss_fragments",        &config->oss_fragments);
+		xmms_cfg_read_int    (cfgfile, section, "oss_fragment_size",    &config->oss_fragment_size);
+		xmms_cfg_read_boolean(cfgfile, section, "volnorm_enable",       &config->volnorm_enable);
+		xmms_cfg_read_boolean(cfgfile, section, "volnorm_use_qa",       &config->volnorm_use_qa);
+		xmms_cfg_read_int    (cfgfile, section, "volnorm_target",       &config->volnorm_target);
+		xmms_cfg_read_boolean(cfgfile, section, "output_keep_opened",   &config->output_keep_opened);
+		xmms_cfg_read_boolean(cfgfile, section, "mixer_software",       &config->mixer_software);
+		xmms_cfg_read_int    (cfgfile, section, "mixer_vol_left",       &config->mixer_vol_left);
+		xmms_cfg_read_int    (cfgfile, section, "mixer_vol_right",      &config->mixer_vol_right);
+
+		/* config items introduced by v0.3.2 */
+		xmms_cfg_read_boolean(cfgfile, section, "no_xfade_if_same_file",&config->no_xfade_if_same_file);
+
+		/* config items introduced by v0.3.3 */
+		xmms_cfg_read_string (cfgfile, section, "alt_mixer_device",     &config->oss_alt_mixer_device);
+		xmms_cfg_read_boolean(cfgfile, section, "gap_crossing",         &config->gap_crossing);
+
+		/* config items introduced by v0.3.6 */
+		xmms_cfg_read_int    (cfgfile, section, "output_quality", &config->output_quality);
+
+		/* fade configs */
+		read_fade_config(cfgfile, section, "fc_xfade",  &config->fc[FADE_CONFIG_XFADE]);
+		read_fade_config(cfgfile, section, "fc_manual", &config->fc[FADE_CONFIG_MANUAL]);
+		read_fade_config(cfgfile, section, "fc_album",  &config->fc[FADE_CONFIG_ALBUM]);
+		read_fade_config(cfgfile, section, "fc_start",  &config->fc[FADE_CONFIG_START]);
+		read_fade_config(cfgfile, section, "fc_stop",   &config->fc[FADE_CONFIG_STOP]);
+		read_fade_config(cfgfile, section, "fc_eop",    &config->fc[FADE_CONFIG_EOP]);
+		read_fade_config(cfgfile, section, "fc_seek",   &config->fc[FADE_CONFIG_SEEK]);
+		read_fade_config(cfgfile, section, "fc_pause",  &config->fc[FADE_CONFIG_PAUSE]);
+
+		xmms_cfg_free(cfgfile);
+		DEBUG(("[crossfade] load_config: configuration loaded\n"));
+	}
+	else
+		DEBUG(("[crossfade] load_config: error loading config, using defaults\n"));
+
+#ifdef PRESET_SUPPORT
+#ifdef COMPILE_FOR_AUDACIOUS
+	filename = g_strconcat(g_get_home_dir(), "/.audacious/crossfade-presets", NULL);
+#else
+	filename = g_strconcat(g_get_home_dir(), "/.xmms/crossfade-presets", NULL);
+#endif
+	scan_presets(filename);
+	g_free(filename);
+#endif
+}
+
+void
+xfade_save_config()
+{
+	gchar *section = "Crossfade";
+	ConfigFile *cfgfile;
+
+	if ((cfgfile = xmms_cfg_open_default_file()))
+	{
+		/* obsolete config items */
+		xmms_cfg_remove_key(cfgfile, section, "underrun_pct");
+		xmms_cfg_remove_key(cfgfile, section, "enable_crossfade");
+		xmms_cfg_remove_key(cfgfile, section, "enable_gapkiller");
+		xmms_cfg_remove_key(cfgfile, section, "mixer_use_master");
+		xmms_cfg_remove_key(cfgfile, section, "late_effect");
+		xmms_cfg_remove_key(cfgfile, section, "gap_lead_length");
+
+		/* config items used in v0.1 */
+		xmms_cfg_write_int    (cfgfile, section, "output_method",        config->output_method);
+		xmms_cfg_write_int    (cfgfile, section, "audio_device",         config->oss_audio_device);
+		xmms_cfg_write_boolean(cfgfile, section, "use_alt_audio_device", config->oss_use_alt_audio_device);
+		xmms_cfg_write_string (cfgfile, section, "alt_audio_device",     config->oss_alt_audio_device ? config->oss_alt_audio_device : DEFAULT_OSS_ALT_AUDIO_DEVICE);
+		xmms_cfg_write_int    (cfgfile, section, "mixer_device",         config->oss_mixer_device);
+		xmms_cfg_write_string (cfgfile, section, "output_plugin",        config->op_name ? config->op_name : DEFAULT_OP_NAME);
+		xmms_cfg_write_string (cfgfile, section, "op_config_string",     config->op_config_string ? config->op_config_string : DEFAULT_OP_CONFIG_STRING);
+		xmms_cfg_write_int    (cfgfile, section, "buffer_size",          config->mix_size_ms);
+		xmms_cfg_write_int    (cfgfile, section, "sync_size",            config->sync_size_ms);
+		xmms_cfg_write_int    (cfgfile, section, "preload_size",         config->preload_size_ms);
+		xmms_cfg_write_int    (cfgfile, section, "songchange_timeout",   config->songchange_timeout);
+		xmms_cfg_write_boolean(cfgfile, section, "enable_mixer",         config->enable_mixer);
+		xmms_cfg_write_boolean(cfgfile, section, "mixer_reverse",        config->mixer_reverse);
+		xmms_cfg_write_boolean(cfgfile, section, "enable_debug",         config->enable_debug);
+		xmms_cfg_write_boolean(cfgfile, section, "enable_monitor",       config->enable_monitor);
+
+		/* config items introduced by v0.2 */
+		xmms_cfg_write_int    (cfgfile, section, "oss_buffer_size",      config->oss_buffer_size_ms);
+		xmms_cfg_write_int    (cfgfile, section, "oss_preload_size",     config->oss_preload_size_ms);
+		xmms_cfg_write_boolean(cfgfile, section, "oss_mixer_use_master", config->oss_mixer_use_master);
+		xmms_cfg_write_boolean(cfgfile, section, "gap_lead_enable",      config->gap_lead_enable);
+		xmms_cfg_write_int    (cfgfile, section, "gap_lead_len_ms",      config->gap_lead_len_ms);
+		xmms_cfg_write_int    (cfgfile, section, "gap_lead_level",       config->gap_lead_level);
+		xmms_cfg_write_boolean(cfgfile, section, "gap_trail_enable",     config->gap_trail_enable);
+		xmms_cfg_write_int    (cfgfile, section, "gap_trail_len_ms",     config->gap_trail_len_ms);
+		xmms_cfg_write_int    (cfgfile, section, "gap_trail_level",      config->gap_trail_level);
+		xmms_cfg_write_int    (cfgfile, section, "gap_trail_locked",     config->gap_trail_locked);
+
+		/* config items introduced by v0.2.1 */
+		xmms_cfg_write_boolean(cfgfile, section, "buffer_size_auto",     config->mix_size_auto);
+
+		/* config items introduced by v0.2.3 */
+		xmms_cfg_write_boolean(cfgfile, section, "album_detection",      config->album_detection);
+
+		/* config items introduced by v0.2.4 */
+		xmms_cfg_write_boolean(cfgfile, section, "http_workaround",      config->enable_http_workaround);
+		xmms_cfg_write_boolean(cfgfile, section, "enable_op_max_used",   config->enable_op_max_used);
+		xmms_cfg_write_int    (cfgfile, section, "op_max_used_ms",       config->op_max_used_ms);
+
+		/* config items introduced by v0.2.6 */
+		xmms_cfg_write_string (cfgfile, section, "effect_plugin",        config->ep_name ? config->ep_name : DEFAULT_EP_NAME);
+		xmms_cfg_write_boolean(cfgfile, section, "effect_enable",        config->ep_enable);
+		xmms_cfg_write_int    (cfgfile, section, "output_rate",          config->output_rate);
+
+		/* config items introduced by v0.2.7 */
+		xmms_cfg_write_boolean(cfgfile, section, "oss_maxbuf_enable",    config->oss_maxbuf_enable);
+
+		/* config items introduced by v0.3.0 */
+		xmms_cfg_write_boolean(cfgfile, section, "use_alt_mixer_device", config->oss_use_alt_mixer_device);
+		xmms_cfg_write_int    (cfgfile, section, "oss_fragments",        config->oss_fragments);
+		xmms_cfg_write_int    (cfgfile, section, "oss_fragment_size",    config->oss_fragment_size);
+#ifdef VOLUME_NORMALIZER
+		xmms_cfg_write_boolean(cfgfile, section, "volnorm_enable",       config->volnorm_enable);
+		xmms_cfg_write_boolean(cfgfile, section, "volnorm_use_qa",       config->volnorm_use_qa);
+		xmms_cfg_write_int    (cfgfile, section, "volnorm_target",       config->volnorm_target);
+#endif
+		xmms_cfg_write_boolean(cfgfile, section, "output_keep_opened",   config->output_keep_opened);
+		xmms_cfg_write_boolean(cfgfile, section, "mixer_software",       config->mixer_software);
+		xmms_cfg_write_int    (cfgfile, section, "mixer_vol_left",       config->mixer_vol_left);
+		xmms_cfg_write_int    (cfgfile, section, "mixer_vol_right",      config->mixer_vol_right);
+
+		/* config items introduced by v0.3.2 */
+		xmms_cfg_write_boolean(cfgfile, section, "no_xfade_if_same_file",config->no_xfade_if_same_file);
+
+		/* config items introduced by v0.3.2 */
+		xmms_cfg_write_string (cfgfile, section, "alt_mixer_device",     config->oss_alt_mixer_device ? config->oss_alt_mixer_device : DEFAULT_OSS_ALT_MIXER_DEVICE);
+		xmms_cfg_write_boolean(cfgfile, section, "gap_crossing",         config->gap_crossing);
+
+		/* config items introduced by v0.3.6 */
+		xmms_cfg_write_int(cfgfile, section, "output_quality",           config->output_quality);
+
+		/* fade configs */
+		write_fade_config(cfgfile, section, "fc_xfade",  &config->fc[FADE_CONFIG_XFADE]);
+		write_fade_config(cfgfile, section, "fc_manual", &config->fc[FADE_CONFIG_MANUAL]);
+		write_fade_config(cfgfile, section, "fc_album",  &config->fc[FADE_CONFIG_ALBUM]);
+		write_fade_config(cfgfile, section, "fc_start",  &config->fc[FADE_CONFIG_START]);
+		write_fade_config(cfgfile, section, "fc_stop",   &config->fc[FADE_CONFIG_STOP]);
+		write_fade_config(cfgfile, section, "fc_eop",    &config->fc[FADE_CONFIG_EOP]);
+		write_fade_config(cfgfile, section, "fc_seek",   &config->fc[FADE_CONFIG_SEEK]);
+		write_fade_config(cfgfile, section, "fc_pause",  &config->fc[FADE_CONFIG_PAUSE]);
+		/* *INDENT-ON* */
+		
+		xmms_cfg_write_default_file(cfgfile);
+		xmms_cfg_free(cfgfile);
+		DEBUG(("[crossfade] save_config: configuration saved\n"));
+	}
+	else
+		DEBUG(("[crossfade] save_config: error saving configuration!\n"));
+}
+
+#define SAFE_FREE(x) if(x) { g_free(x); x = NULL; }
+void
+xfade_free_config()
+{
+	SAFE_FREE(xfg->oss_alt_audio_device);
+	SAFE_FREE(xfg->oss_alt_mixer_device);
+	SAFE_FREE(xfg->op_config_string);
+	SAFE_FREE(xfg->op_name);
+
+	g_list_foreach(config->presets, g_free_f, NULL);
+	g_list_free(config->presets);
+	config->presets = NULL;
+}
+
+void
+xfade_load_plugin_config(gchar *config_string, gchar *plugin_name, plugin_config_t *plugin_config)
+{
+	update_plugin_config(&config_string, plugin_name, plugin_config, FALSE);
+}
+
+void
+xfade_save_plugin_config(gchar **config_string, gchar *plugin_name, plugin_config_t *plugin_config)
+{
+	update_plugin_config(config_string, plugin_name, plugin_config, TRUE);
+}
+
+/*** helpers *****************************************************************/
+
+gint
+xfade_cfg_out_skip(fade_config_t *fc)
+{
+	if (!fc)
+		return 0;
+		
+	switch (fc->config)
+	{
+		case FADE_CONFIG_TIMING:
+			return fc->out_skip_ms;
+	}
+	return 0;
+}
+
+gint
+xfade_cfg_fadeout_len(fade_config_t *fc)
+{
+	if (!fc)
+		return 0;
+		
+	switch (fc->type)
+	{
+		case FADE_TYPE_SIMPLE_XF:
+			return fc->simple_len_ms;
+			
+		case FADE_TYPE_ADVANCED_XF:
+			return fc->out_enable ? fc->out_len_ms : 0;
+			
+		case FADE_TYPE_FADEOUT:
+		case FADE_TYPE_PAUSE_ADV:
+			return fc->out_len_ms;
+	}
+	return 0;
+}
+
+gint
+xfade_cfg_fadeout_volume(fade_config_t *fc)
+{
+	gint volume;
+	if (!fc)
+		return 0;
+		
+	switch (fc->type)
+	{
+		case FADE_TYPE_ADVANCED_XF:
+		case FADE_TYPE_FADEOUT:
+			volume = fc->out_volume;
+			if (volume <   0) volume =   0;
+			if (volume > 100) volume = 100;
+			return volume;
+	}
+	return 0;
+}
+
+gint
+xfade_cfg_offset(fade_config_t *fc)
+{
+	if (!fc)
+		return 0;
+	
+	switch (fc->type)
+	{
+		case FADE_TYPE_FLUSH:
+			return fc->flush_pause_enable ? fc->flush_pause_len_ms : 0;
+			
+		case FADE_TYPE_PAUSE:
+			return fc->pause_len_ms;
+			
+		case FADE_TYPE_SIMPLE_XF:
+			return -fc->simple_len_ms;
+			
+		case FADE_TYPE_ADVANCED_XF:
+			switch (fc->ofs_type)
+			{
+				case FC_OFFSET_LOCK_OUT: return -fc->out_len_ms;
+				case FC_OFFSET_LOCK_IN:  return -fc->in_len_ms;
+				case FC_OFFSET_CUSTOM:   return  fc->ofs_custom_ms;
+			}
+			return 0;
+			
+		case FADE_TYPE_FADEOUT:
+		case FADE_TYPE_PAUSE_ADV:
+			return fc->ofs_custom_ms;
+	}
+	return 0;
+}
+
+gint
+xfade_cfg_in_skip(fade_config_t *fc)
+{
+	if (!fc)
+		return 0;
+		
+	switch (fc->config)
+	{
+		case FADE_CONFIG_TIMING:
+			return fc->in_skip_ms;
+	}
+	return 0;
+}
+
+gint
+xfade_cfg_fadein_len(fade_config_t *fc)
+{
+	if (!fc)
+		return 0;
+		
+	switch (fc->type)
+	{
+		case FADE_TYPE_FLUSH:
+			return fc->flush_in_enable ? fc->flush_in_len_ms : 0;
+			
+		case FADE_TYPE_SIMPLE_XF:
+			return fc->simple_len_ms;
+			
+		case FADE_TYPE_ADVANCED_XF:
+			return fc->in_locked
+			    ? (fc->out_enable ? fc->out_len_ms : 0)
+			    : (fc->in_enable  ? fc->in_len_ms  : 0);
+			
+		case FADE_TYPE_FADEIN:
+		case FADE_TYPE_PAUSE_ADV:
+			return fc->in_len_ms;
+	}
+	return 0;
+}
+
+gint
+xfade_cfg_fadein_volume(fade_config_t *fc)
+{
+	gint volume;
+	if (!fc)
+		return 0;
+
+	switch (fc->type)
+	{
+		case FADE_TYPE_FLUSH:
+			volume = fc->flush_in_volume;
+			break;
+			
+		case FADE_TYPE_ADVANCED_XF:
+			volume = fc->in_locked ? fc->out_volume : fc->in_volume;
+			break;
+			
+		case FADE_TYPE_FADEIN:
+			volume = fc->in_volume;
+			break;
+			
+		default:
+			volume = 0;
+	}
+
+	if (volume <   0) volume =   0;
+	if (volume > 100) volume = 100;
+	return volume;
+}
+
+gboolean
+xfade_cfg_gap_trail_enable(config_t *cfg)
+{
+	return xfg->gap_trail_locked ? xfg->gap_lead_enable : xfg->gap_trail_enable;
+}
+
+gint
+xfade_cfg_gap_trail_len(config_t *cfg)
+{
+	if (!xfade_cfg_gap_trail_enable(cfg))
+		return 0;
+
+	return xfg->gap_trail_locked ? xfg->gap_lead_len_ms : xfg->gap_trail_len_ms;
+}
+
+gint
+xfade_cfg_gap_trail_level(config_t *cfg)
+{
+	return xfg->gap_trail_locked ? xfg->gap_lead_level : xfg->gap_trail_level;
+}
+
+gint
+xfade_mix_size_ms(config_t *cfg)
+{
+	if (xfg->mix_size_auto)
+	{
+		gint i, min_size = 0;
+
+		for (i = 0; i < MAX_FADE_CONFIGS; i++)
+		{
+			gint size = xfade_cfg_fadeout_len(&xfg->fc[i]);
+			gint offset = xfade_cfg_offset(&xfg->fc[i]);
+
+			if (xfg->fc[i].type == FADE_TYPE_PAUSE_ADV)
+				size += xfade_cfg_fadein_len(&xfg->fc[i]);
+
+			if (size < -offset)
+				size = -offset;
+
+			if (size > min_size)
+				min_size = size;
+		}
+		return min_size += xfade_cfg_gap_trail_len(cfg) + xfg->songchange_timeout;
+	}
+	else
+		return xfg->mix_size_ms;
+}
+
+/*****************************************************************************/