summaryrefslogtreecommitdiff
path: root/plugins/shn/shn.c
diff options
context:
space:
mode:
authorGravatar Alexey Yakovenko <wakeroid@gmail.com>2010-06-20 20:29:25 +0200
committerGravatar Alexey Yakovenko <wakeroid@gmail.com>2010-06-20 20:29:25 +0200
commit7088867879c41de81e485f79f60cd2bb08d456d1 (patch)
tree41cbc372885a640681be3aa5551cef43718ea653 /plugins/shn/shn.c
parent9300f16316eb33715193a0f9a916991a2b24ba9d (diff)
initial port of xmms-shorten plugin
no gapless playback, no configuration, no sample-accurate seeking
Diffstat (limited to 'plugins/shn/shn.c')
-rw-r--r--plugins/shn/shn.c1798
1 files changed, 1798 insertions, 0 deletions
diff --git a/plugins/shn/shn.c b/plugins/shn/shn.c
new file mode 100644
index 00000000..17d2e231
--- /dev/null
+++ b/plugins/shn/shn.c
@@ -0,0 +1,1798 @@
+/*
+ DeaDBeeF - ultimate music player for GNU/Linux systems with X11
+ Copyright (C) 2009-2010 Alexey Yakovenko <waker@users.sourceforge.net>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+// based on xmms-shn, http://www.etree.org/shnutils/xmms-shn/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include "shorten.h"
+#include "../../deadbeef.h"
+#include "bitshift.h"
+
+#define trace(...) { fprintf(stderr, __VA_ARGS__); }
+//#define trace(fmt,...)
+
+static DB_decoder_t plugin;
+DB_functions_t *deadbeef;
+
+shn_file *load_shn(const char *filename);
+static void shn_play(char *);
+static void shn_stop(void);
+static int shn_get_time(void);
+static void shn_get_file_info(char *,char **,int *);
+static void shn_display_file_info(char *);
+
+typedef struct {
+ DB_fileinfo_t info;
+ shn_file *shnfile;
+
+ slong **buffer, **offset;
+ slong lpcqoffset;
+ int version, bitshift;
+ int ftype;
+ char *magic;
+ int blocksize, nchan;
+ int i, chan, nwrap, nskip;
+ int *qlpc, maxnlpc, nmean;
+ int cmd;
+ int internal_ftype;
+ int blk_size;
+ int cklen;
+ uchar tmp;
+
+ int currentsample;
+ int startsample;
+ int endsample;
+} shn_fileinfo_t;
+
+shn_config shn_cfg;
+
+DB_fileinfo_t *
+shn_open (void) {
+ DB_fileinfo_t *_info = malloc (sizeof (shn_fileinfo_t));
+ shn_fileinfo_t *info = (shn_fileinfo_t *)_info;
+ memset (info, 0, sizeof (shn_fileinfo_t));
+ return _info;
+}
+
+int
+ddb_getc (DB_FILE *fp) {
+ uint8_t c;
+ if (deadbeef->fread (&c, 1, 1, fp) != 1) {
+ return EOF;
+ }
+ return (int)c;
+}
+
+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
+shn_init_decoder (shn_fileinfo_t *info) {
+ int version = FORMAT_VERSION;
+ info->ftype = TYPE_EOF;
+ info->magic = MAGIC;
+ info->blocksize = DEFAULT_BLOCK_SIZE;
+ info->nchan = DEFAULT_NCHAN;
+ info->nskip = DEFAULT_NSKIP;
+ info->maxnlpc = DEFAULT_MAXNLPC;
+ info->nmean = UNDEFINED_UINT;
+
+ info->shnfile->vars.bytes_in_buf = 0;
+ if (!init_decode_state(info->shnfile)) {
+ trace ("shn: init_decode_state failed\n");
+ return -1;
+ }
+ info->shnfile->vars.going = 1;
+
+ info->blk_size = 512 * (info->shnfile->wave_header.bits_per_sample / 8) * info->shnfile->wave_header.channels;
+
+ /* 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 = ddb_getc(info->shnfile->vars.fd);
+ if(byte == EOF) {
+ shn_error_fatal(info->shnfile,"No magic number");
+ trace ("shn_init: no magic number\n");
+ return -1;
+ }
+ if(info->magic[nscan] != '\0' && byte == info->magic[nscan]) {
+ nscan++;
+ }
+ else {
+ if(info->magic[nscan] == '\0' && byte <= MAX_VERSION)
+ version = byte;
+ else
+ {
+ if(byte == info->magic[0])
+ nscan = 1;
+ else
+ {
+ nscan = 0;
+ }
+ version = MAX_VERSION + 1;
+ }
+ }
+ }
+ }
+
+ /* check version number */
+ if(version > MAX_SUPPORTED_VERSION) {
+ shn_error_fatal(info->shnfile,"Can't decode version %d", version);
+ trace ("shn_init: can't decode version %d\n", version);
+ return -1;
+ }
+
+ /* set up the default nmean, ignoring the command line state */
+ info->nmean = (version < 2) ? DEFAULT_V0NMEAN : DEFAULT_V2NMEAN;
+
+ /* initialise the variable length file read for the compressed stream */
+ trace ("decode_state=%p\n", info->shnfile->decode_state);
+ var_get_init(info->shnfile);
+ if (info->shnfile->vars.fatal_error) {
+ trace ("var_get_init failed\n");
+ return -1;
+ }
+
+ /* initialise the fixed length file write for the uncompressed stream */
+ fwrite_type_init(info->shnfile);
+
+ /* get the internal file type */
+ info->internal_ftype = UINT_GET(TYPESIZE, info->shnfile);
+ trace ("internal_ftype=%d\n", info->internal_ftype);
+
+ /* has the user requested a change in file type? */
+ if(info->internal_ftype != info->ftype) {
+ if(info->ftype == TYPE_EOF) {
+ info->ftype = info->internal_ftype; /* no problems here */
+ }
+ else { /* check that the requested conversion is valid */
+ if(info->internal_ftype == TYPE_AU1 || info->internal_ftype == TYPE_AU2 ||
+ info->internal_ftype == TYPE_AU3 || info->ftype == TYPE_AU1 || info->ftype == TYPE_AU2 || info->ftype == TYPE_AU3) {
+ shn_error_fatal(info->shnfile,"Not able to perform requested output format conversion");
+ trace ("shn_init_decoder: Not able to perform requested output format conversion\n");
+ return -1;
+ }
+ }
+ trace ("ftype=%d\n", info->ftype);
+ }
+
+ info->nchan = UINT_GET(CHANSIZE, info->shnfile);
+
+ /* get blocksize if version > 0 */
+ if(version > 0)
+ {
+ int byte;
+ info->blocksize = UINT_GET((int) (log((double) DEFAULT_BLOCK_SIZE) / M_LN2),info->shnfile);
+ info->maxnlpc = UINT_GET(LPCQSIZE, info->shnfile);
+ info->nmean = UINT_GET(0, info->shnfile);
+ info->nskip = UINT_GET(NSKIPSIZE, info->shnfile);
+ for(int i = 0; i < info->nskip; i++)
+ {
+ byte = uvar_get(XBYTESIZE,info->shnfile);
+ }
+ }
+ else
+ info->blocksize = DEFAULT_BLOCK_SIZE;
+
+ info->nwrap = MAX(NWRAP, info->maxnlpc);
+
+ /* grab some space for the input buffer */
+ info->buffer = long2d((ulong) info->nchan, (ulong) (info->blocksize + info->nwrap),info->shnfile);
+ if (info->shnfile->vars.fatal_error) {
+ trace ("failed to alloc buffer\n");
+ return -1;
+ }
+ info->offset = long2d((ulong) info->nchan, (ulong) MAX(1, info->nmean),info->shnfile);
+ if (info->shnfile->vars.fatal_error) {
+ if (info->buffer) {
+ free(info->buffer);
+ info->buffer = NULL;
+ }
+ trace ("failed to alloc offset\n");
+ return -1;
+ }
+
+ for(info->chan = 0; info->chan < info->nchan; info->chan++)
+ {
+ for(int i = 0; i < info->nwrap; i++)
+ info->buffer[info->chan][i] = 0;
+ info->buffer[info->chan] += info->nwrap;
+ }
+
+ if(info->maxnlpc > 0) {
+ info->qlpc = (int*) pmalloc((ulong) (info->maxnlpc * sizeof(*info->qlpc)),info->shnfile);
+ if (info->shnfile->vars.fatal_error) {
+ if (info->buffer) {
+ free(info->buffer);
+ info->buffer = NULL;
+ }
+ if (info->offset) {
+ free(info->offset);
+ info->offset = NULL;
+ }
+ trace ("failed to alloc qlpc\n");
+ return -1;
+ }
+ }
+
+ if(version > 1)
+ info->lpcqoffset = V2LPCQOFFSET;
+
+ init_offset (info->offset, info->nchan, MAX(1, info->nmean), info->internal_ftype);
+
+ /* get commands from file and execute them */
+ info->chan = 0;
+ info->version = version;
+ trace ("shn_init: success\n");
+ return 0;
+}
+
+int
+shn_init(DB_fileinfo_t *_info, DB_playItem_t *it) {
+ shn_fileinfo_t *info = (shn_fileinfo_t *)_info;
+
+ 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 = 0;
+ shn_cfg.load_textfiles_config_name = "load_textfiles";
+ shn_cfg.textfile_extensions = NULL;
+ shn_cfg.textfile_extensions_config_name = "textfile_extensions";
+
+// {{{ xmms config reader
+#if 0
+ filename = g_strconcat(g_get_home_dir(), "/.xmms/config", NULL);
+ if ((cfg = xmms_cfg_open_file(filename)) != 0)
+ {
+ xmms_cfg_read_int(cfg, XMMS_SHN_VERSION_TAG, shn_cfg.error_output_method_config_name, &shn_cfg.error_output_method);
+ xmms_cfg_read_boolean(cfg, XMMS_SHN_VERSION_TAG, shn_cfg.verbose_config_name, &shn_cfg.verbose);
+ if (!xmms_cfg_read_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 (!xmms_cfg_read_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("");
+ xmms_cfg_read_boolean(cfg, XMMS_SHN_VERSION_TAG, shn_cfg.swap_bytes_config_name, &shn_cfg.swap_bytes);
+ xmms_cfg_read_boolean(cfg, XMMS_SHN_VERSION_TAG, shn_cfg.load_textfiles_config_name, &shn_cfg.load_textfiles);
+ if (!xmms_cfg_read_string(cfg, XMMS_SHN_VERSION_TAG, shn_cfg.textfile_extensions_config_name, &shn_cfg.textfile_extensions))
+ shn_cfg.textfile_extensions = g_strdup("txt,nfo");
+ xmms_cfg_free(cfg);
+ }
+
+ g_free(filename);
+#endif
+// }}}
+
+ char data[4];
+ DB_FILE *f;
+
+ f = deadbeef->fopen (it->fname);
+ if (!f) {
+ trace ("shn: failed to open %s\n", it->fname);
+ return -1;
+ }
+
+ int id3v2_tag_size = deadbeef->junk_get_leading_size (f);
+ if (id3v2_tag_size > 0) {
+ deadbeef->fseek (f, id3v2_tag_size, SEEK_SET);
+ }
+
+ if (deadbeef->fread((void *)data,1,4,f) != 4)
+ {
+ deadbeef->fclose(f);
+ trace ("shn: failed to read magic from %s\n", it->fname);
+ return -1;
+ }
+ deadbeef->fclose(f);
+
+ if (memcmp(data,MAGIC,4)) {
+ trace ("shn: invalid MAGIC\n");
+ return -1;
+ }
+
+ if (!(info->shnfile = load_shn(it->fname))) {
+ trace ("shn: load_shn failed\n");
+ return -1;
+ }
+
+ _info->bps = info->shnfile->wave_header.bits_per_sample;
+ _info->channels = info->shnfile->wave_header.channels;
+ _info->samplerate = info->shnfile->wave_header.samples_per_sec;
+ _info->plugin = &plugin;
+
+ int totalsamples = info->shnfile->wave_header.length * info->shnfile->wave_header.samples_per_sec;
+ trace ("totalsamples: %d\n", totalsamples);
+
+ if (it->endsample > 0) {
+ info->startsample = it->startsample;
+ info->endsample = it->endsample;
+ plugin.seek_sample (_info, 0);
+ }
+ else {
+ info->startsample = 0;
+ info->endsample = totalsamples-1;
+ }
+
+ if (info->shnfile->wave_header.file_has_id3v2_tag) {
+ deadbeef->fseek (info->shnfile->vars.fd, info->shnfile->wave_header.file_has_id3v2_tag, SEEK_SET);
+ }
+ else {
+ deadbeef->rewind (info->shnfile->vars.fd);
+ }
+
+ if (shn_init_decoder (info) < 0) {
+ trace ("shn_init_decoder failed\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+void
+shn_free (DB_fileinfo_t *_info) {
+ shn_fileinfo_t *info = (shn_fileinfo_t *)_info;
+ if (info->shnfile) {
+ if (info->shnfile->decode_state) {
+ if(info->shnfile->decode_state->writebuf != NULL) {
+ free(info->shnfile->decode_state->writebuf);
+ info->shnfile->decode_state->writebuf = NULL;
+ }
+ if(info->shnfile->decode_state->writefub != NULL) {
+ free(info->shnfile->decode_state->writefub);
+ info->shnfile->decode_state->writefub = NULL;
+ }
+ }
+ if (info->shnfile) {
+ shn_unload(info->shnfile);
+ info->shnfile = NULL;
+ }
+ }
+ if (info->buffer) {
+ free(info->buffer);
+ info->buffer = NULL;
+ }
+ if (info->offset) {
+ free(info->offset);
+ info->offset = NULL;
+ }
+ if(info->maxnlpc > 0 && info->qlpc) {
+ free(info->qlpc);
+ info->qlpc = NULL;
+ }
+ free (info);
+}
+
+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;
+ }
+}
+
+int
+shn_decode (shn_fileinfo_t *info) {
+ int i;
+ int version = info->version;
+ while(1)
+ {
+ info->cmd = uvar_get(FNSIZE,info->shnfile);
+ if (info->shnfile->vars.fatal_error) {
+ trace ("shn_decode: uvar_get error\n");
+ return -1;
+ }
+
+ switch(info->cmd)
+ {
+ case FN_ZERO:
+ case FN_DIFF0:
+ case FN_DIFF1:
+ case FN_DIFF2:
+ case FN_DIFF3:
+ case FN_QLPC:
+ {
+ slong coffset, *cbuffer = info->buffer[info->chan];
+ int resn = 0, nlpc, j;
+
+ if(info->cmd != FN_ZERO)
+ {
+ resn = uvar_get(ENERGYSIZE,info->shnfile);
+ if (info->shnfile->vars.fatal_error) {
+ trace ("shn_decode: error 1\n");
+ return -1;
+ }
+ /* this is a hack as version 0 differed in definition of var_get */
+ if(info->version == 0)
+ resn--;
+ }
+
+ /* find mean offset : N.B. this code duplicated */
+ if(info->nmean == 0)
+ coffset = info->offset[info->chan][0];
+ else
+ {
+ slong sum = (info->version < 2) ? 0 : info->nmean / 2;
+ for(i = 0; i < info->nmean; i++)
+ sum += info->offset[info->chan][i];
+ if(info->version < 2)
+ coffset = sum / info->nmean;
+ else
+ coffset = ROUNDEDSHIFTDOWN(sum / info->nmean, info->bitshift);
+ }
+
+ switch(info->cmd)
+ {
+ case FN_ZERO:
+ for(i = 0; i < info->blocksize; i++)
+ cbuffer[i] = 0;
+ break;
+ case FN_DIFF0:
+ for(i = 0; i < info->blocksize; i++) {
+ cbuffer[i] = var_get(resn,info->shnfile) + coffset;
+ if (info->shnfile->vars.fatal_error) {
+ trace ("shn_decode: error 2\n");
+ return -1;
+ }
+ }
+ break;
+ case FN_DIFF1:
+ for(i = 0; i < info->blocksize; i++) {
+ cbuffer[i] = var_get(resn,info->shnfile) + cbuffer[i - 1];
+ if (info->shnfile->vars.fatal_error) {
+ trace ("shn_decode: error 3\n");
+ return -1;
+ }
+ }
+ break;
+ case FN_DIFF2:
+ for(i = 0; i < info->blocksize; i++) {
+ cbuffer[i] = var_get(resn,info->shnfile) + (2 * cbuffer[i - 1] - cbuffer[i - 2]);
+ if (info->shnfile->vars.fatal_error) {
+ trace ("shn_decode: error 4\n");
+ return -1;
+ }
+ }
+ break;
+ case FN_DIFF3:
+ for(i = 0; i < info->blocksize; i++) {
+ cbuffer[i] = var_get(resn,info->shnfile) + 3 * (cbuffer[i - 1] - cbuffer[i - 2]) + cbuffer[i - 3];
+ if (info->shnfile->vars.fatal_error) {
+ trace ("shn_decode: error 5\n");
+ return -1;
+ }
+ }
+ break;
+ case FN_QLPC:
+ nlpc = uvar_get(LPCQSIZE,info->shnfile);
+ if (info->shnfile->vars.fatal_error) {
+ trace ("shn_decode: error 6\n");
+ return -1;
+ }
+
+ for(i = 0; i < nlpc; i++) {
+ info->qlpc[i] = var_get(LPCQUANT,info->shnfile);
+ if (info->shnfile->vars.fatal_error) {
+ trace ("shn_decode: error 7\n");
+ return -1;
+ }
+ }
+ for(i = 0; i < nlpc; i++)
+ cbuffer[i - nlpc] -= coffset;
+ for(i = 0; i < info->blocksize; i++)
+ {
+ slong sum = info->lpcqoffset;
+
+ for(j = 0; j < nlpc; j++)
+ sum += info->qlpc[j] * cbuffer[i - j - 1];
+ cbuffer[i] = var_get(resn,info->shnfile) + (sum >> LPCQUANT);
+ if (info->shnfile->vars.fatal_error) {
+ trace ("shn_decode: error 8\n");
+ return -1;
+ }
+ }
+ if(coffset != 0)
+ for(i = 0; i < info->blocksize; i++)
+ cbuffer[i] += coffset;
+ break;
+ }
+
+ /* store mean value if appropriate : N.B. Duplicated code */
+ if(info->nmean > 0)
+ {
+ slong sum = (info->version < 2) ? 0 : info->blocksize / 2;
+
+ for(i = 0; i < info->blocksize; i++)
+ sum += cbuffer[i];
+
+ for(i = 1; i < info->nmean; i++)
+ info->offset[info->chan][i - 1] = info->offset[info->chan][i];
+ if(info->version < 2)
+ info->offset[info->chan][info->nmean - 1] = sum / info->blocksize;
+ else
+ info->offset[info->chan][info->nmean - 1] = (sum / info->blocksize) << info->bitshift;
+ }
+
+ /* do the wrap */
+ for(i = -info->nwrap; i < 0; i++)
+ cbuffer[i] = cbuffer[i + info->blocksize];
+
+ fix_bitshift(cbuffer, info->blocksize, info->bitshift, info->internal_ftype);
+
+ if(info->chan == info->nchan - 1)
+ {
+ if (!info->shnfile->vars.going || info->shnfile->vars.fatal_error) {
+ trace ("shn_decode: error 9\n");
+ return -1;
+ }
+
+ fwrite_type(info->buffer, info->ftype, info->nchan, info->blocksize, info->shnfile);
+ info->chan = (info->chan + 1) % info->nchan;
+ // now we have buffer of size info->shnfile->vars.bytes_in_buf
+ if (shn_cfg.swap_bytes) {
+ swap_bytes(info->shnfile, info->shnfile->vars.bytes_in_buf);
+ }
+ return info->shnfile->vars.bytes_in_buf;
+
+
+// !!!!!!!!!!!!!!!!!!FIXME
+ // write_and_wait(info->shnfile,blk_size);
+
+#if 0 // seeking
+ if (info->shnfile->vars.seek_to != -1)
+ {
+ shn_seek_entry *seek_info;
+ int j;
+
+ shn_debug("Seeking to %d:%02d",info->shnfile->vars.seek_to/60,info->shnfile->vars.seek_to%60);
+
+ seek_info = shn_seek_entry_search(info->shnfile->seek_table,info->shnfile->vars.seek_to * (ulong)info->shnfile->wave_header.samples_per_sec,0,
+ (ulong)(info->shnfile->vars.seek_table_entries - 1),info->shnfile->vars.seek_resolution);
+
+ /* loop through number of channels in this file */
+ for (i=0;i<info->nchan;i++) {
+ /* load the three sample buffer values for this channel */
+ for (j=0;j<3;j++)
+ info->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,info->nmean);j++)
+ info->offset[i][j] = shn_uchar_to_slong_le(seek_info->data+48+16*i+4*j);
+ }
+
+ info->bitshift = shn_uchar_to_ushort_le(seek_info->data+22);
+
+ info->seekto_offset = shn_uchar_to_ulong_le(seek_info->data+8) + info->shnfile->vars.seek_offset;
+
+ deadbeef->fseek(info->shnfile->vars.fd,(slong)seekto_offset,SEEK_SET);
+ deadbeef->fread((uchar*) info->shnfile->decode_state->getbuf, 1, BUFSIZ, info->shnfile->vars.fd);
+
+ info->shnfile->decode_state->getbufp = info->shnfile->decode_state->getbuf + shn_uchar_to_ushort_le(seek_info->data+14);
+ info->shnfile->decode_state->nbitget = shn_uchar_to_ushort_le(seek_info->data+16);
+ info->shnfile->decode_state->nbyteget = shn_uchar_to_ushort_le(seek_info->data+12);
+ info->shnfile->decode_state->gbuffer = shn_uchar_to_ulong_le(seek_info->data+18);
+
+ info->shnfile->vars.bytes_in_buf = 0;
+
+ shn_ip.output->flush(info->shnfile->vars.seek_to * 1000);
+ info->shnfile->vars.seek_to = -1;
+ }
+#endif
+
+ }
+ info->chan = (info->chan + 1) % info->nchan;
+ break;
+ }
+
+ break;
+
+ case FN_QUIT:
+ /* empty out last of buffer */
+ info->shnfile->vars.eof = 1;
+ if (shn_cfg.swap_bytes) {
+ swap_bytes(info->shnfile, info->shnfile->vars.bytes_in_buf);
+ }
+ return info->shnfile->vars.bytes_in_buf;
+#if 0
+ write_and_wait(info->shnfile,info->shnfile->vars.bytes_in_buf);
+ info->shnfile->vars.eof = 1;
+
+ while (1)
+ {
+ if (!info->shnfile->vars.going)
+ goto finish;
+ if (info->shnfile->vars.seek_to != -1)
+ {
+ var_get_quit(info->shnfile);
+ fwrite_type_quit(info->shnfile);
+
+ if (buffer) free((void *) buffer);
+ if (offset) free((void *) offset);
+ if(maxnlpc > 0 && qlpc)
+ free((void *) qlpc);
+
+ fseek(info->shnfile->vars.fd,0,SEEK_SET);
+ goto restart;
+ }
+ else
+ xmms_usleep(10000);
+ }
+
+ goto cleanup;
+#endif
+ break;
+
+ case FN_BLOCKSIZE:
+ info->blocksize = UINT_GET((int) (log((double) info->blocksize) / M_LN2), info->shnfile);
+ if (info->shnfile->vars.fatal_error) {
+ trace ("shn_decode: error 10\n");
+ return -1;
+ }
+ break;
+ case FN_BITSHIFT:
+ info->bitshift = uvar_get(BITSHIFTSIZE,info->shnfile);
+ if (info->shnfile->vars.fatal_error) {
+ trace ("shn_decode: error 11\n");
+ return -1;
+ }
+ break;
+ case FN_VERBATIM:
+ info->cklen = uvar_get(VERBATIM_CKSIZE_SIZE,info->shnfile);
+ if (info->shnfile->vars.fatal_error) {
+ trace ("shn_decode: error 12\n");
+ return -1;
+ }
+
+ while (info->cklen--) {
+ info->tmp = (uchar)uvar_get(VERBATIM_BYTE_SIZE,info->shnfile);
+ if (info->shnfile->vars.fatal_error) {
+ trace ("shn_decode: error 13\n");
+ return -1;
+ }
+ }
+
+ break;
+
+ default:
+ shn_error_fatal(info->shnfile,"Sanity check fails trying to decode function: %d",info->cmd);
+ trace ("shn_decode: error 14\n");
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int
+shn_read_int16 (DB_fileinfo_t *_info, char *bytes, int size) {
+ shn_fileinfo_t *info = (shn_fileinfo_t *)_info;
+ if (info->currentsample + size / (2 * _info->channels) > info->endsample) {
+ size = (info->endsample - info->currentsample + 1) * 2 * _info->channels;
+ if (size <= 0) {
+ return 0;
+ }
+ }
+ int initsize = size;
+ int ch = min (_info->channels, 2);
+ int sample_size = ch * (_info->bps >> 3);
+
+ while (size > 0) {
+ if (info->shnfile->vars.bytes_in_buf > 0) {
+ int n = size / sample_size;
+ int nsamples = info->shnfile->vars.bytes_in_buf / (_info->channels * 2);
+ n = min (nsamples, n);
+ char *src = info->shnfile->vars.buffer;
+ for (int i = 0; i < n; i++) {
+ memcpy (bytes, src, sample_size);
+ bytes += sample_size;
+ src += _info->channels * 2;
+ }
+
+ size -= n * sample_size;
+ if (n == info->shnfile->vars.bytes_in_buf / (_info->channels * 2)) {
+ info->shnfile->vars.bytes_in_buf = 0;
+ }
+ else {
+ memmove (info->shnfile->vars.buffer, src, info->shnfile->vars.bytes_in_buf - n * (_info->channels * 2));
+ info->shnfile->vars.bytes_in_buf -= n * (_info->channels * 2);
+ }
+ continue;
+ }
+ if (shn_decode (info) <= 0) {
+ trace ("shn_decode returned error\n");
+ break;
+ }
+ }
+
+ info->currentsample += (initsize-size) / sample_size;
+ if (size != 0) {
+ trace ("shn_read_int16 eof\n");
+ }
+ return initsize-size;
+}
+
+int
+shn_seek_sample (DB_fileinfo_t *_info, int sample) {
+ shn_fileinfo_t *info = (shn_fileinfo_t *)_info;
+
+ sample += info->startsample;
+
+ info->shnfile->vars.seek_to = sample / _info->samplerate;
+
+ ulong seekto_offset;
+ int i, j;
+ shn_seek_entry *seek_info;
+
+ trace ("Seeking to %d:%02d\n",info->shnfile->vars.seek_to/60,info->shnfile->vars.seek_to%60);
+
+ seek_info = shn_seek_entry_search(info->shnfile->seek_table,info->shnfile->vars.seek_to * (ulong)info->shnfile->wave_header.samples_per_sec,0,
+ (ulong)(info->shnfile->vars.seek_table_entries - 1),info->shnfile->vars.seek_resolution);
+
+ /* loop through number of channels in this file */
+ for (i=0;i<info->nchan;i++) {
+ /* load the three sample buffer values for this channel */
+ for (j=0;j<3;j++)
+ info->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,info->nmean);j++)
+ info->offset[i][j] = shn_uchar_to_slong_le(seek_info->data+48+16*i+4*j);
+ }
+
+ info->bitshift = shn_uchar_to_ushort_le(seek_info->data+22);
+
+ seekto_offset = shn_uchar_to_ulong_le(seek_info->data+8) + info->shnfile->vars.seek_offset;
+
+ deadbeef->fseek(info->shnfile->vars.fd,(slong)seekto_offset,SEEK_SET);
+ deadbeef->fread((uchar*) info->shnfile->decode_state->getbuf, 1, BUFSIZ, info->shnfile->vars.fd);
+
+ info->shnfile->decode_state->getbufp = info->shnfile->decode_state->getbuf + shn_uchar_to_ushort_le(seek_info->data+14);
+ info->shnfile->decode_state->nbitget = shn_uchar_to_ushort_le(seek_info->data+16);
+ info->shnfile->decode_state->nbyteget = shn_uchar_to_ushort_le(seek_info->data+12);
+ info->shnfile->decode_state->gbuffer = shn_uchar_to_ulong_le(seek_info->data+18);
+
+ info->shnfile->vars.bytes_in_buf = 0;
+
+ info->currentsample = info->shnfile->vars.seek_to * _info->samplerate;
+ _info->readpos = info->shnfile->vars.seek_to;
+ return 0;
+}
+
+int
+shn_seek (DB_fileinfo_t *_info, float time) {
+ return shn_seek_sample (_info, time * _info->samplerate);
+ return 0;
+}
+
+DB_playItem_t *
+shn_insert (DB_playItem_t *after, const char *fname) {
+ shn_file *tmp_file;
+ DB_FILE *f;
+ char data[4];
+
+ f = deadbeef->fopen (fname);
+ if (!f) {
+ return NULL;
+ }
+
+ int id3v2_tag_size = deadbeef->junk_get_leading_size (f);
+ if (id3v2_tag_size > 0) {
+ deadbeef->fseek (f, id3v2_tag_size, SEEK_SET);
+ }
+
+ if (deadbeef->fread((void *)data,1,4,f) != 4)
+ {
+ deadbeef->fclose(f);
+ return NULL;
+ }
+ deadbeef->fclose(f);
+
+ if (memcmp(data,MAGIC,4)) {
+ trace ("shn: invalid MAGIC\n");
+ return NULL;
+ }
+
+ if (!(tmp_file = load_shn(fname))) {
+ trace ("shn: load_shn failed\n");
+ return NULL;
+ }
+
+ DB_playItem_t *it = deadbeef->pl_item_alloc ();
+ it->decoder_id = deadbeef->plug_get_decoder_id (plugin.plugin.id);
+ it->fname = strdup (fname);
+ it->filetype = "Shorten";
+ deadbeef->pl_set_item_duration (it, tmp_file->wave_header.length);
+
+ int apeerr = deadbeef->junk_apev2_read (it, tmp_file->vars.fd);
+ int v2err = deadbeef->junk_id3v2_read (it, tmp_file->vars.fd);
+ int v1err = deadbeef->junk_id3v1_read (it, tmp_file->vars.fd);
+
+ shn_unload(tmp_file);
+
+ deadbeef->pl_add_meta (it, "title", NULL);
+
+ after = deadbeef->pl_insert_item (after, it);
+ deadbeef->pl_item_unref (it);
+ return after;
+}
+
+#define CAPMAXSCHAR(x) ((x > 127) ? 127 : x)
+#define CAPMAXUCHAR(x) ((x > 255) ? 255 : x)
+#define CAPMAXSHORT(x) ((x > 32767) ? 32767 : x)
+#define CAPMAXUSHORT(x) ((x > 65535) ? 65535 : x)
+
+static int sizeof_sample[TYPE_EOF];
+
+void init_sizeof_sample() {
+ sizeof_sample[TYPE_AU1] = sizeof(uchar);
+ sizeof_sample[TYPE_S8] = sizeof(schar);
+ sizeof_sample[TYPE_U8] = sizeof(uchar);
+ sizeof_sample[TYPE_S16HL] = sizeof(ushort);
+ sizeof_sample[TYPE_U16HL] = sizeof(ushort);
+ sizeof_sample[TYPE_S16LH] = sizeof(ushort);
+ sizeof_sample[TYPE_U16LH] = sizeof(ushort);
+ sizeof_sample[TYPE_ULAW] = sizeof(uchar);
+ sizeof_sample[TYPE_AU2] = sizeof(uchar);
+ sizeof_sample[TYPE_AU3] = sizeof(uchar);
+ sizeof_sample[TYPE_ALAW] = sizeof(uchar);
+}
+
+
+/***************/
+/* fixed write */
+/***************/
+
+void fwrite_type_init(shn_file *this_shn) {
+ init_sizeof_sample();
+ this_shn->decode_state->writebuf = (schar*) NULL;
+ this_shn->decode_state->writefub = (schar*) NULL;
+ this_shn->decode_state->nwritebuf = 0;
+}
+
+void fwrite_type_quit(shn_file *this_shn) {
+ if(this_shn->decode_state->writebuf != NULL) {
+ free(this_shn->decode_state->writebuf);
+ this_shn->decode_state->writebuf = NULL;
+ }
+ if(this_shn->decode_state->writefub != NULL) {
+ free(this_shn->decode_state->writefub);
+ this_shn->decode_state->writefub = NULL;
+ }
+}
+
+/* convert from signed ints to a given type and write */
+void fwrite_type(slong **data,int ftype,int nchan,int nitem,shn_file *this_shn)
+{
+ int hiloint = 1, hilo = !(*((char*) &hiloint));
+ int i, nwrite = 0, datasize = sizeof_sample[ftype], chan;
+ slong *data0 = data[0];
+ int bufAvailable = OUT_BUFFER_SIZE - this_shn->vars.bytes_in_buf;
+
+ if(this_shn->decode_state->nwritebuf < nchan * nitem * datasize) {
+ this_shn->decode_state->nwritebuf = nchan * nitem * datasize;
+ if(this_shn->decode_state->writebuf != NULL) free(this_shn->decode_state->writebuf);
+ if(this_shn->decode_state->writefub != NULL) free(this_shn->decode_state->writefub);
+ this_shn->decode_state->writebuf = (schar*) pmalloc((ulong) this_shn->decode_state->nwritebuf,this_shn);
+ if (!this_shn->decode_state->writebuf)
+ return;
+ this_shn->decode_state->writefub = (schar*) pmalloc((ulong) this_shn->decode_state->nwritebuf,this_shn);
+ if (!this_shn->decode_state->writefub)
+ return;
+ }
+
+ switch(ftype) {
+ case TYPE_AU1: /* leave the conversion to fix_bitshift() */
+ case TYPE_AU2: {
+ uchar *writebufp = (uchar*) this_shn->decode_state->writebuf;
+ if(nchan == 1)
+ for(i = 0; i < nitem; i++)
+ *writebufp++ = data0[i];
+ else
+ for(i = 0; i < nitem; i++)
+ for(chan = 0; chan < nchan; chan++)
+ *writebufp++ = data[chan][i];
+ break;
+ }
+ case TYPE_U8: {
+ uchar *writebufp = (uchar*) this_shn->decode_state->writebuf;
+ if(nchan == 1)
+ for(i = 0; i < nitem; i++)
+ *writebufp++ = CAPMAXUCHAR(data0[i]);
+ else
+ for(i = 0; i < nitem; i++)
+ for(chan = 0; chan < nchan; chan++)
+ *writebufp++ = CAPMAXUCHAR(data[chan][i]);
+ break;
+ }
+ case TYPE_S8: {
+ schar *writebufp = (schar*) this_shn->decode_state->writebuf;
+ if(nchan == 1)
+ for(i = 0; i < nitem; i++)
+ *writebufp++ = CAPMAXSCHAR(data0[i]);
+ else
+ for(i = 0; i < nitem; i++)
+ for(chan = 0; chan < nchan; chan++)
+ *writebufp++ = CAPMAXSCHAR(data[chan][i]);
+ break;
+ }
+ case TYPE_S16HL:
+ case TYPE_S16LH: {
+ short *writebufp = (short*) this_shn->decode_state->writebuf;
+ if(nchan == 1)
+ for(i = 0; i < nitem; i++)
+ *writebufp++ = CAPMAXSHORT(data0[i]);
+ else
+ for(i = 0; i < nitem; i++)
+ for(chan = 0; chan < nchan; chan++)
+ *writebufp++ = CAPMAXSHORT(data[chan][i]);
+ break;
+ }
+ case TYPE_U16HL:
+ case TYPE_U16LH: {
+ ushort *writebufp = (ushort*) this_shn->decode_state->writebuf;
+ if(nchan == 1)
+ for(i = 0; i < nitem; i++)
+ *writebufp++ = CAPMAXUSHORT(data0[i]);
+ else
+ for(i = 0; i < nitem; i++)
+ for(chan = 0; chan < nchan; chan++)
+ *writebufp++ = CAPMAXUSHORT(data[chan][i]);
+ break;
+ }
+ case TYPE_ULAW: {
+ uchar *writebufp = (uchar*) this_shn->decode_state->writebuf;
+ if(nchan == 1)
+ for(i = 0; i < nitem; i++)
+ *writebufp++ = Slinear2ulaw(CAPMAXSHORT((data0[i] << 3)));
+ else
+ for(i = 0; i < nitem; i++)
+ for(chan = 0; chan < nchan; chan++)
+ *writebufp++ = Slinear2ulaw(CAPMAXSHORT((data[chan][i] << 3)));
+ break;
+ }
+ case TYPE_AU3: {
+ uchar *writebufp = (uchar*) this_shn->decode_state->writebuf;
+ if(nchan == 1)
+ for(i = 0; i < nitem; i++)
+ if(data0[i] < 0)
+ *writebufp++ = (127 - data0[i]) ^ 0xd5;
+ else
+ *writebufp++ = (data0[i] + 128) ^ 0x55;
+ else
+ for(i = 0; i < nitem; i++)
+ for(chan = 0; chan < nchan; chan++)
+ if(data[chan][i] < 0)
+ *writebufp++ = (127 - data[chan][i]) ^ 0xd5;
+ else
+ *writebufp++ = (data[chan][i] + 128) ^ 0x55;
+ break;
+ }
+ case TYPE_ALAW: {
+ uchar *writebufp = (uchar*) this_shn->decode_state->writebuf;
+ if(nchan == 1)
+ for(i = 0; i < nitem; i++)
+ *writebufp++ = Slinear2alaw(CAPMAXSHORT((data0[i] << 3)));
+ else
+ for(i = 0; i < nitem; i++)
+ for(chan = 0; chan < nchan; chan++)
+ *writebufp++ = Slinear2alaw(CAPMAXSHORT((data[chan][i] << 3)));
+ break;
+ }
+ }
+
+ switch(ftype) {
+ case TYPE_AU1:
+ case TYPE_S8:
+ case TYPE_U8:
+ case TYPE_ULAW:
+ case TYPE_AU2:
+ case TYPE_AU3:
+ case TYPE_ALAW:
+ if (datasize*nchan*nitem <= bufAvailable) {
+ memcpy((void *)&this_shn->vars.buffer[this_shn->vars.bytes_in_buf],(const void *)this_shn->decode_state->writebuf,datasize*nchan*nitem);
+ this_shn->vars.bytes_in_buf += datasize*nchan*nitem;
+ nwrite = nitem;
+ }
+ else
+ shn_debug("Buffer overrun in fwrite_type() [case 1]: %d bytes to read, but only %d bytes are available",datasize*nchan*nitem,bufAvailable);
+ break;
+ case TYPE_S16HL:
+ case TYPE_U16HL:
+ if(hilo)
+ {
+ if (datasize*nchan*nitem <= bufAvailable) {
+ memcpy((void *)&this_shn->vars.buffer[this_shn->vars.bytes_in_buf],(const void *)this_shn->decode_state->writebuf,datasize*nchan*nitem);
+ this_shn->vars.bytes_in_buf += datasize*nchan*nitem;
+ nwrite = nitem;
+ }
+ else
+ shn_debug("Buffer overrun in fwrite_type() [case 2]: %d bytes to read, but only %d bytes are available",datasize*nchan*nitem,bufAvailable);
+ }
+ else
+ {
+ swab(this_shn->decode_state->writebuf, this_shn->decode_state->writefub, datasize * nchan * nitem);
+ if (datasize*nchan*nitem <= bufAvailable) {
+ memcpy((void *)&this_shn->vars.buffer[this_shn->vars.bytes_in_buf],(const void *)this_shn->decode_state->writefub,datasize*nchan*nitem);
+ this_shn->vars.bytes_in_buf += datasize*nchan*nitem;
+ nwrite = nitem;
+ }
+ else
+ shn_debug("Buffer overrun in fwrite_type() [case 3]: %d bytes to read, but only %d bytes are available",datasize*nchan*nitem,bufAvailable);
+ }
+ break;
+ case TYPE_S16LH:
+ case TYPE_U16LH:
+ if(hilo)
+ {
+ swab(this_shn->decode_state->writebuf, this_shn->decode_state->writefub, datasize * nchan * nitem);
+ if (datasize*nchan*nitem <= bufAvailable) {
+ memcpy((void *)&this_shn->vars.buffer[this_shn->vars.bytes_in_buf],(const void *)this_shn->decode_state->writefub,datasize*nchan*nitem);
+ this_shn->vars.bytes_in_buf += datasize*nchan*nitem;
+ nwrite = nitem;
+ }
+ else
+ shn_debug("Buffer overrun in fwrite_type() [case 4]: %d bytes to read, but only %d bytes are available",datasize*nchan*nitem,bufAvailable);
+ }
+ else
+ {
+ if (datasize*nchan*nitem <= bufAvailable) {
+ memcpy((void *)&this_shn->vars.buffer[this_shn->vars.bytes_in_buf],(const void *)this_shn->decode_state->writebuf,datasize*nchan*nitem);
+ this_shn->vars.bytes_in_buf += datasize*nchan*nitem;
+ nwrite = nitem;
+ }
+ else
+ shn_debug("Buffer overrun in fwrite_type() [case 5]: %d bytes to read, but only %d bytes are available",datasize*nchan*nitem,bufAvailable);
+ }
+ break;
+ }
+
+ if(nwrite != nitem)
+ shn_error_fatal(this_shn,"Failed to write decompressed stream -\npossible corrupt or truncated file");
+}
+
+/*************/
+/* bitshifts */
+/*************/
+
+void fix_bitshift(buffer, nitem, bitshift, ftype) slong *buffer; int nitem,
+ bitshift, ftype; {
+ int i;
+
+ if(ftype == TYPE_AU1)
+ for(i = 0; i < nitem; i++)
+ buffer[i] = ulaw_outward[bitshift][buffer[i] + 128];
+ else if(ftype == TYPE_AU2)
+ for(i = 0; i < nitem; i++) {
+ if(buffer[i] >= 0)
+ buffer[i] = ulaw_outward[bitshift][buffer[i] + 128];
+ else if(buffer[i] == -1)
+ buffer[i] = NEGATIVE_ULAW_ZERO;
+ else
+ buffer[i] = ulaw_outward[bitshift][buffer[i] + 129];
+ }
+ else
+ if(bitshift != 0)
+ for(i = 0; i < nitem; i++)
+ buffer[i] <<= bitshift;
+}
+
+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 = ddb_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)
+{
+ if (this_shn)
+ {
+ if (this_shn->vars.fd)
+ {
+ deadbeef->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;
+ }
+}
+
+shn_file *load_shn(const char *filename)
+{
+ 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.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;
+
+ tmp_file->vars.fd = deadbeef->fopen (filename);
+ if (!tmp_file->vars.fd) {
+ shn_debug("Could not open file: '%s'",filename);
+ shn_unload(tmp_file);
+ return NULL;
+ }
+
+ tmp_file->wave_header.id3v2_tag_size = deadbeef->junk_get_leading_size (tmp_file->vars.fd);
+ if (tmp_file->wave_header.id3v2_tag_size > 0) {
+ tmp_file->wave_header.file_has_id3v2_tag = 2;
+ trace ("found id3v2 tag, size: %d\n", tmp_file->wave_header.id3v2_tag_size);
+ if (0 != deadbeef->fseek(tmp_file->vars.fd,(long)tmp_file->wave_header.id3v2_tag_size,SEEK_SET)) {
+ shn_debug("Error while discarding ID3v2 tag in file '%s'.",filename);
+ deadbeef->rewind (tmp_file->vars.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)
+ {
+ deadbeef->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
+ {
+ deadbeef->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;
+}
+
+#if 0
+void write_and_wait(shn_file *this_shn,int block_size)
+{
+ int bytes_to_write,bytes_in_block,i;
+
+ 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 && this_shn->vars.going && this_shn->vars.seek_to == -1)
+ xmms_usleep(10000);
+
+ if(this_shn->vars.going && 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];
+}
+#endif
+
+static const char * exts[] = { "shn", NULL };
+static const char *filetypes[] = { "Shorten", NULL };
+
+// define plugin interface
+static DB_decoder_t plugin = {
+ DB_PLUGIN_SET_API_VERSION
+ .plugin.version_major = 0,
+ .plugin.version_minor = 1,
+ .plugin.type = DB_PLUGIN_DECODER,
+ .plugin.id = "shn",
+ .plugin.name = "SHN player",
+ .plugin.descr = "SHN player based on xmms-shn",
+ .plugin.author = "Alexey Yakovenko",
+ .plugin.email = "waker@users.sourceforge.net",
+ .plugin.website = "http://deadbeef.sf.net",
+ .open = shn_open,
+ .init = shn_init,
+ .free = shn_free,
+ .read_int16 = shn_read_int16,
+ .seek = shn_seek,
+ .seek_sample = shn_seek_sample,
+ .insert = shn_insert,
+ .exts = exts,
+ .filetypes = filetypes
+};
+
+DB_plugin_t *
+shn_load (DB_functions_t *api) {
+ deadbeef = api;
+ return DB_PLUGIN (&plugin);
+}