diff options
Diffstat (limited to 'plugins/shn/shn.c')
-rw-r--r-- | plugins/shn/shn.c | 1798 |
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); +} |