/***************************************************************
 *  Linux Musepack support for XMMS.
 *  E-Mail: thomas.juerges@astro.ruhr-uni-bochum.de
 ***************************************************************/

#define NCH             2
#define BPS            16
#define FRAMELEN        (36 * 32)
#define OTHER_SEEK

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <limits.h>
#include <string.h>
#include <math.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <glib.h>
#include <gtk/gtk.h>
#include <xmms/plugin.h>
#include <xmms/configfile.h>
#include <xmms/util.h>
#include "requant.h"
#include "huffsv46.h"
#include "huffsv7.h"
#include "bitstream.h"
#include "equalizer.h"
#include "mpc_dec.h"
#include "mpplus_blue.xpm"

#ifndef M_LN10
# define M_LN10   2.3025850929940456840179914546843642
#endif
#ifndef M_PI
# define M_PI     3.141592653589793238462643383276
#endif

extern int         EQ_Dezibel;

typedef struct {
    unsigned int  StreamVersion;
    unsigned int  Bitrate;
    unsigned int  Frames;
    unsigned int  MS;
    unsigned int  ByteLength;
    unsigned int  Profile;
    unsigned int  EncVersion;
    unsigned int  SampleRate;
} StreamInfo;

int                TrueGapless      = 0;
float              SAMPLERATE       = 2000;
unsigned int       Profile;
unsigned int       EncVersion;

void    Init_Tags        ( void );
int     FinalizeTags     ( FILE* fp, unsigned int Version );
int     addtag           ( const char* key, size_t keylen, const unsigned char* value, size_t valuelen, int converttoutf8, int flags );
int     gettag           ( const char* key, char* dst, size_t len );
int     CopyTags         ( const char* filename );

int     stderr_printf ( const char* format, ... ) { return 0; }


float  VV [2] [V_MEM + 960];
float  YY [2] [36] [32];

static const unsigned short
                      sftable [4] = { 44100, 48000, 37800, 32000 };

void
Reset_VV ( void )
{
    memset ( VV, 0, sizeof VV );
}

void
Reset_YY ( void )
{
    memset ( YY, 0, sizeof YY );
}

long             MPCHeaderPos = 0;
unsigned short*  SeekTable    = NULL;

extern unsigned int RecommendedResyncPos;


InputPlugin     mod;
InputPlugin*    get_iplugin_info   ( void );
static void     FileInfo           ( const char* );
static void     Config_dialog      ( void );
static void     config_ok          ( GtkWidget*, gpointer );
/*
static int      find_genre_id      ( const gchar* );
static void     remove_tag         ( GtkWidget*, gpointer );
static gint     genre_comp_func    ( gconstpointer, gconstpointer );
static void     get_entry_tag      ( GtkEntry*, gchar*, gint );
*/
static void     set_entry_tag      ( GtkEntry*, gchar*, gint );
static guint    entry_strip_spaces ( char*, size_t );
static void     get_tags           ( const char* );
static int      ReadFileHeader     ( const char*, StreamInfo* );
static int      getlength          ( void );
static int      getoutputtime      ( void );
static void     setoutputtime      ( const int );
static int      isourfile          ( char* );
static void     init               ( void );
static void     quit               ( void );
static void     stop               ( void );
static int      perform_jump       ( int*, int* );
static void     play               ( char* );
static void     setvolume          ( const int, const int );
static void     infoDlg            ( char* );
static void     getfileinfo        ( char*, char**, int* );
static void     config             ( void );
static void     about              ( void );
static void     Pause              ( const short );
static void     write_cfg_file     ( void );
static void     read_cfg_file      ( void );

static void     save_cb            ( GtkWidget*, gpointer );
static void*    DecodeThread       ( void* b );
static char*    extname            ( const char* );
static char*    eval_tag_format    ( const char*, const char*, const char*, const char*, const char*, const char*, const char* genre, const char* track );
static void     Equalizer_Setup    ( int on, float preamp_ctrl, float* eq_ctrl );


char               displayed_info [512];
static GtkWidget*  mp_info = NULL;
static GtkWidget*  mp_conf = NULL;
static GtkWidget*  g_bitrate;
static GtkWidget*  g_clipprev;
static GtkWidget*  g_displaytaginfo;
static GtkWidget*  g_usereplaygain;
static GtkWidget*  g_albummode;
static GtkWidget*  g_equalize;
static GtkWidget*  g_sennheiser;
static GtkWidget*  g_k401k501;
static GtkWidget*  titleformat_entry;
static GtkWidget*  filename_entry;
static GtkWidget*  title_entry;
static GtkWidget*  artist_entry;
static GtkWidget*  album_entry;
static GtkWidget*  year_entry;
static GtkWidget*  comment_entry;
static GtkWidget*  genre_entry;
/*static GtkWidget*  equalize_combo;
static GtkWidget*  colorize_combo;
static GList*      equalize_list;
static GList*      colorize_list;
*/
/*
static GtkWidget*  genre_combo;
static GList*      genre_list;
*/
static gchar*      current_filename;
static gboolean    tag_found = FALSE;
char               lastfilename  [PATH_MAX];    // currently playing file (used for getting info on the current file)
int                decode_pos_ms;               // current decoding position, in milliseconds
int                paused;                      // are we paused?
int                seek_needed;                 // if != -1, it is the point that the decode thread should seek to, in ms.
static char        sample_buffer [ (3*1152) * NCH * (BPS/8) ]; // sample buffer
char               INFOFN        [PATH_MAX];
char               INFO1         [  40];
char               INFO11        [  80];
char               INFO2         [  32];
char               INFO3         [  32];
char               INFO4         [  32];
char               INFO5         [  32];
char               INFO6         [  32];
char               INFO7         [  32];
char               TitleFormat   [1024] = "%1 / %3 %y -- [%0] %2";
int                i_clipprev       = 0;
int                i_albummode      = 0;
int                i_equalize       = 0;
int                i_sennheiser     = 0;
int                i_k401k501       = 0;
int                i_usereplaygain  = 1;
int                i_displaytaginfo = 1;
int                i_bitrate        = 0;
int                MaxBrokenFrames  = 8;
FILE*              inputFile        = NULL;
int                killDecodeThread = 0; // the kill switch for the decode thread
static pthread_t   thread_handle;

static char  artist  [1024] = "";
static char  album   [1024] = "";
static char  title   [1024] = "";
static char  comment [1024] = "";
static char  genre   [1024] = "";
static char  track   [  16] = "";
static char  year    [  32] = "";

const gchar*  HeadphoneList [] = {
    "(none)",
    "AKG K-401",
    "AKG K-501",
    "Sennheiser HD580",
    "Sennheiser HD600",
    "Sonus Faber Amati Hommage",
};

const gchar* GenreList [256] = { // nospam231
    "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",
    "Po""rn 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", "Ha""rd""co""re", "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"
};


InputPlugin  mod = {
    NULL,
    NULL,
    NULL,               // TJ: Description, copied in later when registering the plugin
    init,
    about,
    config,
    isourfile,
    NULL,
    play,
    stop,
    Pause,
    setoutputtime,
    Equalizer_Setup,
    getoutputtime,
    NULL,
    setvolume,
    quit,
    NULL,
    NULL,
    NULL,
    NULL,
    getfileinfo,
    infoDlg,            // TJ: file_info_box with editable tags and other groovy things :)
    NULL
};

static void
Equalizer_Setup ( int     on,
                  float   preamp_ctrl,
                  float*  eq_ctrl )
{
    param_eq_t  typ = linear;

    if ( i_equalize ) {
        if ( i_sennheiser )
            typ = i_k401k501  ?  sennheiser_hd600  :  sennheiser_hd580;
        else
            typ = i_k401k501  ?  akg_k501          :  akg_k401;
    }

    Do_Equalizer_Setup ( SAMPLERATE,
                         on, preamp_ctrl, eq_ctrl,
                         typ, linear );
}


static void
xmms_show_msg ( const char* title, const char* message )
{
    xmms_show_message ( (gchar*)title, (gchar*)message, "Ok", FALSE, NULL, NULL );
}


/* TJ: O.k. tell the world about our great plugin. */
InputPlugin*
get_iplugin_info ( void )
{
    mod.description = g_strdup_printf ( (".mpc Musepack Audio Player %s"), VERSION );
    return & mod;
}


static void
write_cfg_file ( void )
{
    ConfigFile*  config;
    gchar*       conf_filename;

    conf_filename = g_strconcat ( g_get_home_dir (), "/.xmms/config", NULL );
    config        = xmms_cfg_open_file ( conf_filename );
    if ( !config )
        config = xmms_cfg_new ();

    xmms_cfg_write_int    ( config, "Musepack", "EQdB"           , EQ_Dezibel      );
    xmms_cfg_write_int    ( config, "Musepack", "ClipPrevEnabled", i_clipprev      );
    xmms_cfg_write_int    ( config, "Musepack", "DisplayTagNames", i_displaytaginfo);
    xmms_cfg_write_int    ( config, "Musepack", "UseReplayGain"  , i_usereplaygain );
    xmms_cfg_write_int    ( config, "Musepack", "AlbumMode"      , i_albummode     );
    xmms_cfg_write_string ( config, "Musepack", "TitleFormating" , TitleFormat     );
    xmms_cfg_write_int    ( config, "Musepack", "UpdateBitrate"  , i_bitrate       );
    xmms_cfg_write_int    ( config, "Musepack", "MaxBrokenFrames", MaxBrokenFrames );
    xmms_cfg_write_file   ( config,  conf_filename );

    xmms_cfg_free ( config );

    g_free ( conf_filename );
}


static void
read_cfg_file ( void )
{
    ConfigFile*  config;
    gchar*       conf_filename;
    gchar*       dummy;

    conf_filename = g_strconcat ( g_get_home_dir (), "/.xmms/config", NULL );
    config        = xmms_cfg_open_file ( conf_filename );

    if ( config != NULL ) {
        dummy = g_strdup ( TitleFormat );
        xmms_cfg_read_int    ( config, "Musepack", "EQdB"           , (int*) &EQ_Dezibel      );
        xmms_cfg_read_int    ( config, "Musepack", "ClipPrevEnabled", (int*) &i_clipprev      );
        xmms_cfg_read_int    ( config, "Musepack", "DisplayTagNames", (int*) &i_displaytaginfo);
        xmms_cfg_read_int    ( config, "Musepack", "UseReplayGain"  , (int*) &i_usereplaygain );
        xmms_cfg_read_int    ( config, "Musepack", "AlbumMode"      , (int*) &i_albummode     );
        xmms_cfg_read_string ( config, "Musepack", "TitleFormating" , &dummy                  );
        xmms_cfg_read_int    ( config, "Musepack", "UpdateBitrate"  , (int*) &i_bitrate       );
        xmms_cfg_read_int    ( config, "Musepack", "MaxBrokenFrames", (int*) &MaxBrokenFrames );
        xmms_cfg_free        ( config );
        strncpy ( TitleFormat, dummy, sizeof TitleFormat );
    }

    g_free ( conf_filename );
}


/********************* FUNCTIONS *******************/
static int
ReadFileHeader ( const char* filename, StreamInfo* const Info )
{
    unsigned int  HeaderData [7];
    FILE*         fp;
    
    memset ( Info, 0, sizeof (*Info) );

    if ( (fp = fopen ( filename, "rb" )) == NULL )
        return 1;

    ReadLE32  ( fp, HeaderData, 7 );
    fseek  ( fp, 0L, SEEK_END );
    Info -> ByteLength = ftell (fp);
    fclose ( fp );

    if ( (HeaderData[0] & 0xFFFFFF) == 0x2B504D )
        Info -> StreamVersion = HeaderData[0] >> 24;

    if ( Info -> StreamVersion >= 7 ) {
        Info -> Frames        =             HeaderData [1];
        Info -> MS            =            (HeaderData [2] << 1) >> 31;
        Info -> Profile       =            (HeaderData [2] >> 20) & 15;
        Info -> SampleRate    =  sftable [ (HeaderData [2] >> 16) & 3];
        Info -> EncVersion    =             HeaderData [6] >> 24;
    }
    else {
        Info -> Bitrate       = (HeaderData [0] >> 23);
        Info -> Frames        =  HeaderData [1];
        Info -> SampleRate    =  44100;
        Info -> MS            = (HeaderData [0] << 10) >> 31;
        Info -> StreamVersion = (HeaderData [0] << 11) >> 22;
        if ( Info -> StreamVersion < 5 )
            Info -> Frames  >>= 16;
        if ( Info -> StreamVersion < 6 )    // Bugfix: last frame was invalid for up to SV5
            Info -> Frames--;
    }

    return 0;
}


static void
config ( void )
{
    Config_dialog  ();           // open dialog-box
    write_cfg_file ();
}


static void
about ( void )
{
    xmms_show_msg ( "About .mpc Musepack plugin",
                    ".mpc Musepack plugin " VERSION "\n"
                    "\n"
                    "  Homepage: http://sourceforge.net/projects/mpegplus/\n\n"
		    "  Contributions from:\nFrank Klemm Frank.Klemm@uni-jena.de\n"
                    "  See http://www.uni-jena.de/~pfk/mpp/\n"
		    "Tim-Philipp Mueller t.i.m@orang.net\n"
		    "Thomas Juerges thomas.a.juerges@ruhr-uni-bochum.de\n"
		    "Oliver Lemke oliver@uni-bremen.de\n"
		    "Dan Fruehauf malkodan@sf.net\n"
                    "\n"
                    "  - Plays Musepack / MPEGplus (*.mp+, *.mpp, *.mpc) encoded files.\n"
                    "  - Supports Gapless encoding\n"
                    "  - Supports ID3 V1.1 / APE V2.0 only\n"
                    "  - Supports fast EQ\n"
                    "  - Supports cross-fader plugins\n"
                    "\n"
                    "  For more information about the superb music\n"
                    "  encoder MPEGPlus contact the developer and author\n"
                    "  of the encoder, Andree Buschmann:\n"
                    "Andree.Buschmann@web.de\n\n"
		    "Current Maintainer : Dan Fruehauf\n"
                    "malkodan@sf.net\n\n");
    return;
}


static void
display_tag_button ( GtkWidget* widget, gpointer data )
{
    if ( GTK_TOGGLE_BUTTON (g_displaytaginfo) -> active ) {
        i_displaytaginfo = 1;
        strcpy ( TitleFormat, gtk_entry_get_text(GTK_ENTRY(titleformat_entry)) );
        if ( *lastfilename != '\0' )
            get_tags ( lastfilename );
    }
    else {
        i_displaytaginfo = 0;
    }

    gtk_widget_set_sensitive ( titleformat_entry, i_displaytaginfo );
}


static void
config_ok ( GtkWidget* widget, gpointer data )
{
    i_clipprev      = GTK_TOGGLE_BUTTON (g_clipprev      ) -> active  ?  1  :  0;
    i_usereplaygain = GTK_TOGGLE_BUTTON (g_usereplaygain ) -> active  ?  1  :  0;
    i_albummode     = GTK_TOGGLE_BUTTON (g_albummode     ) -> active  ?  1  :  0;
    i_bitrate       = GTK_TOGGLE_BUTTON (g_bitrate       ) -> active  ? 19  :  0;
    i_displaytaginfo= GTK_TOGGLE_BUTTON (g_displaytaginfo) -> active  ?  1  :  0;
    i_equalize      = GTK_TOGGLE_BUTTON (g_equalize      ) -> active  ?  1  :  0;
    i_sennheiser    = GTK_TOGGLE_BUTTON (g_sennheiser    ) -> active  ?  1  :  0;
    i_k401k501      = GTK_TOGGLE_BUTTON (g_k401k501      ) -> active  ?  1  :  0;

    if ( i_displaytaginfo ) {
        strcpy ( TitleFormat, gtk_entry_get_text ( GTK_ENTRY (titleformat_entry) ) );
        if ( *lastfilename != '\0' )
            get_tags ( lastfilename );
    }

    write_cfg_file ();
    gtk_widget_destroy ( mp_conf );
}


static void
Button ( GtkWidget* box, GtkWidget** g, int i, const char* name )
{
    *g = gtk_check_button_new_with_label ( name );
    gtk_box_pack_start                   ( GTK_BOX (box), *g, TRUE, TRUE, 0 );
    gtk_widget_show                      ( *g );
    if ( i )
        gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON (*g), TRUE );
}


static void
Config_dialog ( void )
{
    // Buttons:
    GtkWidget*  ok;
    GtkWidget*  cancel;
    GtkWidget*  mainbox;
    GtkWidget*  vbox;
    GtkWidget*  hbox;
    GtkWidget*  separator;
    GtkWidget*  logo_id;
    GtkWidget*  title_label;
    GtkWidget*  title_box;
    GtkWidget*  tag_description_label1;
    GtkWidget*  tag_description_label2;
    GtkWidget*  format_box;
    GdkPixmap*  logo;
    GdkBitmap*  mask;

    // Check if we've just opened the dialog window:
    if ( mp_conf ) {
        // If the config dialog is already active, bring it to front
        gdk_window_raise ( mp_conf -> window );
    }
    else {
        // No window until now
        // Create a new one
        mp_conf = gtk_window_new   ( GTK_WINDOW_DIALOG );
        gtk_object_set_data        ( GTK_OBJECT (mp_conf), "mp_conf", mp_conf );
        gtk_window_set_title       ( GTK_WINDOW (mp_conf), "Musepack Configuration" );
        gtk_window_set_position    ( GTK_WINDOW (mp_conf), GTK_WIN_POS_MOUSE );

        // We receive the window_close event. O.k., let's close the dialog
        // This is done for us by GTK
        gtk_signal_connect         ( GTK_OBJECT    (mp_conf), "destroy", GTK_SIGNAL_FUNC (gtk_widget_destroyed), &mp_conf );
        gtk_container_border_width ( GTK_CONTAINER (mp_conf), 10 );

        // Create maincontainer for all the visual stuff
        mainbox = gtk_vbox_new     ( FALSE, 0 );
        gtk_container_add          ( GTK_CONTAINER (mp_conf), mainbox );
        gtk_widget_show            ( mainbox );

        // Now we display the Musepack logo.
        logo    = gdk_pixmap_colormap_create_from_xpm_d ( NULL, gtk_widget_get_colormap (mainbox), &mask, NULL, (gchar**)mpplus_blue_xpm );
        logo_id = gtk_pixmap_new   ( logo, mask );
        gdk_pixmap_unref           ( logo );
        gdk_pixmap_unref           ( mask );
        gtk_widget_show            ( logo_id );
        gtk_container_add          ( GTK_CONTAINER (mainbox), logo_id );
        gtk_widget_show            ( logo_id );

        // Herein the checkboxes go:
        vbox = gtk_vbox_new        ( FALSE, 10 );
        gtk_box_pack_start         ( GTK_BOX (mainbox), vbox, TRUE, TRUE, 0 );
        gtk_widget_show            ( vbox );

        // Setup all the configurable stuff
        Button ( vbox, &g_bitrate       , i_bitrate       , "Display current bitrate instead of average bitrate"  );
        Button ( vbox, &g_clipprev      , i_clipprev      , "Do downscale to prevent clipping"                    );
        Button ( vbox, &g_usereplaygain , i_usereplaygain , "Use replay gain (title or album)"                    );
        Button ( vbox, &g_albummode     , i_albummode     , "Use album replay gain (when active)"                 );
        Button ( vbox, &g_displaytaginfo, i_displaytaginfo, "Display Tag information instead of file name"        );
        Button ( vbox, &g_equalize      , i_equalize      , "Equalize headphone"                                  );
        Button ( vbox, &g_sennheiser    , i_sennheiser    , "use Sennheiser (HD580/600) instead of AKG (K401/501)");
        Button ( vbox, &g_k401k501      , i_k401k501      , "Use HD600/K501 instead of HD580/K401"                );

        // the titleformat string gets a label, therefore create a new box to put the required stuff into
        title_box = gtk_hbox_new   ( FALSE, 5 );
        gtk_box_pack_start         ( GTK_BOX (vbox), title_box, FALSE, FALSE, 0 );
        title_label = gtk_label_new( "Titleformat:" );
        gtk_box_pack_start         ( GTK_BOX (title_box), title_label, FALSE, FALSE, 0 );
        gtk_widget_show            ( title_label );

        titleformat_entry = gtk_entry_new_with_max_length ( sizeof TitleFormat );
        gtk_entry_set_text         ( GTK_ENTRY (titleformat_entry), TitleFormat );
        gtk_widget_set_sensitive   ( titleformat_entry, i_displaytaginfo );
        gtk_box_pack_start         ( GTK_BOX (title_box), titleformat_entry, TRUE, TRUE, 0 );
        gtk_widget_show            ( titleformat_entry );
        gtk_widget_show            ( title_box );

        format_box = gtk_hbox_new  ( FALSE, 5 );
        gtk_box_pack_start         ( GTK_BOX (vbox), format_box, FALSE, FALSE, 0 );
        tag_description_label1 = gtk_label_new ( "%0 / %N: Track\n%2 / %T: Title\n%4 / %Y: Year\n%6 / %I : Genre\n%8        : File Path" );
        gtk_misc_set_alignment     ( GTK_MISC  (tag_description_label1), 0, 0 );
        gtk_label_set_justify      ( GTK_LABEL (tag_description_label1), GTK_JUSTIFY_LEFT );
        gtk_box_pack_start         ( GTK_BOX (format_box), tag_description_label1, TRUE, TRUE, 0 );
        gtk_widget_show            ( tag_description_label1 );
        tag_description_label2 = gtk_label_new ( "%1 / %A: Artist\n%3 / %C: Album\n%5 / %R: Comment\n%7        : File name\n%9        : File extension" );
        gtk_misc_set_alignment     ( GTK_MISC  (tag_description_label2), 0, 0 );
        gtk_label_set_justify      ( GTK_LABEL (tag_description_label2), GTK_JUSTIFY_LEFT );
        gtk_box_pack_start         ( GTK_BOX (format_box), tag_description_label2, TRUE, TRUE, 0 );
        gtk_widget_show            ( tag_description_label2 );
        gtk_widget_show            ( format_box );

#if 0
{
            int   i;
GtkWidget*  hbox = gtk_hbox_new    (FALSE, 10);
            gtk_box_pack_start     (GTK_BOX(vbox), hbox, FALSE, TRUE, 0);

{
GtkWidget*  left_vbox = gtk_vbox_new (FALSE, 10);
            gtk_box_pack_start     (GTK_BOX(hbox), left_vbox, FALSE, FALSE, 0);
{
GtkWidget*  tag_frame = gtk_frame_new ("Täg:");
            gtk_box_pack_start     (GTK_BOX(left_vbox), tag_frame, FALSE, FALSE, 0);
{
GtkWidget*  table = gtk_table_new  (4, 5, FALSE);
            gtk_container_set_border_width(GTK_CONTAINER(table), 5);
            gtk_container_add      (GTK_CONTAINER(tag_frame), table);


        equalize_combo = gtk_combo_new ();
        gtk_entry_set_editable (GTK_ENTRY(GTK_COMBO(equalize_combo) -> entry), FALSE);
        if ( !equalize_list ) {
            for ( i = 0; i < sizeof(HeadphoneList) / sizeof(*HeadphoneList); i++ )
                if ( GenreList[i] != NULL ) {
                    equalize_list = g_list_append ( equalize_list, (gchar*)HeadphoneList[i] );
                    equalize_list = g_list_sort   ( equalize_list, string_comp_func );
                }
        }
        gtk_combo_set_popdown_strings (GTK_COMBO(equalize_combo), equalize_list);

        gtk_table_attach (GTK_TABLE(table), equalize_combo, 3, 4, 4, 5, GTK_FILL | GTK_EXPAND | GTK_SHRINK, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 5);
        gtk_table_attach (GTK_TABLE(tag_frame), equalize_combo, 3, 4, 4, 5, GTK_FILL | GTK_EXPAND | GTK_SHRINK, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 5);
        gtk_table_attach (GTK_TABLE(left_vbox), equalize_combo, 3, 4, 4, 5, GTK_FILL | GTK_EXPAND | GTK_SHRINK, GTK_FILL | GTK_EXPAND |GTK_SHRINK, 0, 5);
        gtk_table_attach (GTK_TABLE(hbox), equalize_combo, 3, 4, 4, 5, GTK_FILL | GTK_EXPAND | GTK_SHRINK, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 5);
        gtk_table_attach (GTK_TABLE(vbox), equalize_combo, 3, 4, 4, 5, GTK_FILL | GTK_EXPAND | GTK_SHRINK, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 5);
        gtk_table_attach (GTK_TABLE(mainbox), equalize_combo, 3, 4, 4, 5, GTK_FILL | GTK_EXPAND | GTK_SHRINK, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 5);
        gtk_table_attach (GTK_TABLE(MusePack_conf), equalize_combo, 3, 4, 4, 5, GTK_FILL | GTK_EXPAND | GTK_SHRINK, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 5);
}}}}
#endif

        // The OK & Cancel buttons are separated from the config stuff
        separator = gtk_hseparator_new ();
        gtk_box_pack_start         ( GTK_BOX(vbox), separator, FALSE, TRUE, 0 );
        gtk_widget_show            ( separator );

        hbox=gtk_hbox_new          ( FALSE, 10 );
        gtk_box_pack_start         ( GTK_BOX (vbox), hbox, TRUE, TRUE, 5 );
        gtk_widget_show            ( hbox );

        gtk_signal_connect         ( GTK_OBJECT (g_displaytaginfo), "clicked", GTK_SIGNAL_FUNC (display_tag_button), NULL );// connect button tag to func. display_tag_button
        ok = gtk_button_new_with_label ( "Ok" );                                                        // Now setup an ok button.
        gtk_signal_connect         ( GTK_OBJECT (ok), "clicked", GTK_SIGNAL_FUNC (config_ok), NULL );             // If we press ok, let's go to function config_ok.
        gtk_box_pack_start         ( GTK_BOX    (hbox), ok, TRUE, TRUE, 0 );                                        // Add the ok button to our main dialog window.
        gtk_widget_show            ( ok );                                                                      // Show the ok button:


        cancel = gtk_button_new_with_label ( "Cancel" );                                                // Create the cancel button:
        gtk_signal_connect_object  ( GTK_OBJECT (cancel), "clicked",
                                     GTK_SIGNAL_FUNC (gtk_widget_destroy), GTK_OBJECT (mp_conf) );         // We press cancel -> close dialog.
        GTK_WIDGET_SET_FLAGS       ( cancel, GTK_CAN_DEFAULT );
        gtk_box_pack_start         ( GTK_BOX (hbox), cancel, TRUE, TRUE, 0 );                             // Add the cancel button to the dialog window.
        gtk_widget_show            ( cancel );                                                           // Display the cancel button.
        gtk_widget_grab_default    ( cancel );
        gtk_widget_show            ( mp_conf );                                                          // Setup of GTK dialog is done. Display it:
    }
}

/*
static int
genre_comp_func ( gconstpointer a, gconstpointer b )
{
    return strcasecmp (a, b);
}

static int
find_genre_id ( const gchar* text )
{
    int  i;

    if ( *text == '\0' )
        return -1;

    for ( i = 0; i < sizeof(GenreList)/sizeof(*GenreList); i++ )
        if ( GenreList[i] != NULL  &&  0 == strcmp ( GenreList[i], text) )
            return i;

    return -1;
}
*/

static void
set_entry_tag ( GtkEntry* entry, gchar* dummy_tag, gint length )
{
    gint    stripped_len;
    gchar*  text;

    stripped_len = entry_strip_spaces(dummy_tag, length);
    text = g_strdup_printf ( "%-*.*s", stripped_len, stripped_len, dummy_tag );
    gtk_entry_set_text ( entry, text);
    g_free ( text );
}


static unsigned int
entry_strip_spaces ( char *src, size_t n )
/* strips trailing spaces from string of length n returns length of adjusted string */
{
    gchar *space = NULL,    /* last space in src */
    *start = src;

    while ( n-- )
        switch ( *src++ ) {
        case '\0':
            n = 0;              // breaks out of while loop
            src--;
            break;
        case ' ':
            if ( space == NULL )
                space = src - 1;
            break;
        default:
            space = NULL;       // don't terminate intermediate spaces
            break;
        }

    if ( space != NULL ) {
        src = space;
        *src = '\0';
    }

    return src - start;
}


static char*
extname ( const char* filename )
{
    gchar *ext = strrchr ( filename, '.');

    if ( ext != NULL)
        ++ext;

    return ext;
}


size_t
utf8ncpy ( unsigned char* dst, const unsigned char* src, size_t len )
{
    unsigned char*        p      = dst;
    const unsigned char*  srcend = src + len;
    unsigned long         val;

    while ( src < srcend ) {
        if      ( (src[0] & 0x80) == 0x00 ) {
            val  = src[0];
            src += 1;
        }
        else if ( (src[0] & 0xE0) == 0xC0  &&  (src[1] & 0xC0) == 0x80 ) {
            val  = ((src[0] & 0x1F) <<  6)
                  +((src[1] & 0x3F) <<  0);
            src += 2;
        }
        else if ( (src[0] & 0xF0) == 0xE0  &&  (src[1] & 0xC0) == 0x80  &&  (src[2] & 0xC0) == 0x80 ) {
            val  = ((src[0] & 0x0F) << 12)
                  +((src[1] & 0x3F) <<  6)
                  +((src[2] & 0x3F) <<  0);
            src += 3;
        }
        else if ( (src[0] & 0xF8) == 0xF0  &&  (src[1] & 0xC0) == 0x80  &&  (src[2] & 0xC0) == 0x80  &&  (src[3] & 0xC0) == 0x80 ) {
            val  = ((src[0] & 0x07) << 18)
                  +((src[1] & 0x3F) << 12)
                  +((src[2] & 0x3F) <<  6)
                  +((src[3] & 0x3F) <<  0);
            src += 4;
        }
        else if ( (src[0] & 0xFC) == 0xF8  &&  (src[1] & 0xC0) == 0x80  &&  (src[2] & 0xC0) == 0x80  &&  (src[3] & 0xC0) == 0x80  &&  (src[4] & 0xC0) == 0x80 ) {
            val  = ((src[0] & 0x03) << 24)
                  +((src[1] & 0x3F) << 18)
                  +((src[2] & 0x3F) << 12)
                  +((src[3] & 0x3F) <<  6)
                  +((src[4] & 0x3F) <<  0);
            src += 5;
        }
        else if ( (src[0] & 0xFE) == 0xFC  &&  (src[1] & 0xC0) == 0x80  &&  (src[2] & 0xC0) == 0x80  &&  (src[3] & 0xC0) == 0x80  &&  (src[4] & 0xC0) == 0x80  &&  (src[5] & 0xC0) == 0x80 ) {
            val  = ((src[0] & 0x01) << 30)
                  +((src[1] & 0x3F) << 24)
                  +((src[2] & 0x3F) << 18)
                  +((src[3] & 0x3F) << 12)
                  +((src[4] & 0x3F) <<  6)
                  +((src[5] & 0x3F) <<  0);
            src += 6;
        }
        else {
            p += sprintf ( (char*)p, "[0x%X]", *src++ );
            continue;
        }

        if ( val != '\0'  &&  val != '\r'  &&  val < 0x100 )
            *p++ = val;
        else
            p += sprintf ( (char*)p, "<U+0x%lX>", val );
    }

    return p - dst;
}


static char*
eval_tag_format ( const char*  filename,
                  const char*  artist,
                  const char*  album,
                  const char*  title,
                  const char*  year,
                  const char*  comment,
                  const char*  genre,
                  const char*  track )
// returns tag data or filename as specified in format as in Nullsoft's Nitrane plugin v1.31b (their MPEG decoder)
{
    gchar*        format = TitleFormat;
    gchar*        ans;
    gchar         c;
    gchar*        base;
    gchar*        path;
    gchar*        ext;
    guint         length = 0;
    guint         allocated;
    guint         baselen;
    guint         pathlen;
    guint         extlen;
    guint         tmp;
    int           got_field = 0;
    const char*   field;
    char          tmp2 [16];

    ans     = g_malloc ( allocated = 1280                  );
    pathlen = strlen   ( path      = g_dirname  (filename) );
    base    = g_strdup (             g_basename (filename) );

    if ( ( ext = extname (base) ) == NULL ) {
        ext     = "";
        extlen  = 0;
    }
    else {
        ext[-1] = '\0';
        extlen  = strlen (ext);
    }
    baselen = strlen (base);

    while ( (c = *format++) != '\0' ) {
        tmp = 1;
        if ( c == '%' ) {
            switch ( *format++ ) {
            case '\0':
                format--;                       // otherwise we'll lose terminator
                // fall through
            case '%':
                ans[length] = '%';
                break;
            case '0': case 'N':
                field = track  ; goto copy;
            case '1': case 'A':
                field = artist ; goto copy;
            case '2': case 'T':
                field = title  ; goto copy;
            case '3': case 'C':
                field = album  ; goto copy;
            case '4': case 'Y':
                field = year   ; goto copy;
            case '5': case 'R':
                field = comment; goto copy;
            case '6': case 'I':
                field = genre  ; goto copy;

          copy: tmp = strlen(field);
                if ( tmp > 0 )
                    got_field++;
                printf ("\n%*.*s\n", (int)tmp, (int)tmp, field );
                tmp = utf8ncpy ( (unsigned char*)ans + length, (unsigned char*)field, tmp );
                printf ("%*.*s\n\n", (int)tmp, (int)tmp, ans + length );
                break;
            case 'y':
                if ( *year ) {
                    sprintf ( tmp2, " (%s)", year );
                    field = tmp2;
                    goto copy;
                }
                tmp = 0;
                break;
            case '7':
                strncpy ( ans + length, base, tmp = baselen );
                got_field++;
                break;
            case '8':
                strncpy ( ans + length, path, tmp = pathlen );
                got_field++;
                break;
            case '9':
                strncpy ( ans + length, ext , tmp = extlen  );
                got_field++;
                break;
            default:
                ans[length] = c;
                break;
            }
        }
        else {
            ans[length] = c;
        }

        length += tmp;
        if ( allocated - length <= 1024 )
            ans = g_realloc(ans, allocated *= 2);
    }
    ans[length] = '\0';


    ans = g_realloc ( ans, length + 1 );
    if ( got_field == 0 ) {
        g_free (ans);
        ans = g_strdup (base);
    }
    g_free (base);
    g_free (path);
    return ans;
}


static void
get_tags ( const char* filename )
{
    Init_Tags ();
    CopyTags  ( filename );

                                                 printf ("Filename=%s\n", filename);
    gettag ("Artist" , artist , sizeof artist ); printf ("Artist  =%s\n", artist  );
    gettag ("Album"  , album  , sizeof album  ); printf ("Album   =%s\n", album   );
    gettag ("Title"  , title  , sizeof title  ); printf ("Title   =%s\n", title   );
    gettag ("Comment", comment, sizeof comment); printf ("Comment =%s\n", comment );
    gettag ("Genre"  , genre  , sizeof genre  ); printf ("Genre   =%s\n", genre   );
    gettag ("Year"   , year   , sizeof year   ); printf ("Year    =%s\n", year    );
    gettag ("Track"  , track  , sizeof track  ); printf ("Track   =%s\n", track   );

    tag_found = *artist  &&  *album  &&  (*title || !*track);
    strcpy ( displayed_info, eval_tag_format ( filename, artist, album, title, year, comment, genre, track ) );
}

/*
static void
get_entry_tag ( GtkEntry* entry, gchar* tag, gint length )
{
    gchar *text;

    text = gtk_entry_get_text(entry);
    memset(tag, ' ', length);
    memcpy(tag, text, strlen(text) > length ? length : strlen(text));
}
*/

static void
save_cb ( GtkWidget* w, gpointer data )
{
/*
    int      fd;
    ID3V1_1  file_tag;

    fd = open ( current_filename, O_RDWR );

    if ( fd != -1 ) {
        lseek ( fd, -sizeof(ID3V1_1), SEEK_END );
        read  ( fd, &file_tag, sizeof(ID3V1_1) );

        if ( memcmp ( file_tag.tag, "TAG", 3 ) == 0 )
            lseek ( fd, -sizeof(ID3V1_1), SEEK_END );
        else
            lseek ( fd, 0, SEEK_END );

        file_tag.tag [0] = 'T';
        file_tag.tag [1] = 'A';
        file_tag.tag [2] = 'G';
        get_entry_tag ( GTK_ENTRY(title_entry  ), file_tag.title  , sizeof file_tag.title   );
        get_entry_tag ( GTK_ENTRY(artist_entry ), file_tag.artist , sizeof file_tag.artist  );
        get_entry_tag ( GTK_ENTRY(album_entry  ), file_tag.album  , sizeof file_tag.album   );
        get_entry_tag ( GTK_ENTRY(year_entry   ), file_tag.year   , sizeof file_tag.year    );
        get_entry_tag ( GTK_ENTRY(comment_entry), file_tag.comment, sizeof file_tag.comment );
        file_tag.genre = find_genre_id ( gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(genre_combo) -> entry)) );

        if ( write ( fd, &file_tag, sizeof(ID3V1_1) ) != sizeof(ID3V1_1) )
            xmms_show_msg ( "File Info", "Couldn't write tag!" );
        close (fd);
    }
    else {
        xmms_show_msg ( "File Info", "Couldn't write tag!" );
    }
*/
    gtk_widget_destroy ( mp_info );
}

/*
static void
remove_tag ( GtkWidget* w, gpointer data )
{

    int fd;
    int len;
    ID3V1_1 file_tag;

    fd = open ( current_filename, O_RDWR );
    if ( fd != -1 ) {
        len = lseek ( fd, -sizeof(ID3V1_1), SEEK_END );
        read ( fd, &file_tag, sizeof(ID3V1_1) );
        if ( 0 == memcmp(file_tag.tag, "TAG", 3) ) {
            if ( ftruncate (fd, len) )
                xmms_show_msg ( "File Info", "Couldn't remove tag!" );
        }
        else {
            xmms_show_msg ( "File Info", "No tag to remove!" );
        }
        close(fd);
    }
    else {
        xmms_show_msg ( "File Info", "Couldn't remove tag!" );
    }


    gtk_widget_destroy ( mp_info );
}
*/

static void
FileInfo ( const char* filename )
{
    GtkWidget*  mp_level=NULL;
    GtkWidget*  mp_bitrate=NULL;
    GtkWidget*  mp_samplerate=NULL;
    GtkWidget*  mp_flags=NULL;
    GtkWidget*  mp_fileinfo=NULL;
    GtkWidget*  mp_encoder=NULL;
    gchar*      tmp;
    gchar*      _title;
    //gint        i;

    get_tags(filename);

    if ( !mp_info ) {
        GtkWidget*  vbox;
        GtkWidget*  hbox;
        GtkWidget*  left_vbox;
        GtkWidget*  tag_frame;
        GtkWidget*  table;
        GtkWidget*  mp_frame;
        GtkWidget*  mp_box;
        GtkWidget*  label;
        GtkWidget*  filename_hbox;
        GtkWidget*  bbox;
        GtkWidget*  save;
        GtkWidget*  remove_tag;
        GtkWidget*  cancel;

        mp_info = gtk_window_new(GTK_WINDOW_DIALOG);
        gtk_window_set_policy  (GTK_WINDOW(mp_info), FALSE, FALSE, FALSE);
        gtk_signal_connect     (GTK_OBJECT(mp_info), "destroy", GTK_SIGNAL_FUNC(gtk_widget_destroyed), &mp_info);
        gtk_container_set_border_width(GTK_CONTAINER(mp_info), 10);

        vbox = gtk_vbox_new(FALSE, 10);
        gtk_container_add      (GTK_CONTAINER(mp_info), 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 ("Tags:");
        gtk_box_pack_start     (GTK_BOX(left_vbox), tag_frame, FALSE, FALSE, 0);

        table = gtk_table_new  (4, 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_with_max_length(sizeof title);
        gtk_table_attach       (GTK_TABLE(table), title_entry, 1, 4, 0, 1, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 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);

        artist_entry = gtk_entry_new_with_max_length(sizeof artist);
        gtk_table_attach       (GTK_TABLE(table), artist_entry, 1, 4, 1, 2, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 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_with_max_length(sizeof album);
        gtk_table_attach       (GTK_TABLE(table), album_entry, 1, 4, 2, 3, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 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);

        comment_entry = gtk_entry_new_with_max_length(sizeof comment);
        gtk_table_attach       (GTK_TABLE(table), comment_entry, 1, 4, 3, 4, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 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);

        year_entry = gtk_entry_new_with_max_length(sizeof year);
        gtk_widget_set_usize   (year_entry, 40, -1);
        gtk_table_attach       (GTK_TABLE(table), year_entry, 1, 2, 4, 5, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 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, 2, 3, 4, 5, GTK_FILL, GTK_FILL, 5, 5);

	genre_entry = gtk_entry_new_with_max_length(sizeof genre);
        gtk_widget_set_usize   (genre_entry, 40, -1);
        gtk_table_attach       (GTK_TABLE(table), genre_entry, 3, 4, 4, 5, GTK_FILL | GTK_EXPAND | GTK_SHRINK, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 5);



/*
        genre_combo = gtk_combo_new ();
        gtk_entry_set_editable (GTK_ENTRY(GTK_COMBO(genre_combo) -> entry), FALSE);
        if ( !genre_list ) {
            for ( i = 0; i < sizeof(GenreList) / sizeof(*GenreList); i++ )
                if ( GenreList[i] != NULL ) {
                    genre_list = g_list_append ( genre_list, (gchar*)GenreList[i] );
                    genre_list = g_list_sort   ( genre_list, genre_comp_func );
                }
        }
        gtk_combo_set_popdown_strings (GTK_COMBO(genre_combo), genre_list);

        gtk_table_attach (GTK_TABLE(table), genre_combo, 3, 4, 4, 5, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 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 = gtk_button_new_with_label ("Save");
        gtk_signal_connect     (GTK_OBJECT(save), "clicked", GTK_SIGNAL_FUNC(save_cb), NULL);
        GTK_WIDGET_SET_FLAGS   (save, GTK_CAN_DEFAULT);
        gtk_box_pack_start     (GTK_BOX(bbox), save, TRUE, TRUE, 0);
	gtk_widget_set_sensitive ( save, FALSE );	
        
        remove_tag = gtk_button_new_with_label ("Remove Tag");
        gtk_signal_connect     (GTK_OBJECT(remove_tag), "clicked", GTK_SIGNAL_FUNC(remove_tag), NULL);
        GTK_WIDGET_SET_FLAGS   (remove_tag, GTK_CAN_DEFAULT);
        gtk_box_pack_start     (GTK_BOX(bbox), remove_tag, TRUE, TRUE, 0);
	gtk_widget_set_sensitive ( remove_tag, FALSE );

        cancel = gtk_button_new_with_label("Cancel");
        gtk_signal_connect_object (GTK_OBJECT(cancel), "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy), GTK_OBJECT(mp_info));
        GTK_WIDGET_SET_FLAGS   (cancel, GTK_CAN_DEFAULT);
        gtk_box_pack_start     (GTK_BOX(bbox), cancel, TRUE, TRUE, 0);
	gtk_widget_grab_default(cancel);

        mp_frame = gtk_frame_new ("Musepack Info:");
        gtk_box_pack_start     (GTK_BOX(hbox), mp_frame, FALSE, FALSE, 0);

        mp_box = gtk_vbox_new  (FALSE, 5);
        gtk_container_add      (GTK_CONTAINER(mp_frame), mp_box);
        gtk_container_set_border_width (GTK_CONTAINER(mp_box), 10);
        gtk_box_set_spacing    (GTK_BOX(mp_box), 0);

        mp_level = gtk_label_new ("");
        gtk_widget_set_usize   (mp_level, 250, -2);
        gtk_misc_set_alignment (GTK_MISC(mp_level), 0, 0);
        gtk_box_pack_start     (GTK_BOX(mp_box), mp_level, FALSE, FALSE, 0);

        mp_encoder = gtk_label_new ("");
        gtk_widget_set_usize   (mp_encoder, 250, -2);
        gtk_misc_set_alignment (GTK_MISC(mp_encoder), 0, 0);
        gtk_box_pack_start     (GTK_BOX(mp_box), mp_encoder, FALSE, FALSE, 0);

        mp_bitrate = gtk_label_new ("");
        gtk_misc_set_alignment (GTK_MISC(mp_bitrate), 0, 0);
        gtk_label_set_justify  (GTK_LABEL(mp_bitrate), GTK_JUSTIFY_LEFT);
        gtk_box_pack_start     (GTK_BOX(mp_box), mp_bitrate, FALSE, FALSE, 0);

        mp_samplerate = gtk_label_new ("");
        gtk_misc_set_alignment (GTK_MISC(mp_samplerate), 0, 0);
        gtk_box_pack_start     (GTK_BOX(mp_box), mp_samplerate, FALSE, FALSE, 0);

        mp_flags = gtk_label_new ("");
        gtk_misc_set_alignment (GTK_MISC(mp_flags), 0, 0);
        gtk_label_set_justify  (GTK_LABEL(mp_flags), GTK_JUSTIFY_LEFT);
        gtk_box_pack_start     (GTK_BOX(mp_box), mp_flags, FALSE, FALSE, 0);

        mp_fileinfo = gtk_label_new ("");
        gtk_misc_set_alignment (GTK_MISC(mp_fileinfo), 0, 0);
        gtk_label_set_justify  (GTK_LABEL(mp_fileinfo), GTK_JUSTIFY_LEFT);
        gtk_box_pack_start     (GTK_BOX(mp_box), mp_fileinfo, FALSE, FALSE, 0);

        gtk_widget_show_all    (mp_info);
    }

    if ( current_filename)
        g_free (current_filename);
    current_filename = g_strdup (filename);

    _title = g_strdup_printf("File Info - %s", g_basename(filename));
    gtk_window_set_title(GTK_WINDOW(mp_info), _title);
    g_free(_title);

    gtk_entry_set_text(GTK_ENTRY(filename_entry), current_filename);
    gtk_editable_set_position(GTK_EDITABLE(filename_entry), -1);

    _title = g_strdup(g_basename(current_filename));
    if ( (tmp = strrchr(_title, '.')) != NULL)
        *tmp = '\0';
    gtk_entry_set_text(GTK_ENTRY(title_entry), _title);
    g_free(_title);

    gtk_entry_set_text  ( GTK_ENTRY(artist_entry ), "" );
    gtk_entry_set_text  ( GTK_ENTRY(album_entry  ), "" );
    gtk_entry_set_text  ( GTK_ENTRY(year_entry   ), "" );
    gtk_entry_set_text  ( GTK_ENTRY(comment_entry), "" );
    gtk_entry_set_text  ( GTK_ENTRY(genre_entry  ), "" );
    //gtk_list_select_item( GTK_LIST (GTK_COMBO(genre_combo) -> list), g_list_index(genre_list, "") );
    gtk_label_set_text  ( GTK_LABEL(mp_level     ), INFO1  );
    gtk_label_set_text  ( GTK_LABEL(mp_encoder   ), INFO11 );
    gtk_label_set_text  ( GTK_LABEL(mp_bitrate   ), INFO2  );
    gtk_label_set_text  ( GTK_LABEL(mp_samplerate), INFO3  );
    gtk_label_set_text  ( GTK_LABEL(mp_flags     ), INFO4  );
    gtk_label_set_text  ( GTK_LABEL(mp_fileinfo  ), INFO5  );

    if ( tag_found ) {
        set_entry_tag       ( GTK_ENTRY (title_entry  ), title  , sizeof title   );
        set_entry_tag       ( GTK_ENTRY (artist_entry ), artist , sizeof artist  );
        set_entry_tag       ( GTK_ENTRY (album_entry  ), album  , sizeof album   );
        set_entry_tag       ( GTK_ENTRY (year_entry   ), year   , sizeof year    );
        set_entry_tag       ( GTK_ENTRY (comment_entry), comment, sizeof comment );
	set_entry_tag       ( GTK_ENTRY (genre_entry  ), genre  , sizeof genre   );
        //gtk_list_select_item( GTK_LIST  (GTK_COMBO(genre_combo) -> list), g_list_index(genre_list, (gchar *)genre) );
    }
}


static void
init ( void )
{
    read_cfg_file       ();
    Init_QuantTab       ();
    Huffman_SV6_Decoder ();
    Huffman_SV7_Decoder ();
}


static void
quit ( void )
{
    /* one-time deinit, such as memory freeing */
    if ( inputFile != NULL ) {
        fclose ( inputFile );
        inputFile = NULL;
    }
    if ( mod.output )
        mod.output -> close_audio ();
}


static int
isourfile ( char* filename )
{
    char*  ext = strrchr ( filename, '.' );

    if ( ext != NULL ) {
        if ( !strcasecmp (ext, ".mp+")  ||
             !strcasecmp (ext, ".mpp")  ||
             !strcasecmp (ext, ".mpc") )
            return TRUE;
    }

    return FALSE;
}

/* used for detecting URL streams.. unused here. strncmp(filename,"http://",7) to detect HTTP streams, etc */
static void
play ( char* filename )
{
    float             ClipPrevFactor = 1.0f;
    float             ReplayGain     = 1.0f;
    int               maxlatency;
    unsigned short    PCM_Peak   = 0;
    unsigned short    Title_Peak = 0;
    short             Title_Gain = 0;
    unsigned short    Album_Peak = 0;
    short             Album_Gain = 0;
    unsigned short    Peak = 0;
    short             Gain = 0;
    int               VBR_Bitrate;

    RESET_Globals   ();
    RESET_Synthesis ();

    inputFile = fopen ( filename, "rb" );
    if ( inputFile == NULL ) {
        //char  text [256];
	// uncomment these if you like the annoying messages when the file cannot be read.
        //sprintf ( text, "File \"%s\" not found or is read protected!\n", filename );
        //xmms_show_msg ( "ERROR: file-info()", text );
        return;
    }
    setvbuf ( inputFile, NULL, _IONBF, 0 );

    ReadLE32 ( inputFile, Speicher, MEMSIZE );

    if ( (Speicher[0] & 0xFFFFFF) == 0x2B504D )
        StreamVersion = Speicher[0] >> 24;

    if ( StreamVersion >= 7 ) {
        unsigned int  dummy;
        unsigned int  IS_Flag;

        dword = Speicher [Zaehler];

        (void) Bitstream_read (32);
        OverallFrames    =      Bitstream_read (32);

        IS_Flag          =      Bitstream_read ( 1);
        MS_used          =      Bitstream_read ( 1);
        Max_Band         = (int)Bitstream_read ( 6);

        Profile          =      Bitstream_read ( 4);
        (void) Bitstream_read ( 2);
        SAMPLERATE = sftable [ Bitstream_read ( 2) ];

        PCM_Peak         =      Bitstream_read (16);
        Title_Gain       =      Bitstream_read (16);
        Title_Peak       =      Bitstream_read (16);
        Album_Gain       =      Bitstream_read (16);
        Album_Peak       =      Bitstream_read (16);

        if ( Title_Peak == 0)
            Title_Peak = 1.18 * PCM_Peak;
        if ( Album_Peak == 0)
            Album_Peak = Title_Peak;
        Peak = i_albummode  ?  Album_Peak  :  Title_Peak;
        Gain = i_albummode  ?  Album_Gain  :  Title_Gain;

        ClipPrevFactor   = 32767. / ( Peak + 1. );  // avoid divide by 0
        ReplayGain       = exp ( (M_LN10/2000.) * Gain );

        TrueGapless      =      Bitstream_read ( 1);
        dummy            =      Bitstream_read (11);
        dummy            =      Bitstream_read (20);

        EncVersion       =      Bitstream_read ( 8);

        // modes not supported anymore
        if ( IS_Flag != 0 ) {
            xmms_show_msg ( "ERROR: function play()", "Files uses Intensity Stereo, not supported aynmore!\nPlease decode with command-line tool.\n" );
            fclose (inputFile);
            inputFile = NULL;
            return;
        }
    }
    else {
        unsigned int  Blockgroesse;
        unsigned int  Bitrate;
        unsigned int  IS_Flag;

        // initialize bitstream-decoder (SV6 and below)
        dword = Speicher[Zaehler];

        // read the file-header
        Bitrate          =       Bitstream_read (  9 );
        IS_Flag          =       Bitstream_read (  1 );
        MS_used          =       Bitstream_read (  1 );
        StreamVersion    =       Bitstream_read ( 10 );
        Max_Band         = (int) Bitstream_read (  5 );
        Blockgroesse     =       Bitstream_read (  6 );
        OverallFrames    =       Bitstream_read ( StreamVersion > 4  ?  32  :  16 );

        Profile          =       0;
        EncVersion       =       0;
	SAMPLERATE       =   44100.;

        // modes not supported anymore
        if ( StreamVersion == 7 ) {
            xmms_show_msg ( "ERROR: function play()", "SV7-preview: not supported." );
            fclose ( inputFile );
            inputFile = NULL;
            return;
        }
        if ( Blockgroesse != 1 ) {
            xmms_show_msg ( "ERROR: function play()", "Superframe-size != 1: not supported anymore.\nPlease decode with command-line tool!\n" );
            fclose ( inputFile );
            inputFile = NULL;
            return;
        }
        if ( Bitrate != 0 ) {
            xmms_show_msg ( "ERROR: function play()", "CBR-file: not supported aynmore.\nPlease decode with command-line tool!\n" );
            fclose ( inputFile );
            inputFile = NULL;
            return;
        }
        if ( IS_Flag != 0 ) {
            xmms_show_msg ( "ERROR: function play()", "Files uses Intensity Stereo, which not supported aynmore.\nPlease decode with command-line tool!\n" );
            fclose ( inputFile );
            inputFile = NULL;
            return;
        }
    }

    if ( SeekTable != NULL )
        free (SeekTable);
    SeekTable       = calloc ( sizeof (unsigned short), OverallFrames+64 );

    if ( !i_usereplaygain )
        ReplayGain = 1.0f;

    if ( i_clipprev  &&  (ClipPrevFactor < ReplayGain) ) {
        ClipPrev ( ClipPrevFactor );
    }
    else {
        ClipPrev ( ReplayGain );
    }

    // Bugfix: last frame was possibly invalid for all StreamVersions < 6
    if ( StreamVersion < 6 )
        OverallFrames --;

    // AB: no valid file
    if ( StreamVersion != 4  &&  StreamVersion != 5  &&  StreamVersion != 6  &&  StreamVersion != 7  &&  StreamVersion != 0x17 ) {
        char  tmp [512];
        sprintf ( tmp, "Invalid or unknown Musepack bitstream: %u.%u\nMay be you should update Musepack plugin\nsee: http://www.uni-jena.de/~pfk/mpc/", StreamVersion & 15, StreamVersion >> 4 );
        xmms_show_msg ( "ERROR: function play()", tmp );
        fclose ( inputFile );
        inputFile = NULL;
        return;
    }

    // setting some globals
    strcpy ( lastfilename, filename );
    paused        = 0;
    decode_pos_ms = 0;
    seek_needed   =-1;

    get_tags ( lastfilename );

    // opening sound-device
    maxlatency = mod.output -> open_audio ( FMT_S16_LE, SAMPLERATE, NCH );
    if ( maxlatency < 0 ) {             // cannot open sound device
        fclose (inputFile);
        inputFile = NULL;
        return;
    }

    if ( i_bitrate == 0 ) {
        int  bytelength;
        int  pos = ftell (inputFile);

        fseek ( inputFile, 0, SEEK_END );
        bytelength = ftell (inputFile);

        fseek ( inputFile, pos, SEEK_SET );

        VBR_Bitrate = 1000 * (int) ( 8.*bytelength / (OverallFrames*1152.e3/SAMPLERATE) + 0.5f );
    }
    else {
        VBR_Bitrate = 0;
    }

    mod.set_info ( i_displaytaginfo  &&  tag_found  ?  displayed_info  :  NULL,
                   getlength (),
                   VBR_Bitrate,
                   SAMPLERATE / 1000,
                   NCH );

    // create decoding thread
    killDecodeThread = 0;
    pthread_create ( &thread_handle, NULL, DecodeThread, NULL );
    return;
}


static void
Pause ( short pause )
{
    mod.output -> pause ( paused = pause );
}


static void
stop ( void )
{
    if ( inputFile != NULL ) {
        fclose ( inputFile );
        inputFile = NULL;
    }
    if( thread_handle != 0 )
    {
    	killDecodeThread = 1;
    	pthread_join ( thread_handle, NULL );
    	mod.output -> close_audio ();
    }
}


static int
getlength ( void )
{
    return (int) ( OverallFrames * 1152.e3 / SAMPLERATE + 0.5f );
}


static int
getoutputtime ( void )
{
    if ( !inputFile )
        return -1;

    if ( !mod.output )
        return -1;

    if ( killDecodeThread  &&  !mod.output -> buffer_playing() )
        return -1;

    return decode_pos_ms + (mod.output -> output_time() - mod.output -> written_time() );
}


static void
setoutputtime ( int sec )
{
    seek_needed = 1000 * sec;
}


static void
setvolume ( int lvolume, int rvolume )
{
    mod.output -> set_volume ( lvolume, rvolume );
}


static const char*
Stringify ( unsigned int profile )            // profile is 0...15, where 7...13 is used
{
    static const char   na    [] = "n.a.";
    static const char*  Names [] = {
        na        , "Unstable/Experimental", na                 , na                 ,
        na        , "below 'Telephone'"    , "below 'Telephone'", "'Telephone'"      ,
        "'Thumb'" , "'Radio'"              , "'Standard'"       , "'Xtreme'"         ,
        "'Insane'", "'BrainDead'"          , "above 'BrainDead'", "above 'BrainDead'",
    };

    return profile >= sizeof(Names)/sizeof(*Names)  ?  na  :  Names [profile];
}


static const char*
StringifyEnc ( unsigned int version )
{
    static char  buff [80];

    if ( version == 0 ) {
        sprintf ( buff, "Buschmann 1.7.0...9\n          Klemm 0.90...1.05" );
    }
    else {
        switch ( version % 10 ) {
        case 0:
            sprintf ( buff, "Release %u.%u", version/100, version/10%10 );
            break;
        case 2: case 4: case 6: case 8:
            sprintf ( buff, "Beta %u.%02u", version/100, version%100 );
            break;
        default:
            sprintf ( buff, "--Alpha-- %u.%02u", version/100, version%100 );
            break;
        }
    }

    return buff;
}


static void
infoDlg ( char* filename )
{
    StreamInfo    Info;
    unsigned int  Milliseconds;
    unsigned int  tmpStreamVersion;
    unsigned int  tmpBitrate;
    unsigned int  tmpMS;
    unsigned int  tmpFrames;
    unsigned int  tmpLength;
    unsigned int  tmpSize;
    unsigned int  tmpSamplerate;
    unsigned int  MB;
    unsigned int  KB;
    unsigned int  B;
    unsigned int  M;
    unsigned int  K;
    unsigned int  C;

    // AB: reading file-header
    if ( ReadFileHeader ( filename, &Info) ) {
        //char  text [1024];
	// uncomment these if you like the annoying messages when the file cannot be read.
        //sprintf ( text, "File \"%s\" not found or is read protected!\n", filename );
        //xmms_show_msg ( "ERROR: file-info()", text );
        return;
    }

    // AB: setting local variables
    tmpBitrate       = Info.Bitrate;
    tmpMS            = Info.MS;
    tmpStreamVersion = Info.StreamVersion;
    tmpFrames        = Info.Frames;
    tmpSize          = Info.ByteLength;         // Bytelength should contain only the size without any tags !!!
    tmpSamplerate    = Info.SampleRate;

    tmpLength = Info.ByteLength;

    // calculate duration
    Milliseconds = ( tmpFrames * 1152.e3 / SAMPLERATE );

    // copy path and filename
    strcpy ( INFOFN, filename );

    // StreamVersion and profile
    sprintf ( INFO1, "SV: %u.%u,  Profile: %s", tmpStreamVersion & 15, tmpStreamVersion >> 4, Stringify (Info.Profile) );

    // StreamVersion and profile
    sprintf ( INFO11, "Encoder: %s", StringifyEnc (Info.EncVersion) );

    // calculating and formatting bitrate
    if ( tmpBitrate > 0 )
        sprintf ( INFO2, "Bitrate: CBR %i kbps", tmpBitrate );
    else
        sprintf ( INFO2, "Bitrate: VBR %3.1f kbps", 8*tmpLength / (tmpFrames*1152.e3/tmpSamplerate) );

    // display samplerate
    sprintf ( INFO3, "Sample frequency: %4.1f kHz", SAMPLERATE*1.e-3 );

    // formatting no of frames
    M =  tmpFrames/1000000;           // million frames
    K = (tmpFrames-M*1000000)/1000;   // thousand frames
    C =  tmpFrames%1000;          // single frames

    if ( M )
        sprintf ( INFO4, "Frames: %u.%03u.%03u", M, K, C);
    else if ( K )
        sprintf ( INFO4, "Frames: %5u.%03u", K, C);
    else
        sprintf ( INFO4, "Frames: %9u", C);

    // calculating and formatting duration
    if ( Milliseconds >= 3600000 )
        sprintf ( INFO5, "Duration: %2u:%02u:%02u", Milliseconds / 3600000, Milliseconds % 3600000 / 60000, Milliseconds % 60000 / 1000 );
    else
        sprintf ( INFO5, "Duration: %5u:%02u", Milliseconds / 60000, Milliseconds % 60000 / 1000 );

    // display m/s-coding
    sprintf ( INFO6, "Mid/Side Stereo: %s", tmpMS ? "enabled" : "disabled" );

    // formatting filesize
    MB =  tmpSize/1000000;            // million byte
    KB = (tmpSize-1000000*MB)/1000;   // thousand byte
    B  =  tmpSize%1000;               // single byte

    if ( MB )
        sprintf ( INFO7, "Size: (%1.1f MB) %u.%03u.%03u Byte",tmpSize/1048576.0f,MB,KB,B);
    else if ( KB )
        sprintf ( INFO7, "Size: (%1.1f KB) %u.%03u Byte"     ,tmpSize/1024.0f      ,KB,B);
    else
        sprintf ( INFO7, "Size: (%1.1f KB) %3u Byte"         ,tmpSize/1024.0f         ,B);

    // open file-info dialogbox
    FileInfo ( filename );
    return;
}


static void
getfileinfo ( char* filename, char** _title, int* length_in_ms )
{

    StreamInfo    Info;
    unsigned int  tmpFrames=0;

    get_tags(filename);
    *_title=g_strdup(tag_found && i_displaytaginfo ? displayed_info : (char *) g_basename(filename));

    if ( filename == NULL  ||  filename[0] == '\0' ) {  // currently playing file
        if ( length_in_ms != NULL )
            *length_in_ms = getlength ();
    }
    else {                                              // some other file
        if ( length_in_ms != NULL ) {
            if ( ReadFileHeader ( filename, &Info) )
                return;
	    tmpFrames     = Info.Frames;
	    *length_in_ms = (int) ( tmpFrames * 1152.e3 / Info.SampleRate + 0.5f );
        }
    }


}


//---------------------------------------------------------------
// will seek from the beginning of the file to the desired
// position in ms (given by seek_needed)
//---------------------------------------------------------------

/*
static void
Helper1 ( FILE* fp, unsigned long bitpos )
{
    fseek ( fp, (bitpos>>5) * 4 + MPCHeaderPos, SEEK_SET );
    ReadLE32 ( fp, Speicher, 2 );
    dword = Speicher [ Zaehler = 0];
    pos   = bitpos & 31;
}
*/

static void
Helper2 ( FILE* fp, unsigned long bitpos )
{
    fseek ( fp, (bitpos>>5) * 4 + MPCHeaderPos, SEEK_SET );
    ReadLE32 ( fp, Speicher, MEMSIZE );
    dword = Speicher [ Zaehler = 0];
    pos   = bitpos & 31;
}

static void
Helper3 ( FILE* fp, unsigned long bitpos, long* buffoffs )
{
    pos      = bitpos & 31;
    bitpos >>= 5;
    if ( (unsigned long)(bitpos - *buffoffs) >= MEMSIZE-2 ) {
        *buffoffs = bitpos;
        lseek ( fileno(fp), bitpos * 4L + MPCHeaderPos, SEEK_SET );
        ReadLE32  ( fp, Speicher, MEMSIZE );
    }
    dword = Speicher [ Zaehler = bitpos - *buffoffs];
}


static int
perform_jump ( int* done, int* Frames )
{
    unsigned long  fpos;
    // unsigned int   FrameBitCnt;
    unsigned int   RING;
    int            fwd;
    unsigned long  buffoffs = 0x80000000;

    switch ( StreamVersion ) {                                                  // setting position to the beginning of the data-bitstream
    case 0x04: fpos =  48; break;
    case 0x05:
    case 0x06: fpos =  64; break;
    case 0x07:
    case 0x17: fpos = 200; break;
    default  : return 0;                                                        // was gibt diese Funktion im Falle eines Fehlers zurück ???
    }

    fwd           = (int) ( seek_needed * 1.e-3 * SAMPLERATE / FRAMELEN + .5f );// no of frames to skip
    fwd           = fwd < (int)OverallFrames  ?  fwd  :  (int)OverallFrames;    // prevent from desired position out of allowed range
    DecodedFrames = 0;                                                          // reset number of decoded frames

    if ( fwd > 32 ) {                                                           // only do proceed, if desired position is not in between the first 32 frames
        for ( ; DecodedFrames + 32 < fwd; DecodedFrames++ ) {                   // proceed until 32 frames before (!!) desired position (allows to scan the scalefactors)
            if ( SeekTable [DecodedFrames] == 0 ) {
#ifdef OTHER_SEEK
                Helper3 ( inputFile, fpos, &buffoffs );
#else
                Helper1 ( inputFile, fpos );
#endif
                fpos += SeekTable [DecodedFrames] = Read_Bitstream_Jumper (StreamVersion);   // calculate desired pos (in Bits)
            }
            else {
                fpos += SeekTable [DecodedFrames];
            }
        }
    }
    Helper2 ( inputFile, fpos );

    for ( ; DecodedFrames < fwd; DecodedFrames++ ) {                            // read the last 32 frames before the desired position to scan the scalefactors (artifactless jumping)
        int  tmp;
        RING = Zaehler;

        tmp = Read_Bitstream (StreamVersion);

        if ( tmp < 0 ) {
            xmms_show_msg ( "Jumping ...", "Bug in perform_jump" );
            return 0;
        }

        if ( (RING ^ Zaehler) & MEMSIZE2 )                                      // update buffer
            ReadLE32 ( inputFile, Speicher + (RING & MEMSIZE2), MEMSIZE2 );
    }

    decode_pos_ms = (int) (DecodedFrames * FRAMELEN * 1.e3 / SAMPLERATE + 0.5f);

    *done = 0;                                                                  // file is not done
    RESET_Synthesis ();                                                         // resetting synthesis filter to avoid "clicks"
    mod.output -> flush (decode_pos_ms);                                        // flush sound buffer
    *Frames = -1;                                                               // AB: to restart calculation of avg bitrate

    return 1;
}


static void*
DecodeThread ( void* b )
{
    int           done        =  0;
    int           avg_bitrate =  0;
    int           Frames      = -1;
    int           NumberOfConsecutiveBrokenFrames = 0;
    int           BitCounter  =  0;             // counter for displaying avg bitrate
    int           valid_samples;                // no of valid samples
    int           FrameWasValid = 0;
    unsigned int  RING;

    while ( killDecodeThread == 0 ) {

        /********************** SEEK ************************/
        if ( seek_needed != -1 ) {
            if ( inputFile != NULL  &&  done != 1 ) {           // only perform jump when bitstream is still allocated
                if ( !perform_jump ( &done, &Frames) ) {
                    char  text[256];
                    sprintf ( text, "File seek error in file \"%s\", Frame # %i / %i !", lastfilename, DecodedFrames+1, OverallFrames  );
                    xmms_show_msg ( "ERROR: File seek", text );
                    done = 1;
                }
            }
            else {
                done = 1;
            }

            seek_needed = -1;       // seek is done or impossible
        }

        /********************* QUIT *************************/
        if ( done ) {
            mod.output -> buffer_free ();

            while ( mod.output -> buffer_playing () )
                xmms_usleep ( 25000 );

            // does not jump back to file in playlist, sets "green" on idle or out of snyc
            if ( i_bitrate ) {
                mod.set_info ( i_displaytaginfo  &&  tag_found  ?  displayed_info  :  NULL,
                               getlength (),
                               avg_bitrate,
                               SAMPLERATE / 1000,
                               NCH );
            }

            killDecodeThread = 1;
            pthread_exit (NULL);
            return 0;
        }

        /******************* DECODE ***********************/
        else if ( mod.output -> buffer_free () >= (1152*NCH*(BPS/8)) << (mod.output -> buffer_playing () ? 1 : 0) ) {
            RING = Zaehler;

            valid_samples = DECODE ( sample_buffer, &FrameWasValid, VV, YY );

            /**************** ERROR CONCEALMENT *****************/
            if ( FrameWasValid <= 0 ) {                         // error in bitstream occured

                if ( ++NumberOfConsecutiveBrokenFrames > MaxBrokenFrames ) {       // too much broken frames -> cancel decoding
                    char  text [256];
                    sprintf ( text, "Lost sync in file\n\"%s\"\nFrame # %i/%i, %i bits read, valid: %i, valid samples %d !",
                              lastfilename, DecodedFrames, OverallFrames, BitsRead(), FrameWasValid, valid_samples );
                    xmms_show_msg ( "ERROR: Out of sync", text );
                    valid_samples = -1;
                }
                else {                                                  // conceal error -> send zeroes, try to re-sync
                    Zaehler = RecommendedResyncPos >> 5;                // calculate and set desired position
                    pos     = RecommendedResyncPos & 31;

                    dword   = Speicher [Zaehler];                       // set current decoded word

                    valid_samples = 1152;
                    // memset ( sample_buffer, 0, 4*valid_samples );    // filling broken frames with zeroes
                }
            }
            else {
                NumberOfConsecutiveBrokenFrames = 0;
            }
            /***************************************************/

            // update buffer
            if ( (RING^Zaehler) & MEMSIZE2 )
                ReadLE32 ( inputFile, Speicher + (RING & MEMSIZE2), MEMSIZE2 );

            // update bitrate-display
            Frames++;
            if ( i_bitrate  &&  Frames % i_bitrate == 0 ) {
                avg_bitrate = 1000 * (int) ( (BitsRead()-BitCounter) * SAMPLERATE / 1152.e3 / i_bitrate );
                mod.set_info ( i_displaytaginfo  &&  tag_found  ?  displayed_info  :  NULL,
                               getlength (),
                               avg_bitrate,
                               SAMPLERATE / 1000,
                               NCH );

                BitCounter = BitsRead ();
            }

            // copy pcm-buffer to output-, visualization- and dsp-buffer
            if ( valid_samples > 0 ) {
                mod . add_vis_pcm           ( mod.output -> written_time(), FMT_S16_LE, NCH, 4*valid_samples, sample_buffer );
                mod . output -> write_audio ( sample_buffer, 4*valid_samples );
            }
            else if ( valid_samples < 0 ) {
                done = 1;
                if ( inputFile != NULL ) {
                    fclose (inputFile);
                    inputFile = NULL;
                }
            }
            decode_pos_ms = (int) ( DecodedFrames * 1152.e3 / SAMPLERATE + 0.5f );
        }
        else {
            xmms_usleep ( 25000 );
        }
    }

    mod.output -> close_audio ();
    pthread_exit (NULL);
    return 0;
}

/* end of in_mpc.c */
