changeset 109:38ce41606f10 trunk

[svn] - wavpack input plugin -- under construction
author nenolod
date Tue, 24 Oct 2006 19:03:53 -0700
parents 0eb1e99b7748
children 6855fee890ff
files ChangeLog src/wavpack/Makefile src/wavpack/libwavpack.cxx src/wavpack/tags.cxx src/wavpack/tags.h src/wavpack/ui.cxx
diffstat 6 files changed, 1605 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Tue Oct 24 18:15:53 2006 -0700
+++ b/ChangeLog	Tue Oct 24 19:03:53 2006 -0700
@@ -1,3 +1,13 @@
+2006-10-25 01:15:53 +0000  William Pitcock <nenolod@nenolod.net>
+  revision [216]
+  - move prefswin_page_destroy(cfgdlg) to before the plugin logs out of last.fm;
+    otherwise the prefswin_page handle isn't always removed.
+    Reported on IRC by Tim Yamin <plasmaroo -at- gentoo.org>
+  
+  trunk/src/scrobbler/xmms_scrobbler.c |    4 ++--
+  1 file changed, 2 insertions(+), 2 deletions(-)
+
+
 2006-10-24 08:44:06 +0000  William Pitcock <nenolod@nenolod.net>
   revision [214]
   - 1.2.2 release
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/wavpack/Makefile	Tue Oct 24 19:03:53 2006 -0700
@@ -0,0 +1,16 @@
+include ../../mk/rules.mk
+include ../../mk/init.mk
+
+OBJECTIVE_LIBS = libwavpack$(SHARED_SUFFIX)
+
+LIBDIR = $(plugindir)/$(INPUT_PLUGIN_DIR)
+
+SOURCES = tags.cxx ui.cxx libwavpack.cxx
+
+LIBADD = -L/usr/local/lib
+
+CXXFLAGS += $(PICFLAGS) $(GTK_CFLAGS) -I../../intl -I../..
+
+OBJECTS = ${SOURCES:.cxx=.o}
+
+include ../../mk/objective.mk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/wavpack/libwavpack.cxx	Tue Oct 24 19:03:53 2006 -0700
@@ -0,0 +1,411 @@
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <wavpack/wputils.h>
+extern "C" {
+#include <audacious/plugin.h>
+#include <audacious/configdb.h>
+#include <audacious/titlestring.h>
+#include <audacious/util.h>
+}
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <iconv.h>
+#include <math.h>
+#include "equalizer.h"
+#include "tags.h"
+#ifndef M_LN10
+#define M_LN10   2.3025850929940456840179914546843642
+#endif
+
+#define DBG(format, args...) fprintf(stderr, format, ## args)
+#define BUFFER_SIZE 256 // read buffer size, in samples
+
+extern "C" InputPlugin * get_iplugin_info(void);
+static void wv_load_config();
+static int wv_is_our_file(char *);
+static void wv_play(char *);
+static void wv_stop(void);
+static void wv_pause(short);
+static void wv_seek(int);
+static int wv_get_time(void);
+static void wv_get_song_info(char *, char **, int *);
+static char *generate_title(const char *, WavpackContext *ctx);
+static double isSeek;
+static short paused;
+static bool killDecodeThread;
+static bool AudioError;
+static GThread *thread_handle;
+static gboolean EQ_on;
+
+// in ui.cpp
+void wv_configure();
+void wv_about_box(void);
+void wv_file_info_box(char *);
+extern gboolean clipPreventionEnabled;
+extern gboolean dynBitrateEnabled;
+extern gboolean replaygainEnabled;
+extern gboolean albumReplaygainEnabled;
+extern gboolean openedAudio;
+
+InputPlugin mod = {
+    NULL,                       //handle
+    NULL,                       //filename
+    NULL,
+    wv_load_config,
+    wv_about_box,
+    wv_configure,
+    wv_is_our_file,
+    NULL,                       //no use
+    wv_play,
+    wv_stop,
+    wv_pause,
+    wv_seek,
+    NULL,                       //set eq
+    wv_get_time,
+    NULL,                       //get volume
+    NULL,                       //set volume
+    NULL,                       //cleanup
+    NULL,                       //obsolete
+    NULL,                       //add_vis
+    NULL,
+    NULL,
+    wv_get_song_info,
+    wv_file_info_box,          //info box
+    NULL,                       //output
+};
+
+class WavpackDecoder
+{
+public:
+    InputPlugin *mod;
+    int32_t *input;
+    int16_t *output;
+    int sample_rate;
+    int num_channels;
+    WavpackContext *ctx;
+    char error_buff[4096]; // TODO: fixme!
+
+    WavpackDecoder(InputPlugin *mod) : mod(mod)
+    {
+        ctx = NULL;
+        input = NULL;
+        output = NULL;
+    }
+
+    ~WavpackDecoder()
+    {
+        if (input != NULL) {
+            free(input);
+            input = NULL;
+        }
+        if (output != NULL) {
+            free(output);
+            output = NULL;
+        }
+        if (ctx != NULL) {
+            WavpackCloseFile(ctx);
+            ctx = NULL;
+        }
+    }
+
+    bool attach(const char *filename)
+    {
+        ctx = WavpackOpenFileInput(filename, error_buff, OPEN_TAGS | OPEN_WVC, 0);
+
+        if (ctx == NULL) {
+            return false;
+        }
+
+        sample_rate = WavpackGetSampleRate(ctx);
+        num_channels = WavpackGetNumChannels(ctx);
+        input = (int32_t *)calloc(BUFFER_SIZE, num_channels * sizeof(int32_t));
+        output = (int16_t *)calloc(BUFFER_SIZE, num_channels * sizeof(int16_t));
+        mod->set_info(generate_title(filename, ctx),
+                      (int) (WavpackGetNumSamples(ctx) / sample_rate) * 1000,
+                      (int) WavpackGetAverageBitrate(ctx, num_channels),
+                      (int) sample_rate, num_channels);
+        return true;
+    }
+
+    bool open_audio()
+    {
+        return mod->output->open_audio(FMT_S16_LE, sample_rate, num_channels);
+    }
+
+    void process_buffer(size_t num_samples)
+    {
+        for (int i = 0; i < num_samples * num_channels; i++) {
+            output[i] = input[i];
+        }
+        produce_audio(mod->output->get_output_time(), FMT_S16_LE, 
+		sample_rate, num_channels, output, 
+		num_samples * num_channels * sizeof(int16_t));
+    }
+};
+
+extern "C" InputPlugin *
+get_iplugin_info(void)
+{
+    mod.description =
+        g_strdup_printf(("Wavpack Decoder Plugin %s"), VERSION);
+    return &mod;
+}
+
+static int
+wv_is_our_file(char *filename)
+{
+    char *ext;
+
+    ext = strrchr(filename, '.');
+    if (ext) {
+        if (!strcasecmp(ext, ".wv")) {
+            return TRUE;
+        }
+    }
+    return FALSE;
+}
+
+void
+load_tag(ape_tag *tag, WavpackContext *ctx) 
+{
+    memset(tag, 0, sizeof(ape_tag));
+    WavpackGetTagItem(ctx, "Album", tag->album, sizeof(tag->album));
+    WavpackGetTagItem(ctx, "Artist", tag->artist, sizeof(tag->artist));
+    WavpackGetTagItem(ctx, "Comment", tag->comment, sizeof(tag->comment));
+    WavpackGetTagItem(ctx, "Genre", tag->genre, sizeof(tag->genre));
+    WavpackGetTagItem(ctx, "Title", tag->title, sizeof(tag->title));
+    WavpackGetTagItem(ctx, "Track", tag->track, sizeof(tag->track));
+    WavpackGetTagItem(ctx, "Year", tag->year, sizeof(tag->year));
+}
+
+static char *
+convertUTF8toLocale(char *utf8)
+{
+    // note - opens a new iconv descriptor for each call
+    // will have to find a way to reuse the descriptor if this turns
+    // out to be too slow
+    iconv_t idesc = iconv_open("", "UTF-8");
+    if (idesc == (iconv_t) -1) {
+        perror("iconv_open failed");
+        return g_strdup(utf8);
+    }
+
+    size_t in_left = strlen(utf8);
+    size_t out_left = 2 * in_left + 1;
+    char *buf = (char *)g_malloc(out_left);
+    char *in = utf8;
+    char *out = buf;
+
+    memset(buf, 0, out_left);
+    size_t err = iconv(idesc, &in, &in_left, &out, &out_left);
+    iconv_close(idesc);
+    return buf;
+}
+
+static void *
+end_thread()
+{
+    return 0;
+}
+
+static void *
+DecodeThread(void *a)
+{
+    ape_tag tag;
+    char *filename = (char *) a;
+    int bps_updateCounter = 0;
+    int bps;
+    int i;
+    WavpackDecoder d(&mod);
+
+    if (!d.attach(filename)) {
+        printf("wavpack: Error opening file: \"%s\"\n", filename);
+        killDecodeThread = true;
+        return end_thread();
+    }
+    bps = WavpackGetBytesPerSample(d.ctx) * d.num_channels;
+    DBG("reading %s at %d rate with %d channels\n", filename, d.sample_rate, d.num_channels);
+
+    if (!d.open_audio()) {
+        DBG("error opening xmms audio channel\n");
+        killDecodeThread = true;
+        AudioError = true;
+        openedAudio = false;
+    }
+    else {
+        DBG("opened xmms audio channel\n");
+        openedAudio = true;
+    }
+    unsigned status;
+    char *display = generate_title(filename, d.ctx);
+    int length = (int) (1000 * WavpackGetNumSamples(d.ctx));
+
+    while (!killDecodeThread) {
+        if (isSeek != -1) {
+            DBG("seeking to position %d\n", isSeek);
+            WavpackSeekSample(d.ctx, isSeek * d.sample_rate);
+            isSeek = -1;
+        }
+        if (paused == 0
+            && (mod.output->buffer_free() >=
+                (1152 * 2 *
+                 (16 / 8)) << (mod.output->buffer_playing()? 1 : 0))) {
+            status =
+                WavpackUnpackSamples(d.ctx, d.input, BUFFER_SIZE);
+            if (status == (unsigned) (-1)) {
+                printf("wavpack: Error decoding file.\n");
+                break;
+            }
+            else if (status == 0) {
+                killDecodeThread = true;
+                break;
+            }
+            else {
+                d.process_buffer(status);
+            }
+        }
+        else {
+            xmms_usleep(10000);
+        }
+    }
+    return end_thread();
+}
+
+static void
+wv_play(char *filename)
+{
+    paused = 0;
+    isSeek = -1;
+    killDecodeThread = false;
+    AudioError = false;
+    thread_handle = g_thread_create(DecodeThread, (void *) filename, TRUE, NULL);
+    return;
+}
+
+static char *
+generate_title(const char *fn, WavpackContext *ctx)
+{
+    static char *displaytitle = NULL;
+    ape_tag tag;
+    TitleInput *ti;
+
+    ti = (TitleInput *) g_malloc0(sizeof(TitleInput));
+    ti->__size = XMMS_TITLEINPUT_SIZE;
+    ti->__version = XMMS_TITLEINPUT_VERSION;
+
+    ti->file_name = g_strdup(g_basename(fn));
+    ti->file_ext = "wv";
+
+    load_tag(&tag, ctx);
+
+    // xmms doesn't support unicode...
+    ti->track_name = convertUTF8toLocale(tag.title);
+    ti->performer = convertUTF8toLocale(tag.artist);
+    ti->album_name = convertUTF8toLocale(tag.album);
+    ti->date = convertUTF8toLocale(tag.year);
+    ti->track_number = atoi(tag.track);
+    if (ti->track_number < 0)
+        ti->track_number = 0;
+    ti->year = atoi(tag.year);
+    if (ti->year < 0)
+        ti->year = 0;
+    ti->genre = convertUTF8toLocale(tag.genre);
+    ti->comment = convertUTF8toLocale(tag.comment);
+
+    displaytitle = xmms_get_titlestring(xmms_get_gentitle_format(), ti);
+    if (!displaytitle || *displaytitle == '\0'
+        || (strlen(tag.title) == 0 && strlen(tag.artist) == 0))
+        displaytitle = ti->file_name;
+    g_free(ti->track_name);
+    g_free(ti->performer);
+    g_free(ti->album_name);
+    g_free(ti->genre);
+    g_free(ti->comment);
+    g_free(ti);
+
+    return displaytitle;
+}
+
+static void
+wv_get_song_info(char *filename, char **title, int *length)
+{
+    assert(filename != NULL);
+    char error_buff[4096]; // TODO: fixme!
+    WavpackContext *ctx = WavpackOpenFileInput(filename, error_buff, OPEN_TAGS | OPEN_WVC, 0);
+    if (ctx == NULL) {
+        printf("wavpack: Error opening file: \"%s: %s\"\n", filename, error_buff);
+        return;
+    }
+    int sample_rate = WavpackGetSampleRate(ctx);
+    int num_channels = WavpackGetNumChannels(ctx);
+    DBG("reading %s at %d rate with %d channels\n", filename, sample_rate, num_channels);
+
+    *length = (int)(WavpackGetNumSamples(ctx) / sample_rate) * 1000,
+    *title = generate_title(filename, ctx);
+    DBG("title for %s = %s\n", filename, *title);
+    WavpackCloseFile(ctx);
+}
+
+static int
+wv_get_time(void)
+{
+    if (!mod.output)
+        return -1;
+    if (AudioError)
+        return -2;
+    if (killDecodeThread && !mod.output->buffer_playing())
+        return -1;
+    return mod.output->output_time();
+}
+
+
+static void
+wv_seek(int sec)
+{
+    isSeek = sec;
+    mod.output->flush((int) (1000 * isSeek));
+}
+
+static void
+wv_pause(short pause)
+{
+    mod.output->pause(paused = pause);
+}
+
+static void
+wv_stop(void)
+{
+    killDecodeThread = true;
+    if (thread_handle != 0) {
+        g_thread_join(thread_handle);
+        if (openedAudio) {
+            mod.output->buffer_free();
+            mod.output->close_audio();
+        }
+        openedAudio = false;
+        if (AudioError)
+            printf("Could not open Audio\n");
+    }
+
+}
+
+static void
+wv_load_config()
+{
+    ConfigDb *cfg;
+
+    cfg = bmp_cfg_db_open();
+
+    bmp_cfg_db_get_bool(cfg, "wavpack", "clip_prevention",
+                          &clipPreventionEnabled);
+    bmp_cfg_db_get_bool(cfg, "wavpack", "album_replaygain",
+                          &albumReplaygainEnabled);
+    bmp_cfg_db_get_bool(cfg, "wavpack", "dyn_bitrate", &dynBitrateEnabled);
+    bmp_cfg_db_get_bool(cfg, "wavpack", "replaygain", &replaygainEnabled);
+    bmp_cfg_db_close(cfg);
+
+    openedAudio = false;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/wavpack/tags.cxx	Tue Oct 24 19:03:53 2006 -0700
@@ -0,0 +1,537 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <wchar.h>
+#include <audacious/util.h>
+#include "tags.h"
+
+struct APETagFooterStruct {
+    unsigned char ID[8];
+    unsigned char Version[4];
+    unsigned char Length[4];
+    unsigned char TagCount[4];
+    unsigned char Flags[4];
+    unsigned char Reserved[8];
+};
+
+typedef struct {
+    char *key;
+    size_t keylen;
+    unsigned char *value;
+    size_t valuelen;
+    unsigned int flags;
+} TagItem;
+
+unsigned long
+Read_LE_Uint32(const unsigned char *p)
+{
+    return ((unsigned long) p[0] << 0) |
+        ((unsigned long) p[1] << 8) |
+        ((unsigned long) p[2] << 16) | ((unsigned long) p[3] << 24);
+}
+
+// Convert UTF-8 coded string to UNICODE
+// Return number of characters converted
+int
+utf8ToUnicode(const char *lpMultiByteStr, wchar_t * lpWideCharStr,
+              int cmbChars)
+{
+    const unsigned char *pmb = (unsigned char *) lpMultiByteStr;
+    unsigned short *pwc = (unsigned short *) lpWideCharStr;
+    const unsigned char *pmbe;
+    size_t cwChars = 0;
+
+    if (cmbChars >= 0) {
+        pmbe = pmb + cmbChars;
+    }
+    else {
+        pmbe = NULL;
+    }
+
+    while ((pmbe == NULL) || (pmb < pmbe)) {
+        char mb = *pmb++;
+        unsigned int cc = 0;
+        unsigned int wc;
+
+        while ((cc < 7) && (mb & (1 << (7 - cc)))) {
+            cc++;
+        }
+
+        if (cc == 1 || cc > 6)  // illegal character combination for UTF-8
+            continue;
+
+        if (cc == 0) {
+            wc = mb;
+        }
+        else {
+            wc = (mb & ((1 << (7 - cc)) - 1)) << ((cc - 1) * 6);
+            while (--cc > 0) {
+                if (pmb == pmbe)    // reached end of the buffer
+                    return cwChars;
+                mb = *pmb++;
+                if (((mb >> 6) & 0x03) != 2)    // not part of multibyte character
+                    return cwChars;
+                wc |= (mb & 0x3F) << ((cc - 1) * 6);
+            }
+        }
+
+        if (wc & 0xFFFF0000)
+            wc = L'?';
+        *pwc++ = wc;
+        cwChars++;
+        if (wc == L'\0')
+            return cwChars;
+    }
+
+    return cwChars;
+}
+
+void
+tag_insert(char *buffer, const char *value, long unsigned int len,
+           long unsigned int maxlen, bool decode_utf8)
+{
+    char *p;
+    wchar_t wValue[MAX_LEN];
+    char temp[MAX_LEN];
+    long unsigned int c;
+    const wchar_t *src = wValue;
+
+    if (len >= maxlen)
+        len = maxlen - 1;
+    if (decode_utf8) {
+        if ((c = utf8ToUnicode(value, wValue, len)) <= 0)
+            return;
+        if (wValue[c] != L'\0')
+            wValue[c++] = L'\0';
+        if ((c = wcsrtombs(temp, &src, MAX_LEN, NULL)) == 0)
+            return;
+    }
+    else {
+        c = len;
+        strncpy(temp, value, len);
+        while (temp[len - 1] == 0x20 || len < 1) {
+            len--;
+        }
+        temp[len] = '\0';
+    }
+
+    //if ( *buffer == '\0' ) {    // new value
+    p = buffer;
+    //} else {                    // append to existing value
+    //    p = strchr (buffer, '\0' );
+    //    p += sprintf ( p, ", " );
+    //}
+
+    if ((p - buffer) + c >= maxlen)
+        c = maxlen - (p - buffer) - 1;
+    strncpy(p, temp, c);
+    p[c] = '\0';
+}
+
+// Returns the Type of Tag (Ape or ID3)
+int
+GetTageType(FILE * fp)
+{
+    struct APETagFooterStruct T;
+    unsigned char tagheader[3];
+    int size;
+
+    if (fp == NULL) {
+        return TAG_NONE;
+    }
+
+    if (fseek(fp, 0, SEEK_END) != 0)
+        return TAG_NONE;
+    size = ftell(fp);
+    if (fseek(fp, size - sizeof T, SEEK_SET) != 0)
+        return TAG_NONE;
+    if (fread(&T, 1, sizeof T, fp) != sizeof T)
+        return TAG_NONE;
+    if (memcmp(T.ID, "APETAGEX", sizeof T.ID) == 0)
+        return TAG_APE;
+    if (fseek(fp, -128L, SEEK_END) != 0)
+        return TAG_NONE;
+    if (fread(tagheader, 1, 3, fp) != 3)
+        return TAG_NONE;
+    if (0 == memcmp(tagheader, "TAG", 3))
+        return TAG_ID3;
+    return TAG_NONE;
+}
+
+
+int
+ReadID3Tag(FILE * fp, ape_tag * Tag)
+{
+    char *tag;
+    char *buff;
+    unsigned int genre;
+
+    buff = (char *) malloc(128);
+
+    *(Tag->title) = '\0';
+    *(Tag->artist) = '\0';
+    *(Tag->album) = '\0';
+    *(Tag->comment) = '\0';
+    *(Tag->genre) = '\0';
+    *(Tag->track) = '\0';
+    *(Tag->year) = '\0';
+
+    if (fseek(fp, -128L, SEEK_END) != 0)
+        return 0;
+    if (fread(buff, 1, 128, fp) != 128)
+        return 0;
+    tag = buff;
+    tag_insert(Tag->title, (tag + 3), 30, 32, false);
+    tag_insert(Tag->artist, (tag + 33), 30, 32, false);
+    tag_insert(Tag->album, (tag + 63), 30, 32, false);
+    tag_insert(Tag->year, (tag + 93), 4, 32, false);
+    tag_insert(Tag->comment, (tag + 97), 30, 32, false);
+    genre = (unsigned char) tag[127];
+    if (genre >= sizeof(GenreList) / sizeof(int))
+        genre = 12;
+    tag_insert(Tag->genre, GenreList[genre], 30, 32, false);
+    sprintf(tag, "%u", tag[126]);
+    tag_insert(Tag->track, tag, 30, 32, false);
+    free(buff);
+    return 1;
+}
+
+// Reads APE v2.0 tag
+int
+ReadAPE2Tag(FILE * fp, ape_tag * Tag)
+{
+    unsigned long vsize;
+    unsigned long isize;
+    unsigned long flags;
+    unsigned char *buff;
+    unsigned char *p;
+    unsigned char *end;
+    struct APETagFooterStruct T;
+    unsigned long TagLen;
+    unsigned long TagCount;
+    long size;
+
+    *(Tag->title) = '\0';
+    *(Tag->artist) = '\0';
+    *(Tag->album) = '\0';
+    *(Tag->comment) = '\0';
+    *(Tag->genre) = '\0';
+    *(Tag->track) = '\0';
+    *(Tag->year) = '\0';
+
+    if (fseek(fp, 0, SEEK_END) != 0)
+        return 0;
+    size = ftell(fp);
+    if (fseek(fp, size - sizeof T, SEEK_SET) != 0)
+        return 0;
+    if (fread(&T, 1, sizeof T, fp) != sizeof T)
+        return 0;
+    if (memcmp(T.ID, "APETAGEX", sizeof T.ID) != 0)
+        return 0;
+    if (Read_LE_Uint32(T.Version) != 2000)
+        return 0;
+    TagLen = Read_LE_Uint32(T.Length);
+    if (TagLen < sizeof T)
+        return 0;
+    if (fseek(fp, size - TagLen, SEEK_SET) != 0)
+        return 0;
+    if ((buff = (unsigned char *) malloc(TagLen)) == NULL)
+        return 0;
+    if (fread(buff, 1, TagLen - sizeof T, fp) != TagLen - sizeof T) {
+        free(buff);
+        return 0;
+    }
+
+    TagCount = Read_LE_Uint32(T.TagCount);
+    end = buff + TagLen - sizeof(T);
+    for (p = buff; p < end && TagCount--;) {
+        vsize = Read_LE_Uint32(p);
+        p += 4;
+        flags = Read_LE_Uint32(p);
+        p += 4;
+        isize = strlen((char *) p);
+
+        if (isize > 0 && vsize > 0) {
+            if (!(flags & 1 << 1)) {    // insert UTF-8 string (skip binary values)
+                if (!strcasecmp((char *) p, "Title")) {
+                    tag_insert(Tag->title, (char *) (p + isize + 1), vsize,
+                               MAX_LEN, false);
+                }
+                else if (!strcasecmp((char *) p, "Artist")) {
+                    tag_insert(Tag->artist, (char *) (p + isize + 1), vsize,
+                               MAX_LEN, false);
+                }
+                else if (!strcasecmp((char *) p, "Album")) {
+                    tag_insert(Tag->album, (char *) (p + isize + 1), vsize,
+                               MAX_LEN, false);
+                }
+                else if (!strcasecmp((char *) p, "Comment")) {
+                    tag_insert(Tag->comment, (char *) (p + isize + 1), vsize,
+                               MAX_LEN, false);
+                }
+                else if (!strcasecmp((char *) p, "Genre")) {
+                    tag_insert(Tag->genre, (char *) (p + isize + 1), vsize,
+                               MAX_LEN, false);
+                }
+                else if (!strcasecmp((char *) p, "Track")) {
+                    tag_insert(Tag->track, (char *) (p + isize + 1), vsize,
+                               128, false);
+                }
+                else if (!strcasecmp((char *) p, "Year")) {
+                    tag_insert(Tag->year, (char *) (p + isize + 1), vsize,
+                               128, false);
+                }
+            }
+        }
+        p += isize + 1 + vsize;
+    }
+    free(buff);
+    return 1;
+}
+
+int
+DeleteTag(char *filename)
+{
+
+    FILE *fp = fopen(filename, "rb+");
+    int tagtype;
+    int fd;
+    long filelength = 0;
+    long dellength = -1;
+    char *tagheader;
+    unsigned long *apelength;
+    int res = -1;
+
+    if (fp == NULL) {
+        char text[256];
+
+        sprintf(text, "File \"%s\" not found or is read protected!\n",
+                filename);
+        xmms_show_message("File-Error", (gchar *) text, "Ok", FALSE, NULL,
+                          NULL);
+        return -1;
+    }
+    tagtype = GetTageType(fp);
+
+    // get Length of File
+    fseek(fp, 0L, SEEK_END);
+    filelength = ftell(fp);
+
+    apelength = (unsigned long *) malloc(4);
+    tagheader = (char *) malloc(9);
+
+    if (tagtype == TAG_ID3) {
+        dellength = 128L;
+    }
+    else if (tagtype == TAG_APE) {
+        fseek(fp, -32L, SEEK_END);
+        fread(tagheader, 8, 1, fp);
+        if (0 == memcmp(tagheader, "APETAGEX", 8)) {
+            fseek(fp, -20L, SEEK_END);
+            fread(apelength, 4, 1, fp);
+            dellength = *apelength + 32;
+        }
+    }
+
+
+    if (dellength > -1)         //if TAG was found, delete it 
+    {
+        fd = open(filename, O_RDWR);
+        res = ftruncate(fd, (off_t) (filelength - dellength));
+        close(fd);
+    }
+
+    free(tagheader);
+    free(apelength);
+
+    //returns 0 if everything is ok
+    return res;
+}
+
+// Returns bytes used in APE-Tag for this value
+int
+addValue(TagItem * item, char *key, char *value)
+{
+    item->keylen = strlen(key);
+    item->valuelen = strlen(value);
+    item->key = (char *) malloc(item->keylen + 1);
+    item->value = (unsigned char *) malloc(item->valuelen + 1);
+    strcpy((char *) item->value, value);
+    strcpy(item->key, key);
+    item->flags = 0;
+    return (9 + item->keylen + item->valuelen);
+}
+
+int
+WriteAPE2Tag(char *filename, ape_tag * Tag)
+{
+    FILE *fp;
+    unsigned char H[32] = "APETAGEX";
+    unsigned long Version = 2000;
+    unsigned char dw[8];
+    unsigned long estimatedbytes = 32;  // 32 byte footer + all items, these are the 32 bytes footer, the items are added later
+    long writtenbytes = -32;    // actually writtenbytes-32, which should be equal to estimatedbytes (= footer + all items)
+    unsigned int TagCount = 0;
+    TagItem T[7];
+
+
+    // Delete Tag if there is one
+    fp = fopen(filename, "rb+");
+    if (fp == NULL) {
+        char text[256];
+
+        sprintf(text, "File \"%s\" not found or is read protected!\n",
+                filename);
+        xmms_show_message("File-Error", (gchar *) text, "Ok", FALSE, NULL,
+                          NULL);
+        return -1;
+    }
+
+    int tagtype = GetTageType(fp);
+
+    if (tagtype != TAG_NONE)
+        if (DeleteTag(filename) != 0)
+            return 0;
+
+    // Produce TagItem-Array
+    if (strlen(Tag->title) > 0) {
+        char *value = (char *) malloc(strlen(Tag->title) + 1);
+
+        strcpy(value, Tag->title);
+        int res = addValue(&T[TagCount], "Title", value);
+
+        estimatedbytes += res;
+        if (res > 0)
+            TagCount++;
+        free(value);
+    }
+
+    if (strlen(Tag->artist) > 0) {
+        char *value = (char *) malloc(strlen(Tag->artist) + 1);
+
+        strcpy(value, Tag->artist);
+        int res = addValue(&T[TagCount], "Artist", value);
+
+        estimatedbytes += res;
+        if (res > 0)
+            TagCount++;
+        free(value);
+    }
+
+    if (strlen(Tag->album) > 0) {
+        char *value = (char *) malloc(strlen(Tag->album) + 1);
+
+        strcpy(value, Tag->album);
+        int res = addValue(&T[TagCount], "Album", value);
+
+        estimatedbytes += res;
+        if (res > 0)
+            TagCount++;
+        free(value);
+    }
+
+    if (strlen(Tag->comment) > 0) {
+        char *value = (char *) malloc(strlen(Tag->comment) + 1);
+
+        strcpy(value, Tag->comment);
+        int res = addValue(&T[TagCount], "Comment", value);
+
+        estimatedbytes += res;
+        if (res > 0)
+            TagCount++;
+        free(value);
+    }
+
+    if (strlen(Tag->genre) > 0) {
+        char *value = (char *) malloc(strlen(Tag->genre) + 1);
+
+        strcpy(value, Tag->genre);
+        int res = addValue(&T[TagCount], "Genre", value);
+
+        estimatedbytes += res;
+        if (res > 0)
+            TagCount++;
+        free(value);
+    }
+
+    if (strlen(Tag->track) > 0) {
+        char *value = (char *) malloc(strlen(Tag->track) + 1);
+
+        strcpy(value, Tag->track);
+        int res = addValue(&T[TagCount], "Track", value);
+
+        estimatedbytes += res;
+        if (res > 0)
+            TagCount++;
+        free(value);
+    }
+
+    if (strlen(Tag->year) > 0) {
+        char *value = (char *) malloc(strlen(Tag->year) + 1);
+
+        strcpy(value, Tag->year);
+        int res = addValue(&T[TagCount], "Year", value);
+
+        estimatedbytes += res;
+        if (res > 0)
+            TagCount++;
+        free(value);
+    }
+    // Start writing the new Ape2 Tag
+    fseek(fp, 0L, SEEK_END);
+
+    if (TagCount == 0) {
+        printf("no tag to write");
+        return 0;
+    }
+
+    if (estimatedbytes >= 8192 + 103) {
+        printf
+            ("\nTag is %.1f Kbyte long. This is longer than the maximum recommended 8 KByte.\n\a",
+             estimatedbytes / 1024.);
+        return 0;
+    }
+
+    H[8] = Version >> 0;
+    H[9] = Version >> 8;
+    H[10] = Version >> 16;
+    H[11] = Version >> 24;
+    H[12] = estimatedbytes >> 0;
+    H[13] = estimatedbytes >> 8;
+    H[14] = estimatedbytes >> 16;
+    H[15] = estimatedbytes >> 24;
+    H[16] = TagCount >> 0;
+    H[17] = TagCount >> 8;
+    H[18] = TagCount >> 16;
+    H[19] = TagCount >> 24;
+
+    H[23] = 0x80 | 0x20;
+    writtenbytes += fwrite(H, 1, 32, fp);
+
+    for (unsigned int i = 0; i < TagCount; i++) {
+        dw[0] = T[i].valuelen >> 0;
+        dw[1] = T[i].valuelen >> 8;
+        dw[2] = T[i].valuelen >> 16;
+        dw[3] = T[i].valuelen >> 24;
+        dw[4] = T[i].flags >> 0;
+        dw[5] = T[i].flags >> 8;
+        dw[6] = T[i].flags >> 16;
+        dw[7] = T[i].flags >> 24;
+        writtenbytes += fwrite(dw, 1, 8, fp);
+        writtenbytes += fwrite(T[i].key, 1, T[i].keylen, fp);
+        writtenbytes += fwrite("", 1, 1, fp);
+        if (T[i].valuelen > 0)
+            writtenbytes += fwrite(T[i].value, 1, T[i].valuelen, fp);
+    }
+
+    H[23] = 0x80;
+    writtenbytes += fwrite(H, 1, 32, fp);
+
+    if (estimatedbytes != (unsigned long) writtenbytes)
+        printf("\nError writing APE tag.\n");
+    fclose(fp);
+    TagCount = 0;
+    return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/wavpack/tags.h	Tue Oct 24 19:03:53 2006 -0700
@@ -0,0 +1,63 @@
+#ifndef _tags_h
+#define _tags_h
+
+#include <stdio.h>
+
+const int MAX_LEN = 2048;
+const int TAG_NONE = 0;
+const int TAG_ID3 = 1;
+const int TAG_APE = 2;
+
+typedef struct {
+    char    title           [MAX_LEN];
+    char    artist          [MAX_LEN];
+    char    album           [MAX_LEN];
+    char    comment         [MAX_LEN];
+    char    genre           [MAX_LEN];
+    char    track           [128];
+    char    year            [128];
+    int     _genre;
+} ape_tag;
+
+static const char*  GenreList [] = {
+    "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge",
+    "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B",
+    "Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska",
+    "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient",
+    "Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical",
+    "Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise",
+    "AlternRock", "Bass", "Soul", "Punk", "Space", "Meditative",
+    "Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic", "Darkwave",
+    "Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance", "Dream",
+    "Southern Rock", "Comedy", "Cult", "Gangsta", "Top 40", "Christian Rap",
+    "Pop/Funk", "Jungle", "Native American", "Cabaret", "New Wave",
+    "Psychadelic", "Rave", "Showtunes", "Trailer", "Lo-Fi", "Tribal",
+    "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll",
+    "Hard Rock", "Folk", "Folk/Rock", "National Folk", "Swing", "Fast-Fusion",
+    "Bebob", "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde",
+    "Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock",
+    "Slow Rock", "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour",
+    "Speech", "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony",
+    "Booty Bass", "Primus", "Porn Groove", "Satire", "Slow Jam", "Club",
+    "Tango", "Samba", "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul",
+    "Freestyle", "Duet", "Punk Rock", "Drum Solo", "A capella", "Euro-House",
+    "Dance Hall", "Goa", "Drum & Bass", "Club House", "Hardcore", "Terror",
+    "Indie", "BritPop", "NegerPunk", "Polsk Punk", "Beat", "Christian Gangsta",
+    "Heavy Metal", "Black Metal", "Crossover", "Contemporary C",
+    "Christian Rock", "Merengue", "Salsa", "Thrash Metal", "Anime", "JPop",
+    "SynthPop"
+};
+
+int utf8ToUnicode ( const char* lpMultiByteStr, wchar_t* lpWideCharStr, int cmbChars );
+
+int GetTageType ( FILE *fp );
+
+int DeleteTag ( char* filename);
+
+int WriteAPE2Tag ( char* fp, ape_tag *Tag );
+
+int ReadAPE2Tag ( FILE *fp, ape_tag *Tag );
+
+int ReadID3Tag ( FILE *fp, ape_tag *Tag );
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/wavpack/ui.cxx	Tue Oct 24 19:03:53 2006 -0700
@@ -0,0 +1,568 @@
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <wavpack/wputils.h>
+extern "C" {
+#include <audacious/plugin.h>
+#include <audacious/configdb.h>
+#include <audacious/titlestring.h>
+#include <audacious/util.h>
+}
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <iconv.h>
+#include <math.h>
+#include "tags.h"
+#ifndef M_LN10
+#define M_LN10   2.3025850929940456840179914546843642
+#endif
+
+#define DBG(format, args...) fprintf(stderr, format, ## args)
+
+void load_tag(ape_tag *tag, WavpackContext *ctx);
+gboolean clipPreventionEnabled;
+gboolean dynBitrateEnabled;
+gboolean replaygainEnabled;
+gboolean albumReplaygainEnabled;
+gboolean openedAudio;
+static GtkWidget *window = NULL;
+static GtkWidget *title_entry;
+static GtkWidget *album_entry;
+static GtkWidget *performer_entry;
+static GtkWidget *tracknumber_entry;
+static GtkWidget *date_entry;
+static GtkWidget *genre_entry;
+static GtkWidget *user_comment_entry;
+static char *filename;
+
+void
+wv_about_box()
+{
+    static GtkWidget *about_window;
+
+    if (about_window)
+        gdk_window_raise(about_window->window);
+
+    about_window =
+        xmms_show_message(g_strdup_printf
+                          ("Wavpack Decoder Plugin %s", VERSION),
+                          ("Plugin code by \n" "Miles Egan\n"
+                           "Adapted from xmms-musepack plugin by Lefungus\n"
+                           "Visit the Wavpack site at http://www.wavpack.com/\n"),
+                          ("Ok"), FALSE, NULL, NULL);
+    gtk_signal_connect(GTK_OBJECT(about_window), "destroy",
+                       GTK_SIGNAL_FUNC(gtk_widget_destroyed), &about_window);
+}
+
+static void
+label_set_text(GtkWidget * label, char *str, ...)
+{
+    va_list args;
+    gchar *tempstr;
+
+    va_start(args, str);
+    tempstr = g_strdup_vprintf(str, args);
+    va_end(args);
+
+    gtk_label_set_text(GTK_LABEL(label), tempstr);
+    g_free(tempstr);
+}
+
+static void
+remove_cb(GtkWidget * w, gpointer data)
+{
+    DeleteTag(filename);
+    g_free(filename);
+    gtk_widget_destroy(window);
+}
+
+static void
+save_cb(GtkWidget * w, gpointer data)
+{
+    ape_tag Tag;
+
+    strcpy(Tag.title, gtk_entry_get_text(GTK_ENTRY(title_entry)));
+    strcpy(Tag.artist, gtk_entry_get_text(GTK_ENTRY(performer_entry)));
+    strcpy(Tag.album, gtk_entry_get_text(GTK_ENTRY(album_entry)));
+    strcpy(Tag.comment, gtk_entry_get_text(GTK_ENTRY(user_comment_entry)));
+    strcpy(Tag.track, gtk_entry_get_text(GTK_ENTRY(tracknumber_entry)));
+    strcpy(Tag.year, gtk_entry_get_text(GTK_ENTRY(date_entry)));
+    strcpy(Tag.genre, gtk_entry_get_text(GTK_ENTRY(genre_entry)));
+    WriteAPE2Tag(filename, &Tag);
+    g_free(filename);
+    gtk_widget_destroy(window);
+}
+
+static void
+close_window(GtkWidget * w, gpointer data)
+{
+    g_free(filename);
+    gtk_widget_destroy(window);
+}
+
+void
+wv_file_info_box(char *fn)
+{
+    gchar *tmp;
+    gint time, minutes, seconds;
+    ape_tag tag;
+
+    assert(fn != NULL);
+    char error_buff[4096]; // TODO: fixme!
+    WavpackContext *ctx = WavpackOpenFileInput(fn, error_buff, OPEN_TAGS | OPEN_WVC, 0);
+    if (ctx == NULL) {
+        printf("wavpack: Error opening file: \"%s: %s\"\n", fn, error_buff);
+        return;
+    }
+    int sample_rate = WavpackGetSampleRate(ctx);
+    int num_channels = WavpackGetNumChannels(ctx);
+    load_tag(&tag, ctx);
+    DBG("opened %s at %d rate with %d channels\n", fn, sample_rate, num_channels);
+
+    filename = g_strdup(fn);
+    static GtkWidget *info_frame, *info_box, *bitrate_label, *rate_label;
+    static GtkWidget *version_label, *bits_per_sample_label;
+    static GtkWidget *channel_label, *length_label, *filesize_label;
+    static GtkWidget *peakTitle_label, *peakAlbum_label, *gainTitle_label;
+    static GtkWidget *gainAlbum_label, *filename_entry, *tag_frame;
+
+    if (!window) {
+        GtkWidget *hbox, *label, *filename_hbox, *vbox, *left_vbox;
+        GtkWidget *table, *bbox, *cancel_button;
+        GtkWidget *save_button, *remove_button;
+
+        window = gtk_window_new(GTK_WINDOW_DIALOG);
+        gtk_window_set_policy(GTK_WINDOW(window), FALSE, FALSE, FALSE);
+        gtk_signal_connect(GTK_OBJECT(window), "destroy",
+                           GTK_SIGNAL_FUNC(gtk_widget_destroyed), &window);
+        gtk_container_set_border_width(GTK_CONTAINER(window), 10);
+
+        vbox = gtk_vbox_new(FALSE, 10);
+        gtk_container_add(GTK_CONTAINER(window), vbox);
+
+        filename_hbox = gtk_hbox_new(FALSE, 5);
+        gtk_box_pack_start(GTK_BOX(vbox), filename_hbox, FALSE, TRUE, 0);
+
+        label = gtk_label_new("Filename:");
+        gtk_box_pack_start(GTK_BOX(filename_hbox), label, FALSE, TRUE, 0);
+        filename_entry = gtk_entry_new();
+        gtk_editable_set_editable(GTK_EDITABLE(filename_entry), FALSE);
+        gtk_box_pack_start(GTK_BOX(filename_hbox), filename_entry,
+                           TRUE, TRUE, 0);
+
+        hbox = gtk_hbox_new(FALSE, 10);
+        gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
+
+        left_vbox = gtk_vbox_new(FALSE, 10);
+        gtk_box_pack_start(GTK_BOX(hbox), left_vbox, FALSE, FALSE, 0);
+
+        tag_frame = gtk_frame_new("Ape2 Tag");
+        gtk_box_pack_start(GTK_BOX(left_vbox), tag_frame, FALSE, FALSE, 0);
+
+        table = gtk_table_new(5, 5, FALSE);
+        gtk_container_set_border_width(GTK_CONTAINER(table), 5);
+        gtk_container_add(GTK_CONTAINER(tag_frame), table);
+
+        label = gtk_label_new("Title:");
+        gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
+        gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1,
+                         GTK_FILL, GTK_FILL, 5, 5);
+
+        title_entry = gtk_entry_new();
+        gtk_table_attach(GTK_TABLE(table), title_entry, 1, 4, 0, 1,
+                         (GtkAttachOptions) (GTK_FILL | GTK_EXPAND |
+                                             GTK_SHRINK),
+                         (GtkAttachOptions) (GTK_FILL | GTK_EXPAND |
+                                             GTK_SHRINK), 0, 5);
+
+        label = gtk_label_new("Artist:");
+        gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
+        gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
+                         GTK_FILL, GTK_FILL, 5, 5);
+
+        performer_entry = gtk_entry_new();
+        gtk_table_attach(GTK_TABLE(table), performer_entry, 1, 4, 1, 2,
+                         (GtkAttachOptions) (GTK_FILL | GTK_EXPAND |
+                                             GTK_SHRINK),
+                         (GtkAttachOptions) (GTK_FILL | GTK_EXPAND |
+                                             GTK_SHRINK), 0, 5);
+
+        label = gtk_label_new("Album:");
+        gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
+        gtk_table_attach(GTK_TABLE(table), label, 0, 1, 2, 3,
+                         GTK_FILL, GTK_FILL, 5, 5);
+
+        album_entry = gtk_entry_new();
+        gtk_table_attach(GTK_TABLE(table), album_entry, 1, 4, 2, 3,
+                         (GtkAttachOptions) (GTK_FILL | GTK_EXPAND |
+                                             GTK_SHRINK),
+                         (GtkAttachOptions) (GTK_FILL | GTK_EXPAND |
+                                             GTK_SHRINK), 0, 5);
+
+        label = gtk_label_new("Comment:");
+        gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
+        gtk_table_attach(GTK_TABLE(table), label, 0, 1, 3, 4,
+                         GTK_FILL, GTK_FILL, 5, 5);
+
+        user_comment_entry = gtk_entry_new();
+        gtk_table_attach(GTK_TABLE(table), user_comment_entry, 1, 4, 3,
+                         4,
+                         (GtkAttachOptions) (GTK_FILL | GTK_EXPAND |
+                                             GTK_SHRINK),
+                         (GtkAttachOptions) (GTK_FILL | GTK_EXPAND |
+                                             GTK_SHRINK), 0, 5);
+
+        label = gtk_label_new("Year:");
+        gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
+        gtk_table_attach(GTK_TABLE(table), label, 0, 1, 4, 5,
+                         GTK_FILL, GTK_FILL, 5, 5);
+
+        date_entry = gtk_entry_new();
+        gtk_widget_set_usize(date_entry, 60, -1);
+        gtk_table_attach(GTK_TABLE(table), date_entry, 1, 2, 4, 5,
+                         (GtkAttachOptions) (GTK_FILL | GTK_EXPAND |
+                                             GTK_SHRINK),
+                         (GtkAttachOptions) (GTK_FILL | GTK_EXPAND |
+                                             GTK_SHRINK), 0, 5);
+
+        label = gtk_label_new("Track n°:");
+        gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
+        gtk_table_attach(GTK_TABLE(table), label, 2, 3, 4, 5,
+                         GTK_FILL, GTK_FILL, 5, 5);
+
+        tracknumber_entry = gtk_entry_new_with_max_length(4);
+        gtk_widget_set_usize(tracknumber_entry, 20, -1);
+        gtk_table_attach(GTK_TABLE(table), tracknumber_entry, 3, 4, 4,
+                         5,
+                         (GtkAttachOptions) (GTK_FILL | GTK_EXPAND |
+                                             GTK_SHRINK),
+                         (GtkAttachOptions) (GTK_FILL | GTK_EXPAND |
+                                             GTK_SHRINK), 0, 5);
+
+        label = gtk_label_new("Genre:");
+        gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
+        gtk_table_attach(GTK_TABLE(table), label, 0, 1, 5, 6,
+                         GTK_FILL, GTK_FILL, 5, 5);
+
+        genre_entry = gtk_entry_new();
+        gtk_widget_set_usize(genre_entry, 20, -1);
+        gtk_table_attach(GTK_TABLE(table), genre_entry, 1, 4, 5,
+                         6,
+                         (GtkAttachOptions) (GTK_FILL | GTK_EXPAND |
+                                             GTK_SHRINK),
+                         (GtkAttachOptions) (GTK_FILL | GTK_EXPAND |
+                                             GTK_SHRINK), 0, 5);
+
+        bbox = gtk_hbutton_box_new();
+        gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
+        gtk_button_box_set_spacing(GTK_BUTTON_BOX(bbox), 5);
+        gtk_box_pack_start(GTK_BOX(left_vbox), bbox, FALSE, FALSE, 0);
+
+        save_button = gtk_button_new_with_label("Save");
+        gtk_signal_connect(GTK_OBJECT(save_button), "clicked",
+                           GTK_SIGNAL_FUNC(save_cb), NULL);
+        GTK_WIDGET_SET_FLAGS(save_button, GTK_CAN_DEFAULT);
+        gtk_box_pack_start(GTK_BOX(bbox), save_button, TRUE, TRUE, 0);
+
+        remove_button = gtk_button_new_with_label("Remove Tag");
+        gtk_signal_connect_object(GTK_OBJECT(remove_button),
+                                  "clicked",
+                                  GTK_SIGNAL_FUNC(remove_cb), NULL);
+        GTK_WIDGET_SET_FLAGS(remove_button, GTK_CAN_DEFAULT);
+        gtk_box_pack_start(GTK_BOX(bbox), remove_button, TRUE, TRUE, 0);
+
+        cancel_button = gtk_button_new_with_label("Cancel");
+        gtk_signal_connect_object(GTK_OBJECT(cancel_button),
+                                  "clicked",
+                                  GTK_SIGNAL_FUNC(close_window), NULL);
+        GTK_WIDGET_SET_FLAGS(cancel_button, GTK_CAN_DEFAULT);
+        gtk_box_pack_start(GTK_BOX(bbox), cancel_button, TRUE, TRUE, 0);
+        gtk_widget_grab_default(cancel_button);
+
+        info_frame = gtk_frame_new("Wavpack Info:");
+        gtk_box_pack_start(GTK_BOX(hbox), info_frame, FALSE, FALSE, 0);
+
+        info_box = gtk_vbox_new(FALSE, 5);
+        gtk_container_add(GTK_CONTAINER(info_frame), info_box);
+        gtk_container_set_border_width(GTK_CONTAINER(info_box), 10);
+        gtk_box_set_spacing(GTK_BOX(info_box), 0);
+
+        version_label = gtk_label_new("");
+        gtk_misc_set_alignment(GTK_MISC(version_label), 0, 0);
+        gtk_label_set_justify(GTK_LABEL(version_label),
+                              GTK_JUSTIFY_LEFT);
+        gtk_box_pack_start(GTK_BOX(info_box), version_label, FALSE,
+                           FALSE, 0);
+
+        bits_per_sample_label = gtk_label_new("");
+        gtk_misc_set_alignment(GTK_MISC(bits_per_sample_label), 0, 0);
+        gtk_label_set_justify(GTK_LABEL(bits_per_sample_label), GTK_JUSTIFY_LEFT);
+        gtk_box_pack_start(GTK_BOX(info_box), bits_per_sample_label, FALSE, FALSE, 0);
+
+        bitrate_label = gtk_label_new("");
+        gtk_misc_set_alignment(GTK_MISC(bitrate_label), 0, 0);
+        gtk_label_set_justify(GTK_LABEL(bitrate_label), GTK_JUSTIFY_LEFT);
+        gtk_box_pack_start(GTK_BOX(info_box), bitrate_label, FALSE, FALSE, 0);
+
+        rate_label = gtk_label_new("");
+        gtk_misc_set_alignment(GTK_MISC(rate_label), 0, 0);
+        gtk_label_set_justify(GTK_LABEL(rate_label), GTK_JUSTIFY_LEFT);
+        gtk_box_pack_start(GTK_BOX(info_box), rate_label, FALSE, FALSE, 0);
+
+        channel_label = gtk_label_new("");
+        gtk_misc_set_alignment(GTK_MISC(channel_label), 0, 0);
+        gtk_label_set_justify(GTK_LABEL(channel_label), GTK_JUSTIFY_LEFT);
+        gtk_box_pack_start(GTK_BOX(info_box), channel_label, FALSE, FALSE, 0);
+
+        length_label = gtk_label_new("");
+        gtk_misc_set_alignment(GTK_MISC(length_label), 0, 0);
+        gtk_label_set_justify(GTK_LABEL(length_label), GTK_JUSTIFY_LEFT);
+        gtk_box_pack_start(GTK_BOX(info_box), length_label, FALSE, FALSE, 0);
+
+        filesize_label = gtk_label_new("");
+        gtk_misc_set_alignment(GTK_MISC(filesize_label), 0, 0);
+        gtk_label_set_justify(GTK_LABEL(filesize_label), GTK_JUSTIFY_LEFT);
+        gtk_box_pack_start(GTK_BOX(info_box), filesize_label, FALSE,
+                           FALSE, 0);
+
+        peakTitle_label = gtk_label_new("");
+        gtk_misc_set_alignment(GTK_MISC(peakTitle_label), 0, 0);
+        gtk_label_set_justify(GTK_LABEL(peakTitle_label), GTK_JUSTIFY_LEFT);
+        gtk_box_pack_start(GTK_BOX(info_box), peakTitle_label, FALSE,
+                           FALSE, 0);
+
+        peakAlbum_label = gtk_label_new("");
+        gtk_misc_set_alignment(GTK_MISC(peakAlbum_label), 0, 0);
+        gtk_label_set_justify(GTK_LABEL(peakAlbum_label), GTK_JUSTIFY_LEFT);
+        gtk_box_pack_start(GTK_BOX(info_box), peakAlbum_label, FALSE,
+                           FALSE, 0);
+
+        gainTitle_label = gtk_label_new("");
+        gtk_misc_set_alignment(GTK_MISC(gainTitle_label), 0, 0);
+        gtk_label_set_justify(GTK_LABEL(gainTitle_label), GTK_JUSTIFY_LEFT);
+        gtk_box_pack_start(GTK_BOX(info_box), gainTitle_label, FALSE,
+                           FALSE, 0);
+
+        gainAlbum_label = gtk_label_new("");
+        gtk_misc_set_alignment(GTK_MISC(gainAlbum_label), 0, 0);
+        gtk_label_set_justify(GTK_LABEL(gainAlbum_label), GTK_JUSTIFY_LEFT);
+        gtk_box_pack_start(GTK_BOX(info_box), gainAlbum_label, FALSE,
+                           FALSE, 0);
+
+        gtk_widget_show_all(window);
+    }
+    else
+        gdk_window_raise(window->window);
+
+    gtk_widget_set_sensitive(tag_frame, TRUE);
+
+    gtk_label_set_text(GTK_LABEL(version_label), "");
+    gtk_label_set_text(GTK_LABEL(bits_per_sample_label), "");
+    gtk_label_set_text(GTK_LABEL(bitrate_label), "");
+    gtk_label_set_text(GTK_LABEL(rate_label), "");
+    gtk_label_set_text(GTK_LABEL(channel_label), "");
+    gtk_label_set_text(GTK_LABEL(length_label), "");
+    gtk_label_set_text(GTK_LABEL(filesize_label), "");
+    gtk_label_set_text(GTK_LABEL(peakTitle_label), "");
+    gtk_label_set_text(GTK_LABEL(peakAlbum_label), "");
+    gtk_label_set_text(GTK_LABEL(gainTitle_label), "");
+    gtk_label_set_text(GTK_LABEL(gainAlbum_label), "");
+
+    time = WavpackGetNumSamples(ctx) / WavpackGetSampleRate(ctx);
+    minutes = time / 60;
+    seconds = time % 60;
+
+    label_set_text(version_label, "version %d", WavpackGetVersion(ctx));
+    label_set_text(bitrate_label, "average bitrate: %6.1f kbps", WavpackGetAverageBitrate(ctx, 0) / 1000);
+    label_set_text(rate_label, "samplerate: %d Hz", WavpackGetSampleRate(ctx));
+    label_set_text(bits_per_sample_label, "bits per sample: %d", WavpackGetBitsPerSample(ctx));
+    label_set_text(channel_label, "channels: %d", WavpackGetNumChannels(ctx));
+    label_set_text(length_label, "length: %d:%.2d", minutes, seconds);
+    label_set_text(filesize_label, "file size: %d Bytes", WavpackGetFileSize(ctx));
+    /*
+    label_set_text(peakTitle_label, "Title Peak: %5u", 100);
+    label_set_text(peakAlbum_label, "Album Peak: %5u", 100);
+    label_set_text(gainTitle_label, "Title Gain: %-+5.2f dB", 100.0);
+    label_set_text(gainAlbum_label, "Album Gain: %-+5.2f dB", 100.0);
+    */
+    label_set_text(peakTitle_label, "Title Peak: ?");
+    label_set_text(peakAlbum_label, "Album Peak: ?");
+    label_set_text(gainTitle_label, "Title Gain: ?");
+    label_set_text(gainAlbum_label, "Album Gain: ?");
+
+    gtk_entry_set_text(GTK_ENTRY(title_entry), tag.title);
+    gtk_entry_set_text(GTK_ENTRY(performer_entry), tag.artist);
+    gtk_entry_set_text(GTK_ENTRY(album_entry), tag.album);
+    gtk_entry_set_text(GTK_ENTRY(user_comment_entry), tag.comment);
+    gtk_entry_set_text(GTK_ENTRY(genre_entry), tag.genre);
+    gtk_entry_set_text(GTK_ENTRY(tracknumber_entry), tag.track);
+    gtk_entry_set_text(GTK_ENTRY(date_entry), tag.year);
+    gtk_entry_set_text(GTK_ENTRY(filename_entry), fn);
+    gtk_editable_set_position(GTK_EDITABLE(filename_entry), -1);
+
+    tmp = g_strdup_printf("File Info - %s", g_basename(fn));
+    gtk_window_set_title(GTK_WINDOW(window), tmp);
+    g_free(tmp);
+}
+
+static GtkWidget *wv_configurewin = NULL;
+static GtkWidget *vbox, *notebook;
+static GtkWidget *rg_switch, *rg_clip_switch, *rg_track_gain, *rg_dyn_bitrate;
+
+static void
+wv_configurewin_ok(GtkWidget * widget, gpointer data)
+{
+    ConfigDb *cfg;
+    GtkToggleButton *tb;
+
+    tb = GTK_TOGGLE_BUTTON(rg_switch);
+    replaygainEnabled = gtk_toggle_button_get_active(tb);
+    tb = GTK_TOGGLE_BUTTON(rg_clip_switch);
+    clipPreventionEnabled = gtk_toggle_button_get_active(tb);
+    tb = GTK_TOGGLE_BUTTON(rg_dyn_bitrate);
+    dynBitrateEnabled = gtk_toggle_button_get_active(tb);
+    tb = GTK_TOGGLE_BUTTON(rg_track_gain);
+    albumReplaygainEnabled = !gtk_toggle_button_get_active(tb);
+
+    cfg = bmp_cfg_db_open();
+
+    bmp_cfg_db_set_bool(cfg, "wavpack", "clip_prevention",
+                           clipPreventionEnabled);
+    bmp_cfg_db_set_bool(cfg, "wavpack", "album_replaygain",
+                           albumReplaygainEnabled);
+    bmp_cfg_db_set_bool(cfg, "wavpack", "dyn_bitrate", dynBitrateEnabled);
+    bmp_cfg_db_set_bool(cfg, "wavpack", "replaygain", replaygainEnabled);
+    bmp_cfg_db_close(cfg);
+    gtk_widget_destroy(wv_configurewin);
+}
+
+static void
+rg_switch_cb(GtkWidget * w, gpointer data)
+{
+    gtk_widget_set_sensitive(GTK_WIDGET(data),
+                             gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
+                                                          (w)));
+}
+
+void
+wv_configure(void)
+{
+
+    GtkWidget *rg_frame, *rg_vbox;
+    GtkWidget *bbox, *ok, *cancel;
+    GtkWidget *rg_type_frame, *rg_type_vbox, *rg_album_gain;
+
+    if (wv_configurewin != NULL) {
+        gdk_window_raise(wv_configurewin->window);
+        return;
+    }
+
+    wv_configurewin = gtk_window_new(GTK_WINDOW_DIALOG);
+    gtk_signal_connect(GTK_OBJECT(wv_configurewin), "destroy",
+                       GTK_SIGNAL_FUNC(gtk_widget_destroyed),
+                       &wv_configurewin);
+    gtk_window_set_title(GTK_WINDOW(wv_configurewin),
+                         "Musepack Configuration");
+    gtk_window_set_policy(GTK_WINDOW(wv_configurewin), FALSE, FALSE, FALSE);
+    gtk_container_border_width(GTK_CONTAINER(wv_configurewin), 10);
+
+    vbox = gtk_vbox_new(FALSE, 10);
+    gtk_container_add(GTK_CONTAINER(wv_configurewin), vbox);
+
+    notebook = gtk_notebook_new();
+    gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 0);
+
+
+    /* Plugin Settings */
+
+    rg_frame = gtk_frame_new("General Plugin Settings:");
+    gtk_container_border_width(GTK_CONTAINER(rg_frame), 5);
+
+    rg_vbox = gtk_vbox_new(FALSE, 10);
+    gtk_container_border_width(GTK_CONTAINER(rg_vbox), 5);
+    gtk_container_add(GTK_CONTAINER(rg_frame), rg_vbox);
+
+    rg_dyn_bitrate =
+        gtk_check_button_new_with_label("Enable Dynamic Bitrate Display");
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rg_dyn_bitrate),
+                                 dynBitrateEnabled);
+    gtk_box_pack_start(GTK_BOX(rg_vbox), rg_dyn_bitrate, FALSE, FALSE, 0);
+
+    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), rg_frame,
+                             gtk_label_new("Plugin"));
+
+    /* Replay Gain.. */
+
+    rg_frame = gtk_frame_new("ReplayGain Settings:");
+    gtk_container_border_width(GTK_CONTAINER(rg_frame), 5);
+
+    rg_vbox = gtk_vbox_new(FALSE, 10);
+    gtk_container_border_width(GTK_CONTAINER(rg_vbox), 5);
+    gtk_container_add(GTK_CONTAINER(rg_frame), rg_vbox);
+
+    rg_clip_switch =
+        gtk_check_button_new_with_label("Enable Clipping Prevention");
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rg_clip_switch),
+                                 clipPreventionEnabled);
+    gtk_box_pack_start(GTK_BOX(rg_vbox), rg_clip_switch, FALSE, FALSE, 0);
+
+    rg_switch = gtk_check_button_new_with_label("Enable ReplayGain");
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rg_switch),
+                                 replaygainEnabled);
+    gtk_box_pack_start(GTK_BOX(rg_vbox), rg_switch, FALSE, FALSE, 0);
+
+    rg_type_frame = gtk_frame_new("ReplayGain Type:");
+    gtk_box_pack_start(GTK_BOX(rg_vbox), rg_type_frame, FALSE, FALSE, 0);
+
+    gtk_signal_connect(GTK_OBJECT(rg_switch), "toggled",
+                       GTK_SIGNAL_FUNC(rg_switch_cb), rg_type_frame);
+
+    rg_type_vbox = gtk_vbox_new(FALSE, 5);
+    gtk_container_set_border_width(GTK_CONTAINER(rg_type_vbox), 5);
+    gtk_container_add(GTK_CONTAINER(rg_type_frame), rg_type_vbox);
+
+    rg_track_gain =
+        gtk_radio_button_new_with_label(NULL, "use Track Gain/Peak");
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rg_track_gain),
+                                 !albumReplaygainEnabled);
+    gtk_box_pack_start(GTK_BOX(rg_type_vbox), rg_track_gain, FALSE, FALSE, 0);
+
+    rg_album_gain =
+        gtk_radio_button_new_with_label(gtk_radio_button_group
+                                        (GTK_RADIO_BUTTON(rg_track_gain)),
+                                        "use Album Gain/Peak");
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rg_album_gain),
+                                 albumReplaygainEnabled);
+    gtk_box_pack_start(GTK_BOX(rg_type_vbox), rg_album_gain, FALSE, FALSE, 0);
+
+    gtk_widget_set_sensitive(rg_type_frame, replaygainEnabled);
+
+    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), rg_frame,
+                             gtk_label_new("ReplayGain"));
+
+    /* Buttons */
+
+    bbox = gtk_hbutton_box_new();
+    gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
+    gtk_button_box_set_spacing(GTK_BUTTON_BOX(bbox), 5);
+    gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0);
+
+    ok = gtk_button_new_with_label("Ok");
+    gtk_signal_connect(GTK_OBJECT(ok), "clicked",
+                       GTK_SIGNAL_FUNC(wv_configurewin_ok), NULL);
+    GTK_WIDGET_SET_FLAGS(ok, GTK_CAN_DEFAULT);
+    gtk_box_pack_start(GTK_BOX(bbox), ok, TRUE, TRUE, 0);
+    gtk_widget_grab_default(ok);
+
+    cancel = gtk_button_new_with_label("Cancel");
+    gtk_signal_connect_object(GTK_OBJECT(cancel), "clicked",
+                              GTK_SIGNAL_FUNC(gtk_widget_destroy),
+                              GTK_OBJECT(wv_configurewin));
+    GTK_WIDGET_SET_FLAGS(cancel, GTK_CAN_DEFAULT);
+    gtk_box_pack_start(GTK_BOX(bbox), cancel, TRUE, TRUE, 0);
+
+    gtk_widget_show_all(wv_configurewin);
+}