/*
** $Id: vboxconvert.c,v 1.8 1997/05/10 10:58:54 michael Exp $
**
** Copyright (C) 1996, 1997 Michael 'Ghandi' Herold
**
** The most converting routines are taken from the mgetty/vgetty v0.98
** package - this routines are copyright by there autors!
*/

#include "config.h"

#if TIME_WITH_SYS_TIME
#   include <sys/time.h>
#   include <time.h>
#else
#   if HAVE_SYS_TIME_H
#      include <sys/time.h>
#   else
#      include <time.h>
#   endif
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <unistd.h>
#include <fcntl.h>
#include <pwd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <sys/utsname.h>

#include "libvbox.h"
#include "vboxconvert.h"

/** Variables ************************************************************/
                
static state_t InitState = { 0x0000, 0 };
                
static int mx[3][8] =
{
	{ 0x3800, 0x5600, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 },
	{ 0x399a, 0x3a9f, 0x4d14, 0x6607, 0x0000, 0x0000, 0x0000, 0x0000 },
	{ 0x3556, 0x3556, 0x399A, 0x3A9F, 0x4200, 0x4D14, 0x6607, 0x6607 },
};

static int bitmask[9] =
{
	0, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff
}; 

static char *sun_format_names[] =
{
	"unspecified format",
	"8-bit mu-law samples",
	"8-bit linear samples",
	"16-bit linear samples",
	"24-bit linear samples",
	"32-bit linear samples",
	"floating-point samples",
	"double-precision float samples",
	"fragmented sampled data",
	"?",
	"dsp program",
	"8-bit fixed-point samples",
	"16-bit fixed-point samples",
	"24-bit fixed-point samples",
	"32-bit fixed-point samples",
	"?",
	"non-audio display data",
	"?",
	"16-bit linear with emphasis",
	"16-bit linear with compression",
	"16-bit linear with emphasis & compression",
	"music Kit DSP commands",
	"?"
};

static struct option args_vboxtoau[] =
{
	{ "help"      , no_argument      , NULL, 'h' },
	{ "version"   , no_argument      , NULL, 'v' },
	{ "samplerate", required_argument, NULL, 'r' },
	{ "ulaw"      , no_argument      , NULL, 'u' },
	{ "linear8"   , no_argument      , NULL, '1' },
	{ "linear16"  , no_argument      , NULL, '2' },
	{ NULL        , 0                , NULL,  0  }
};

static struct option args_vboxmode[] =
{
	{ "help"      , no_argument      , NULL, 'h' },
	{ "version"   , no_argument      , NULL, 'v' },
	{ "quiet"     , no_argument      , NULL, 'q' },
	{ NULL        , 0                , NULL,  0  }
};

static struct option args_autovbox[] =
{
	{ "help"      , no_argument      , NULL, 'h' },
	{ "version"   , no_argument      , NULL, 'v' },
	{ "adpcm-2"	,	no_argument			, NULL, '2' },
	{ "adpcm-3" ,  no_argument			, NULL, '3' },
	{ "adpcm-4" ,  no_argument       , NULL, '4' },
	{ "ulaw"    ,  no_argument       , NULL, 'u' },
	{ "name"		, required_argument	, NULL, 'n' },
	{ "callerid", required_argument  , NULL, 'c' },
	{ "phone"   , required_argument	, NULL, 'p' },
	{ "location", required_argument	, NULL, 'l'	},
	
	{ NULL        , 0                , NULL,  0  }
};

static char *vbasename = NULL;
static FILE *vboxtmpfile   = NULL;
static char *vboxtmpname   = NULL;

/** Prototypes ***********************************************************/

static int				start_vboxmode(char *, int);
static void			   start_vboxtoau(int, int);
static void				start_autovbox(int, char *, char *, char *, char *);
static void				usage_vboxtoau(void);
static void				usage_vboxmode(void);
static void				usage_autovbox(void);
static void          leave_vboxtoau(int);
static void          leave_autovbox(int);
static void				version(void);

static int				convert_adpcm_to_pvf(int, FILE *, FILE *);
static int				convert_pvf_to_adpcm(int, FILE *, FILE *);
static int				convert_au_to_pvf(FILE *, FILE *);
static int				convert_pvf_to_au(int, int, FILE *, FILE *);
static int				convert_ulaw_to_pvf(FILE *, FILE *);
static int				convert_pvf_to_ulaw(FILE *, FILE *);
static unsigned char	byte_linear_to_ulaw(int);
static int				byte_ulaw_to_linear(unsigned char);

static int				test_sample_is_vbox(char *, int);
static int				test_sample_is_au(char *, int);
static void				write_one_word(int, FILE *);
static int				read_one_word(FILE *);
static int				zget(FILE *);
static void				zput(int, FILE *);
static int				get_bits(int, state_t *, FILE *);
static void				put_bits(int, int, state_t *, FILE *);
static int				check_io_error(FILE *, FILE *);

/*************************************************************************/
/** The magic main...																	**/
/*************************************************************************/

int main(int argc, char **argv)
{
	struct utsname  utsname;
	struct passwd	*passwd;

	char	realname[VAH_MAX_NAME + 1];
	int	opts;
	int	rate;
	int	mode;
	char *name;
	char *phone;
	char *location;
	int	i;

	if (!(vbasename = rindex(argv[0], '/')))
		vbasename = argv[0];
	else
		vbasename++;

		/* Called as 'vboxtoau' converts a messages saved with vbox to	*/
		/* sun's au format.															*/

	if (strcasecmp(vbasename, "vboxtoau") == 0)
	{
		rate = 8000;
		mode = SND_FORMAT_LINEAR_16;

		while ((opts = getopt_long(argc, argv, "vhu12r:", args_vboxtoau, (int *)0)) != EOF)
		{
			switch (opts)
			{
				case 'r':
					rate = atoi(optarg);
					break;
					
				case 'u':
					mode = SND_FORMAT_MULAW_8;
					break;
					
				case '1':
					mode = SND_FORMAT_LINEAR_8;
					break;
					
				case '2':
					mode = SND_FORMAT_LINEAR_16;
					break;

				case 'v':
					version();
					break;

				case 'h':
				default:
					usage_vboxtoau();
					break;
			}
		}

		start_vboxtoau(mode, rate);
	}

		/* Called as 'autovbox' converts a sun au sample to any of the	*/
		/* vbox message formats.													*/

	if (strcasecmp(vbasename, "autovbox") == 0)
	{
		name		= "*** Unknown ***";
		phone		= "*** Unknown ***";
		location	= "*** Unknown ***";
		mode		= 4;

		if (uname(&utsname) == 0) location = utsname.nodename;

		if ((passwd = getpwuid(getuid())))
		{
			xstrncpy(realname, passwd->pw_gecos, VAH_MAX_NAME);
			
			for (i = 0; i < strlen(realname); i++)
			{
				if ((realname[i] == ',') || (realname[i] == ';'))
				{
					realname[i] = '\0';
					
					break;
				}
			}

			name = realname;
		}

		while ((opts = getopt_long(argc, argv, "hv234un:c:p:l:", args_autovbox, (int *)0)) != EOF)
		{
			switch (opts)
			{
				case '2':
					mode = 2;
					break;
					
				case '3':
					mode = 3;
					break;
					
				case '4':
					mode = 4;
					break;
					
				case 'u':
					mode = 6;
					break;
					
				case 'n':
					name = optarg;
					break;
					
				case 'p':
					phone = optarg;
					break;
					
				case 'l':
					location = optarg;
					break;

				case 'v':
					version();
					break;

				case 'h':
				default:
					usage_autovbox();
					break;
			}
		}

		start_autovbox(mode, name, "0", phone, location);
	}

		/* Called as 'vboxmode' displays information about the sample	*/
		/* format.																		*/

	if (strcasecmp(vbasename, "vboxmode") == 0)
	{
		mode = FALSE;
		name = "";

		while ((opts = getopt_long(argc, argv, "vhq", args_vboxmode, (int *)0)) != EOF)
		{
			switch (opts)
			{
				case 'v':
					version();
					break;
					
				case 'q':
					mode = TRUE;
					break;
					
				case 'h':
				default:
					usage_vboxmode();
					break;
			}
		}

		for (i = 1; i < argc; i++)
		{
			if (argv[i][0] != '-') name = argv[i];
		}

		if ((argc < 2) || (!*name)) usage_vboxmode();

		exit(start_vboxmode(name, mode));
	};

	fprintf(stderr, "\n");
	fprintf(stderr, "The vbox converter can be called as:\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "vboxtoau - to convert vbox messages to au format.\n");
	fprintf(stderr, "autovbox - to convert au format samples to vbox messages.\n");
	fprintf(stderr, "vboxmode - to display message information (works also with au).\n");
	fprintf(stderr, "\n");

	exit(255);
}

/*************************************************************************/
/** start_vboxtoau(): Converts vbox messages to sun's au format.		   **/
/*************************************************************************/

static void start_vboxtoau(int samplemode, int samplerate)
{
	vaheader_t	header;
	int	      compression;
	int 	      result;

	vboxtmpname = NULL;
	vboxtmpfile = NULL;
	
	signal(SIGINT , leave_vboxtoau);
	signal(SIGQUIT, leave_vboxtoau);
	signal(SIGTERM, leave_vboxtoau);
	signal(SIGHUP , leave_vboxtoau);
	signal(SIGPIPE, leave_vboxtoau);
	
	if (!(vboxtmpname = tempnam("/tmp", "vbox")))
	{
		fprintf(stderr, "%s: can't create a temporary file.\n", vbasename);
		
		leave_vboxtoau(255);
	}

	if (!(vboxtmpfile = fopen(vboxtmpname, "w+")))
	{
		fprintf(stderr, "%s: can't create \"%s\".\n", vbasename, vboxtmpname);
		
		leave_vboxtoau(255);
	}

	if (fread(&header, sizeof(vaheader_t), 1, stdin) != 1)
	{
		fprintf(stderr, "%s: can't read vbox audio header.\n", vbasename);

		leave_vboxtoau(255);
	}

	if (strncmp(header.magic, VAH_MAGIC, VAH_MAX_MAGIC) != 0)
	{
		fprintf(stderr, "%s: sample contains no vbox audio header.\n", vbasename);
		
		leave_vboxtoau(255);
	}

	compression = (int)ntohl(header.compression);

	result = 255;

	switch (compression)
	{
		case 2:
		case 3:
		case 4:
			result = convert_adpcm_to_pvf(compression, stdin, vboxtmpfile);
			break;

		case 6:
			result = convert_ulaw_to_pvf(stdin, vboxtmpfile);
			break;

		default:
			fprintf(stderr, "%s: unknown/unsupported compression %d.\n", vbasename, compression);
			break;
	}

	if (result == 0)
	{
		result = convert_pvf_to_au(samplemode, samplerate, vboxtmpfile, stdout);
	}

	leave_vboxtoau(result);
}

/**************************************************************************/
/** leave_vboxtoau(): Leave vboxtoau mode.                               **/
/**************************************************************************/

static void leave_vboxtoau(int sig)
{
fprintf(stderr, "sig: %d\n", sig);

	if (vboxtmpfile) fclose(vboxtmpfile);
	
	if (vboxtmpname)
	{
		unlink(vboxtmpname);
		free(vboxtmpname);
	}

	exit(sig);
}

/*************************************************************************/
/** usage_vboxtoau(): Usage message for "vboxtoau".                     **/
/*************************************************************************/

static void usage_vboxtoau(void)
{
	fprintf(stderr, "\n");
	fprintf(stderr, "Usage: %s OPTION [ OPTION ] [ ... ] <INFILE >OUTFILE\n", vbasename);
	fprintf(stderr, "\n");
	fprintf(stderr, "-r, --samplerate RATE   Header sampling rate (default 8000).\n");
	fprintf(stderr, "-u, --ulaw              Use 8-bit uLaw output.\n");
	fprintf(stderr, "-1, --linear8           Use 8-bit linear output.\n");
	fprintf(stderr, "-2, --linear16          Use 16-Bit linear output (default).\n");
	fprintf(stderr, "-h, --help              Prints this usage message.\n");
	fprintf(stderr, "-v, --version           Prints the package version.\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "Note: The sampling rate is written into the file header only, it\n      doesn't do any sampling rate conversion!\n");
	fprintf(stderr, "\n");

	exit(255);
}

/*************************************************************************/
/** start_vboxmode(): Displays sample information.							   **/
/*************************************************************************/

static int start_vboxmode(char *name, int quiet)
{
	int result;

	result = test_sample_is_vbox(name, quiet);
	
	if (result == 255)
	{
		result = test_sample_is_au(name, quiet);
	}

	if (result == 255)
	{
		fprintf(stderr, "%s: sample contains no vbox or sun au header.\n", vbasename);
	}

	return(result);
}

/*************************************************************************/
/** usage_vboxmode(): Usage message for "vboxmode".							**/
/*************************************************************************/

static void usage_vboxmode(void)
{
	fprintf(stderr, "\n");
	fprintf(stderr, "Usage: %s OPTION [ OPTION ] [ ... ] SAMPLENAME\n", vbasename);
	fprintf(stderr, "\n");
	fprintf(stderr, "-q, --quiet             Returns only the errorcode (no descriptions).\n");
	fprintf(stderr, "-h, --help              Prints this usage message.\n");
	fprintf(stderr, "-v, --version           Prints the package version.\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "The program returns the sample format as errorcode:\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "- For au samples it returns %d-%d,\n", SUN_FORMAT_MIN + 128, SUN_FORMAT_MAX + 128);
	fprintf(stderr, "- For vbox messages it returns 2-6,\n");
	fprintf(stderr, "- For unknown formats or on errors it returns 255.\n");
	fprintf(stderr, "\n");

	exit(255);
}

/*************************************************************************/
/** start_autovbox(): Converts au samples to vbox messages.				   **/
/*************************************************************************/

static void start_autovbox(int compression, char *name, char *id, char *phone, char *location)
{
	vaheader_t	header;
	int	      result;

	vboxtmpname = NULL;
	vboxtmpfile = NULL;

	signal(SIGINT , leave_autovbox);
	signal(SIGQUIT, leave_autovbox);
	signal(SIGTERM, leave_autovbox);
	signal(SIGHUP , leave_autovbox);
	signal(SIGPIPE, leave_autovbox);
	
	if (!(vboxtmpname = tempnam("/tmp", "vbox")))
	{
		fprintf(stderr, "%s: can't create a temporary file.\n", vbasename);
		
		leave_autovbox(255);
	}

	if (!(vboxtmpfile = fopen(vboxtmpname, "w+")))
	{
		fprintf(stderr, "%s: can't create \"%s\".\n", vbasename, vboxtmpname);
		
		leave_autovbox(255);
	}

	xstrncpy(header.magic	, VAH_MAGIC	, VAH_MAX_MAGIC	);
	xstrncpy(header.callerid, id			, VAH_MAX_CALLERID);
	xstrncpy(header.name		, name		, VAH_MAX_NAME		);
	xstrncpy(header.phone	, phone		, VAH_MAX_PHONE	);
	xstrncpy(header.location, location	, VAH_MAX_LOCATION);
	
	header.time				= htonl(time(NULL));
	header.compression	= htonl(compression);
	
	if (fwrite(&header, sizeof(vaheader_t), 1, stdout) != 1)
	{
		fprintf(stderr, "%s: can't write vbox audio header.\n", vbasename);
		
		leave_autovbox(255);
	}

	result = convert_au_to_pvf(stdin, vboxtmpfile);

	if (result == 0)
	{
		result = 255;

		switch (compression)
		{
			case 2:
			case 3:
			case 4:
				result = convert_pvf_to_adpcm(compression, vboxtmpfile, stdout);
				break;

			case 6:
				result = convert_pvf_to_ulaw(vboxtmpfile, stdout);
				break;
				
			default:
				fprintf(stderr, "%s: unsupported compression type.\n", vbasename);
				break;

		}
	}

	leave_autovbox(result);
}

/**************************************************************************/
/** leave_autovbox(): Leave autovbox mode.                               **/
/**************************************************************************/

static void leave_autovbox(int sig)
{
	if (vboxtmpfile) fclose(vboxtmpfile);
	
	if (vboxtmpname)
	{
		unlink(vboxtmpname);
		free(vboxtmpname);
	}

	exit(sig);
}

/*************************************************************************/
/** usage_autovbox(): Usage message for "autovbox".							**/
/*************************************************************************/

static void usage_autovbox(void)
{
	fprintf(stderr, "\n");
	fprintf(stderr, "Usage: %s OPTION [ OPTION ] [ ... ] <INFILE >OUTFILE\n", vbasename);
	fprintf(stderr, "\n");
	fprintf(stderr, "-2, --adpcm-2             Converts sample to adpcm-2.\n");
	fprintf(stderr, "-3, --adpcm-3             Converts sample to adpcm-3.\n");
	fprintf(stderr, "-4, --adpcm-4             Converts sample to adpcm-4 (default).\n");
	fprintf(stderr, "-u, --ulaw                Converts sample to ulaw.\n");
	fprintf(stderr, "-n, --name NAME           Sets header information.\n");
	fprintf(stderr, "-p, --phone PHONE         Sets header information.\n");
	fprintf(stderr, "-l, --location LOCATION   Sets header information.\n");
	fprintf(stderr, "-h, --help                Prints this usage message.\n");
	fprintf(stderr, "-v, --version             Prints the package version.\n");
	fprintf(stderr, "\n");

	exit(255);
}

/*************************************************************************/
/** convert_pvf_to_adpcm(): Converts protable voice format to adpcm-2	**/
/**								 adpcm-3 or adpcm-4.								   **/
/*************************************************************************/

static int convert_pvf_to_adpcm(int nbits, FILE *in, FILE *out)
{
	state_t	s;
	int		a;
	int		d;
	int		e;
	int		nmax;
	int		sign;
	int		delta;

	rewind(in);
	clearerr(in);
	clearerr(out);

	a = 0;
	d = 5;
	s = InitState;

	while (1)
	{
		e = 0;
		nmax = 1 << (nbits-1);
	
		delta = (zget(in) >> 2) - a;

		if (feof(in)) break;
	
		if (delta < 0)
		{
			e = nmax;
			delta = -delta;
		}

		while(--nmax && delta > d)
		{
			delta -= d;
			e++;
		}
	
		if (nbits==4 && ((e & 0x0F) == 0)) e = 0x08;

		put_bits( e, nbits, &s, out);
	
		a = (a * 4093 + 2048) >> 12;
	
		sign = (e >> (nbits - 1)) ? -1 : 1;
		e = (e & bitmask[nbits - 1]);
	
		a += sign * ((e<<1)+1) * d >>1;

		if ((d & 1)) a++;
	
		d = (d * mx[nbits - 2][e] + 0x2000) >> 14;

		if (d < 5) d = 5;
	}

	if (s.nleft) put_bits(0, 8-s.nleft, &s, out);

	return(check_io_error(in, out));
}

/*************************************************************************/
/** convert_adpcm_to_pvf(): Converts adpcm-2, adpcm-3 or adpcm-4 to	   **/
/**								 portable voice format.							   **/
/*************************************************************************/

static int convert_adpcm_to_pvf(int nbits, FILE *in, FILE *out)
{
	state_t	state;
	int		a;
	int		d;
	int		e;
	int		sign;

	state = InitState;
	a		= 0;
	d		= 5;

	clearerr(in);
	clearerr(out);

	while (1)
	{
		if ((e = get_bits(nbits, &state, in)) == EOF) break;

		if ((nbits == 4) && (e == 0)) d = 4;
	
		sign = (e >> (nbits - 1)) ? -1 : 1;

		e = e & bitmask[nbits - 1];
		a = (a * 4093 + 2048) >> 12;
	
		a += sign * ((e << 1) + 1) * d >> 1;

		if ((d & 1)) a++;

		zput(a << 2, out);
	
		d = (d * mx[nbits - 2][e] + 0x2000) >> 14;

		if (d < 5) d = 5;	    
	}

	return(check_io_error(in, out));
}

/*************************************************************************/
/** convert_au_to_pvf(): Converts sun's au format to portable voice	   **/
/**							 format.													   **/
/*************************************************************************/

static int convert_au_to_pvf(FILE *in, FILE *out)
{
	SNDSoundStruct hdr;
	int i;
	int c;

	rewind(in);
	clearerr(in);
	clearerr(out);
         
	hdr.magic			= read_one_word(in);
	hdr.dataLocation	= read_one_word(in);
	hdr.dataSize		= read_one_word(in);
	hdr.dataFormat		= read_one_word(in);
	hdr.samplingRate	= read_one_word(in);
	hdr.channelCount	= read_one_word(in);
    
	if (hdr.magic != SND_MAGIC)
	{
		fprintf(stderr, "%s: illegal magic number for an au file.\n", vbasename);
		
		return(255);
	}

	if (hdr.channelCount != 1)
	{
		fprintf(stderr, "%s: number of channels != 1 (only mono supported).\n", vbasename);
		
		return(255);
	}

	for (i = (SND_HEADER_SIZE - 4); i < hdr.dataLocation; i++)
	{
		if (getc(in) == EOF)
		{
			fprintf(stderr, "%s: unexpected EOF.\n", vbasename);
			
			return(255);
		}
	}
    
	switch (hdr.dataFormat)
	{
		case SND_FORMAT_MULAW_8:
			while(1)
			{
				c = getc(in);
				if (feof(in)) break;
				zput(byte_ulaw_to_linear(c), out);
			}
			break;

		case SND_FORMAT_LINEAR_8:
			while(1)
			{
				c = (getc(in) & 0xFF);
				if (c >= 0x80) c -= 0x100;
				if (feof(in)) break;
				zput(c<<8, out);
			}
			break;

			case SND_FORMAT_LINEAR_16:
				while(1)
				{
					c = zget(in);
					if(feof(in)) break;
					zput(c, out);
				}
				break;

			default:
				fprintf(stderr, "%s: unsupported or illegal sound encoding.\n", vbasename);
				return(255);
	}

	return(check_io_error(in, out));
}

/*************************************************************************/
/** convert_pvf_to_au(): Converts portable voice format to sun's au	   **/
/**							 format.													   **/
/*************************************************************************/

static int convert_pvf_to_au(int mode, int rate, FILE *in, FILE *out)
{
	SNDSoundStruct Snd;
	int s;

	rewind(in);
	clearerr(in);
	clearerr(out);

	Snd.dataFormat		= mode;
	Snd.samplingRate	= rate;
	Snd.magic			= SND_MAGIC;
	Snd.dataLocation	= SND_HEADER_SIZE;
	Snd.dataSize		= SND_UNKNOWN_SIZE;
	Snd.channelCount	= 1;
	Snd.info[0]			= 0;
    
	write_one_word((int)Snd.magic			, out);
	write_one_word((int)Snd.dataLocation, out);
	write_one_word((int)Snd.dataSize		, out);
	write_one_word((int)Snd.dataFormat	, out);
	write_one_word((int)Snd.samplingRate, out);
	write_one_word((int)Snd.channelCount, out);
	write_one_word((int)Snd.info			, out);

	while (1)
	{
		s = zget(in);
		
		if ((feof(in)) || (ferror(in))) break;

		switch (Snd.dataFormat)
		{
			case SND_FORMAT_MULAW_8:
				putc(byte_linear_to_ulaw(s), out);
				break;

			case SND_FORMAT_LINEAR_8:
				putc(s >> 8, out);
				break;

			case SND_FORMAT_LINEAR_16:
				zput(s, out);
				break;
		}
	}

	return(check_io_error(in, out));
}

/*************************************************************************/
/** convert_pvf_to_ulaw():	Converts portable voice format to ulaw.		**/
/*************************************************************************/

static int convert_pvf_to_ulaw(FILE *in, FILE *out)
{
	int sample;

	rewind(in);
	clearerr(in);
	clearerr(out);

	while (1)
	{
		sample = zget(in);
		if(feof(in)) break;
		putc(byte_linear_to_ulaw(sample), out);
	}

	return(check_io_error(in, out));
}

/*************************************************************************/
/** convert_ulaw_to_pvf():	Converts ulaw to portable voice format.		**/
/*************************************************************************/

static int convert_ulaw_to_pvf(FILE *in, FILE *out)
{
	int c;

	clearerr(in);
	clearerr(out);

	while (1)
	{
		if ((c = getc(in)) == EOF) break;

		zput(byte_ulaw_to_linear(c), out);
	}

	return(check_io_error(in, out));
}

/*************************************************************************/
/** put_bits():																			**/
/*************************************************************************/

static void put_bits(int data, int nbits, state_t *s, FILE *out)
{
	int d;

	s->word = (s->word << nbits) | (data & bitmask[nbits]);
	s->nleft += nbits;

	while (s->nleft >= 8)
	{
		d = (s->word >> (s->nleft - 8));
		putc(d & 255, out);
		s->nleft -= 8;
	}
}

/*************************************************************************/
/** get_bits():																			**/
/*************************************************************************/

static int get_bits(int nbits, state_t *state, FILE *in)
{
	int d;

	while (state->nleft < nbits)
	{
		if ((d = getc(in)) == EOF) return(EOF);
		
		state->word   = (state->word << 8) | d;
		state->nleft += 8;
	}

	state->nleft -= nbits;

	return(state->word >> state->nleft) & bitmask[nbits];
}

/*************************************************************************/
/** zput():																					**/
/*************************************************************************/

static void zput(int d, FILE *out)
{
    if (d >  0x7fff) d =  0x7fff;
    if (d < -0x8000) d = -0x8000;
    
    putc((d >> 8) & 0xFF, out);
    putc(d & 0xFF, out);
}

/*************************************************************************/
/** zget():																					**/
/*************************************************************************/

static int zget(FILE *in)
{
	int d;

	d  = (getc(in) & 0xFF) << 8;
	d |= (getc(in) & 0xFF);

	if (d >= 0x8000) d -= 0x10000;

	return(d);
}

/*************************************************************************/
/** write_one_word():																	**/
/*************************************************************************/

static void write_one_word(int w, FILE *out)
{
    putc((w & 0xFF000000) >> 24, out);
    putc((w & 0x00FF0000) >> 16, out);
    putc((w & 0x0000FF00) >>  8, out);
    putc((w & 0x000000FF)      , out);
}

/*************************************************************************/
/** read_one_word():																		**/
/*************************************************************************/

static int read_one_word(FILE *in)
{
	int w;

	w =            getc(in);
	w = (w << 8) | getc(in);
	w = (w << 8) | getc(in);
	w = (w << 8) | getc(in);

	return(w);
}

/*************************************************************************/
/** byte_ulaw_to_linear():	Converts 8 bit ulaw sample to 16 bit linear	**/
/**								sample.													**/
/*************************************************************************/

static int byte_ulaw_to_linear(unsigned char ulawbyte)
{
	static int exp[8] = { 0, 132, 396, 924, 1980, 4092, 8316, 16764 };

	int	sign;
	int	exponent;
	int	mantissa;
	int	sample;

	ulawbyte = ~ulawbyte;
	sign		= (ulawbyte & 0x80);
	exponent = (ulawbyte >> 4) & 0x07;
	mantissa = ulawbyte & 0x0F;
	sample	= exp[exponent] + (mantissa << (exponent + 3));

	if (sign != 0) sample = -sample;

	return(sample);
}

/*************************************************************************/
/** byte_linear_to_ulaw():	Converts from signed 16 bit linear to 8 bit	**/
/**								ulaw.														**/
/*************************************************************************/
/** Sample						16 bit linear sample.								**/
/** <RC>							8 bit ulaw sample.									**/
/*************************************************************************/

#define BIAS 0x84				/* Define the add-in bias for 16 bit samples	*/
#define CLIP 32635

static unsigned char byte_linear_to_ulaw(int sample)
{
	static int exp[256] =
	{
		0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,
		4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
		5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
		5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
		6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
		6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
		6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
		6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
		7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
		7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
		7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
		7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
		7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
		7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
		7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
		7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7
	};

	unsigned char	ulawbyte;
	int				sign;
	int				exponent;
	int				mantissa;
	
	sign = (sample >> 8) & 0x80;

	if (sign != 0) sample = -sample;

	if (sample > CLIP) sample = CLIP;

	sample	= sample + BIAS;
	exponent = exp[(sample >> 7) & 0xFF];
	mantissa = (sample >> (exponent + 3)) & 0x0F;
	ulawbyte = ~(sign | (exponent << 4) | mantissa);

	return(ulawbyte);
}

/*************************************************************************/
/** test_sample_is_vbox():	Tests if the sample is in vbox format.			**/
/*************************************************************************/

static int test_sample_is_vbox(char *name, int quiet)
{
	struct tm   *timelocal;
	struct stat  status;
	vaheader_t	 header;
	time_t		 timestamp;
	long int		 compression;
	int			 fd;
	char         timestring[256];

	compression = 255;
	
	if ((fd = open(name, O_RDONLY)) != -1)
	{
		if (header_get(fd, &header))
		{
			compression = ntohl(header.compression);

			if ((compression < 2) || (compression > 6)) compression = 0;

			if (fstat(fd, &status) == 0)
			{
				timestamp = ntohl(header.time);
				timelocal = localtime(&timestamp);
				
				if (strftime(timestring, 255, "%c\n", timelocal) == 255)
				{
					printstring(timestring, "???\n");
				}

				if (!quiet)
				{
					fprintf(stdout, "\n");
					fprintf(stdout, "Creation time...........: %s", timestring);
					fprintf(stdout, "Compression.............: %s\n", compressions[compression]);
					fprintf(stdout, "Length..................: %d seconds\n", get_message_ptime(compression, (status.st_size - sizeof(vaheader_t))));
					fprintf(stdout, "Speaker name............: %s\n", header.name);
					fprintf(stdout, "Speaker caller number...: %s\n", header.callerid);
					fprintf(stdout, "Speaker phone number....: %s\n", header.phone);
					fprintf(stdout, "Speaker location........: %s\n", header.location);
					fprintf(stdout, "\n");
				}
			}
		}

		close(fd);
	}
	else fprintf(stderr, "%s: can't open \"%s\" (vbox sample test).\n", vbasename, name);

	return(compression);
}

/*************************************************************************/
/** test_sample_is_au(): Tests if the sample is in sun's au format.	   **/
/*************************************************************************/

static int test_sample_is_au(char *name, int quiet)
{
	SNDSoundStruct snd;

	FILE *in;

	if (!(in = fopen(name, "r")))
	{
		fprintf(stderr, "%s: can't open \"%s\" (au sample test).\n", vbasename, name);
		
		return(255);
	}

	clearerr(in);

	snd.magic			= read_one_word(in);
	snd.dataLocation	= read_one_word(in);
	snd.dataSize		= read_one_word(in);
	snd.dataFormat		= read_one_word(in);
	snd.samplingRate	= read_one_word(in);
	snd.channelCount	= read_one_word(in);

	if ((snd.magic != SND_MAGIC) || (ferror(in) != 0))
	{
		fclose(in);
		return(255);
	}

	fclose(in);

	if ((snd.dataFormat < SUN_FORMAT_MIN) || (snd.dataFormat > SUN_FORMAT_MAX))
	{
		return(255);
	}

	if (!quiet)
	{
		fprintf(stdout, "\n");
		fprintf(stdout, "Format..........: %s\n", sun_format_names[snd.dataFormat]);
		fprintf(stdout, "Sampling rate...: %d\n", snd.samplingRate);
		fprintf(stdout, "Channels........: %d\n", snd.channelCount);
		fprintf(stdout, "Size............: %d %s\n", snd.dataSize, snd.dataSize == -1 ? "(unknown size)" : "bytes");
		fprintf(stdout, "Data location...: %d\n", snd.dataLocation);
		fprintf(stdout, "\n");
	}

	return(128 + snd.dataFormat);
}

/*************************************************************************/
/** version():	Package version.														**/
/*************************************************************************/

static void version(void)
{
	fprintf(stderr, "\n");
	fprintf(stderr, "%s version %s (%s)\n", vbasename, VERSION, VERDATE);
	fprintf(stderr, "\n");
	
	exit(255);
}

/*************************************************************************/
/** check_io_error():																	**/
/*************************************************************************/

static int check_io_error(FILE *in, FILE *out)
{
	if (ferror(in) != 0)
	{
		fprintf(stderr, "%s: can't read input.\n", vbasename);
		
		return(255);
	}

	if (ferror(out) != 0)
	{
		fprintf(stderr, "%s: can't write output.\n", vbasename);
		
		return(255);
	}

	return(0);
}
