view src/xs_config.c @ 957:0e60e5d56fdd

Change how the backend emulator library is initialized for libSIDPlay2 and FP, as it seems the engine configuration has some persistence despite reconfiguration between loaded files if same engine object is retained. This caused, for example, 2SID stereo tunes being played "mono" if played after a normal 1-SID tune. Duh.
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 20 Nov 2012 22:13:48 +0200
parents f8e1de328ac1
children 5711abf1ff39
line wrap: on
line source

/*  
   XMMS-SID - SIDPlay input plugin for X MultiMedia System (XMMS)

   Configuration dialog
   
   Programmed and designed by Matti 'ccr' Hamalainen <ccr@tnsp.org>
   (C) Copyright 1999-2009 Tecnic Software productions (TNSP)
   
   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.,
   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "xs_config.h"

#ifdef AUDACIOUS_PLUGIN
#include <audacious/plugin.h>
#define XS_CONFIG_FILE          mcs_handle_t
#define XS_CONFIG_OPEN          aud_cfg_db_open
#define XS_CONFIG_FREE          aud_cfg_db_close

#define XS_CFG_SET_STRING(q,z)  aud_cfg_db_set_string(cfg, XS_CONFIG_IDENT, q, z)
#define XS_CFG_SET_FLOAT(q,z)   aud_cfg_db_set_float(cfg, XS_CONFIG_IDENT, q, z)
#define XS_CFG_SET_INT(q,z)     aud_cfg_db_set_int(cfg, XS_CONFIG_IDENT, q, z)
#define XS_CFG_SET_BOOL(q,z)    aud_cfg_db_set_bool(cfg, XS_CONFIG_IDENT, q, z)
#define XS_CFG_GET_STRING(q,z)  aud_cfg_db_get_string(cfg, XS_CONFIG_IDENT, q, z)
#define XS_CFG_GET_FLOAT(q,z)   aud_cfg_db_get_float(cfg, XS_CONFIG_IDENT, q, z)
#define XS_CFG_GET_INT(q,z)     aud_cfg_db_get_int(cfg, XS_CONFIG_IDENT, q, z)
#define XS_CFG_GET_BOOL(q,z)    aud_cfg_db_get_bool(cfg, XS_CONFIG_IDENT, q, z)
#else
#include <xmms/configfile.h>
#define XS_CONFIG_FILE          ConfigFile
#define XS_CONFIG_OPEN          xmms_cfg_open_default_file
#define XS_CONFIG_FREE          xmms_cfg_free

#define XS_CFG_SET_STRING(q,z)  xmms_cfg_write_string(cfg, XS_CONFIG_IDENT, q, z)
#define XS_CFG_SET_FLOAT(q,z)   xmms_cfg_write_float(cfg, XS_CONFIG_IDENT, q, z)
#define XS_CFG_SET_INT(q,z)     xmms_cfg_write_int(cfg, XS_CONFIG_IDENT, q, z)
#define XS_CFG_SET_BOOL(q,z)    xmms_cfg_write_boolean(cfg, XS_CONFIG_IDENT, q, z)
#define XS_CFG_GET_STRING(q,z)  xmms_cfg_read_string(cfg, XS_CONFIG_IDENT, q, z)
#define XS_CFG_GET_FLOAT(q,z)   xmms_cfg_read_float(cfg, XS_CONFIG_IDENT, q, z)
#define XS_CFG_GET_INT(q,z)     xmms_cfg_read_int(cfg, XS_CONFIG_IDENT, q, z)
#define XS_CFG_GET_BOOL(q,z)    xmms_cfg_read_boolean(cfg, XS_CONFIG_IDENT, q, z)
#endif
#include "xs_support.h"
#include "xs_glade.h"
#include "xs_interface.h"
#include "xs_backend.h"

/*
 * Global widgets
 */
static GtkWidget *xs_configwin = NULL,
    *xs_sldb_fileselector = NULL,
    *xs_stil_fileselector = NULL,
    *xs_hvsc_selector = NULL,
    *xs_rom_selector = NULL,
    *xs_filt_importselector = NULL,
    *xs_filt_exportselector = NULL;


#define LUW(x)    lookup_widget(xs_configwin, x)


/* Samplerates
 */
static const gchar *xs_samplerates_table[] =
{
    "8000", "11025", "22050", 
    "44100", "48000", "64000",
    "96000"
};

static const gint xs_nsamplerates_table = sizeof(xs_samplerates_table) / sizeof(xs_samplerates_table[0]);

/*
 * Configuration specific stuff
 */
XS_MUTEX(xs_cfg);
struct xs_cfg_t xs_cfg;

static xs_cfg_item_t xs_cfgtable[] = {
{ CTYPE_INT,    &xs_cfg.audioBitsPerSample,     "audioBitsPerSample" },
{ CTYPE_INT,    &xs_cfg.audioChannels,          "audioChannels" },
{ CTYPE_INT,    &xs_cfg.audioFrequency,         "audioFrequency" },

{ CTYPE_BOOL,   &xs_cfg.mos8580,                "mos8580" },
{ CTYPE_BOOL,   &xs_cfg.forceModel,             "forceModel" },
{ CTYPE_BOOL,   &xs_cfg.emulateFilters,         "emulateFilters" },
{ CTYPE_FLOAT,  &xs_cfg.sid1Filter.fs,          "filterFs" },
{ CTYPE_FLOAT,  &xs_cfg.sid1Filter.fm,          "filterFm" },
{ CTYPE_FLOAT,  &xs_cfg.sid1Filter.ft,          "filterFt" },
{ CTYPE_INT,    &xs_cfg.memoryMode,             "memoryMode" },
{ CTYPE_INT,    &xs_cfg.clockSpeed,             "clockSpeed" },
{ CTYPE_BOOL,   &xs_cfg.forceSpeed,             "forceSpeed" },

{ CTYPE_INT,    &xs_cfg.playerEngine,           "playerEngine" },

{ CTYPE_INT,    &xs_cfg.sid2Builder,            "sid2Builder" },
{ CTYPE_INT,    &xs_cfg.sid2OptLevel,           "sid2OptLevel" },
{ CTYPE_INT,    &xs_cfg.sid2NFilterPresets,     "sid2NFilterPresets" },

{ CTYPE_INT,    &xs_cfg.residSampling,          "residSampling" },

{ CTYPE_BOOL,   &xs_cfg.playMaxTimeEnable,      "playMaxTimeEnable" },
{ CTYPE_BOOL,   &xs_cfg.playMaxTimeUnknown,     "playMaxTimeUnknown" },
{ CTYPE_INT,    &xs_cfg.playMaxTime,            "playMaxTime" },
{ CTYPE_BOOL,   &xs_cfg.playMinTimeEnable,      "playMinTimeEnable" },
{ CTYPE_INT,    &xs_cfg.playMinTime,            "playMinTime" },
{ CTYPE_BOOL,   &xs_cfg.songlenDBEnable,        "songlenDBEnable" },
{ CTYPE_STR,    &xs_cfg.songlenDBPath,          "songlenDBPath" },

{ CTYPE_BOOL,   &xs_cfg.stilDBEnable,           "stilDBEnable" },
{ CTYPE_STR,    &xs_cfg.stilDBPath,             "stilDBPath" },
{ CTYPE_STR,    &xs_cfg.hvscPath,               "hvscPath" },

{ CTYPE_STR,    &xs_cfg.romPath,                "romPath" },

#ifndef AUDACIOUS_PLUGIN
{ CTYPE_INT,    &xs_cfg.subsongControl,         "subsongControl" },
{ CTYPE_BOOL,   &xs_cfg.detectMagic,            "detectMagic" },
#endif

{ CTYPE_BOOL,   &xs_cfg.titleOverride,          "titleOverride" },
{ CTYPE_STR,    &xs_cfg.titleFormat,            "titleFormat" },

{ CTYPE_BOOL,   &xs_cfg.subAutoEnable,          "subAutoEnable" },
{ CTYPE_BOOL,   &xs_cfg.subAutoMinOnly,         "subAutoMinOnly" },
{ CTYPE_INT,    &xs_cfg.subAutoMinTime,         "subAutoMinTime" },
};

static const gint xs_ncfgtable = sizeof(xs_cfgtable) / sizeof(xs_cfgtable[0]);


static xs_wid_item_t xs_cfgitems[] = {
{ WTYPE_BGROUP, CTYPE_INT,      "cfg_res_16bit",        &xs_cfg.audioBitsPerSample,     XS_RES_16BIT },
{ WTYPE_BGROUP, CTYPE_INT,      "cfg_res_8bit",         &xs_cfg.audioBitsPerSample,     XS_RES_8BIT },
{ WTYPE_BGROUP, CTYPE_INT,      "cfg_chn_mono",         &xs_cfg.audioChannels,          XS_CHN_MONO },
{ WTYPE_BGROUP, CTYPE_INT,      "cfg_chn_stereo",       &xs_cfg.audioChannels,          XS_CHN_STEREO },
{ WTYPE_BGROUP, CTYPE_INT,      "cfg_chn_autopan",      &xs_cfg.audioChannels,          XS_CHN_AUTOPAN },
{ WTYPE_COMBO,  CTYPE_INT,      "cfg_samplerate",       &xs_cfg.audioFrequency,         XS_AUDIO_FREQ },

{ WTYPE_BGROUP, CTYPE_INT,      "cfg_emu_sidplay1",     &xs_cfg.playerEngine,           XS_ENG_SIDPLAY1 },
{ WTYPE_BGROUP, CTYPE_INT,      "cfg_emu_sidplay2",     &xs_cfg.playerEngine,           XS_ENG_SIDPLAY2 },
{ WTYPE_BGROUP, CTYPE_INT,      "cfg_emu_sidplayfp",    &xs_cfg.playerEngine,           XS_ENG_SIDPLAYFP },
{ WTYPE_BGROUP, CTYPE_INT,      "cfg_emu_mem_real",     &xs_cfg.memoryMode,             XS_MPU_REAL },
{ WTYPE_BGROUP, CTYPE_INT,      "cfg_emu_mem_banksw",   &xs_cfg.memoryMode,             XS_MPU_BANK_SWITCHING },
{ WTYPE_BGROUP, CTYPE_INT,      "cfg_emu_mem_transrom", &xs_cfg.memoryMode,             XS_MPU_TRANSPARENT_ROM },
{ WTYPE_BGROUP, CTYPE_INT,      "cfg_emu_mem_playsid",  &xs_cfg.memoryMode,             XS_MPU_PLAYSID_ENVIRONMENT },

{ WTYPE_BUTTON, CTYPE_BOOL,     "cfg_emu_mos8580",      &xs_cfg.mos8580,                0 },
{ WTYPE_BUTTON, CTYPE_BOOL,     "cfg_emu_sid_force",    &xs_cfg.forceModel,             0 },
{ WTYPE_BGROUP, CTYPE_INT,      "cfg_emu_clock_ntsc",   &xs_cfg.clockSpeed,             XS_CLOCK_NTSC },
{ WTYPE_BGROUP, CTYPE_INT,      "cfg_emu_clock_pal",    &xs_cfg.clockSpeed,             XS_CLOCK_PAL },
{ WTYPE_BUTTON, CTYPE_BOOL,     "cfg_emu_clock_force",  &xs_cfg.forceSpeed,             0 },
{ WTYPE_BUTTON, CTYPE_BOOL,     "cfg_emu_sp2_opt",      &xs_cfg.sid2OptLevel,           0 },

{ WTYPE_BGROUP, CTYPE_INT,      "cfg_emu_sp2_resid_fp", &xs_cfg.sid2Builder,            XS_BLD_RESID_FP },
{ WTYPE_BGROUP, CTYPE_INT,      "cfg_emu_sp2_resid",    &xs_cfg.sid2Builder,            XS_BLD_RESID },
{ WTYPE_BGROUP, CTYPE_INT,      "cfg_emu_sp2_hardsid",  &xs_cfg.sid2Builder,            XS_BLD_HARDSID },

{ WTYPE_BGROUP, CTYPE_INT,      "cfg_emu_resid_interpolate", &xs_cfg.residSampling,     XS_RESID_INTERPOLATE },
{ WTYPE_BGROUP, CTYPE_INT,      "cfg_emu_resid_resample_fir", &xs_cfg.residSampling,    XS_RESID_RESAMPLE_FIR },

{ WTYPE_TEXT,   CTYPE_STR,      "cfg_emu_rom_path",     &xs_cfg.romPath,                0 },

{ WTYPE_BUTTON, CTYPE_BOOL,     "cfg_emu_filters",      &xs_cfg.emulateFilters,         0 },
{ WTYPE_SCALE,  CTYPE_FLOAT,    "cfg_sp1_filter_fs",    &xs_cfg.sid1Filter.fs,          0 },
{ WTYPE_SCALE,  CTYPE_FLOAT,    "cfg_sp1_filter_fm",    &xs_cfg.sid1Filter.fm,          0 },
{ WTYPE_SCALE,  CTYPE_FLOAT,    "cfg_sp1_filter_ft",    &xs_cfg.sid1Filter.ft,          0 },

{ WTYPE_BUTTON, CTYPE_BOOL,     "cfg_maxtime_enable",   &xs_cfg.playMaxTimeEnable,      0 },
{ WTYPE_BUTTON, CTYPE_BOOL,     "cfg_maxtime_unknown",  &xs_cfg.playMaxTimeUnknown,     0 },
{ WTYPE_SPIN,   CTYPE_INT,      "cfg_maxtime",          &xs_cfg.playMaxTime,            0 },
{ WTYPE_BUTTON, CTYPE_BOOL,     "cfg_mintime_enable",   &xs_cfg.playMinTimeEnable,      0 },
{ WTYPE_SPIN,   CTYPE_INT,      "cfg_mintime",          &xs_cfg.playMinTime,            0 },
{ WTYPE_BUTTON, CTYPE_BOOL,     "cfg_sld_enable",       &xs_cfg.songlenDBEnable,        0 },
{ WTYPE_TEXT,   CTYPE_STR,      "cfg_sld_dbpath",       &xs_cfg.songlenDBPath,          0 },

{ WTYPE_BUTTON, CTYPE_BOOL,     "cfg_stil_enable",      &xs_cfg.stilDBEnable,           0 },
{ WTYPE_TEXT,   CTYPE_STR,      "cfg_stil_dbpath",      &xs_cfg.stilDBPath,             0 },
{ WTYPE_TEXT,   CTYPE_STR,      "cfg_hvsc_path",        &xs_cfg.hvscPath,               0 },


#ifndef AUDACIOUS_PLUGIN
{ WTYPE_BGROUP, CTYPE_INT,      "cfg_subctrl_none",     &xs_cfg.subsongControl,         XS_SSC_NONE },
{ WTYPE_BGROUP, CTYPE_INT,      "cfg_subctrl_seek",     &xs_cfg.subsongControl,         XS_SSC_SEEK },
{ WTYPE_BGROUP, CTYPE_INT,      "cfg_subctrl_popup",    &xs_cfg.subsongControl,         XS_SSC_POPUP },
{ WTYPE_BGROUP, CTYPE_INT,      "cfg_subctrl_patch",    &xs_cfg.subsongControl,         XS_SSC_PATCH },

{ WTYPE_BUTTON, CTYPE_BOOL,     "cfg_detectmagic",      &xs_cfg.detectMagic,            0 },
#endif

{ WTYPE_BUTTON, CTYPE_BOOL,     "cfg_ftitle_override",  &xs_cfg.titleOverride,          0 },
{ WTYPE_TEXT,   CTYPE_STR,      "cfg_ftitle_format",    &xs_cfg.titleFormat,            0 },

{ WTYPE_BUTTON, CTYPE_BOOL,     "cfg_subauto_enable",   &xs_cfg.subAutoEnable,          0 },
{ WTYPE_BUTTON, CTYPE_BOOL,     "cfg_subauto_min_only", &xs_cfg.subAutoMinOnly,         0 },
{ WTYPE_SPIN,   CTYPE_INT,      "cfg_subauto_mintime",  &xs_cfg.subAutoMinTime,         0 },
};

static const gint xs_nwidtable = sizeof(xs_cfgitems) / sizeof(xs_cfgitems[0]);


/* Check configuration
 */
gboolean xs_check_configuration(void)
{
    // Check for plain errors
    switch (xs_cfg.playerEngine)
    {
        case XS_ENG_SIDPLAYFP:
            {
            if (xs_cfg.romPath == NULL || xs_cfg.romPath[0] == 0)
            {
                xs_messagebox(_("Error"),
                _("You have selected libSIDPlayFP backend, but not set the C64 ROM images directory.\n"));
                return FALSE;
            }
            
            if (!xs_is_dir_path(xs_cfg.romPath))
            {
                xs_messagebox(_("Error"),
                _("The C64 ROM images directory path '%s' is not a directory.\n"),
                    xs_cfg.romPath);
                return FALSE;
            }
            
            gint i;
            gchar *result = NULL;
            for (i = 0; i < XS_C64_ROM_IMAGES; i++)
            {
                guint8 *buf = NULL;
                if (!xs_load_rom_image(i, &buf))
                {
                    const XSROMImageData *rom = &xs_rom_images[i];
                    gchar *tmp = g_strdup_printf("%s%s file '%s'",
                        result != NULL ? ", " : "",
                        rom->name, rom->filename);

                    xs_pstrcat(&result, tmp);
                    g_free(tmp);
                }
                g_free(buf);
            }

            if (result != NULL)
            {
                xs_messagebox(_("Error"),
                _("Could not load the required C64 ROM image files from '%s'.\n"
                "\n"
                "Following files could not be found: %s."),
                xs_cfg.romPath, result);
                g_free(result);
                return FALSE;
            }
            }
            break;
    }

    // Automatic adjustments
    switch (xs_cfg.sid2Builder)
    {
#if !defined(HAVE_RESID_BUILDER) && defined(HAVE_RESID_FP_BUILDER)
        case XS_BLD_RESID:     xs_cfg.sid2Builder = XS_BLD_RESID_FP; break;
#endif
#if defined(HAVE_RESID_BUILDER) && !defined(HAVE_RESID_FP_BUILDER)
        case XS_BLD_RESID_FP:  xs_cfg.sid2Builder = XS_BLD_RESID; break;
#endif
#if !defined(HAVE_HARDSID_BUILDER)
        case XS_BLD_HARDSID:
#if defined(HAVE_RESID_FP_BUILDER)
           xs_cfg.sid2Builder = XS_BLD_RESID_FP; break;
#elif defined(HAVE_RESID_BUILDER)
           xs_cfg.sid2Builder = XS_BLD_RESID; break;
#endif
#endif
    }

    return TRUE;
}


/* Reset/initialize the configuration
 */
void xs_init_configuration(void)
{
    /* Lock configuration mutex */
    XSDEBUG("initializing configuration ...\n");
    XS_MUTEX_LOCK(xs_cfg);

    memset(&xs_cfg, 0, sizeof(xs_cfg));
    
    /* Initialize values with sensible defaults */
    xs_cfg.audioBitsPerSample = XS_RES_16BIT;
    xs_cfg.audioChannels = XS_CHN_MONO;
    xs_cfg.audioFrequency = XS_AUDIO_FREQ;

    xs_cfg.mos8580 = FALSE;
    xs_cfg.forceModel = FALSE;

    /* Filter values */
    xs_cfg.emulateFilters = TRUE;
    xs_cfg.sid1Filter.fs = XS_SIDPLAY1_FS;
    xs_cfg.sid1Filter.fm = XS_SIDPLAY1_FM;
    xs_cfg.sid1Filter.ft = XS_SIDPLAY1_FT;

#if defined(HAVE_SIDPLAY2)
    xs_cfg.playerEngine = XS_ENG_SIDPLAY2;
    xs_cfg.memoryMode = XS_MPU_REAL;
#elif defined(HAVE_SIDPLAY1)
    xs_cfg.playerEngine = XS_ENG_SIDPLAY1;
    xs_cfg.memoryMode = XS_MPU_BANK_SWITCHING;
#elif !defined(HAVE_SIDPLAYFP)
#error This should not happen! No emulator engines configured in!
#endif

    xs_cfg.clockSpeed = XS_CLOCK_PAL;
    xs_cfg.forceSpeed = FALSE;

    xs_cfg.sid2OptLevel = 0;
    xs_cfg.sid2NFilterPresets = 0;

#if defined(HAVE_RESID_BUILDER) || defined(HAVE_RESID_FP_BUILDER)
    xs_cfg.sid2Builder = XS_BLD_RESID;
#elif defined(HAVE_HARDSID_BUILDER)
    xs_cfg.sid2Builder = XS_BLD_HARDSID;
#elif defined(HAVE_SIDPLAY2)
#  error This should not happen! No supported SIDPlay2 builders configured in!
#endif

    xs_cfg.playMaxTimeEnable = FALSE;
    xs_cfg.playMaxTimeUnknown = FALSE;
    xs_cfg.playMaxTime = 150;

    xs_cfg.playMinTimeEnable = FALSE;
    xs_cfg.playMinTime = 15;

    xs_cfg.songlenDBEnable = FALSE;
    xs_pstrcpy(&xs_cfg.songlenDBPath, "~/C64Music/Songlengths.txt");

    xs_cfg.stilDBEnable = FALSE;
    xs_pstrcpy(&xs_cfg.stilDBPath, "~/C64Music/DOCUMENTS/STIL.txt");
    xs_pstrcpy(&xs_cfg.hvscPath, "~/C64Music");

    xs_pstrcpy(&xs_cfg.romPath, "/usr/lib/vice/C64");

#if defined(HAVE_SONG_POSITION) && !defined(AUDACIOUS_PLUGIN)
    xs_cfg.subsongControl = XS_SSC_PATCH;
#else
    xs_cfg.subsongControl = XS_SSC_POPUP;
#endif
    xs_cfg.detectMagic = FALSE;

    xs_cfg.titleOverride = FALSE;

#ifdef AUDACIOUS_PLUGIN
    xs_pstrcpy(&xs_cfg.titleFormat, "${artist} - ${title} (${copyright}) <${subsong-id}/${subsong-num}> [${sid-model}/${sid-speed}]");
#else
    xs_pstrcpy(&xs_cfg.titleFormat, "%p - %t (%c) <%n/%N> [%m/%C]");
#endif

    xs_cfg.subAutoEnable = FALSE;
    xs_cfg.subAutoMinOnly = TRUE;
    xs_cfg.subAutoMinTime = 15;


    /* Unlock the configuration */
    XS_MUTEX_UNLOCK(xs_cfg);
}


/* Filter configuration handling
 */
static void xs_filters_error(const gchar *fmt, ...)
{
    va_list ap;
    gchar *msg;
    
    va_start(ap, fmt);
    msg = g_strdup_vprintf(fmt, ap);
    va_end(ap);
    
    xmms_show_message(_(PACKAGE_NAME " Error"), msg, _("OK"), TRUE, NULL, NULL);
    g_free(msg);
}


static void xs_filter_free(xs_sid_filter_t *filter)
{
    if (filter)
    {
        g_free(filter->name);
        filter->name = NULL;
        g_free(filter);
    }
}


#define XS_FITEM (4 * 2)

static gboolean xs_filter_load_into(XS_CONFIG_FILE *cfg, gint nFilter, xs_sid_filter_t *filter)
{
    gchar tmpKey[64], *tmpStr;
    gint i, j;

    /* Get fields from config */
    g_snprintf(tmpKey, sizeof(tmpKey), "filter%dType", nFilter);
    if (!XS_CFG_GET_INT(tmpKey, &(filter->type)))
        return FALSE;
    
    g_snprintf(tmpKey, sizeof(tmpKey), "filter%dName", nFilter);
    if (!XS_CFG_GET_STRING(tmpKey, &tmpStr))
        return FALSE;
    
    filter->name = g_strdup(tmpStr);
    if (filter->name == NULL)
        return FALSE;
    
    switch (filter->type)
    {
        case 1:
            
            /* Types 1 has points */
            g_snprintf(tmpKey, sizeof(tmpKey), "filter%dNPoints", nFilter);
            if (!XS_CFG_GET_INT(tmpKey, &(filter->npoints)))
                return FALSE;
        
            g_snprintf(tmpKey, sizeof(tmpKey), "filter%dPoints", nFilter);
            if (!XS_CFG_GET_STRING(tmpKey, &tmpStr))
                return FALSE;
        
            for (i = 0, j = 0; i < filter->npoints; i++, j += XS_FITEM)
            {
                if (sscanf(&tmpStr[j], "%4x%4x",
                    &(filter->points[i].x),
                    &(filter->points[i].y)) != 2)
                    return FALSE;
            }
            break;
        
        case 3:
            /* Type 3 has tunables */
            g_snprintf(tmpKey, sizeof(tmpKey), "filter%dData", nFilter);
            if (!XS_CFG_GET_STRING(tmpKey, &tmpStr))
                return FALSE;
            
            if (sscanf(tmpStr, "%f,%f,%f,%f", &filter->rate, &filter->point,
                &filter->voice_nonlinearity, &filter->cf_treshold) != 4)
                return FALSE;
            
            g_snprintf(tmpKey, sizeof(tmpKey), "filter%dData3", nFilter);
            if (!XS_CFG_GET_STRING(tmpKey, &tmpStr))
                return FALSE;
            
            if (sscanf(tmpStr, "%f,%f,%f,%f", &filter->baseresistance,
                &filter->offset, &filter->steepness,
                &filter->minimumfetresistance) != 4)
                return FALSE;
            break;
        
        case 4:
            /* Type 4 has fewer tunables */
            g_snprintf(tmpKey, sizeof(tmpKey), "filter%dData4", nFilter);
            if (!XS_CFG_GET_STRING(tmpKey, &tmpStr))
                return FALSE;
            
            if (sscanf(tmpStr, "%f,%f", &filter->k, &filter->b) != 2)
                return FALSE;
            break;
        
        default:
            xs_error("Unknown filter type %d for '%s' (#%d).\n", filter->type, filter->name, nFilter);
            return FALSE;
    }
    
    return TRUE;
}


static xs_sid_filter_t * xs_filter_load(XS_CONFIG_FILE *cfg, gint nFilter)
{
    xs_sid_filter_t *filter;
    
    /* Allocate filter struct */
    if ((filter = g_malloc0(sizeof(xs_sid_filter_t))) == NULL)
        return NULL;
    
    if (!xs_filter_load_into(cfg, nFilter, filter))
    {
        xs_error("Error loading filter %d from configuration.\n", nFilter);
        xs_filter_free(filter);
        return NULL;
    } else
        return filter;
}


#if 0
static gboolean xs_filter_save(XS_CONFIG_FILE *cfg, xs_sid_filter_t *pFilter, gint nFilter)
{
    gchar *tmpValue, tmpKey[64];
    gint i, j;
    
    /* Allocate memory for value string */
    tmpValue = g_malloc(sizeof(gchar) * XS_FITEM * (pFilter->npoints + 1));
    if (tmpValue == NULL)
        return FALSE;
    
    /* Make value string */
    for (i = 0, j = 0; i < pFilter->npoints; i++, j += XS_FITEM)
    {
        g_snprintf(&tmpValue[j], XS_FITEM+1, "%04x%04x",
            pFilter->points[i].x,
            pFilter->points[i].y);
    }
    
    /* Write into the configuration */
    g_snprintf(tmpKey, sizeof(tmpKey), "filter%dName", nFilter);
    XS_CFG_SET_STRING(tmpKey, pFilter->name);
    
    g_snprintf(tmpKey, sizeof(tmpKey), "filter%dNPoints", nFilter);
    XS_CFG_SET_INT(tmpKey, pFilter->npoints);

    g_snprintf(tmpKey, sizeof(tmpKey), "filter%dPoints", nFilter);
    XS_CFG_SET_STRING(tmpKey, tmpValue);
    
    g_free(tmpValue);
    return TRUE;
}
#endif


/* Filter exporting and importing. These functions export/import
 * filter settings to/from SIDPlay2 INI-type files.
 */
static gboolean xs_fgetitem(gchar *inLine, size_t *linePos, gchar sep, gchar *tmpStr, size_t tmpMax)
{
    size_t i;
    
    for (i = 0; i < tmpMax && inLine[*linePos] &&
        inLine[*linePos] != sep; i++, (*linePos)++)
        tmpStr[i] = inLine[*linePos];
    
    tmpStr[i] = 0;
    while (--i > 0 && isspace(tmpStr[i])) tmpStr[i] = 0;
    
    xs_findnext(inLine, linePos);
    return (inLine[*linePos] == sep);
}


static gboolean xs_chkf(xs_sid_filter_t *filter, const gchar *str, const gchar *name, gint type)
{
    if (g_strncasecmp(str, name, strlen(name)))
        return FALSE;
    
    if (filter->type != type)
    {
        if (filter->type == -1)
        {
            filter->type = type;
            return TRUE;
        }
        else
        {
            xs_error("Unexpected key '%s' for filter type %d.\n");
            return FALSE;
        }
    } else
        return TRUE;
}

static gboolean xs_filters_import(const gchar *filename, xs_sid_filter_t **pFilters, gint *nFilters)
{
    FILE *inFile;
    gchar inLine[XS_BUF_SIZE], tmpStr[XS_BUF_SIZE];
    gchar *sectName = NULL;
    gboolean inSection, isError = FALSE;
    size_t lineNum, i;
    xs_sid_filter_t *filter = NULL;

fprintf(stderr, "xs_filters_import(%s)\n", filename);

    if ((inFile = fopen(filename, "ra")) == NULL)
    {
        xs_filters_error("");
        return FALSE;
    }

fprintf(stderr, "importing...\n");
    
    inSection = FALSE;
    lineNum = 0;
    while (fgets(inLine, XS_BUF_SIZE, inFile) != NULL && !isError)
    {
        size_t linePos = 0;
        lineNum++;
        
        xs_findnext(inLine, &linePos);
        if (isalpha(inLine[linePos]))
        {
            /* A new key/value pair */
            if (!xs_fgetitem(inLine, &linePos, '=', tmpStr, XS_BUF_SIZE))
            {
                xs_error("Invalid line '%s' :: expected =", inLine);
                isError = TRUE;
            }
            else
            if (inSection)
            {
                linePos++;
                xs_findnext(inLine, &linePos);
                
                if (xs_chkf(filter, tmpStr, "points", 1))
                {
                }
                else
                if (xs_chkf(filter, tmpStr, "point", 1))
                {
                
                }
                else
                if (!g_strncasecmp(tmpStr, "type", 4))
                {
                    if (filter->type != -1)
                    {
                        xs_error("Filter type %d already set for '%s'\n",
                            filter->type, sectName);
                    }
                }
                else
                {
                    xs_error("Unsupported definition '%s' @ '%s'\n",
                        tmpStr, sectName);
                }
            }
        }
        else
        if (inLine[linePos] == '[')
        {
            /* Check for existing section */
            if (inSection)
            {
                /* Submit definition */
                fprintf(stderr, "filter ends: %s\n", sectName);
                if ((filter = g_malloc0(sizeof(xs_sid_filter_t))) == NULL)
                {
                    fprintf(stderr, "could not allocate ..\n");
                }
                else
                {
                    
                }
                g_free(sectName);
            }
            
            /* New filter(?) section starts */
            linePos++;
            for (i = 0; i < XS_BUF_SIZE-1 && inLine[linePos] && inLine[linePos] != ']'; i++, linePos++)
                tmpStr[i] = inLine[linePos];
            tmpStr[i] = 0;
            
            if (inLine[linePos] != ']')
            {
                fprintf(stderr, "invalid! expected ']': %s\n", inLine);
            }
            else
            {
                if (!g_strncasecmp(tmpStr, "filter", 6))
                {
                    sectName = strdup(tmpStr + 6);
                    fprintf(stderr, "filter: %s\n", sectName);
                    inSection = TRUE;
                }
                else
                {
                    fprintf(stderr, "ignoring section: %s\n", tmpStr);
                    inSection = FALSE;
                }
            }
        }
        else if (inLine[linePos] != '#' && inLine[linePos] != ';' && inLine[linePos] != 0)
        {
            /* Syntax error */
            xs_error("Syntax error: '%s'\n", inLine);
            isError = TRUE;
        }
    }
    
    fclose(inFile);
    return TRUE;
}


static gboolean xs_filter_export(FILE *outFile, xs_sid_filter_t *filter)
{
    gint i;

    fprintf(outFile,
    "[Filter%s]\n"
    "type=%d\n",
    filter->name, filter->type);
    
    switch (filter->type)
    {
        case 1:
            fprintf(outFile, "points=%d\n", filter->npoints);
            
            for (i = 0; i < filter->npoints; i++)
            {
                fprintf(outFile,
                "point%d=%d,%d\n",
                i + 1,
                filter->points[i].x,
                filter->points[i].y);
            }
            break;
        
        case 3:
            fprintf(outFile,
            "DistortionRate            = %f\n"
            "DistortionPoint           = %f\n"
            "VoiceNonlinearity         = %f\n"
            "DistortionCFThreshold     = %f\n",
            filter->rate, filter->point,
            filter->voice_nonlinearity,
            filter->cf_treshold);
            
            fprintf(outFile,
            "Type3BaseResistance       = %f\n"
            "Type3Offset               = %f\n"
            "Type3Steepness            = %f\n"
            "Type3MinimumFETResistance = %f\n",
            filter->baseresistance, filter->offset,
            filter->steepness, filter->minimumfetresistance);
            break;

        case 4:
            fprintf(outFile,
            "Type4K=%f\n"
            "Type4B=%f\n",
            filter->k, filter->b);
            break;
        
        default:
            xs_error("Filter '%s' has type %d, which is unsupported by export.\n",
            filter->name, filter->type);
            return FALSE;
    }
    
    fprintf(outFile, "\n");
    return TRUE;
}


static gboolean xs_filters_export(const gchar *filename, xs_sid_filter_t **filters, gint nfilters)
{
    gboolean result = TRUE;
    FILE *outFile;
    gint n;
    
    /* Open/create the file */
    if ((outFile = fopen(filename, "wa")) == NULL)
    {
        xs_filters_error("Could not open '%s' for writing! Not exporting.", filename);
        return FALSE;
    }
    
    /* Header */
    fprintf(outFile,
    "; SIDPlay2 compatible filter definition file\n"
    "; Exported by " PACKAGE_STRING "\n\n");
    
    /* Write each filter spec in "INI"-style format */
    for (n = 0; n < nfilters; n++)
    {
        if (!xs_filter_export(outFile, filters[n]))
        {
            result = FALSE;
            break;
        }
    }
    
    fclose(outFile);
    
    if (!result)
        xs_filters_error("Some filters could not be exported!");
    
    return result;
}


/* Get the configuration (from file or default)
 */
gboolean xs_read_configuration(void)
{
    XS_CONFIG_FILE *cfg;
    gint i;
    gboolean configOK = TRUE;

    /* Try to open the XMMS configuration file  */
    XS_MUTEX_LOCK(xs_cfg);
    XSDEBUG("loading from config-file ...\n");

    cfg = XS_CONFIG_OPEN();
    if (cfg == NULL)
    {
        configOK = FALSE;
        goto error;
    }

    /* Read the new settings from XMMS configuration file */
    for (i = 0; i < xs_ncfgtable; i++)
    switch (xs_cfgtable[i].itemType)
    {
        case CTYPE_INT:
            if (!XS_CFG_GET_INT(xs_cfgtable[i].itemName,
                (gint *) xs_cfgtable[i].itemData))
                configOK = FALSE;
            break;

        case CTYPE_BOOL:
            if (!XS_CFG_GET_BOOL(xs_cfgtable[i].itemName,
                (gboolean *) xs_cfgtable[i].itemData))
                configOK = FALSE;
            break;

        case CTYPE_FLOAT:
            if (!XS_CFG_GET_FLOAT(xs_cfgtable[i].itemName,
                (gfloat *) xs_cfgtable[i].itemData))
                configOK = FALSE;
            break;
        
        case CTYPE_STR:
            {
                gchar *tmpStr;
                if (XS_CFG_GET_STRING(xs_cfgtable[i].itemName,
                    (gchar **) &tmpStr))
                {
                    xs_pstrcpy((gchar **) xs_cfgtable[i].itemData, tmpStr);
                    g_free(tmpStr);
                }
                else
                    configOK = FALSE;
            }
            break;
    }
    
    /* Filters and presets are a special case */
    xs_filter_load_into(cfg, 0, &xs_cfg.sid2Filter);
    
    if (xs_cfg.sid2NFilterPresets > 0)
    {
        xs_cfg.sid2FilterPresets = g_malloc0(xs_cfg.sid2NFilterPresets * sizeof(xs_sid_filter_t *));
        if (!xs_cfg.sid2FilterPresets)
        {
            xs_error("Allocation of sid2FilterPresets structure failed!\n");
            configOK = FALSE;
        }
        else
        {
            for (i = 0; i < xs_cfg.sid2NFilterPresets; i++)
                xs_cfg.sid2FilterPresets[i] = xs_filter_load(cfg, i);
        }
    }

error:
    if (cfg != NULL)
    {
        XS_CONFIG_FREE(cfg);
    }
    XS_MUTEX_UNLOCK(xs_cfg);
    XSDEBUG("%s\n", configOK ? "OK" : "ERRORS!");
    return configOK;
}


/* Write the current configuration
 */
gint xs_write_configuration(void)
{
    XS_CONFIG_FILE *cfg;
    gint i;

    XSDEBUG("writing configuration ...\n");
    XS_MUTEX_LOCK(xs_cfg);

    /* Try to open the XMMS configuration file  */
    cfg = XS_CONFIG_OPEN();

#ifndef AUDACIOUS_PLUGIN
    if (!cfg) cfg = xmms_cfg_new();
    if (!cfg) return -1;
#endif

    /* Write the new settings to XMMS configuration file */
    for (i = 0; i < xs_ncfgtable; i++)
    switch (xs_cfgtable[i].itemType)
    {
        case CTYPE_INT:
            XS_CFG_SET_INT(xs_cfgtable[i].itemName,
                *(gint *) xs_cfgtable[i].itemData);
            break;

        case CTYPE_BOOL:
            XS_CFG_SET_BOOL(xs_cfgtable[i].itemName,
                *(gboolean *) xs_cfgtable[i].itemData);
            break;

        case CTYPE_FLOAT:
            XS_CFG_SET_FLOAT(xs_cfgtable[i].itemName,
                *(gfloat *) xs_cfgtable[i].itemData);
            break;

        case CTYPE_STR:
            XS_CFG_SET_STRING(xs_cfgtable[i].itemName,
                *(gchar **) xs_cfgtable[i].itemData);
            break;
    }


#ifndef AUDACIOUS_PLUGIN
    xmms_cfg_write_default_file(cfg);
#endif
    XS_CONFIG_FREE(cfg);

    XS_MUTEX_UNLOCK(xs_cfg);

    return 0;
}


/* Configuration panel was canceled
 */
XS_DEF_WINDOW_CLOSE(cfg_cancel, configwin)


/* Configuration was accepted, save the settings
 */
void xs_cfg_ok(void)
{
    gint i;
    gfloat tmpValue;
    gint tmpInt;
    const gchar *tmpStr;

    /* Get lock on configuration */
    XS_MUTEX_LOCK(xs_cfg);

    XSDEBUG("get data from widgets to config...\n");

    for (i = 0; i < xs_nwidtable; i++)
    switch (xs_cfgitems[i].widType)
    {
        case WTYPE_BGROUP:
            /* Check if toggle-button is active */
            if (GTK_TOGGLE_BUTTON(LUW(xs_cfgitems[i].widName))->active)
            {
                /* Yes, set the constant value */
                *((gint *) xs_cfgitems[i].itemData) = xs_cfgitems[i].itemSet;
            }
            break;

        case WTYPE_COMBO:
            /* Get text from text-widget */
            tmpStr = gtk_entry_get_text(GTK_ENTRY(LUW(xs_cfgitems[i].widName)));
            if (sscanf(tmpStr, "%d", &tmpInt) != 1)
                tmpInt = xs_cfgitems[i].itemSet;

            *((gint *) xs_cfgitems[i].itemData) = tmpInt;
            break;
            
        case WTYPE_SPIN:
        case WTYPE_SCALE:
            /* Get the value */
            switch (xs_cfgitems[i].widType)
            {
                case WTYPE_SPIN:
                    tmpValue = gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(LUW(xs_cfgitems[i].widName)))->value;
                    break;

                case WTYPE_SCALE:
                    tmpValue = gtk_range_get_adjustment(GTK_RANGE(LUW(xs_cfgitems[i].widName)))->value;
                    break;
                
                default:
                    tmpValue = -1;
                    break;
            }

            /* Set the value */
            switch (xs_cfgitems[i].itemType)
            {
                case CTYPE_INT:
                    *((gint *) xs_cfgitems[i].itemData) = (gint) tmpValue;
                    break;

                case CTYPE_FLOAT:
                    *((gfloat *) xs_cfgitems[i].itemData) = tmpValue;
                    break;
            }
            break;

        case WTYPE_BUTTON:
            /* Check if toggle-button is active */
            *((gboolean *) xs_cfgitems[i].itemData) =
                (GTK_TOGGLE_BUTTON(LUW(xs_cfgitems[i].widName))->active);
            break;

        case WTYPE_TEXT:
            /* Get text from text-widget */
            xs_pstrcpy((gchar **) xs_cfgitems[i].itemData,
                gtk_entry_get_text(GTK_ENTRY(LUW(xs_cfgitems[i].widName))));
            break;
    }
    
    // Check the settings
    if (!xs_check_configuration())
        goto error;

    /* Get filter settings */
    /*
    if (!xs_curve_get_points(XS_CURVE(LUW("")), &xs_cfg.sid2Filter.points, &xs_cfg.sid2Filter.npoints)) {
        xs_error("Warning: Could not get filter curve widget points!\n");
    }
    */

    /* Release lock */
    XS_MUTEX_UNLOCK(xs_cfg);
    
    /* Close window */
    gtk_widget_destroy(xs_configwin);
    xs_configwin = NULL;

    /* Write settings */
    xs_write_configuration();

    /* Re-initialize */
    xs_reinit();
    return;

error:
    /* Release lock */
    XS_MUTEX_UNLOCK(xs_cfg);
}


void xs_messagebox_ok(GtkButton *button, gpointer user_data)
{
    (void) button;
    gtk_widget_destroy(GTK_WIDGET(user_data));
}


void xs_messagebox_const(const gchar *title, const gchar *msg)
{
    GtkWidget *wid, *win = create_xs_messagebox();
    gchar *ntitle;

    if (win == NULL)
        return;

    gtk_signal_connect(
        GTK_OBJECT(lookup_widget(win, "xs_messagebox_ok")),
        "clicked", GTK_SIGNAL_FUNC(xs_messagebox_ok), win);

    ntitle = g_strdup_printf("%s %s", PACKAGE_NAME, title != NULL ? title : "message");
    if (ntitle != NULL)
    {
        gtk_window_set_title(GTK_WINDOW(win), ntitle);

        if ((wid = lookup_widget(win, "xs_messagebox_frame")) != NULL)
            gtk_frame_set_label(GTK_FRAME(wid), ntitle);

        g_free(ntitle);
    }

    if ((wid = lookup_widget(win, "xs_messagebox_msg")) != NULL)
    {
        gtk_label_set_text(GTK_LABEL(wid), msg);
    }

    gtk_widget_show(win);
}


void xs_messagebox(const gchar *title, const gchar *fmt, ...)
{
    va_list ap;
    gchar *msg;

    va_start(ap, fmt);
    msg = g_strdup_vprintf(fmt, ap);
    va_end(ap);

    xs_messagebox_const(title, msg);
    g_free(msg);
}

/* Confirmation window
 */
gboolean xs_confirmwin_delete(GtkWidget *widget, GdkEvent *event, gpointer user_data)
{
    (void) widget;
    (void) event;
    (void) user_data;
    
    return FALSE;
}



/* HVSC songlength-database file selector response-functions
 */
void xs_cfg_sldb_browse(GtkButton * button, gpointer user_data)
{
    (void) button;
    (void) user_data;

    if (xs_sldb_fileselector != NULL)
    {
        XS_WINDOW_PRESENT(xs_sldb_fileselector);
        return;
    }

    xs_sldb_fileselector = create_xs_sldb_fs();
    XS_MUTEX_LOCK(xs_cfg);
    gtk_file_selection_set_filename(GTK_FILE_SELECTION(xs_sldb_fileselector), xs_cfg.songlenDBPath);
    XS_MUTEX_UNLOCK(xs_cfg);
    gtk_widget_show(xs_sldb_fileselector);
}


void xs_sldb_fs_ok(GtkButton *button, gpointer user_data)
{
    (void) button;
    (void) user_data;
    
    /* Selection was accepted! */
    gtk_entry_set_text(GTK_ENTRY(LUW("cfg_sld_dbpath")),
        gtk_file_selection_get_filename(GTK_FILE_SELECTION(xs_sldb_fileselector)));

    /* Close file selector window */
    gtk_widget_destroy(xs_sldb_fileselector);
    xs_sldb_fileselector = NULL;
}

XS_DEF_WINDOW_CLOSE(sldb_fs_cancel, sldb_fileselector)
XS_DEF_WINDOW_DELETE(sldb_fs, sldb_fileselector)


/* STIL-database file selector response-functions
 */
void xs_cfg_stil_browse(GtkButton * button, gpointer user_data)
{
    (void) button;
    (void) user_data;

    if (xs_stil_fileselector != NULL)
    {
        XS_WINDOW_PRESENT(xs_stil_fileselector);
        return;
    }

    xs_stil_fileselector = create_xs_stil_fs();
    XS_MUTEX_LOCK(xs_cfg);
    gtk_file_selection_set_filename(GTK_FILE_SELECTION(xs_stil_fileselector),
        gtk_entry_get_text(GTK_ENTRY(LUW("cfg_stil_dbpath"))));
    XS_MUTEX_UNLOCK(xs_cfg);
    gtk_widget_show(xs_stil_fileselector);
}


void xs_stil_fs_ok(GtkButton *button, gpointer user_data)
{
    (void) button;
    (void) user_data;

    /* Selection was accepted! */
    gtk_entry_set_text(GTK_ENTRY(LUW("cfg_stil_dbpath")),
        gtk_file_selection_get_filename(GTK_FILE_SELECTION(xs_stil_fileselector)));

    /* Close file selector window */
    gtk_widget_destroy(xs_stil_fileselector);
    xs_stil_fileselector = NULL;
}


XS_DEF_WINDOW_CLOSE(stil_fs_cancel, stil_fileselector)
XS_DEF_WINDOW_DELETE(stil_fs, stil_fileselector)


/* HVSC location selector response-functions
 */
void xs_cfg_hvsc_browse(GtkButton * button, gpointer user_data)
{
    (void) button;
    (void) user_data;

    if (xs_hvsc_selector != NULL)
    {
        XS_WINDOW_PRESENT(xs_hvsc_selector);
        return;
    }

    xs_hvsc_selector = create_xs_hvsc_fs();
    XS_MUTEX_LOCK(xs_cfg);
    gtk_file_selection_set_filename(GTK_FILE_SELECTION(xs_hvsc_selector),
        gtk_entry_get_text(GTK_ENTRY(LUW("cfg_hvsc_path"))));
    XS_MUTEX_UNLOCK(xs_cfg);
    gtk_widget_show(xs_hvsc_selector);
}


void xs_hvsc_fs_ok(GtkButton *button, gpointer user_data)
{
    (void) button;
    (void) user_data;
    gchar *path, *result = gtk_file_selection_get_filename(GTK_FILE_SELECTION(xs_hvsc_selector));
    if (!xs_is_dir_path(result))
    {
        path = xs_get_dir_path(result);
        xs_messagebox(_("Warning"),
            _("Selected HVSC path '%s' does not seem like a path, adjusting to '%s'.\n"),
            result, path);
    }
    else
        path = result;

    gtk_entry_set_text(GTK_ENTRY(LUW("cfg_hvsc_path")), path);

    /* Close file selector window */
    gtk_widget_destroy(xs_hvsc_selector);
    xs_hvsc_selector = NULL;
}


XS_DEF_WINDOW_CLOSE(hvsc_fs_cancel, hvsc_selector)
XS_DEF_WINDOW_DELETE(hvsc_fs, hvsc_selector)


/* C64 ROM path selector response-functions
 */
void xs_cfg_rom_browse(GtkButton * button, gpointer user_data)
{
    (void) button;
    (void) user_data;

    if (xs_rom_selector != NULL)
    {
        XS_WINDOW_PRESENT(xs_rom_selector);
        return;
    }

    xs_rom_selector = create_xs_rom_fs();
    XS_MUTEX_LOCK(xs_cfg);
    gtk_file_selection_set_filename(GTK_FILE_SELECTION(xs_rom_selector),
        gtk_entry_get_text(GTK_ENTRY(LUW("cfg_emu_rom_path"))));
    XS_MUTEX_UNLOCK(xs_cfg);
    gtk_widget_show(xs_rom_selector);
}


void xs_rom_fs_ok(GtkButton *button, gpointer user_data)
{
    (void) button;
    (void) user_data;
    gchar *path, *result = gtk_file_selection_get_filename(GTK_FILE_SELECTION(xs_rom_selector));
    if (!xs_is_dir_path(result))
    {
        path = xs_get_dir_path(result);
        xs_messagebox(_("Warning"),
            _("Selected ROM path '%s' does not seem like a path, adjusting to '%s'.\n"),
            result, path);
    }
    else
        path = result;

    gtk_entry_set_text(GTK_ENTRY(LUW("cfg_emu_rom_path")), path);

    /* Close file selector window */
    gtk_widget_destroy(xs_rom_selector);
    xs_rom_selector = NULL;
}


XS_DEF_WINDOW_CLOSE(rom_fs_cancel, rom_selector)
XS_DEF_WINDOW_DELETE(rom_fs, rom_selector)


/* Filter handling
 */
void xs_cfg_sp1_filter_reset(GtkButton * button, gpointer user_data)
{
    (void) button;
    (void) user_data;

    gtk_adjustment_set_value(gtk_range_get_adjustment(GTK_RANGE(LUW("cfg_sp1_filter_fs"))), XS_SIDPLAY1_FS);
    gtk_adjustment_set_value(gtk_range_get_adjustment(GTK_RANGE(LUW("cfg_sp1_filter_fm"))), XS_SIDPLAY1_FM);
    gtk_adjustment_set_value(gtk_range_get_adjustment(GTK_RANGE(LUW("cfg_sp1_filter_ft"))), XS_SIDPLAY1_FT);
}


void xs_cfg_sp2_filter_update(XSCurve *curve, xs_sid_filter_t *f)
{
    assert(curve);
    assert(f);
    
    xs_curve_reset(curve);
    xs_curve_set_range(curve, 0,0, XS_SIDPLAY2_NFPOINTS, XS_SIDPLAY2_FMAX);
    if (!xs_curve_set_points(curve, f->points, f->npoints))
    {
        // FIXME
        xs_error("Warning: Could not set filter curve widget points!\n");
    }
}


void xs_cfg_sp2_presets_update(void)
{
    GList *tmpList = NULL;
    gint i;
    
    for (i = 0; i < xs_cfg.sid2NFilterPresets; i++)
    {
        tmpList = g_list_append(tmpList,
            (gpointer) xs_cfg.sid2FilterPresets[i]->name);
    }
    
    if (tmpList != NULL)
        gtk_combo_set_popdown_strings(GTK_COMBO(LUW("cfg_sp2_filter_combo")), tmpList);

    g_list_free(tmpList);
}


void xs_cfg_sp2_filter_load(GtkButton *button, gpointer user_data)
{
    const gchar *tmpStr;
    gint i, j;
    
    (void) button;
    (void) user_data;
    
    XS_MUTEX_LOCK(xs_cfg);
    
    tmpStr = gtk_entry_get_text(GTK_ENTRY(LUW("cfg_sp2_filter_combo_entry")));
    for (i = 0, j = -1; i < xs_cfg.sid2NFilterPresets; i++)
    {
        if (!strcmp(tmpStr, xs_cfg.sid2FilterPresets[i]->name))
        {
            j = i;
            break;
        }
    }
    
    if (j != -1)
    {
        fprintf(stderr, "Updating from '%s'\n", tmpStr);
        xs_cfg_sp2_filter_update(
            XS_CURVE(LUW("cfg_sp2_filter_curve")),
            xs_cfg.sid2FilterPresets[i]);
    }
    else
    {
        /* error/warning: no such filter preset */
        fprintf(stderr, "No such filter preset '%s'!\n", tmpStr);
    }
    
    XS_MUTEX_UNLOCK(xs_cfg);
}


void xs_cfg_sp2_filter_save(GtkButton *button, gpointer user_data)
{
    /*
    1) check if textentry matches any current filter name
        yes) ask if saving over ok?
        no) ...
        
    2) save current filter to the name        
    */
    const gchar *tmpStr;
    gint i, j;
    
    (void) button;
    (void) user_data;
    
    XS_MUTEX_LOCK(xs_cfg);
    
    tmpStr = gtk_entry_get_text(GTK_ENTRY(LUW("cfg_sp2_filter_combo_entry")));
    for (i = 0, j = -1; i < xs_cfg.sid2NFilterPresets; i++)
    {
        if (!strcmp(tmpStr, xs_cfg.sid2FilterPresets[i]->name))
        {
            j = i;
            break;
        }
    }
    
    if (j != -1)
    {
        fprintf(stderr, "Found, confirm overwrite?\n");
    }
    
    fprintf(stderr, "saving!\n");
    
    xs_cfg_sp2_presets_update();
    
    XS_MUTEX_UNLOCK(xs_cfg);
}


void xs_cfg_sp2_filter_delete(GtkButton *button, gpointer user_data)
{
    (void) button;
    (void) user_data;
    /*
    1) confirm
    2) delete
    */
}


void xs_cfg_sp2_filter_import(GtkButton *button, gpointer user_data)
{
    (void) button;
    (void) user_data;

    if (xs_filt_importselector != NULL)
    {
        XS_WINDOW_PRESENT(xs_filt_importselector);
        return;
    }

    xs_filt_importselector = create_xs_filter_import_fs();
    gtk_widget_show(xs_filt_importselector);
}


void xs_filter_import_fs_ok(GtkButton *button, gpointer user_data)
{
    const gchar *tmpStr;
    (void) button;
    (void) user_data;
    
    XS_MUTEX_LOCK(xs_cfg);

    /* Selection was accepted! */
    tmpStr = gtk_file_selection_get_filename(GTK_FILE_SELECTION(xs_filt_importselector));
    xs_filters_import(tmpStr, xs_cfg.sid2FilterPresets, &xs_cfg.sid2NFilterPresets);
    xs_cfg_sp2_presets_update();

    /* Close file selector window */
    gtk_widget_destroy(xs_filt_importselector);
    xs_filt_importselector = NULL;
    XS_MUTEX_UNLOCK(xs_cfg);
}


XS_DEF_WINDOW_CLOSE(filter_import_fs_cancel, filt_importselector)
XS_DEF_WINDOW_DELETE(filter_import_fs, filt_importselector)


void xs_cfg_sp2_filter_export(GtkButton *button, gpointer user_data)
{
    (void) button;
    (void) user_data;

    if (xs_filt_exportselector != NULL)
    {
        XS_WINDOW_PRESENT(xs_filt_exportselector);
        return;
    }

    xs_filt_exportselector = create_xs_filter_export_fs();
    gtk_widget_show(xs_filt_exportselector);
}


void xs_filter_export_fs_ok(GtkButton *button, gpointer user_data)
{
    const gchar *tmpStr;
    (void) button;
    (void) user_data;

    XS_MUTEX_LOCK(xs_cfg);

    /* Selection was accepted! */
    tmpStr = gtk_file_selection_get_filename(GTK_FILE_SELECTION(xs_filt_exportselector));
    xs_filters_export(tmpStr, xs_cfg.sid2FilterPresets, xs_cfg.sid2NFilterPresets);

    /* Close file selector window */
    gtk_widget_destroy(xs_filt_exportselector);
    xs_filt_exportselector = NULL;
    XS_MUTEX_UNLOCK(xs_cfg);
}


XS_DEF_WINDOW_CLOSE(filter_export_fs_cancel, filt_exportselector)
XS_DEF_WINDOW_DELETE(filter_export_fs, filt_exportselector)


/* Selection toggle handlers
 */
void xs_cfg_emu_filters_toggled(GtkToggleButton * togglebutton, gpointer user_data)
{
    gboolean isActive = GTK_TOGGLE_BUTTON(togglebutton)->active;

    (void) user_data;

    gtk_widget_set_sensitive(LUW("cfg_filters_notebook"), isActive);
}


void xs_cfg_ftitle_override_toggled(GtkToggleButton * togglebutton, gpointer user_data)
{
    gboolean isActive = GTK_TOGGLE_BUTTON(togglebutton)->active;

    (void) user_data;

    gtk_widget_set_sensitive(LUW("cfg_ftitle_box"), isActive);
}


void xs_cfg_emu_sidplay1_toggled(GtkToggleButton * togglebutton, gpointer user_data)
{
    (void) togglebutton;
    (void) user_data;
}


void xs_cfg_emu_sidplay2_toggled(GtkToggleButton * togglebutton, gpointer user_data)
{
    gboolean isSP2Active = GTK_TOGGLE_BUTTON(LUW("cfg_emu_sidplay2"))->active,
             isFPActive = GTK_TOGGLE_BUTTON(LUW("cfg_emu_sidplayfp"))->active,
             isEither = isSP2Active || isFPActive;

    (void) togglebutton;
    (void) user_data;

    gtk_widget_set_sensitive(LUW("cfg_memmode_frame"), !isFPActive);
    gtk_widget_set_sensitive(LUW("cfg_emu_mem_real"), isSP2Active);

    gtk_widget_set_sensitive(LUW("cfg_sidplay2_frame"), isEither);

    /* Optimization mode removed from distortion patch */
    gtk_widget_set_sensitive(LUW("cfg_emu_sp2_opt"), isSP2Active);

    gtk_widget_set_sensitive(LUW("cfg_chn_autopan"), !isEither);

#if defined(HAVE_SIDPLAYFP) && defined(HAVE_RESID_FP_BUILDER)
    gtk_widget_set_sensitive(LUW("cfg_emu_sp2_resid_fp"), isFPActive);
#else
    gtk_widget_set_sensitive(LUW("cfg_emu_sp2_resid_fp"), FALSE);
#endif

#ifdef HAVE_RESID_BUILDER
    gtk_widget_set_sensitive(LUW("cfg_emu_sp2_resid"), isEither);
    gtk_widget_set_sensitive(LUW("cfg_resid_frame"), isFPActive);
#else
    gtk_widget_set_sensitive(LUW("cfg_emu_sp2_resid"), FALSE);
    gtk_widget_set_sensitive(LUW("cfg_resid_frame"), FALSE);
#endif

#ifdef HAVE_HARDSID_BUILDER
    gtk_widget_set_sensitive(LUW("cfg_emu_sp2_hardsid"), isEither);
#else
    gtk_widget_set_sensitive(LUW("cfg_emu_sp2_hardsid"), FALSE);
#endif

}


void xs_cfg_mintime_enable_toggled(GtkToggleButton * togglebutton, gpointer user_data)
{
    gboolean isActive = GTK_TOGGLE_BUTTON(togglebutton)->active;

    (void) user_data;

    gtk_widget_set_sensitive(LUW("cfg_mintime_box"), isActive);
}


void xs_cfg_maxtime_enable_toggled(GtkToggleButton * togglebutton, gpointer user_data)
{
    gboolean isActive = GTK_TOGGLE_BUTTON(LUW("cfg_maxtime_enable"))->active;

    (void) togglebutton;
    (void) user_data;

    gtk_widget_set_sensitive(LUW("cfg_maxtime_unknown"), isActive);
    gtk_widget_set_sensitive(LUW("cfg_maxtime_box"), isActive);
}


void xs_cfg_sldb_enable_toggled(GtkToggleButton * togglebutton, gpointer user_data)
{
    gboolean isActive = GTK_TOGGLE_BUTTON(togglebutton)->active;

    (void) user_data;

    gtk_widget_set_sensitive(LUW("cfg_sld_box"), isActive);
}


void xs_cfg_stil_enable_toggled(GtkToggleButton * togglebutton, gpointer user_data)
{
    gboolean isActive = GTK_TOGGLE_BUTTON(togglebutton)->active;

    (void) user_data;

    gtk_widget_set_sensitive(LUW("cfg_stil_box1"), isActive);
    gtk_widget_set_sensitive(LUW("cfg_stil_box2"), isActive);
}


void xs_cfg_subauto_enable_toggled(GtkToggleButton * togglebutton, gpointer user_data)
{
    gboolean isActive = GTK_TOGGLE_BUTTON(togglebutton)->active;

    (void) user_data;

    gtk_widget_set_sensitive(LUW("cfg_subauto_min_only"), isActive);
    gtk_widget_set_sensitive(LUW("cfg_subauto_box"), isActive);
}


void xs_cfg_subauto_min_only_toggled(GtkToggleButton * togglebutton, gpointer user_data)
{
    gboolean isActive = GTK_TOGGLE_BUTTON(togglebutton)->active &&
        GTK_TOGGLE_BUTTON(LUW("cfg_subauto_enable"))->active;

    (void) user_data;

    gtk_widget_set_sensitive(LUW("cfg_subauto_box"), isActive);
}


void xs_cfg_mintime_changed(GtkEditable * editable, gpointer user_data)
{
    gint tmpValue;
    GtkAdjustment *tmpAdj;

    (void) user_data;

    tmpAdj = gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(LUW("cfg_maxtime")));

    tmpValue = (gint) gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(editable))->value;

    if (tmpValue > tmpAdj->value)
        gtk_adjustment_set_value(tmpAdj, tmpValue);
}


void xs_cfg_maxtime_changed(GtkEditable * editable, gpointer user_data)
{
    gint tmpValue;
    GtkAdjustment *tmpAdj;

    (void) user_data;

    tmpAdj = gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(LUW("cfg_mintime")));

    tmpValue = (gint) gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(editable))->value;

    if (tmpValue < tmpAdj->value)
        gtk_adjustment_set_value(tmpAdj, tmpValue);
}


XS_DEF_WINDOW_DELETE(configwin, configwin)


/* Execute the configuration panel
 */
void xs_configure(void)
{
    gint i;
    gfloat tmpValue;
    gchar tmpStr[64];
    GList *tmpList = NULL;
    GtkWidget *tmpCurve;

    /* Check if the window already exists */
    if (xs_configwin)
    {
        XS_WINDOW_PRESENT(xs_configwin);
        return;
    }

    /* Create the window */
    xs_configwin = create_xs_configwin();
    
    /* Get lock on configuration */
    XS_MUTEX_LOCK(xs_cfg);

    /* Add samplerates */
    for (i = 0; i < xs_nsamplerates_table; i++)
    {
        tmpList = g_list_append (tmpList,
            (gpointer) xs_samplerates_table[i]);
    }
    gtk_combo_set_popdown_strings(GTK_COMBO(LUW("cfg_samplerate_combo")), tmpList);
    g_list_free(tmpList);
    
    /* Create the custom filter curve widget for libSIDPlay2 */
    xs_cfg_sp2_presets_update();
    tmpCurve = xs_curve_new();
    xs_cfg_sp2_filter_update(XS_CURVE(tmpCurve), &xs_cfg.sid2Filter);
    gtk_widget_set_name(tmpCurve, "cfg_sp2_filter_curve");
    gtk_widget_ref(tmpCurve);
    gtk_object_set_data_full(GTK_OBJECT(xs_configwin),
        "cfg_sp2_filter_curve", tmpCurve, (GtkDestroyNotify) gtk_widget_unref);
    gtk_widget_show(tmpCurve);
    gtk_container_add(GTK_CONTAINER(LUW("cfg_sp2_filter_frame")), tmpCurve);


    /* Based on available optional parts, gray out options */
#ifndef HAVE_SIDPLAY1
    gtk_widget_set_sensitive(LUW("cfg_emu_sidplay1"), FALSE);
    gtk_widget_set_sensitive(LUW("cfg_box_filter_sidplay1"), FALSE);
#endif

#ifndef HAVE_SIDPLAY2
    gtk_widget_set_sensitive(LUW("cfg_emu_sidplay2"), FALSE);
#endif
#ifndef HAVE_SIDPLAYFP
    gtk_widget_set_sensitive(LUW("cfg_emu_sidplayfp"), FALSE);
#endif

#if !defined(HAVE_SIDPLAY2) && !defined(HAVE_SIDPLAYFP)
    gtk_widget_set_sensitive(LUW("cfg_box_filter_sidplay2"), FALSE);
#endif


#if !defined(HAVE_SONG_POSITION) && !defined(AUDACIOUS_PLUGIN)
    gtk_widget_set_sensitive(LUW("cfg_subctrl_patch"), FALSE);
#endif

    xs_cfg_ftitle_override_toggled(GTK_TOGGLE_BUTTON(LUW("cfg_ftitle_override")), NULL);
    xs_cfg_emu_filters_toggled(GTK_TOGGLE_BUTTON(LUW("cfg_emu_filters")), NULL);
    xs_cfg_emu_sidplay1_toggled(GTK_TOGGLE_BUTTON(LUW("cfg_emu_sidplay1")), NULL);
    xs_cfg_emu_sidplay2_toggled(GTK_TOGGLE_BUTTON(LUW("cfg_emu_sidplay2")), NULL);
    xs_cfg_mintime_enable_toggled(GTK_TOGGLE_BUTTON(LUW("cfg_mintime_enable")), NULL);
    xs_cfg_maxtime_enable_toggled(GTK_TOGGLE_BUTTON(LUW("cfg_maxtime_enable")), NULL);
    xs_cfg_sldb_enable_toggled(GTK_TOGGLE_BUTTON(LUW("cfg_sld_enable")), NULL);
    xs_cfg_stil_enable_toggled(GTK_TOGGLE_BUTTON(LUW("cfg_stil_enable")), NULL);
    xs_cfg_subauto_enable_toggled(GTK_TOGGLE_BUTTON(LUW("cfg_subauto_enable")), NULL);
    xs_cfg_subauto_min_only_toggled(GTK_TOGGLE_BUTTON(LUW("cfg_subauto_min_only")), NULL);


    /* Set current data to widgets */
    for (i = 0; i < xs_nwidtable; i++)
    {
        switch (xs_cfgitems[i].widType)
        {
        case WTYPE_BGROUP:
            assert(xs_cfgitems[i].itemType == CTYPE_INT);
            /* Check if current value matches the given one */
            gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(LUW(xs_cfgitems[i].widName)),
                (*((gint *) xs_cfgitems[i].itemData) == xs_cfgitems[i].itemSet));
            break;

        case WTYPE_COMBO:
            assert(xs_cfgitems[i].itemType == CTYPE_INT);
            g_snprintf(tmpStr, sizeof(tmpStr), "%d", *(gint *) xs_cfgitems[i].itemData);
            gtk_entry_set_text(GTK_ENTRY(LUW(xs_cfgitems[i].widName)), tmpStr);
            break;
            
        case WTYPE_SPIN:
        case WTYPE_SCALE:
            /* Get the value */
            switch (xs_cfgitems[i].itemType)
            {
                case CTYPE_INT:
                    tmpValue = (gfloat) * ((gint *) xs_cfgitems[i].itemData);
                    break;

                case CTYPE_FLOAT:
                    tmpValue = *((gfloat *) xs_cfgitems[i].itemData);
                    break;

                default:
                    tmpValue = -1;
                    assert(0);
                    break;
            }

            /* Set the value */
            switch (xs_cfgitems[i].widType)
            {
                case WTYPE_SPIN:
                    gtk_adjustment_set_value(gtk_spin_button_get_adjustment
                        (GTK_SPIN_BUTTON(LUW(xs_cfgitems[i].widName))), tmpValue);
                    break;

                case WTYPE_SCALE:
                    gtk_adjustment_set_value(gtk_range_get_adjustment
                        (GTK_RANGE(LUW(xs_cfgitems[i].widName))), tmpValue);
                    break;
            }
            break;

        case WTYPE_BUTTON:
            assert(xs_cfgitems[i].itemType == CTYPE_BOOL);
            /* Set toggle-button */
            gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(LUW(xs_cfgitems[i].widName)),
                *((gboolean *) xs_cfgitems[i].itemData));
            break;

        case WTYPE_TEXT:
            assert(xs_cfgitems[i].itemType == CTYPE_STR);
            /* Set text to text-widget */
            if (*(gchar **) xs_cfgitems[i].itemData != NULL)
            {
                gtk_entry_set_text(GTK_ENTRY(LUW(xs_cfgitems[i].widName)),
                *(gchar **) xs_cfgitems[i].itemData);
            }
            break;
        }
    }

    /* Release the configuration */
    XS_MUTEX_UNLOCK(xs_cfg);

    /* Show the widget */
    gtk_widget_show(xs_configwin);
}