1161 lines
32 KiB
C
1161 lines
32 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <inttypes.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
|
|
#include "decode.h"
|
|
|
|
#ifndef TRUE
|
|
#define TRUE 1
|
|
#endif
|
|
#ifndef FALSE
|
|
#define FALSE 0
|
|
#endif
|
|
|
|
/* Derek start */
|
|
pthread_mutex_t mRingLock;
|
|
pthread_cond_t mRunCond;
|
|
pthread_t mThread;
|
|
/* Derek end */
|
|
|
|
int shn_seek(shn_file *this_shn, unsigned int time);
|
|
int shn_seekable(shn_file *this_shn);
|
|
shn_file *shn_load(char *filename, shn_config config);
|
|
int shn_init_decoder(shn_file *this_shn);
|
|
int shn_cleanup_decoder(shn_file *this_shn);
|
|
unsigned int shn_get_song_length(shn_file *this_shn);
|
|
int shn_get_buffer_block_size(shn_file *this_shn, int blocks);
|
|
int shn_read(shn_file *this_shn, uchar *read_buffer, int bytes_to_read);
|
|
void shn_unload(shn_file *this_shn);
|
|
unsigned int shn_get_samplerate(shn_file *this_shn);
|
|
unsigned int shn_get_channels(shn_file *this_shn);
|
|
unsigned int shn_get_bitspersample(shn_file *this_shn);
|
|
static void swap_bytes(shn_file *this_shn,int bytes);
|
|
static int get_wave_header(shn_file *this_shn);
|
|
static int shn_init_decode_state(shn_file *this_shn);
|
|
static int write_to_buffer(shn_file *this_shn, uchar *read_buffer, int bytes_to_read);
|
|
|
|
static int buffer_is_full = 0;
|
|
static int buffer_ret = 0;
|
|
|
|
static slong **buffer = NULL, **offset = NULL;
|
|
static slong lpcqoffset = 0;
|
|
static int version = FORMAT_VERSION, bitshift = 0;
|
|
static int ftype = TYPE_EOF;
|
|
static char *magic = MAGIC;
|
|
static int blocksize = DEFAULT_BLOCK_SIZE, nchan = DEFAULT_NCHAN;
|
|
static int i, chan, nwrap, nskip = DEFAULT_NSKIP;
|
|
static int *qlpc = NULL, maxnlpc = DEFAULT_MAXNLPC, nmean = UNDEFINED_UINT;
|
|
static int cmd;
|
|
static int internal_ftype;
|
|
static int cklen;
|
|
static uchar tmp;
|
|
static ulong seekto_offset;
|
|
|
|
int shn_seekable(shn_file *this_shn) {
|
|
if(!this_shn)
|
|
return 0;
|
|
|
|
if(this_shn->vars.seek_table_entries == NO_SEEK_TABLE) {
|
|
shn_debug(this_shn->config, "File not seekable");
|
|
return 0;
|
|
}
|
|
|
|
/* File is seekable */
|
|
return 1;
|
|
}
|
|
|
|
int shn_seek(shn_file *this_shn, unsigned int time)
|
|
{
|
|
if (NULL == this_shn)
|
|
return 0;
|
|
|
|
if (this_shn->vars.seek_table_entries == NO_SEEK_TABLE)
|
|
{
|
|
shn_error(this_shn->config, "Cannot seek to %d:%02d because there is no seek information for this file.",time/60,time%60);
|
|
return 0;
|
|
}
|
|
|
|
if(time > (unsigned int)(shn_get_song_length(this_shn) / 1000)) {
|
|
shn_error(this_shn->config, "You cannot seek to this position! It is out of range!");
|
|
return 0;
|
|
}
|
|
this_shn->vars.seek_to = time;
|
|
|
|
this_shn->vars.eof = FALSE;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
}
|
|
|
|
static int get_wave_header(shn_file *this_shn)
|
|
{
|
|
if(!this_shn)
|
|
return 0;
|
|
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 (!shn_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 = 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);
|
|
|
|
/* get blocksize if version > 0 */
|
|
if(version > 0)
|
|
{
|
|
int byte;
|
|
blocksize = UINT_GET((int) (log((double) DEFAULT_BLOCK_SIZE) / M_LN2),this_shn);
|
|
maxnlpc = UINT_GET(LPCQSIZE, this_shn);
|
|
nmean = UINT_GET(0, this_shn);
|
|
nskip = UINT_GET(NSKIPSIZE, this_shn);
|
|
for(i = 0; i < nskip; i++)
|
|
{
|
|
byte = uvar_get(XBYTESIZE,this_shn);
|
|
}
|
|
}
|
|
else
|
|
blocksize = DEFAULT_BLOCK_SIZE;
|
|
|
|
nwrap = MAX(NWRAP, maxnlpc);
|
|
|
|
/* grab some space for the input buffer */
|
|
buffer = long2d((ulong) nchan, (ulong) (blocksize + nwrap),this_shn);
|
|
if (this_shn->vars.fatal_error)
|
|
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(this_shn->config,"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.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)
|
|
{
|
|
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 *shn_load(char *filename, shn_config config)
|
|
{
|
|
shn_file *tmp_file;
|
|
shn_seek_entry *first_seek_table;
|
|
|
|
if (!(tmp_file = malloc(sizeof(shn_file))))
|
|
{
|
|
fprintf(stderr, "Could not allocate memory for SHN data structure");
|
|
return NULL;
|
|
}
|
|
|
|
memset(tmp_file, 0, sizeof(shn_file));
|
|
|
|
/*Copying config */
|
|
tmp_file->config = config;
|
|
|
|
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.bitshift = 0;
|
|
tmp_file->vars.seek_offset = 0;
|
|
|
|
tmp_file->decode_state = NULL;
|
|
|
|
tmp_file->wave_header.filename = filename;
|
|
tmp_file->wave_header.wave_format = 0;
|
|
tmp_file->wave_header.channels = 0;
|
|
tmp_file->wave_header.block_align = 0;
|
|
tmp_file->wave_header.bits_per_sample = 0;
|
|
tmp_file->wave_header.samples_per_sec = 0;
|
|
tmp_file->wave_header.avg_bytes_per_sec = 0;
|
|
tmp_file->wave_header.rate = 0;
|
|
tmp_file->wave_header.header_size = 0;
|
|
tmp_file->wave_header.data_size = 0;
|
|
tmp_file->wave_header.file_has_id3v2_tag = 0;
|
|
tmp_file->wave_header.id3v2_tag_size = 0;
|
|
|
|
tmp_file->seek_header.version = NO_SEEK_TABLE;
|
|
tmp_file->seek_header.shnFileSize = 0;
|
|
|
|
tmp_file->seek_trailer.seekTableSize = 0;
|
|
|
|
tmp_file->seek_table = NULL;
|
|
|
|
if (!(tmp_file->vars.fd = shn_open_and_discard_id3v2_tag(filename,&tmp_file->wave_header.file_has_id3v2_tag,&tmp_file->wave_header.id3v2_tag_size)))
|
|
{
|
|
shn_debug(tmp_file->config, "Could not open file: '%s'",filename);
|
|
shn_unload(tmp_file);
|
|
return NULL;
|
|
}
|
|
|
|
if (0 == get_wave_header(tmp_file))
|
|
{
|
|
shn_debug(tmp_file->config, "Unable to read WAVE header from file '%s'",filename);
|
|
shn_unload(tmp_file);
|
|
return NULL;
|
|
}
|
|
|
|
if (tmp_file->wave_header.file_has_id3v2_tag)
|
|
{
|
|
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
|
|
{
|
|
fseek(tmp_file->vars.fd,0,SEEK_SET);
|
|
}
|
|
|
|
if (0 == shn_verify_header(tmp_file))
|
|
{
|
|
shn_debug(tmp_file->config, "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)
|
|
{
|
|
first_seek_table = (shn_seek_entry *)tmp_file->seek_table;
|
|
|
|
/* check for broken seek tables - if found, disable seeking */
|
|
if (0 == tmp_file->seek_header.version)
|
|
{
|
|
/* test, if the bitshift value in the file is identical to the bitshift value of the first seektable entry */
|
|
if (tmp_file->vars.bitshift != shn_uchar_to_ushort_le(first_seek_table->data+22))
|
|
{
|
|
shn_debug(tmp_file->config, "Broken seek table detected - seeking disabled for file '%s'.",tmp_file->wave_header.filename);
|
|
tmp_file->vars.seek_table_entries = NO_SEEK_TABLE;
|
|
}
|
|
}
|
|
|
|
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(tmp_file->config, "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);
|
|
}
|
|
}
|
|
|
|
fseek(tmp_file->vars.fd,0,SEEK_SET);
|
|
tmp_file->vars.going = 1;
|
|
tmp_file->vars.seek_to = -1;
|
|
|
|
shn_debug(tmp_file->config, "Successfully loaded file: '%s'",filename);
|
|
|
|
return tmp_file;
|
|
}
|
|
|
|
static int shn_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(this_shn->config, "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_file *this_shn) {
|
|
if(!this_shn)
|
|
return 0;
|
|
if(!shn_init_decode_state(this_shn)) {
|
|
shn_error(this_shn->config, "shn_init_decode state failed!\n");
|
|
return 0;
|
|
}
|
|
/* 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");
|
|
return 0;
|
|
}
|
|
|
|
/* 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 = getc(this_shn->vars.fd);
|
|
if(byte == EOF) {
|
|
shn_error_fatal(this_shn,"No magic number");
|
|
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) {
|
|
shn_error_fatal(this_shn,"Can't decode version %d", 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) {
|
|
shn_error_fatal(this_shn,"Not able to perform requested output format conversion");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
nchan = UINT_GET(CHANSIZE, this_shn);
|
|
|
|
/* get blocksize if version > 0 */
|
|
if(version > 0) {
|
|
int byte;
|
|
blocksize = UINT_GET((int) (log((double) DEFAULT_BLOCK_SIZE) / M_LN2),this_shn);
|
|
maxnlpc = UINT_GET(LPCQSIZE, this_shn);
|
|
nmean = UINT_GET(0, this_shn);
|
|
nskip = UINT_GET(NSKIPSIZE, this_shn);
|
|
for(i = 0; i < nskip; i++) {
|
|
byte = uvar_get(XBYTESIZE,this_shn);
|
|
}
|
|
}
|
|
else
|
|
blocksize = DEFAULT_BLOCK_SIZE;
|
|
|
|
nwrap = MAX(NWRAP, maxnlpc);
|
|
|
|
/* grab some space for the input buffer */
|
|
buffer = long2d((ulong) nchan, (ulong) (blocksize + nwrap),this_shn);
|
|
if (this_shn->vars.fatal_error)
|
|
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);
|
|
|
|
this_shn->vars.eof = FALSE;
|
|
chan = 0;
|
|
|
|
/* Success */
|
|
return 1;
|
|
}
|
|
|
|
int shn_cleanup_decoder(shn_file *this_shn) {
|
|
if(!this_shn)
|
|
return 0;
|
|
this_shn->vars.seek_to = -1;
|
|
this_shn->vars.eof = TRUE;
|
|
|
|
/* wind up */
|
|
var_get_quit(this_shn);
|
|
fwrite_type_quit(this_shn);
|
|
|
|
if (buffer) free((void *) buffer);
|
|
if (offset) free((void *) offset);
|
|
if(maxnlpc > 0 && qlpc)
|
|
free((void *) qlpc);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int write_to_buffer(shn_file *this_shn, uchar *read_buffer, int block_size)
|
|
{
|
|
int bytes_to_write,bytes_in_block,i;
|
|
|
|
if (this_shn->vars.bytes_in_buf < block_size)
|
|
return 0;
|
|
|
|
bytes_in_block = min(this_shn->vars.bytes_in_buf, block_size);
|
|
|
|
if (bytes_in_block <= 0)
|
|
return 0;
|
|
|
|
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;
|
|
|
|
if(this_shn->vars.going && this_shn->vars.seek_to == -1) {
|
|
if (this_shn->config.swap_bytes)
|
|
swap_bytes(this_shn, bytes_to_write);
|
|
memcpy((uchar *)read_buffer, (uchar *)(this_shn->vars.buffer), bytes_to_write);
|
|
} else
|
|
return 0;
|
|
|
|
/* 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];
|
|
|
|
return bytes_to_write;
|
|
}
|
|
|
|
int shn_get_buffer_block_size(shn_file *this_shn, int blocks) {
|
|
int blk_size = blocks * (this_shn->wave_header.bits_per_sample / 8) * this_shn->wave_header.channels;
|
|
if(blk_size > OUT_BUFFER_SIZE) {
|
|
shn_debug(this_shn->config, "Resetting to default blk_size!\n");
|
|
blk_size = NUM_DEFAULT_BUFFER_BLOCKS * (this_shn->wave_header.bits_per_sample / 8) * this_shn->wave_header.channels;
|
|
}
|
|
|
|
return blk_size;
|
|
}
|
|
|
|
unsigned int shn_get_song_length(shn_file *this_shn) {
|
|
if(this_shn) {
|
|
if(this_shn->wave_header.length > 0)
|
|
return (unsigned int)(1000 * this_shn->wave_header.length);
|
|
}
|
|
/* Something failed or just isn't correct */
|
|
return (unsigned int)0;
|
|
}
|
|
int shn_read(shn_file *this_shn, uchar *read_buffer, int bytes_to_read) {
|
|
if(!this_shn)
|
|
return 0;
|
|
if(!read_buffer)
|
|
return 0;
|
|
|
|
/***********************/
|
|
/* EXTRACT starts here */
|
|
/***********************/
|
|
|
|
buffer_is_full = 0;
|
|
while(!buffer_is_full) {
|
|
cmd = uvar_get(FNSIZE,this_shn);
|
|
if (this_shn->vars.fatal_error)
|
|
goto cleanup;
|
|
|
|
switch(cmd) {
|
|
case FN_ZERO:
|
|
case FN_DIFF0:
|
|
case FN_DIFF1:
|
|
case FN_DIFF2:
|
|
case FN_DIFF3:
|
|
case FN_QLPC:
|
|
{
|
|
slong coffset, *cbuffer = buffer[chan];
|
|
int resn = 0, nlpc, j;
|
|
|
|
if(cmd != FN_ZERO) {
|
|
resn = uvar_get(ENERGYSIZE,this_shn);
|
|
if (this_shn->vars.fatal_error)
|
|
goto cleanup;
|
|
/* this is a hack as version 0 differed in definition of var_get */
|
|
if(version == 0)
|
|
resn--;
|
|
}
|
|
|
|
/* find mean offset : N.B. this code duplicated */
|
|
if(nmean == 0)
|
|
coffset = offset[chan][0];
|
|
else
|
|
{
|
|
slong sum = (version < 2) ? 0 : nmean / 2;
|
|
for(i = 0; i < nmean; i++)
|
|
sum += offset[chan][i];
|
|
if(version < 2)
|
|
coffset = sum / nmean;
|
|
else
|
|
coffset = ROUNDEDSHIFTDOWN(sum / nmean, bitshift);
|
|
}
|
|
|
|
switch(cmd) {
|
|
case FN_ZERO:
|
|
for(i = 0; i < blocksize; i++)
|
|
cbuffer[i] = 0;
|
|
break;
|
|
case FN_DIFF0:
|
|
for(i = 0; i < blocksize; i++) {
|
|
cbuffer[i] = var_get(resn,this_shn) + coffset;
|
|
if (this_shn->vars.fatal_error)
|
|
goto cleanup;
|
|
}
|
|
break;
|
|
case FN_DIFF1:
|
|
for(i = 0; i < blocksize; i++) {
|
|
cbuffer[i] = var_get(resn,this_shn) + cbuffer[i - 1];
|
|
if (this_shn->vars.fatal_error)
|
|
goto cleanup;
|
|
}
|
|
break;
|
|
case FN_DIFF2:
|
|
for(i = 0; i < blocksize; i++) {
|
|
cbuffer[i] = var_get(resn,this_shn) + (2 * cbuffer[i - 1] - cbuffer[i - 2]);
|
|
if (this_shn->vars.fatal_error)
|
|
goto cleanup;
|
|
}
|
|
break;
|
|
case FN_DIFF3:
|
|
for(i = 0; i < blocksize; i++) {
|
|
cbuffer[i] = var_get(resn,this_shn) + 3 * (cbuffer[i - 1] - cbuffer[i - 2]) + cbuffer[i - 3];
|
|
if (this_shn->vars.fatal_error)
|
|
goto cleanup;
|
|
}
|
|
break;
|
|
case FN_QLPC:
|
|
nlpc = uvar_get(LPCQSIZE,this_shn);
|
|
if (this_shn->vars.fatal_error)
|
|
goto cleanup;
|
|
|
|
for(i = 0; i < nlpc; i++) {
|
|
qlpc[i] = var_get(LPCQUANT,this_shn);
|
|
if (this_shn->vars.fatal_error)
|
|
goto cleanup;
|
|
}
|
|
for(i = 0; i < nlpc; i++)
|
|
cbuffer[i - nlpc] -= coffset;
|
|
for(i = 0; i < blocksize; i++) {
|
|
slong sum = lpcqoffset;
|
|
|
|
for(j = 0; j < nlpc; j++)
|
|
sum += qlpc[j] * cbuffer[i - j - 1];
|
|
cbuffer[i] = var_get(resn,this_shn) + (sum >> LPCQUANT);
|
|
if (this_shn->vars.fatal_error)
|
|
goto cleanup;
|
|
}
|
|
if(coffset != 0)
|
|
for(i = 0; i < blocksize; i++)
|
|
cbuffer[i] += coffset;
|
|
break;
|
|
}
|
|
|
|
/* store mean value if appropriate : N.B. Duplicated code */
|
|
if(nmean > 0) {
|
|
slong sum = (version < 2) ? 0 : blocksize / 2;
|
|
|
|
for(i = 0; i < blocksize; i++)
|
|
sum += cbuffer[i];
|
|
|
|
for(i = 1; i < nmean; i++)
|
|
offset[chan][i - 1] = offset[chan][i];
|
|
if(version < 2)
|
|
offset[chan][nmean - 1] = sum / blocksize;
|
|
else
|
|
offset[chan][nmean - 1] = (sum / blocksize) << bitshift;
|
|
}
|
|
|
|
/* do the wrap */
|
|
for(i = -nwrap; i < 0; i++)
|
|
cbuffer[i] = cbuffer[i + blocksize];
|
|
|
|
fix_bitshift(cbuffer, blocksize, bitshift, internal_ftype);
|
|
|
|
if(chan == nchan - 1) {
|
|
if (!this_shn->vars.going || this_shn->vars.fatal_error)
|
|
goto cleanup;
|
|
|
|
fwrite_type(buffer, ftype, nchan, blocksize, this_shn);
|
|
|
|
buffer_ret = write_to_buffer(this_shn,read_buffer,bytes_to_read);
|
|
if(buffer_ret == bytes_to_read)
|
|
buffer_is_full = 1;
|
|
|
|
if (this_shn->vars.seek_to != -1) {
|
|
shn_seek_entry *seek_info;
|
|
|
|
shn_debug(this_shn->config, "Seeking to %d:%02d",this_shn->vars.seek_to/60,this_shn->vars.seek_to%60);
|
|
|
|
seek_info = shn_seek_entry_search(this_shn->config,this_shn->seek_table,this_shn->vars.seek_to * (ulong)this_shn->wave_header.samples_per_sec,0,(ulong)(this_shn->vars.seek_table_entries - 1),this_shn->vars.seek_resolution);
|
|
|
|
buffer[0][-1] = shn_uchar_to_slong_le(seek_info->data+24);
|
|
buffer[0][-2] = shn_uchar_to_slong_le(seek_info->data+28);
|
|
buffer[0][-3] = shn_uchar_to_slong_le(seek_info->data+32);
|
|
offset[0][0] = shn_uchar_to_slong_le(seek_info->data+48);
|
|
offset[0][1] = shn_uchar_to_slong_le(seek_info->data+52);
|
|
offset[0][2] = shn_uchar_to_slong_le(seek_info->data+56);
|
|
offset[0][3] = shn_uchar_to_slong_le(seek_info->data+60);
|
|
if (nchan > 1) {
|
|
buffer[1][-1] = shn_uchar_to_slong_le(seek_info->data+36);
|
|
buffer[1][-2] = shn_uchar_to_slong_le(seek_info->data+40);
|
|
buffer[1][-3] = shn_uchar_to_slong_le(seek_info->data+44);
|
|
offset[1][0] = shn_uchar_to_slong_le(seek_info->data+64);
|
|
offset[1][1] = shn_uchar_to_slong_le(seek_info->data+68);
|
|
offset[1][2] = shn_uchar_to_slong_le(seek_info->data+72);
|
|
offset[1][3] = shn_uchar_to_slong_le(seek_info->data+76);
|
|
}
|
|
|
|
bitshift = shn_uchar_to_ushort_le(seek_info->data+22);
|
|
|
|
seekto_offset = shn_uchar_to_ulong_le(seek_info->data+8) + this_shn->vars.seek_offset;
|
|
|
|
fseek(this_shn->vars.fd,(slong)seekto_offset,SEEK_SET);
|
|
fread((uchar*) this_shn->decode_state->getbuf, 1, BUFSIZ, this_shn->vars.fd);
|
|
|
|
this_shn->decode_state->getbufp = this_shn->decode_state->getbuf + shn_uchar_to_ushort_le(seek_info->data+14);
|
|
this_shn->decode_state->nbitget = shn_uchar_to_ushort_le(seek_info->data+16);
|
|
this_shn->decode_state->nbyteget = shn_uchar_to_ushort_le(seek_info->data+12);
|
|
this_shn->decode_state->gbuffer = shn_uchar_to_ulong_le(seek_info->data+18);
|
|
|
|
this_shn->vars.bytes_in_buf = 0;
|
|
|
|
this_shn->vars.seek_to = -1;
|
|
}
|
|
}
|
|
chan = (chan + 1) % nchan;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case FN_QUIT:
|
|
/* empty out last of buffer */
|
|
buffer_ret = write_to_buffer(this_shn,read_buffer,this_shn->vars.bytes_in_buf);
|
|
if(buffer_ret == bytes_to_read)
|
|
buffer_is_full = 1;
|
|
|
|
this_shn->vars.eof = TRUE;
|
|
|
|
goto cleanup;
|
|
break;
|
|
|
|
case FN_BLOCKSIZE:
|
|
blocksize = UINT_GET((int) (log((double) blocksize) / M_LN2), this_shn);
|
|
if (this_shn->vars.fatal_error)
|
|
goto cleanup;
|
|
break;
|
|
|
|
case FN_BITSHIFT:
|
|
bitshift = uvar_get(BITSHIFTSIZE,this_shn);
|
|
if (this_shn->vars.fatal_error)
|
|
goto cleanup;
|
|
break;
|
|
|
|
case FN_VERBATIM:
|
|
cklen = uvar_get(VERBATIM_CKSIZE_SIZE,this_shn);
|
|
if (this_shn->vars.fatal_error)
|
|
goto cleanup;
|
|
|
|
while (cklen--) {
|
|
tmp = (uchar)uvar_get(VERBATIM_BYTE_SIZE,this_shn);
|
|
if (this_shn->vars.fatal_error)
|
|
goto cleanup;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
shn_error_fatal(this_shn,"Sanity check fails trying to decode function: %d",cmd);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
goto exit_func;
|
|
|
|
cleanup:
|
|
|
|
buffer_ret = write_to_buffer(this_shn,read_buffer,this_shn->vars.bytes_in_buf);
|
|
|
|
exit_func:
|
|
return buffer_ret;
|
|
|
|
}
|
|
|
|
unsigned int shn_get_samplerate(shn_file *this_shn) {
|
|
if(this_shn) {
|
|
if(this_shn->wave_header.samples_per_sec > 0)
|
|
return (unsigned int)this_shn->wave_header.samples_per_sec;
|
|
}
|
|
/* Something failed or just isn't correct */
|
|
return (unsigned int)0;
|
|
}
|
|
|
|
unsigned int shn_get_channels(shn_file *this_shn) {
|
|
if(this_shn) {
|
|
if(this_shn->wave_header.channels > 0)
|
|
return (unsigned int)this_shn->wave_header.channels;
|
|
}
|
|
/* Something failed or just isn't correct */
|
|
return (unsigned int)0;
|
|
}
|
|
|
|
unsigned int shn_get_bitspersample(shn_file *this_shn) {
|
|
if(this_shn) {
|
|
if(this_shn->wave_header.bits_per_sample > 0)
|
|
return (unsigned int)this_shn->wave_header.bits_per_sample;
|
|
}
|
|
/* Something failed or just isn't correct */
|
|
return (unsigned int)0;
|
|
}
|
|
|