diff src/shnplug/shn.c @ 1305:51bf0e431e02

Add SHNplug.
author William Pitcock <nenolod@atheme-project.org>
date Fri, 20 Jul 2007 10:29:54 -0500
parents
children 761e17b23e0c
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/shnplug/shn.c	Fri Jul 20 10:29:54 2007 -0500
@@ -0,0 +1,1379 @@
+/*  shn.c - main functions for xmms-shn
+ *  Copyright (C) 2000-2007  Jason Jordan <shnutils@freeshell.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * $Id: shn.c,v 1.38 2007/03/23 05:49:48 jason Exp $
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <glib.h>
+#include <audacious/util.h>
+#include <audacious/configdb.h>
+#include "shorten.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+static void shn_about(void);
+static void shn_configure(void);
+static void shn_init(void);
+static int  shn_is_our_fd(char *, VFSFile *fd);
+static void shn_play(InputPlayback *);
+static void shn_stop(InputPlayback *);
+static void shn_seek(InputPlayback *, int);
+static void shn_pause(InputPlayback *, short);
+static void shn_get_file_info(char *,char **,int *);
+static void shn_display_file_info(char *);
+
+gchar *shn_fmts[] = { "shn", NULL };
+
+InputPlugin shn_ip =
+{
+	NULL,
+	NULL,
+	"SHN Player " VERSION,
+	shn_init,
+	shn_about,
+	shn_configure,
+	NULL,
+	NULL,
+	shn_play,
+	shn_stop,
+	shn_pause,
+	shn_seek,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	shn_get_file_info,
+	shn_display_file_info,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	shn_is_our_fd,
+	shn_fmts,
+};
+
+InputPlugin *shn_iplist[] = { &shn_ip, NULL };
+
+DECLARE_PLUGIN(shnplug, NULL, NULL, shn_iplist, NULL, NULL, NULL, NULL);
+
+shn_file *shnfile;
+shn_config shn_cfg;
+
+static pthread_t decode_thread;
+static gboolean audio_error = FALSE;
+
+static void shn_init()
+{
+	ConfigDb *cfg;
+
+	shn_cfg.error_output_method = ERROR_OUTPUT_DEVNULL;
+	shn_cfg.error_output_method_config_name = "error_output_method";
+	shn_cfg.seek_tables_path = NULL;
+	shn_cfg.seek_tables_path_config_name = "seek_tables_path";
+	shn_cfg.relative_seek_tables_path = NULL;
+	shn_cfg.relative_seek_tables_path_config_name = "relative_seek_tables_path";
+	shn_cfg.verbose = 0;
+	shn_cfg.verbose_config_name = "verbose";
+	shn_cfg.swap_bytes = 0;
+	shn_cfg.swap_bytes_config_name = "swap_bytes";
+	shn_cfg.load_textfiles = FALSE;
+	shn_cfg.load_textfiles_config_name = "load_textfiles";
+	shn_cfg.textfile_extensions = NULL;
+	shn_cfg.textfile_extensions_config_name = "textfile_extensions";
+
+	if ((cfg = bmp_cfg_db_open()) != 0)
+	{
+		bmp_cfg_db_get_int(cfg, XMMS_SHN_VERSION_TAG, shn_cfg.error_output_method_config_name, &shn_cfg.error_output_method);
+		bmp_cfg_db_get_bool(cfg, XMMS_SHN_VERSION_TAG, shn_cfg.verbose_config_name, &shn_cfg.verbose);
+		if (!bmp_cfg_db_get_string(cfg, XMMS_SHN_VERSION_TAG, shn_cfg.seek_tables_path_config_name, &shn_cfg.seek_tables_path))
+			shn_cfg.seek_tables_path = g_strdup(g_get_home_dir());
+		if (!bmp_cfg_db_get_string(cfg, XMMS_SHN_VERSION_TAG, shn_cfg.relative_seek_tables_path_config_name, &shn_cfg.relative_seek_tables_path))
+			shn_cfg.relative_seek_tables_path = g_strdup("");
+		bmp_cfg_db_get_bool(cfg, XMMS_SHN_VERSION_TAG, shn_cfg.swap_bytes_config_name, &shn_cfg.swap_bytes);
+		bmp_cfg_db_get_bool(cfg, XMMS_SHN_VERSION_TAG, shn_cfg.load_textfiles_config_name, &shn_cfg.load_textfiles);
+		if (!bmp_cfg_db_get_string(cfg, XMMS_SHN_VERSION_TAG, shn_cfg.textfile_extensions_config_name, &shn_cfg.textfile_extensions))
+			shn_cfg.textfile_extensions = g_strdup("txt,nfo");
+		bmp_cfg_db_close(cfg);
+	}
+}
+
+static void shn_about()
+{
+	shn_display_about();
+}
+
+static void shn_configure()
+{
+	shn_display_configure();
+}
+
+int init_decode_state(shn_file *this_shn)
+{
+	if (this_shn->decode_state)
+	{
+		if (this_shn->decode_state->getbuf)
+		{
+			free(this_shn->decode_state->getbuf);
+			this_shn->decode_state->getbuf = NULL;
+		}
+
+		if (this_shn->decode_state->writebuf)
+		{
+			free(this_shn->decode_state->writebuf);
+			this_shn->decode_state->writebuf = NULL;
+		}
+
+		if (this_shn->decode_state->writefub)
+		{
+			free(this_shn->decode_state->writefub);
+			this_shn->decode_state->writefub = NULL;
+		}
+
+		free(this_shn->decode_state);
+		this_shn->decode_state = NULL;
+	}
+
+	if (!(this_shn->decode_state = malloc(sizeof(shn_decode_state))))
+	{
+		shn_debug("Could not allocate memory for decode state data structure");
+		return 0;
+	}
+
+	this_shn->decode_state->getbuf = NULL;
+	this_shn->decode_state->getbufp = NULL;
+	this_shn->decode_state->nbitget = 0;
+	this_shn->decode_state->nbyteget = 0;
+	this_shn->decode_state->gbuffer = 0;
+	this_shn->decode_state->writebuf = NULL;
+	this_shn->decode_state->writefub = NULL;
+	this_shn->decode_state->nwritebuf = 0;
+
+	this_shn->vars.bytes_in_buf = 0;
+
+	return 1;
+}
+
+int get_wave_header(shn_file *this_shn)
+{
+  slong  **buffer = NULL, **offset = NULL;
+  slong  lpcqoffset = 0;
+  int   version = FORMAT_VERSION, bitshift = 0;
+  int   ftype = TYPE_EOF;
+  char  *magic = MAGIC;
+  int   blocksize = DEFAULT_BLOCK_SIZE, nchan = DEFAULT_NCHAN;
+  int   i, chan, nwrap, nskip = DEFAULT_NSKIP;
+  int   *qlpc = NULL, maxnlpc = DEFAULT_MAXNLPC, nmean = UNDEFINED_UINT;
+  int   cmd;
+  int   internal_ftype;
+  int   cklen;
+  int   retval = 0;
+
+  if (!init_decode_state(this_shn))
+    return 0;
+
+    /***********************/
+    /* EXTRACT starts here */
+    /***********************/
+
+    /* read magic number */
+#ifdef STRICT_FORMAT_COMPATABILITY
+    if(FORMAT_VERSION < 2)
+    {
+      for(i = 0; i < strlen(magic); i++) {
+        if(getc_exit(this_shn->vars.fd) != magic[i])
+          return 0;
+        this_shn->vars.bytes_read++;
+      }
+
+      /* get version number */
+      version = getc_exit(this_shn->vars.fd);
+      this_shn->vars.bytes_read++;
+    }
+    else
+#endif /* STRICT_FORMAT_COMPATABILITY */
+    {
+      int nscan = 0;
+
+      version = MAX_VERSION + 1;
+      while(version > MAX_VERSION)
+      {
+        int byte = vfs_getc(this_shn->vars.fd);
+        this_shn->vars.bytes_read++;
+        if(byte == EOF)
+          return 0;
+        if(magic[nscan] != '\0' && byte == magic[nscan])
+          nscan++;
+        else
+          if(magic[nscan] == '\0' && byte <= MAX_VERSION)
+            version = byte;
+        else
+        {
+          if(byte == magic[0])
+            nscan = 1;
+          else
+          {
+            nscan = 0;
+          }
+         version = MAX_VERSION + 1;
+        }
+      }
+    }
+
+    /* check version number */
+    if(version > MAX_SUPPORTED_VERSION)
+      return 0;
+
+    /* set up the default nmean, ignoring the command line state */
+    nmean = (version < 2) ? DEFAULT_V0NMEAN : DEFAULT_V2NMEAN;
+
+    /* initialise the variable length file read for the compressed stream */
+    var_get_init(this_shn);
+    if (this_shn->vars.fatal_error)
+      return 0;
+
+    /* initialise the fixed length file write for the uncompressed stream */
+    fwrite_type_init(this_shn);
+
+    /* get the internal file type */
+    internal_ftype = UINT_GET(TYPESIZE, this_shn);
+
+    /* has the user requested a change in file type? */
+    if(internal_ftype != ftype) {
+      if(ftype == TYPE_EOF) {
+        ftype = internal_ftype;    /*  no problems here */
+      }
+      else {           /* check that the requested conversion is valid */
+        if(internal_ftype == TYPE_AU1 || internal_ftype == TYPE_AU2 ||
+           internal_ftype == TYPE_AU3 || ftype == TYPE_AU1 ||ftype == TYPE_AU2 || ftype == TYPE_AU3)
+        {
+          retval = 0;
+          goto got_enough_data;
+        }
+      }
+    }
+
+    nchan = UINT_GET(CHANSIZE, this_shn);
+    this_shn->vars.actual_nchan = nchan;
+
+    /* get blocksize if version > 0 */
+    if(version > 0)
+    {
+      int byte;
+      blocksize = UINT_GET((int) (log((double) DEFAULT_BLOCK_SIZE) / M_LN2),this_shn);
+      maxnlpc = UINT_GET(LPCQSIZE, this_shn);
+      this_shn->vars.actual_maxnlpc = maxnlpc;
+      nmean = UINT_GET(0, this_shn);
+      this_shn->vars.actual_nmean = nmean;
+      nskip = UINT_GET(NSKIPSIZE, this_shn);
+      for(i = 0; i < nskip; i++)
+      {
+        byte = uvar_get(XBYTESIZE,this_shn);
+      }
+    }
+    else
+      blocksize = DEFAULT_BLOCK_SIZE;
+
+    nwrap = MAX(NWRAP, maxnlpc);
+
+    /* grab some space for the input buffer */
+    buffer  = long2d((ulong) nchan, (ulong) (blocksize + nwrap),this_shn);
+    if (this_shn->vars.fatal_error)
+      return 0;
+    offset  = long2d((ulong) nchan, (ulong) MAX(1, nmean),this_shn);
+    if (this_shn->vars.fatal_error) {
+      if (buffer) {
+        free(buffer);
+        buffer = NULL;
+      }
+      return 0;
+    }
+
+    for(chan = 0; chan < nchan; chan++)
+    {
+      for(i = 0; i < nwrap; i++)
+      	buffer[chan][i] = 0;
+      buffer[chan] += nwrap;
+    }
+
+    if(maxnlpc > 0) {
+      qlpc = (int*) pmalloc((ulong) (maxnlpc * sizeof(*qlpc)),this_shn);
+      if (this_shn->vars.fatal_error) {
+        if (buffer) {
+          free(buffer);
+          buffer = NULL;
+        }
+        if (offset) {
+          free(offset);
+          buffer = NULL;
+        }
+        return 0;
+      }
+    }
+
+    if(version > 1)
+      lpcqoffset = V2LPCQOFFSET;
+
+    init_offset(offset, nchan, MAX(1, nmean), internal_ftype);
+
+    /* get commands from file and execute them */
+    chan = 0;
+    while(1)
+    {
+      this_shn->vars.reading_function_code = 1;
+      cmd = uvar_get(FNSIZE,this_shn);
+      this_shn->vars.reading_function_code = 0;
+
+        switch(cmd)
+        {
+          case FN_ZERO:
+          case FN_DIFF0:
+          case FN_DIFF1:
+          case FN_DIFF2:
+          case FN_DIFF3:
+          case FN_QLPC:
+          {
+            slong coffset, *cbuffer = buffer[chan];
+            int resn = 0, nlpc, j;
+
+            if(cmd != FN_ZERO)
+            {
+              resn = uvar_get(ENERGYSIZE,this_shn);
+              if (this_shn->vars.fatal_error) {
+                retval = 0;
+                goto got_enough_data;
+              }
+              /* this is a hack as version 0 differed in definition of var_get */
+              if(version == 0)
+                resn--;
+            }
+
+            /* find mean offset : N.B. this code duplicated */
+            if(nmean == 0)
+              coffset = offset[chan][0];
+            else
+            {
+              slong sum = (version < 2) ? 0 : nmean / 2;
+              for(i = 0; i < nmean; i++)
+                sum += offset[chan][i];
+              if(version < 2)
+                coffset = sum / nmean;
+              else
+                coffset = ROUNDEDSHIFTDOWN(sum / nmean, bitshift);
+            }
+
+            switch(cmd)
+            {
+              case FN_ZERO:
+                for(i = 0; i < blocksize; i++)
+                  cbuffer[i] = 0;
+                break;
+              case FN_DIFF0:
+                for(i = 0; i < blocksize; i++) {
+                  cbuffer[i] = var_get(resn,this_shn) + coffset;
+                  if (this_shn->vars.fatal_error) {
+                    retval = 0;
+                    goto got_enough_data;
+                  }
+                }
+                break;
+              case FN_DIFF1:
+                for(i = 0; i < blocksize; i++) {
+                  cbuffer[i] = var_get(resn,this_shn) + cbuffer[i - 1];
+                  if (this_shn->vars.fatal_error) {
+                    retval = 0;
+                    goto got_enough_data;
+                  }
+                }
+                break;
+              case FN_DIFF2:
+                for(i = 0; i < blocksize; i++) {
+                  cbuffer[i] = var_get(resn,this_shn) + (2 * cbuffer[i - 1] -	cbuffer[i - 2]);
+                  if (this_shn->vars.fatal_error) {
+                    retval = 0;
+                    goto got_enough_data;
+                  }
+                }
+                break;
+              case FN_DIFF3:
+                for(i = 0; i < blocksize; i++) {
+                  cbuffer[i] = var_get(resn,this_shn) + 3 * (cbuffer[i - 1] -  cbuffer[i - 2]) + cbuffer[i - 3];
+                  if (this_shn->vars.fatal_error) {
+                    retval = 0;
+                    goto got_enough_data;
+                  }
+                }
+                break;
+              case FN_QLPC:
+                nlpc = uvar_get(LPCQSIZE,this_shn);
+                if (this_shn->vars.fatal_error) {
+                  retval = 0;
+                  goto got_enough_data;
+                }
+
+                for(i = 0; i < nlpc; i++) {
+                  qlpc[i] = var_get(LPCQUANT,this_shn);
+                  if (this_shn->vars.fatal_error) {
+                    retval = 0;
+                    goto got_enough_data;
+                  }
+                }
+                for(i = 0; i < nlpc; i++)
+                  cbuffer[i - nlpc] -= coffset;
+                for(i = 0; i < blocksize; i++)
+                {
+                  slong sum = lpcqoffset;
+
+                  for(j = 0; j < nlpc; j++)
+                    sum += qlpc[j] * cbuffer[i - j - 1];
+                  cbuffer[i] = var_get(resn,this_shn) + (sum >> LPCQUANT);
+                  if (this_shn->vars.fatal_error) {
+                    retval = 0;
+                    goto got_enough_data;
+                  }
+                }
+                if(coffset != 0)
+                  for(i = 0; i < blocksize; i++)
+                    cbuffer[i] += coffset;
+                break;
+            }
+
+            /* store mean value if appropriate : N.B. Duplicated code */
+            if(nmean > 0)
+            {
+              slong sum = (version < 2) ? 0 : blocksize / 2;
+
+              for(i = 0; i < blocksize; i++)
+                sum += cbuffer[i];
+
+              for(i = 1; i < nmean; i++)
+                offset[chan][i - 1] = offset[chan][i];
+              if(version < 2)
+                offset[chan][nmean - 1] = sum / blocksize;
+              else
+                offset[chan][nmean - 1] = (sum / blocksize) << bitshift;
+            }
+
+			if (0 == chan) {
+              this_shn->vars.initial_file_position = this_shn->vars.last_file_position_no_really;
+              goto got_enough_data;
+            }
+
+            /* do the wrap */
+            for(i = -nwrap; i < 0; i++)
+              cbuffer[i] = cbuffer[i + blocksize];
+
+            fix_bitshift(cbuffer, blocksize, bitshift, internal_ftype);
+
+            if(chan == nchan - 1)
+            {
+              fwrite_type(buffer, ftype, nchan, blocksize, this_shn);
+              this_shn->vars.bytes_in_buf = 0;
+            }
+
+            chan = (chan + 1) % nchan;
+            break;
+          }
+          break;
+
+          case FN_BLOCKSIZE:
+            UINT_GET((int) (log((double) blocksize) / M_LN2), this_shn);
+            break;
+
+          case FN_VERBATIM:
+            cklen = uvar_get(VERBATIM_CKSIZE_SIZE,this_shn);
+
+            while (cklen--) {
+              if (this_shn->vars.bytes_in_header >= OUT_BUFFER_SIZE) {
+                  shn_debug("Unexpectedly large header - " PACKAGE " can only handle a maximum of %d bytes",OUT_BUFFER_SIZE);
+                  goto got_enough_data;
+              }
+              this_shn->vars.bytes_in_buf = 0;
+              this_shn->vars.header[this_shn->vars.bytes_in_header++] = (char)uvar_get(VERBATIM_BYTE_SIZE,this_shn);
+            }
+            retval = 1;
+            break;
+
+          case FN_BITSHIFT:
+            bitshift = uvar_get(BITSHIFTSIZE,this_shn);
+            this_shn->vars.actual_bitshift = bitshift;
+            break;
+
+          default:
+            goto got_enough_data;
+        }
+    }
+
+got_enough_data:
+
+    /* wind up */
+    var_get_quit(this_shn);
+    fwrite_type_quit(this_shn);
+
+    if (buffer) free((void *) buffer);
+    if (offset) free((void *) offset);
+    if(maxnlpc > 0 && qlpc)
+      free((void *) qlpc);
+
+    this_shn->vars.bytes_in_buf = 0;
+
+    return retval;
+}
+
+void shn_unload(shn_file *this_shn)
+{
+	int this_shn_is_shnfile = (this_shn == shnfile) ? 1 : 0;
+
+	if (this_shn)
+	{
+		if (this_shn->vars.fd)
+		{
+			vfs_fclose(this_shn->vars.fd);
+			this_shn->vars.fd = NULL;
+		}
+
+		if (this_shn->decode_state)
+		{
+			if (this_shn->decode_state->getbuf)
+			{
+				free(this_shn->decode_state->getbuf);
+				this_shn->decode_state->getbuf = NULL;
+			}
+
+			if (this_shn->decode_state->writebuf)
+			{
+				free(this_shn->decode_state->writebuf);
+				this_shn->decode_state->writebuf = NULL;
+			}
+
+			if (this_shn->decode_state->writefub)
+			{
+				free(this_shn->decode_state->writefub);
+				this_shn->decode_state->writefub = NULL;
+			}
+
+			free(this_shn->decode_state);
+			this_shn->decode_state = NULL;
+		}
+
+		if (this_shn->seek_table)
+		{
+			free(this_shn->seek_table);
+			this_shn->seek_table = NULL;
+		}
+
+		free(this_shn);
+		this_shn = NULL;
+		if (this_shn_is_shnfile)
+			shnfile = NULL;
+	}
+}
+
+shn_file *load_shn(InputPlayback *playback, char *filename, VFSFile *fd)
+{
+	shn_file *tmp_file;
+	shn_seek_entry *first_seek_table;
+
+	shn_debug("Loading file: '%s'", filename);
+
+	if (!(tmp_file = malloc(sizeof(shn_file))))
+	{
+		shn_debug("Could not allocate memory for SHN data structure");
+		return NULL;
+	}
+
+	memset(tmp_file, 0, sizeof(shn_file));
+
+	tmp_file->vars.fd = NULL;
+	tmp_file->vars.seek_to = -1;
+	tmp_file->vars.eof = 0;
+	tmp_file->vars.going = 0;
+	tmp_file->vars.playback = playback;
+	tmp_file->vars.seek_table_entries = NO_SEEK_TABLE;
+	tmp_file->vars.bytes_in_buf = 0;
+	tmp_file->vars.bytes_in_header = 0;
+	tmp_file->vars.reading_function_code = 0;
+	tmp_file->vars.initial_file_position = 0;
+	tmp_file->vars.last_file_position = 0;
+	tmp_file->vars.last_file_position_no_really = 0;
+	tmp_file->vars.bytes_read = 0;
+	tmp_file->vars.actual_bitshift = 0;
+	tmp_file->vars.actual_maxnlpc = 0;
+	tmp_file->vars.actual_nmean = 0;
+	tmp_file->vars.actual_nchan = 0;
+	tmp_file->vars.seek_offset = 0;
+
+	tmp_file->decode_state = NULL;
+
+	tmp_file->wave_header.filename = filename;
+	tmp_file->wave_header.wave_format = 0;
+	tmp_file->wave_header.channels = 0;
+	tmp_file->wave_header.block_align = 0;
+	tmp_file->wave_header.bits_per_sample = 0;
+	tmp_file->wave_header.samples_per_sec = 0;
+	tmp_file->wave_header.avg_bytes_per_sec = 0;
+	tmp_file->wave_header.rate = 0;
+	tmp_file->wave_header.header_size = 0;
+	tmp_file->wave_header.data_size = 0;
+	tmp_file->wave_header.file_has_id3v2_tag = 0;
+	tmp_file->wave_header.id3v2_tag_size = 0;
+
+	tmp_file->seek_header.version = NO_SEEK_TABLE;
+	tmp_file->seek_header.shnFileSize = 0;
+
+	tmp_file->seek_trailer.seekTableSize = 0;
+
+	tmp_file->seek_table = NULL;
+
+	if (!fd)
+	{
+		if (!(tmp_file->vars.fd = shn_open_and_discard_id3v2_tag(filename,&tmp_file->wave_header.file_has_id3v2_tag,&tmp_file->wave_header.id3v2_tag_size)))
+		{
+			shn_debug("Could not open file: '%s'",filename);
+			shn_unload(tmp_file);
+			return NULL;
+		}
+	}
+	else
+		tmp_file->vars.fd = fd;
+
+	if (0 == get_wave_header(tmp_file))
+	{
+		shn_debug("Unable to read WAVE header from file '%s'",filename);
+		shn_unload(tmp_file);
+		return NULL;
+	}
+
+	if (tmp_file->wave_header.file_has_id3v2_tag)
+	{
+		vfs_fseek(tmp_file->vars.fd,tmp_file->wave_header.id3v2_tag_size,SEEK_SET);
+		tmp_file->vars.bytes_read += tmp_file->wave_header.id3v2_tag_size;
+		tmp_file->vars.seek_offset = tmp_file->wave_header.id3v2_tag_size;
+	}
+    else
+	{
+		vfs_fseek(tmp_file->vars.fd,0,SEEK_SET);
+	}
+
+	if (0 == shn_verify_header(tmp_file))
+	{
+		shn_debug("Invalid WAVE header in file: '%s'",filename);
+		shn_unload(tmp_file);
+		return NULL;
+	}
+
+	if (tmp_file->decode_state)
+	{
+		free(tmp_file->decode_state);
+		tmp_file->decode_state = NULL;
+	}
+
+	shn_load_seek_table(tmp_file,filename);
+
+	if (NO_SEEK_TABLE != tmp_file->vars.seek_table_entries)
+	{
+		/* verify seek tables */
+
+		first_seek_table = (shn_seek_entry *)tmp_file->seek_table;
+
+		if (tmp_file->vars.actual_bitshift != shn_uchar_to_ushort_le(first_seek_table->data+22))
+		{
+			/* initial bitshift value in the file does not match the first bitshift value of the first seektable entry - seeking is broken */
+			shn_debug("Broken seek table detected (invalid bitshift) - seeking disabled for this file.");
+			tmp_file->vars.seek_table_entries = NO_SEEK_TABLE;
+		}
+		else if (tmp_file->vars.actual_nchan > 2)
+		{
+			/* nchan is greater than the number of such entries stored in a seek table entry - seeking won't work */
+			shn_debug("Broken seek table detected (nchan %d not in range [1 .. 2]) - seeking disabled for this file.",tmp_file->vars.actual_nchan);
+			tmp_file->vars.seek_table_entries = NO_SEEK_TABLE;
+		}
+		else if (tmp_file->vars.actual_maxnlpc > 3)
+		{
+			/* maxnlpc is greater than the number of such entries stored in a seek table entry - seeking won't work */
+			shn_debug("Broken seek table detected (maxnlpc %d not in range [0 .. 3]) - seeking disabled for this file.",tmp_file->vars.actual_maxnlpc);
+			tmp_file->vars.seek_table_entries = NO_SEEK_TABLE;
+		}
+		else if (tmp_file->vars.actual_nmean > 4)
+		{
+			/* nmean is greater than the number of such entries stored in a seek table entry - seeking won't work */
+			shn_debug("Broken seek table detected (nmean %d not in range [0 .. 4]) - seeking disabled for this file.",tmp_file->vars.actual_nmean);
+			tmp_file->vars.seek_table_entries = NO_SEEK_TABLE;
+		}
+		else
+		{
+			/* seek table appears to be valid - now adjust byte offsets in seek table to match the file */
+			tmp_file->vars.seek_offset += tmp_file->vars.initial_file_position - shn_uchar_to_ulong_le(first_seek_table->data+8);
+
+			if (0 != tmp_file->vars.seek_offset)
+			{
+				shn_debug("Adjusting seek table offsets by %ld bytes due to mismatch between seek table values and input file - seeking might not work correctly.",
+					tmp_file->vars.seek_offset);
+			}
+		}
+	}
+
+	shn_debug("Successfully loaded file: '%s'",filename);
+
+	return tmp_file;
+}
+
+static int shn_is_our_fd(char *fn, VFSFile *fd)
+{
+	char data[4];
+
+	if (vfs_fread((void *)data,1,4,fd) != 4)
+		return FALSE;
+
+	if (memcmp(data,MAGIC,4))
+		return FALSE;
+
+#if 0
+	if (!(tmp_file = load_shn(NULL, filename, fd)))
+		return FALSE;
+
+	shn_unload(tmp_file);
+#endif
+
+	return TRUE;
+}
+
+void swap_bytes(shn_file *this_shn,int bytes)
+{
+	int i;
+	uchar tmp;
+
+	for (i=0;i<bytes;i=i+2) {
+		tmp = this_shn->vars.buffer[i+1];
+		this_shn->vars.buffer[i+1] = this_shn->vars.buffer[i];
+		this_shn->vars.buffer[i] = tmp;
+	}
+}
+
+void write_and_wait(shn_file *this_shn,int block_size)
+{
+	int bytes_to_write,bytes_in_block,i;
+	InputPlayback *playback = this_shn->vars.playback;
+
+	if (this_shn->vars.bytes_in_buf < block_size)
+		return;
+
+	bytes_in_block = min(this_shn->vars.bytes_in_buf, block_size);
+
+	if (bytes_in_block <= 0)
+		return;
+
+	bytes_to_write = bytes_in_block;
+	while ((bytes_to_write + bytes_in_block) <= this_shn->vars.bytes_in_buf)
+		bytes_to_write += bytes_in_block;
+
+	shn_ip.add_vis_pcm(shn_ip.output->written_time(), (this_shn->wave_header.bits_per_sample == 16) ? FMT_S16_LE : FMT_U8,
+		this_shn->wave_header.channels, bytes_to_write, this_shn->vars.buffer);
+
+	while(shn_ip.output->buffer_free() < bytes_to_write && playback->playing && this_shn->vars.seek_to == -1)
+		xmms_usleep(10000);
+
+	if(playback->playing && this_shn->vars.seek_to == -1) {
+		if (shn_cfg.swap_bytes)
+			swap_bytes(this_shn, bytes_to_write);
+		shn_ip.output->write_audio(this_shn->vars.buffer, bytes_to_write);
+	} else
+		return;
+
+	/* shift data from end of buffer to the front */
+	this_shn->vars.bytes_in_buf -= bytes_to_write;
+
+	for(i=0;i<this_shn->vars.bytes_in_buf;i++)
+		this_shn->vars.buffer[i] = this_shn->vars.buffer[i+bytes_to_write];
+}
+
+static void *play_loop_shn(void *arg)
+{
+  slong  **buffer = NULL, **offset = NULL;
+  slong  lpcqoffset = 0;
+  int   version = FORMAT_VERSION, bitshift = 0;
+  int   ftype = TYPE_EOF;
+  char  *magic = MAGIC;
+  int   blocksize = DEFAULT_BLOCK_SIZE, nchan = DEFAULT_NCHAN;
+  int   i, chan, nwrap, nskip = DEFAULT_NSKIP;
+  int   *qlpc = NULL, maxnlpc = DEFAULT_MAXNLPC, nmean = UNDEFINED_UINT;
+  int   cmd;
+  int   internal_ftype;
+  shn_file *this_shn = shnfile;
+  int   blk_size;
+  int   cklen;
+  uchar tmp;
+  ulong seekto_offset;
+  InputPlayback *playback = this_shn->vars.playback;
+
+restart:
+
+  this_shn->vars.bytes_in_buf = 0;
+
+  if (!init_decode_state(this_shn))
+    goto exit_thread;
+
+  blk_size = 512 * (this_shn->wave_header.bits_per_sample / 8) * this_shn->wave_header.channels;
+
+    /***********************/
+    /* EXTRACT starts here */
+    /***********************/
+
+    /* read magic number */
+#ifdef STRICT_FORMAT_COMPATABILITY
+    if(FORMAT_VERSION < 2)
+    {
+      for(i = 0; i < strlen(magic); i++)
+        if(getc_exit(this_shn->vars.fd) != magic[i]) {
+          shn_error_fatal(this_shn,"Bad magic number");
+          goto exit_thread;
+        }
+
+      /* get version number */
+      version = getc_exit(this_shn->vars.fd);
+    }
+    else
+#endif /* STRICT_FORMAT_COMPATABILITY */
+    {
+      int nscan = 0;
+
+      version = MAX_VERSION + 1;
+      while(version > MAX_VERSION)
+      {
+  	int byte = vfs_getc(this_shn->vars.fd);
+ 	if(byte == EOF) {
+	  shn_error_fatal(this_shn,"No magic number");
+          goto exit_thread;
+        }
+	if(magic[nscan] != '\0' && byte == magic[nscan])
+          nscan++;
+	else
+        if(magic[nscan] == '\0' && byte <= MAX_VERSION)
+          version = byte;
+	else
+        {
+	  if(byte == magic[0])
+  	    nscan = 1;
+	  else
+          {
+	    nscan = 0;
+	  }
+	  version = MAX_VERSION + 1;
+	}
+      }
+    }
+
+    /* check version number */
+    if(version > MAX_SUPPORTED_VERSION) {
+      shn_error_fatal(this_shn,"Can't decode version %d", version);
+      goto exit_thread;
+    }
+
+    /* set up the default nmean, ignoring the command line state */
+    nmean = (version < 2) ? DEFAULT_V0NMEAN : DEFAULT_V2NMEAN;
+
+    /* initialise the variable length file read for the compressed stream */
+    var_get_init(this_shn);
+    if (this_shn->vars.fatal_error)
+      goto exit_thread;
+
+    /* initialise the fixed length file write for the uncompressed stream */
+    fwrite_type_init(this_shn);
+
+    /* get the internal file type */
+    internal_ftype = UINT_GET(TYPESIZE, this_shn);
+
+    /* has the user requested a change in file type? */
+    if(internal_ftype != ftype) {
+      if(ftype == TYPE_EOF)
+	ftype = internal_ftype;    /*  no problems here */
+      else             /* check that the requested conversion is valid */
+	if(internal_ftype == TYPE_AU1 || internal_ftype == TYPE_AU2 ||
+	   internal_ftype == TYPE_AU3 || ftype == TYPE_AU1 ||ftype == TYPE_AU2 || ftype == TYPE_AU3) {
+	  shn_error_fatal(this_shn,"Not able to perform requested output format conversion");
+          goto cleanup;
+        }
+    }
+
+    nchan = UINT_GET(CHANSIZE, this_shn);
+
+    /* get blocksize if version > 0 */
+    if(version > 0)
+    {
+      int byte;
+      blocksize = UINT_GET((int) (log((double) DEFAULT_BLOCK_SIZE) / M_LN2),this_shn);
+      maxnlpc = UINT_GET(LPCQSIZE, this_shn);
+      nmean = UINT_GET(0, this_shn);
+      nskip = UINT_GET(NSKIPSIZE, this_shn);
+      for(i = 0; i < nskip; i++)
+      {
+        byte = uvar_get(XBYTESIZE,this_shn);
+      }
+    }
+    else
+      blocksize = DEFAULT_BLOCK_SIZE;
+
+    nwrap = MAX(NWRAP, maxnlpc);
+
+    /* grab some space for the input buffer */
+    buffer  = long2d((ulong) nchan, (ulong) (blocksize + nwrap),this_shn);
+    if (this_shn->vars.fatal_error)
+      goto exit_thread;
+    offset  = long2d((ulong) nchan, (ulong) MAX(1, nmean),this_shn);
+    if (this_shn->vars.fatal_error) {
+      if (buffer) {
+        free(buffer);
+        buffer = NULL;
+      }
+      goto exit_thread;
+    }
+
+    for(chan = 0; chan < nchan; chan++)
+    {
+      for(i = 0; i < nwrap; i++)
+      	buffer[chan][i] = 0;
+      buffer[chan] += nwrap;
+    }
+
+    if(maxnlpc > 0) {
+      qlpc = (int*) pmalloc((ulong) (maxnlpc * sizeof(*qlpc)),this_shn);
+      if (this_shn->vars.fatal_error) {
+        if (buffer) {
+          free(buffer);
+          buffer = NULL;
+        }
+        if (offset) {
+          free(offset);
+          buffer = NULL;
+        }
+        goto exit_thread;
+      }
+    }
+
+    if(version > 1)
+      lpcqoffset = V2LPCQOFFSET;
+
+    init_offset(offset, nchan, MAX(1, nmean), internal_ftype);
+
+    /* get commands from file and execute them */
+    chan = 0;
+    while(1)
+    {
+        cmd = uvar_get(FNSIZE,this_shn);
+        if (this_shn->vars.fatal_error)
+          goto cleanup;
+
+        switch(cmd)
+        {
+          case FN_ZERO:
+          case FN_DIFF0:
+          case FN_DIFF1:
+          case FN_DIFF2:
+          case FN_DIFF3:
+          case FN_QLPC:
+          {
+            slong coffset, *cbuffer = buffer[chan];
+            int resn = 0, nlpc, j;
+
+            if(cmd != FN_ZERO)
+            {
+              resn = uvar_get(ENERGYSIZE,this_shn);
+              if (this_shn->vars.fatal_error)
+                goto cleanup;
+              /* this is a hack as version 0 differed in definition of var_get */
+              if(version == 0)
+                resn--;
+            }
+
+            /* find mean offset : N.B. this code duplicated */
+            if(nmean == 0)
+              coffset = offset[chan][0];
+            else
+            {
+              slong sum = (version < 2) ? 0 : nmean / 2;
+              for(i = 0; i < nmean; i++)
+                sum += offset[chan][i];
+              if(version < 2)
+                coffset = sum / nmean;
+              else
+                coffset = ROUNDEDSHIFTDOWN(sum / nmean, bitshift);
+            }
+
+            switch(cmd)
+            {
+              case FN_ZERO:
+                for(i = 0; i < blocksize; i++)
+                  cbuffer[i] = 0;
+                break;
+              case FN_DIFF0:
+                for(i = 0; i < blocksize; i++) {
+                  cbuffer[i] = var_get(resn,this_shn) + coffset;
+                  if (this_shn->vars.fatal_error)
+                    goto cleanup;
+                }
+                break;
+              case FN_DIFF1:
+                for(i = 0; i < blocksize; i++) {
+                  cbuffer[i] = var_get(resn,this_shn) + cbuffer[i - 1];
+                  if (this_shn->vars.fatal_error)
+                    goto cleanup;
+                }
+                break;
+              case FN_DIFF2:
+                for(i = 0; i < blocksize; i++) {
+                  cbuffer[i] = var_get(resn,this_shn) + (2 * cbuffer[i - 1] -	cbuffer[i - 2]);
+                  if (this_shn->vars.fatal_error)
+                    goto cleanup;
+                }
+                break;
+              case FN_DIFF3:
+                for(i = 0; i < blocksize; i++) {
+                  cbuffer[i] = var_get(resn,this_shn) + 3 * (cbuffer[i - 1] -  cbuffer[i - 2]) + cbuffer[i - 3];
+                  if (this_shn->vars.fatal_error)
+                    goto cleanup;
+                }
+                break;
+              case FN_QLPC:
+                nlpc = uvar_get(LPCQSIZE,this_shn);
+                if (this_shn->vars.fatal_error)
+                  goto cleanup;
+
+                for(i = 0; i < nlpc; i++) {
+                  qlpc[i] = var_get(LPCQUANT,this_shn);
+                  if (this_shn->vars.fatal_error)
+                    goto cleanup;
+                }
+                for(i = 0; i < nlpc; i++)
+                  cbuffer[i - nlpc] -= coffset;
+                for(i = 0; i < blocksize; i++)
+                {
+                  slong sum = lpcqoffset;
+
+                  for(j = 0; j < nlpc; j++)
+                    sum += qlpc[j] * cbuffer[i - j - 1];
+                  cbuffer[i] = var_get(resn,this_shn) + (sum >> LPCQUANT);
+                  if (this_shn->vars.fatal_error)
+                    goto cleanup;
+                }
+                if(coffset != 0)
+                  for(i = 0; i < blocksize; i++)
+                    cbuffer[i] += coffset;
+                break;
+            }
+
+            /* store mean value if appropriate : N.B. Duplicated code */
+            if(nmean > 0)
+            {
+              slong sum = (version < 2) ? 0 : blocksize / 2;
+
+              for(i = 0; i < blocksize; i++)
+                sum += cbuffer[i];
+
+              for(i = 1; i < nmean; i++)
+                offset[chan][i - 1] = offset[chan][i];
+              if(version < 2)
+                offset[chan][nmean - 1] = sum / blocksize;
+              else
+                offset[chan][nmean - 1] = (sum / blocksize) << bitshift;
+            }
+
+            /* do the wrap */
+            for(i = -nwrap; i < 0; i++)
+              cbuffer[i] = cbuffer[i + blocksize];
+
+            fix_bitshift(cbuffer, blocksize, bitshift, internal_ftype);
+
+            if(chan == nchan - 1)
+            {
+              if (!playback->playing || this_shn->vars.fatal_error)
+                goto cleanup;
+
+              fwrite_type(buffer, ftype, nchan, blocksize, this_shn);
+
+              write_and_wait(this_shn,blk_size);
+
+              if (this_shn->vars.seek_to != -1)
+              {
+                shn_seek_entry *seek_info;
+                int j;
+
+                shn_debug("Seeking to %d:%02d",this_shn->vars.seek_to/60,this_shn->vars.seek_to%60);
+
+                seek_info = shn_seek_entry_search(this_shn->seek_table,this_shn->vars.seek_to * (ulong)this_shn->wave_header.samples_per_sec,0,
+							      (ulong)(this_shn->vars.seek_table_entries - 1),this_shn->vars.seek_resolution);
+
+                /* loop through number of channels in this file */
+                for (i=0;i<nchan;i++) {
+                  /* load the three sample buffer values for this channel */
+                  for (j=0;j<3;j++)
+                    buffer[i][j-3] = shn_uchar_to_slong_le(seek_info->data+32+12*i-4*j);
+
+                  /* load the variable number of offset history values for this channel */
+                  for (j=0;j<MAX(1,nmean);j++)
+                    offset[i][j]  = shn_uchar_to_slong_le(seek_info->data+48+16*i+4*j);
+                }
+
+                bitshift = shn_uchar_to_ushort_le(seek_info->data+22);
+
+                seekto_offset = shn_uchar_to_ulong_le(seek_info->data+8) + this_shn->vars.seek_offset;
+
+                vfs_fseek(this_shn->vars.fd,(slong)seekto_offset,SEEK_SET);
+                vfs_fread((uchar*) this_shn->decode_state->getbuf, 1, BUFSIZ, this_shn->vars.fd);
+
+                this_shn->decode_state->getbufp = this_shn->decode_state->getbuf + shn_uchar_to_ushort_le(seek_info->data+14);
+                this_shn->decode_state->nbitget = shn_uchar_to_ushort_le(seek_info->data+16);
+                this_shn->decode_state->nbyteget = shn_uchar_to_ushort_le(seek_info->data+12);
+                this_shn->decode_state->gbuffer = shn_uchar_to_ulong_le(seek_info->data+18);
+
+                this_shn->vars.bytes_in_buf = 0;
+
+                shn_ip.output->flush(this_shn->vars.seek_to * 1000);
+                this_shn->vars.seek_to = -1;
+              }
+
+            }
+            chan = (chan + 1) % nchan;
+            break;
+          }
+
+          break;
+
+          case FN_QUIT:
+            /* empty out last of buffer */
+            write_and_wait(this_shn,this_shn->vars.bytes_in_buf);
+
+            playback->eof = TRUE;
+
+            while (1)
+            {
+              if (!playback->playing)
+                goto finish;
+              if (this_shn->vars.seek_to != -1)
+              {
+                var_get_quit(this_shn);
+                fwrite_type_quit(this_shn);
+
+                if (buffer) free((void *) buffer);
+                if (offset) free((void *) offset);
+                if(maxnlpc > 0 && qlpc)
+                  free((void *) qlpc);
+
+                vfs_fseek(this_shn->vars.fd,0,SEEK_SET);
+                goto restart;
+              }
+              else
+                xmms_usleep(10000);
+            }
+
+            goto cleanup;
+            break;
+
+          case FN_BLOCKSIZE:
+            blocksize = UINT_GET((int) (log((double) blocksize) / M_LN2), this_shn);
+            if (this_shn->vars.fatal_error)
+              goto cleanup;
+            break;
+          case FN_BITSHIFT:
+            bitshift = uvar_get(BITSHIFTSIZE,this_shn);
+            if (this_shn->vars.fatal_error)
+              goto cleanup;
+            break;
+          case FN_VERBATIM:
+            cklen = uvar_get(VERBATIM_CKSIZE_SIZE,this_shn);
+            if (this_shn->vars.fatal_error)
+              goto cleanup;
+
+            while (cklen--) {
+              tmp = (uchar)uvar_get(VERBATIM_BYTE_SIZE,this_shn);
+              if (this_shn->vars.fatal_error)
+                goto cleanup;
+            }
+
+            break;
+
+          default:
+            shn_error_fatal(this_shn,"Sanity check fails trying to decode function: %d",cmd);
+            goto cleanup;
+        }
+    }
+
+cleanup:
+
+    write_and_wait(this_shn,this_shn->vars.bytes_in_buf);
+    shn_ip.output->buffer_free();
+    shn_ip.output->buffer_free();
+    xmms_usleep(10000);
+
+finish:
+
+    this_shn->vars.seek_to = -1;
+    playback->eof = TRUE;
+
+    /* wind up */
+    var_get_quit(this_shn);
+    fwrite_type_quit(this_shn);
+
+    if (buffer) free((void *) buffer);
+    if (offset) free((void *) offset);
+    if(maxnlpc > 0 && qlpc)
+      free((void *) qlpc);
+
+exit_thread:
+
+    pthread_exit(NULL);
+}
+
+static void shn_play(InputPlayback *playback)
+{
+	char *name, *temp;
+	char *filename = playback->filename;
+
+	audio_error = FALSE;
+
+	if (!(shnfile = load_shn(playback, playback->filename, NULL)))
+	{
+		shn_debug("Could not load file for playing: '%s'", playback->filename);
+		return;
+	}
+
+	vfs_fseek(shnfile->vars.fd,0,SEEK_SET);
+
+	playback->playing = TRUE;
+
+	if (shn_ip.output->open_audio((shnfile->wave_header.bits_per_sample == 16) ? FMT_S16_LE : FMT_U8, shnfile->wave_header.samples_per_sec, shnfile->wave_header.channels) == 0)
+	{
+		audio_error = TRUE;
+		shn_debug("Could not open audio device for playback (check your output plugin configuration)");
+		return;
+	}
+	temp = strrchr(filename, '/');
+	if (!temp)
+		temp = filename;
+	else
+		temp++;
+	name = malloc(strlen(temp) + 1);
+	strcpy(name, temp);
+	if (shn_filename_contains_a_dot(name))
+		*(strrchr(name,'.')) = '\0';
+	shn_ip.set_info(name, 1000 * shnfile->wave_header.length, 8 * shnfile->wave_header.rate, shnfile->wave_header.samples_per_sec, shnfile->wave_header.channels);
+	free(name);
+	shnfile->vars.seek_to = -1;
+	pthread_create(&decode_thread, NULL, play_loop_shn, NULL);
+}
+
+static void shn_stop(InputPlayback *playback)
+{
+	int was_fatal;
+	char error_msg[BUF_SIZE];
+
+	if (!shnfile)
+		return;
+
+	if ((was_fatal = shnfile->vars.fatal_error))
+		shn_snprintf(error_msg,BUF_SIZE,"%s.\nAffected file was:\n%s",shnfile->vars.fatal_error_msg,shnfile->wave_header.filename);
+
+	if (playback->playing || was_fatal)
+	{
+		playback->playing = FALSE;
+		pthread_join(decode_thread, NULL);
+		shn_ip.output->close_audio();
+		shn_unload(shnfile);
+	}
+
+	if (was_fatal)
+		shn_error(error_msg);
+}
+
+static void shn_pause(InputPlayback *playback, short p)
+{
+	playback->output->pause(p);
+}
+
+static void shn_seek(InputPlayback *playback, int time)
+{
+	if (NULL == shnfile)
+		return;
+
+	if (shnfile->vars.seek_table_entries == NO_SEEK_TABLE)
+	{
+		shn_error("Cannot seek to %d:%02d because there is no seek information for this file.",time/60,time%60);
+		return;
+	}
+
+	shnfile->vars.seek_to = time;
+
+	while (shnfile->vars.seek_to != -1)
+		xmms_usleep(10000);
+}
+
+static void shn_get_file_info(char *filename, char **title, int *length)
+{
+	char *name, *temp;
+	shn_file *tmp_file;
+
+	temp = strrchr(filename, '/');
+	if (!temp)
+		temp = filename;
+	else
+		temp++;
+
+	name = g_malloc(strlen(temp) + 1);
+	strcpy(name, temp);
+
+	if (shn_filename_contains_a_dot(name))
+		*(strrchr(name,'.')) = '\0';
+
+	*title = name;
+
+	*length = 0;
+
+	if (!(tmp_file = load_shn(NULL, filename, NULL)))
+	{
+		shn_debug("Could not get information from file: '%s'",filename);
+		return;
+	}
+
+	*length = 1000 * tmp_file->wave_header.length;
+
+	shn_unload(tmp_file);
+}
+
+static void shn_display_file_info(char *filename)
+{
+	shn_file *tmp_file;
+
+	if (!(tmp_file = load_shn(NULL, filename, NULL)))
+	{
+		shn_debug("Could not get information from file: '%s'",filename);
+		return;
+	}
+
+	shn_display_info(tmp_file);
+
+	shn_unload(tmp_file);
+}