////////////////////////////////////////////////////////////////////////////
//                           **** WAVPACK ****                            //
//                  Hybrid Lossless Wavefile Compressor                   //
//              Copyright (c) 1998 - 2005 Conifer Software.               //
//                          All Rights Reserved.                          //
//      Distributed under the BSD Software License (see license.txt)      //
////////////////////////////////////////////////////////////////////////////

// wavpack.c

// This is the main module for the WavPack command-line compressor.

#if defined(WIN32)
#include <windows.h>
#include <io.h>
#else
#include <sys/param.h>
#include <sys/stat.h>
#endif

#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <math.h>

#include "wavpack.h"
#include "md5.h"

#ifdef __BORLANDC__
#include <dir.h>
#else
#if defined (__GNUC__) && !defined(WIN32)
#include <unistd.h>
#include <glob.h>
#include <sys/time.h>
#else
#include <sys/timeb.h>
#endif
#endif

#ifdef DEBUG_ALLOC
#define malloc malloc_db
#define realloc realloc_db
#define free free_db
void *malloc_db (uint32_t size);
void *realloc_db (void *ptr, uint32_t size);
void free_db (void *ptr);
int32_t dump_alloc (void);
static char *strdup (const char *s)
 { char *d = malloc (strlen (s) + 1); return strcpy (d, s); }
#endif

///////////////////////////// local variable storage //////////////////////////

static const char *sign_on = "\n"
" WAVPACK  Hybrid Lossless Wavefile Compressor  %s Version %s %s\n"
" Copyright (c) 1998 - 2005 Conifer Software.  All Rights Reserved.\n\n";

static const char *usage =
" Usage:   WAVPACK [-options] [@]infile[.wav]|- [[@]outfile[.wv]|outpath|-]\n"
"             (default is lossless; infile may contain wildcards: ?,*)\n\n"
" Options: -a  = Adobe Audition (CoolEdit) mode for 32-bit floats\n"
"          -bn = enable hybrid compression, n = 2.0 to 23.9 bits/sample, or\n"
"                                           n = 24-9600 kbits/second (kbps)\n"
"          -c  = create correction file (.wvc) for hybrid mode (=lossless)\n"
"          -cc = maximum hybrid compression (hurts lossy quality & decode speed)\n"
"          -d  = delete source file if successful (use with caution!)\n"
#if defined (WIN32)
"          -e  = create self-extracting executable (needs wvselfx.exe)\n"
#endif
"          -f  = fast mode (fast, but some compromise in compression ratio)\n"
"          -h  = high quality (best compression in all modes, but slower)\n"
"          -i  = ignore length in wav header (no pipe output allowed)\n"
"          -jn = joint-stereo override (0 = left/right, 1 = mid/side)\n"
"          -m  = compute & store MD5 signature of raw audio data\n"
"          -n  = calculate average and peak quantization noise (hybrid only)\n"
"          -p  = practical float storage (also 32-bit ints, not lossless)\n"
"          -q  = quiet (keep console output to a minimum)\n"
"          -sn = noise shaping override (hybrid only, n = -1.0 to 1.0, 0 = off)\n"
#if defined (WIN32)
"          -t  = copy input file's time stamp to output file(s)\n"
#endif
"          -w \"Field=Value\" = write specified metadata to APEv2 tag\n"
"          -x[n] = extra encode processing (optional n = 1-6 for less/more)\n"
"          -y  = yes to all warnings (use with caution!)\n\n"
" Web:     Visit www.wavpack.com for latest version and info\n";

static int overwrite_all, num_files, file_index;

#if defined (WIN32)
static char *wvselfx_image;
static uint32_t wvselfx_size;
#endif

/////////////////////////// local function declarations ///////////////////////

static int pack_file (char *infilename, char *outfilename, char *out2filename, const WavpackConfig *config);
static int pack_audio (WavpackContext *wpc, FILE *infile);
static void display_progress (double file_progress);

#define NO_ERROR 0L
#define SOFT_ERROR 1
#define HARD_ERROR 2

//////////////////////////////////////////////////////////////////////////////
// The "main" function for the command-line WavPack compressor.             //
//////////////////////////////////////////////////////////////////////////////

int main (argc, argv) int argc; char **argv;
{
    int delete_source = 0, usage_error = 0, filelist = 0, tag_next_arg = 0;
    char *infilename = NULL, *outfilename = NULL, *out2filename = NULL;
    char **matches = NULL;
    WavpackConfig config;
    int result, i;

#if defined (WIN32)
    char *selfname = malloc (strlen (*argv) + PATH_MAX);
    struct _finddata_t _finddata_t;

    strcpy (selfname, *argv);
#else
    glob_t globs;
    struct stat fstats;
#endif

    CLEAR (config);

#if 0
    {
	char **argv_t = argv;
	int argc_t = argc;

	while (--argc_t)
	    error_line ("%d: %s", argc - argc_t, *++argv_t);
    }
#endif

    // loop through command-line arguments

    while (--argc)
#if defined (WIN32)
	if ((**++argv == '-' || **argv == '/') && (*argv)[1])
#else
	if ((**++argv == '-') && (*argv)[1])
#endif
	    while (*++*argv)
		switch (**argv) {

		    case 'Y': case 'y':
			overwrite_all = 1;
			break;

		    case 'D': case 'd':
			delete_source = 1;
			break;

		    case 'C': case 'c':
			if (config.flags & CONFIG_CREATE_WVC)
			    config.flags |= CONFIG_OPTIMIZE_WVC;
			else
			    config.flags |= CONFIG_CREATE_WVC;

			break;

		    case 'X': case 'x':
			config.xmode = strtol (++*argv, argv, 10);

			if (config.xmode < 0 || config.xmode > 6) {
			    error_line ("extra mode only goes from 1 to 6!");
			    usage_error = 1;
			}
			else
			    config.flags |= CONFIG_EXTRA_MODE;

			--*argv;
			break;

		    case 'F': case 'f':
			if (config.flags & CONFIG_FAST_FLAG)
			    config.flags |= CONFIG_VERY_FAST_FLAG;
			else
			    config.flags |= CONFIG_FAST_FLAG;

			break;

		    case 'H': case 'h':
			if (config.flags & CONFIG_HIGH_FLAG)
			    config.flags |= CONFIG_VERY_HIGH_FLAG;
			else
			    config.flags |= CONFIG_HIGH_FLAG;

			break;

		    case 'N': case 'n':
			config.flags |= CONFIG_CALC_NOISE;
			break;

		    case 'A': case 'a':
			config.flags |= CONFIG_ADOBE_MODE;
			break;
#if defined (WIN32)
		    case 'E': case 'e':
			config.flags |= CONFIG_CREATE_EXE;
			break;

		    case 'T': case 't':
			config.flags |= CONFIG_COPY_TIME;
			break;
#endif
		    case 'P': case 'p':
			config.flags |= CONFIG_SKIP_WVX;
			break;

		    case 'Q': case 'q':
			config.flags |= CONFIG_QUIET_MODE;
			break;

		    case 'M': case 'm':
			config.flags |= CONFIG_MD5_CHECKSUM;
			break;

		    case 'I': case 'i':
			config.flags |= CONFIG_IGNORE_LENGTH;
			break;

		    case 'K': case 'k':
			config.block_samples = strtol (++*argv, argv, 10);
			--*argv;
			break;

		    case 'B': case 'b':
			config.flags |= CONFIG_HYBRID_FLAG;
			config.bitrate = strtod (++*argv, argv);
			--*argv;

			if (config.bitrate < 2.0 || config.bitrate > 9600.0) {
			    error_line ("hybrid spec must be 2.0 to 9600!");
			    usage_error = 1;
			}

			if (config.bitrate >= 24.0)
			    config.flags |= CONFIG_BITRATE_KBPS;

			break;

		    case 'J': case 'j':
			switch (strtol (++*argv, argv, 10)) {

			    case 0:
				config.flags |= CONFIG_JOINT_OVERRIDE;
				config.flags &= ~CONFIG_JOINT_STEREO;
				break;

			    case 1:
				config.flags |= (CONFIG_JOINT_OVERRIDE | CONFIG_JOINT_STEREO);
				break;

			    default:
				error_line ("-j0 or -j1 only!");
				usage_error = 1;
			}
			
			--*argv;
			break;

		    case 'S': case 's':
			config.shaping_weight = strtod (++*argv, argv);

			if (!config.shaping_weight) {
			    config.flags |= CONFIG_SHAPE_OVERRIDE;
			    config.flags &= ~CONFIG_HYBRID_SHAPE;
			}
			else if (config.shaping_weight >= -1.0 && config.shaping_weight <= 1.0)
			    config.flags |= (CONFIG_HYBRID_SHAPE | CONFIG_SHAPE_OVERRIDE);
			else {
			    error_line ("-s-1.00 to -s1.00 only!");
			    usage_error = 1;
			}
			
			--*argv;
			break;

		    case 'W': case 'w':
			tag_next_arg = 1;
			break;

		    default:
			error_line ("illegal option: %c !", **argv);
			usage_error = 1;
		}
	else {
	    if (tag_next_arg) {
		char *cp = strchr (*argv, '='), *string = NULL;

		tag_next_arg = 0;

		if (cp && cp > *argv) {
		    int item_len = cp - *argv;

		    if (cp [1] == '@') {
			FILE *file = fopen (cp+2, "rb");

			if (file) {
			    uint32_t bcount, file_len;

			    file_len = DoGetFileSize (file);

			    if (file_len < 1048576 && (string = malloc (item_len + file_len + 2)) != NULL) {
				memcpy (string, *argv, item_len + 1);

				if (!DoReadFile (file, string + item_len + 1, file_len, &bcount) || bcount != file_len) {
				    free (string);
				    string = NULL;
				}
				else
				    string [item_len + file_len + 1] = 0;
			    }

			    DoCloseHandle (file);
			}
		    }
		    else
			string = *argv;
		}

		if (!string) {
		    error_line ("error in tag spec: %s !", *argv);
		    usage_error = 1;
		}
		else if (cp [1]) {
		    config.tag_strings = realloc (config.tag_strings, ++config.num_tag_strings * sizeof (*config.tag_strings));
		    config.tag_strings [config.num_tag_strings - 1] = string;
		}
	    }
	    else if (!infilename) {
		infilename = malloc (strlen (*argv) + PATH_MAX);
		strcpy (infilename, *argv);
	    }
	    else if (!outfilename) {
		outfilename = malloc (strlen (*argv) + PATH_MAX);
		strcpy (outfilename, *argv);
	    }
	    else if (!out2filename) {
		out2filename = malloc (strlen (*argv) + PATH_MAX);
		strcpy (out2filename, *argv);
	    }
	    else {
		error_line ("extra unknown argument: %s !", *argv);
		usage_error = 1;
	    }
	}

    setup_break ();	// set up console and detect ^C and ^Break

    // check for various command-line argument problems

    if (!(~config.flags & (CONFIG_HIGH_FLAG | CONFIG_FAST_FLAG))) {
	error_line ("high and fast modes are mutually exclusive!");
	usage_error = 1;
    }

    if ((config.flags & CONFIG_IGNORE_LENGTH) && outfilename && *outfilename == '-') {
	error_line ("can't ignore length in header when using stdout!");
	usage_error = 1;
     }

    if (config.flags & CONFIG_HYBRID_FLAG) {
	if ((config.flags & CONFIG_CREATE_WVC) && outfilename && *outfilename == '-') {
	    error_line ("can't create correction file when using stdout!");
	    usage_error = 1;
	}
    }
    else {
	if (config.flags & (CONFIG_CALC_NOISE | CONFIG_SHAPE_OVERRIDE | CONFIG_CREATE_WVC)) {
	    error_line ("-s, -n and -c options are for hybrid mode (-b) only!");
	    usage_error = 1;
	}
    }

    if (!(config.flags & CONFIG_QUIET_MODE) && !usage_error)
	fprintf (stderr, sign_on, VERSION_OS, VERSION_STR, DATE_STR);

    if (!infilename) {
	printf ("%s", usage);
	return 0;
    }

    if (usage_error) {
	free(infilename);
	return 0;
    }

    // If we are trying to create self-extracting .exe files, this is where
    // we read the wvselfx.exe file into memory in preparation for pre-pending
    // it to the WavPack files.

#if defined (WIN32)
    if (config.flags & CONFIG_CREATE_EXE) {
	FILE *wvselfx_file;
	uint32_t bcount;

	strcpy (filespec_name (selfname), "wvselfx.exe");

	wvselfx_file = fopen (selfname, "rb");

	if (!wvselfx_file) {
	    _searchenv ("wvselfx.exe", "PATH", selfname);
	    wvselfx_file = fopen (selfname, "rb");
	}

	if (wvselfx_file) {
	    wvselfx_size = DoGetFileSize (wvselfx_file);

	    if (wvselfx_size && wvselfx_size != 26624 && wvselfx_size < 49152) {
		wvselfx_image = malloc (wvselfx_size);

		if (!DoReadFile (wvselfx_file, wvselfx_image, wvselfx_size, &bcount) || bcount != wvselfx_size) {
		    free (wvselfx_image);
		    wvselfx_image = NULL;
		}
	    }

	    DoCloseHandle (wvselfx_file);
	}

	if (!wvselfx_image) {
	    error_line ("wvselfx.exe file is not readable or is outdated!");
	    free (wvselfx_image);
	    exit (1);
	}
    }
#endif

    // If the infile specification begins with a '@', then it actually points
    // to a file that contains the names of the files to be converted. This
    // was included for use by Wim Speekenbrink's frontends, but could be used
    // for other purposes.

    if (infilename [0] == '@') {
	FILE *list = fopen (infilename+1, "rt");
	int c;

	if (list == NULL) {
	    error_line ("file %s not found!", infilename+1);
	    free (infilename);
	    return 1;
	}

	while ((c = getc (list)) != EOF) {

	    while (c == '\n')
		c = getc (list);

	    if (c != EOF) {
		char *fname = malloc (PATH_MAX);
		int ci = 0;

		do
		    fname [ci++] = c;
		while ((c = getc (list)) != '\n' && c != EOF && ci < PATH_MAX);

		fname [ci++] = '\0';
		matches = realloc (matches, ++num_files * sizeof (*matches));
		matches [num_files - 1] = realloc (fname, ci);
	    }
	}

	fclose (list);
	free (infilename);
	infilename = NULL;
	filelist = 1;
    }
    else if (*infilename != '-') {	// skip this if infile is stdin (-)
	if (!(config.flags & CONFIG_RAW_FLAG) && !filespec_ext (infilename))
	    strcat (infilename, ".wav");

#ifdef NO_WILDCARDS
	matches = malloc (sizeof (*matches));
	matches [num_files++] = infilename;
	filelist = 1;
#else
	// search for and store any filenames that match the user supplied spec

#ifdef __BORLANDC__
	if (findfirst (infilename, &ffblk, 0) == 0) {
	    do {
		matches = realloc (matches, ++num_files * sizeof (*matches));
		matches [num_files - 1] = strdup (ffblk.ff_name);
	    } while (findnext (&ffblk) == 0);
	}
#elif defined (WIN32)
	if ((i = _findfirst (infilename, &_finddata_t)) != -1L) {
	    do {
		if (!(_finddata_t.attrib & _A_SUBDIR)) {
		    matches = realloc (matches, ++num_files * sizeof (*matches));
		    matches [num_files - 1] = strdup (_finddata_t.name);
		}
	    } while (_findnext (i, &_finddata_t) == 0);

	    _findclose (i);
	}
#else
        i = 0;
        if (glob(infilename, 0, NULL, &globs) == 0 && globs.gl_pathc > 0) {
            do {
                if (stat(globs.gl_pathv[i], &fstats) == 0 && !(fstats.st_mode & S_IFDIR)) {
		    matches = realloc (matches, ++num_files * sizeof (*matches));
		    matches [num_files - 1] = strdup (globs.gl_pathv[i]);
                }
            } while (i++ < globs.gl_pathc);
        }
        globfree(&globs);
#endif
#endif
    }
    else {	// handle case of stdin (-)
	matches = malloc (sizeof (*matches));
	matches [num_files++] = infilename;
    }

    // If the outfile specification begins with a '@', then it actually points
    // to a file that contains the output specification. This was included for
    // use by Wim Speekenbrink's frontends because certain filenames could not
    // be passed on the command-line, but could be used for other purposes.

    if (outfilename && outfilename [0] == '@') {
	FILE *list = fopen (outfilename+1, "rt");
	int c;

	if (list == NULL) {
	    error_line ("file %s not found!", outfilename+1);
	    free(outfilename);
	    return 1;
	}

	while ((c = getc (list)) == '\n');

	if (c != EOF) {
	    int ci = 0;

	    do
		outfilename [ci++] = c;
	    while ((c = getc (list)) != '\n' && c != EOF && ci < PATH_MAX);

	    outfilename [ci] = '\0';
	}
	else {
	    error_line ("output spec file is empty!");
	    free(outfilename);
	    fclose (list);
	    return 1;
	}

	fclose (list);
    }

    if (out2filename && (num_files > 1 || !(config.flags & CONFIG_CREATE_WVC))) {
	error_line ("extra unknown argument: %s !", out2filename);
	return 1;
    }

    // if we found any files to process, this is where we start

    if (num_files) {
	char outpath, addext;
	int soft_errors = 0;

	if (outfilename && *outfilename != '-') {
	    outpath = (filespec_path (outfilename) != NULL);

	    if (num_files > 1 && !outpath) {
		error_line ("%s is not a valid output path", outfilename);
		free(outfilename);
		return 1;
	    }
	}
	else
	    outpath = 0;

	addext = !outfilename || outpath || !filespec_ext (outfilename);

	// loop through and process files in list

	for (file_index = 0; file_index < num_files; ++file_index) {
	    if (check_break ())
		break;

	    // get input filename from list

	    if (filelist)
		infilename = matches [file_index];
	    else if (*infilename != '-') {
		*filespec_name (infilename) = '\0';
		strcat (infilename, matches [file_index]);
	    }

	    // generate output filename

	    if (outpath) {
		strcat (outfilename, filespec_name (matches [file_index]));

		if (filespec_ext (outfilename))
		    *filespec_ext (outfilename) = '\0';
	    }
	    else if (!outfilename) {
		outfilename = malloc (strlen (infilename) + 10);
		strcpy (outfilename, infilename);

		if (filespec_ext (outfilename))
		    *filespec_ext (outfilename) = '\0';
	    }

	    if (addext && *outfilename != '-')
		strcat (outfilename, (config.flags & CONFIG_CREATE_EXE) ? ".exe" : ".wv");

	    // if "correction" file is desired, generate name for that

	    if (config.flags & CONFIG_CREATE_WVC) {
		if (!out2filename) {
		    out2filename = malloc (strlen (outfilename) + 10);
		    strcpy (out2filename, outfilename);
		}
		else {
		    char *temp = malloc (strlen (outfilename) + PATH_MAX);

		    strcpy (temp, outfilename);
		    strcpy (filespec_name (temp), filespec_name (out2filename));
		    strcpy (out2filename, temp);
		    free (temp);
		}

		if (filespec_ext (out2filename))
		    *filespec_ext (out2filename) = '\0';

		strcat (out2filename, ".wvc");
	    }
	    else
		out2filename = NULL;

	    if (num_files > 1)
		fprintf (stderr, "\n%s:\n", infilename);

	    result = pack_file (infilename, outfilename, out2filename, &config);

	    if (result == HARD_ERROR)
		break;
	    else if (result == SOFT_ERROR)
		++soft_errors;

	    // delete source file if that option is enabled

	    if (result == NO_ERROR && delete_source)
		error_line ("%s source file %s", DoDeleteFile (infilename) ?
		    "deleted" : "can't delete", infilename);

	    // clean up in preparation for potentially another file

	    if (outpath)
		*filespec_name (outfilename) = '\0';
	    else if (*outfilename != '-') {
		free (outfilename);
		outfilename = NULL;
	    }

	    if (out2filename) {
		free (out2filename);
		out2filename = NULL;
	    }

	    free (matches [file_index]);
	}

	if (num_files > 1) {
	    if (soft_errors)
		fprintf (stderr, "\n **** warning: errors occurred in %d of %d files! ****\n", soft_errors, num_files);
	    else if (!(config.flags & CONFIG_QUIET_MODE))
		fprintf (stderr, "\n **** %d files successfully processed ****\n", num_files);
	}

	free (matches);
    }
    else
	error_line (filespec_wild (infilename) ? "nothing to do!" :
	    "file %s not found!", infilename);

    if (outfilename)
	free(outfilename);

#ifdef DEBUG_ALLOC
    error_line ("malloc_count = %d", dump_alloc ());
#endif

    return 0;
}

// This structure and function are used to write completed WavPack blocks in
// a device independent way.

typedef struct {
    uint32_t bytes_written, first_block_size;
    FILE *file;
    int error;
} write_id;

static int write_block (void *id, void *data, int32_t length)
{
    write_id *wid = (write_id *) id;
    uint32_t bcount;

    if (wid->error)
	return FALSE;

    if (wid && wid->file && data && length) {
	if (!DoWriteFile (wid->file, data, length, &bcount) || bcount != length) {
	    DoTruncateFile (wid->file);
	    DoCloseHandle (wid->file);
	    wid->file = NULL;
	    wid->error = 1;
	    return FALSE;
	}
	else {
	    wid->bytes_written += length;

	    if (!wid->first_block_size)
		wid->first_block_size = bcount;
	}
    }

    return TRUE;
}

// This function packs a single file "infilename" and stores the result at
// "outfilename". If "out2filename" is specified, then the "correction"
// file would go there. The files are opened and closed in this function
// and the "config" structure specifies the mode of compression.

static int pack_file (char *infilename, char *outfilename, char *out2filename, const WavpackConfig *config)
{
    uint32_t total_samples = 0, wrapper_size = 0, bcount;
    WavpackConfig loc_config = *config;
    RiffChunkHeader riff_chunk_header;
    write_id wv_file, wvc_file;
    ChunkHeader chunk_header;
    WaveHeader WaveHeader;
    WavpackContext *wpc;
    double dtime;
    FILE *infile;
    int result;

#ifdef __BORLANDC__
    struct time time1, time2;
#elif defined(WIN32)
    struct _timeb time1, time2;
#else
    struct timeval time1, time2;
    struct timezone timez;
#endif

    CLEAR (wv_file);
    CLEAR (wvc_file);
    wpc = WavpackOpenFileOutput (write_block, &wv_file, out2filename ? &wvc_file : NULL);

    // open the source file for reading

    if (*infilename == '-') {
	infile = stdin;
#if defined(WIN32)
	setmode (fileno (stdin), O_BINARY);
#endif
    }
    else if ((infile = fopen (infilename, "rb")) == NULL) {
	error_line ("can't open file %s!", infilename);
	WavpackCloseFile (wpc);
	return SOFT_ERROR;
    }

    // check both output files for overwrite warning required

    if (*outfilename != '-' && !overwrite_all && (wv_file.file = fopen (outfilename, "rb")) != NULL) {
	DoCloseHandle (wv_file.file);
	fprintf (stderr, "overwrite %s (yes/no/all)? ", FN_FIT (outfilename));
	SetConsoleTitle ("overwrite?");

	switch (yna ()) {
	    case 'n':
		DoCloseHandle (infile);
		WavpackCloseFile (wpc);
		return SOFT_ERROR;

	    case 'a':
		overwrite_all = 1;
	}
    }

    if (out2filename && !overwrite_all && (wvc_file.file = fopen (out2filename, "rb")) != NULL) {
	DoCloseHandle (wvc_file.file);
	fprintf (stderr, "overwrite %s (yes/no/all)? ", FN_FIT (out2filename));
	SetConsoleTitle ("overwrite?");

	switch (yna ()) {

	    case 'n':
		DoCloseHandle (infile);
		WavpackCloseFile (wpc);
		return SOFT_ERROR;

	    case 'a':
		overwrite_all = 1;
	}
    }

#ifdef __BORLANDC__
    gettime (&time1);
#elif defined(WIN32)
    _ftime (&time1);
#else
    gettimeofday(&time1,&timez);
#endif

    // open output file for writing

    if (*outfilename == '-') {
	wv_file.file = stdout;
#if defined(WIN32)
	setmode (fileno (stdout), O_BINARY);
#endif
    }
    else if ((wv_file.file = fopen (outfilename, "w+b")) == NULL) {
	error_line ("can't create file %s!", outfilename);
	DoCloseHandle (infile);
	WavpackCloseFile (wpc);
	return SOFT_ERROR;
    }

    if (!(loc_config.flags & CONFIG_QUIET_MODE)) {
	if (*outfilename == '-')
	    fprintf (stderr, "packing %s to stdout,", *infilename == '-' ? "stdin" : FN_FIT (infilename));
	else if (out2filename)
	    fprintf (stderr, "creating %s (+%s),", FN_FIT (outfilename), filespec_ext (out2filename));
	else
	    fprintf (stderr, "creating %s,", FN_FIT (outfilename));
    }

#if defined (WIN32)
    if (loc_config.flags & CONFIG_CREATE_EXE)
	if (!DoWriteFile (wv_file.file, wvselfx_image, wvselfx_size, &bcount) || bcount != wvselfx_size) {
	    error_line ("can't write WavPack data, disk probably full!");
	    DoCloseHandle (infile);
	    DoCloseHandle (wv_file.file);
	    DoDeleteFile (outfilename);
	    WavpackCloseFile (wpc);
	    return SOFT_ERROR;
	}
#endif

    // if not in "raw" mode, read (and copy to output) initial RIFF form header

    if (!(loc_config.flags & CONFIG_RAW_FLAG)) {
	if ((!DoReadFile (infile, &riff_chunk_header, sizeof (RiffChunkHeader), &bcount) ||
	    bcount != sizeof (RiffChunkHeader) || strncmp (riff_chunk_header.ckID, "RIFF", 4) ||
	    strncmp (riff_chunk_header.formType, "WAVE", 4))) {
		error_line ("%s is not a valid .WAV file!", infilename);
		DoCloseHandle (infile);
		DoCloseHandle (wv_file.file);
		DoDeleteFile (outfilename);
		WavpackCloseFile (wpc);
		return SOFT_ERROR;
	}
	else if (!WavpackAddWrapper (wpc, &riff_chunk_header, sizeof (RiffChunkHeader))) {
	    error_line ("%s", wpc->error_message);
	    DoCloseHandle (infile);
	    DoCloseHandle (wv_file.file);
	    DoDeleteFile (outfilename);
	    WavpackCloseFile (wpc);
	    return SOFT_ERROR;
	}

	wrapper_size += sizeof (RiffChunkHeader);
    }

    // if not in "raw" mode, loop through all elements of the RIFF wav header
    // (until the data chuck) and copy them to the output file

    while (!(loc_config.flags & CONFIG_RAW_FLAG)) {

	if (!DoReadFile (infile, &chunk_header, sizeof (ChunkHeader), &bcount) ||
	    bcount != sizeof (ChunkHeader)) {
		error_line ("%s is not a valid .WAV file!", infilename);
		DoCloseHandle (infile);
		DoCloseHandle (wv_file.file);
		DoDeleteFile (outfilename);
		WavpackCloseFile (wpc);
		return SOFT_ERROR;
	}
	else if (!WavpackAddWrapper (wpc, &chunk_header, sizeof (ChunkHeader))) {
	    error_line ("%s", wpc->error_message);
	    DoCloseHandle (infile);
	    DoCloseHandle (wv_file.file);
	    DoDeleteFile (outfilename);
	    WavpackCloseFile (wpc);
	    return SOFT_ERROR;
	}

	wrapper_size += sizeof (ChunkHeader);
	little_endian_to_native (&chunk_header, ChunkHeaderFormat);

	// if it's the format chunk, we want to get some info out of there and
	// make sure it's a .wav file we can handle

	if (!strncmp (chunk_header.ckID, "fmt ", 4)) {
	    int supported = TRUE, format;

	    if (chunk_header.ckSize < 16 || chunk_header.ckSize > sizeof (WaveHeader) ||
		!DoReadFile (infile, &WaveHeader, chunk_header.ckSize, &bcount) ||
		bcount != chunk_header.ckSize) {
		    error_line ("%s is not a valid .WAV file!", infilename);
		    DoCloseHandle (infile);
		    DoCloseHandle (wv_file.file);
		    DoDeleteFile (outfilename);
		    WavpackCloseFile (wpc);
		    return SOFT_ERROR;
	    }
	    else if (!WavpackAddWrapper (wpc, &WaveHeader, chunk_header.ckSize)) {
		error_line ("%s", wpc->error_message);
		DoCloseHandle (infile);
		DoCloseHandle (wv_file.file);
		DoDeleteFile (outfilename);
		WavpackCloseFile (wpc);
		return SOFT_ERROR;
	    }

	    wrapper_size += chunk_header.ckSize;
	    little_endian_to_native (&WaveHeader, WaveHeaderFormat);

#if 0
	    error_line ("format tag size = %d", chunk_header.ckSize);
	    error_line ("FormatTag = %x, NumChannels = %d, BitsPerSample = %d",
		WaveHeader.FormatTag, WaveHeader.NumChannels, WaveHeader.BitsPerSample);
	    error_line ("BlockAlign = %d, SampleRate = %d, BytesPerSecond = %d",
		WaveHeader.BlockAlign, WaveHeader.SampleRate, WaveHeader.BytesPerSecond);

	    if (chunk_header.ckSize > 16)
		error_line ("cbSize = %d, ValidBitsPerSample = %d", WaveHeader.cbSize,
		    WaveHeader.ValidBitsPerSample);

	    if (chunk_header.ckSize > 20)
		error_line ("ChannelMask = %x, SubFormat = %d",
		    WaveHeader.ChannelMask, WaveHeader.SubFormat);
#endif

	    if (chunk_header.ckSize > 16 && WaveHeader.cbSize == 2)
		loc_config.flags |= CONFIG_ADOBE_MODE;

	    format = (WaveHeader.FormatTag == 0xfffe && chunk_header.ckSize == 40) ?
		WaveHeader.SubFormat : WaveHeader.FormatTag;

	    loc_config.bits_per_sample = chunk_header.ckSize == 40 ?
		WaveHeader.ValidBitsPerSample : WaveHeader.BitsPerSample;

	    if (format != 1 && format != 3)
		supported = FALSE;

	    if (!WaveHeader.NumChannels ||
		WaveHeader.BlockAlign / WaveHeader.NumChannels > 4)
		    supported = FALSE;

	    if (loc_config.bits_per_sample < 1 || loc_config.bits_per_sample > 32)
		supported = FALSE;

	    if (!supported) {
		error_line ("%s is an unsupported .WAV format!", infilename);
		DoCloseHandle (infile);
		DoCloseHandle (wv_file.file);
		DoDeleteFile (outfilename);
		WavpackCloseFile (wpc);
		return SOFT_ERROR;
	    }

	    if (chunk_header.ckSize < 40) {
		if (WaveHeader.NumChannels <= 2)
		    loc_config.channel_mask = 0x5 - WaveHeader.NumChannels;
		else
		    loc_config.channel_mask = (1 << WaveHeader.NumChannels) - 1;
	    }
	    else
		loc_config.channel_mask = WaveHeader.ChannelMask;

	    if (format == 3)
		loc_config.float_norm_exp = 127;
	    else if ((loc_config.flags & CONFIG_ADOBE_MODE) &&
		WaveHeader.BlockAlign / WaveHeader.NumChannels == 4) {
		    if (WaveHeader.BitsPerSample == 24)
			loc_config.float_norm_exp = 127 + 23;
		    else if (WaveHeader.BitsPerSample == 32)
			loc_config.float_norm_exp = 127 + 15;
	    }
	}
	else if (!strncmp (chunk_header.ckID, "data", 4)) {

	    // on the data chunk, get size and exit loop

	    total_samples = chunk_header.ckSize / WaveHeader.BlockAlign;
	    break;
	}
	else {		// just copy unknown chunks to output file

	    int bytes_to_copy = (chunk_header.ckSize + 1) & ~1L;
	    char *buff = malloc (bytes_to_copy);
#if 0
	    error_line ("extra unknown chunk \"%c%c%c%c\" of %d bytes",
		chunk_header.ckID [0], chunk_header.ckID [1], chunk_header.ckID [2],
		chunk_header.ckID [3], chunk_header.ckSize);
#endif
	    if (!DoReadFile (infile, buff, bytes_to_copy, &bcount) ||
		bcount != bytes_to_copy || !WavpackAddWrapper (wpc, buff, bytes_to_copy)) {
		    error_line ("%s", wpc->error_message);
		    DoCloseHandle (infile);
		    DoCloseHandle (wv_file.file);
		    DoDeleteFile (outfilename);
		    free (buff);
		    WavpackCloseFile (wpc);
		    return SOFT_ERROR;
	    }

	    wrapper_size += bytes_to_copy;
	    free (buff);
	}
    }

    loc_config.bytes_per_sample = WaveHeader.BlockAlign / WaveHeader.NumChannels;
    loc_config.num_channels = WaveHeader.NumChannels;
    loc_config.sample_rate = WaveHeader.SampleRate;

    WavpackSetConfiguration (wpc, &loc_config, total_samples);

    // if we are creating a "correction" file, open it now for writing

    if (out2filename) {
	if ((wvc_file.file = fopen (out2filename, "w+b")) == NULL) {
	    error_line ("can't create correction file!");
	    DoCloseHandle (infile);
	    DoCloseHandle (wv_file.file);
	    DoDeleteFile (outfilename);
	    WavpackCloseFile (wpc);
	    return SOFT_ERROR;
	}
    }

    // pack the audio portion of the file now

    result = pack_audio (wpc, infile);

    // if everything went well (and we're not ignoring length) try to read
    // anything else that might be appended to the audio data and write that
    // to the WavPack metadata as "wrapper"

    if (result == NO_ERROR && !(loc_config.flags & CONFIG_IGNORE_LENGTH)) {
	uchar buff [16];

	while (DoReadFile (infile, buff, sizeof (buff), &bcount) && bcount)
	    if (!WavpackAddWrapper (wpc, buff, bcount)) {
		error_line ("%s", wpc->error_message);
		result = HARD_ERROR;
		break;
	    }
    }

    DoCloseHandle (infile);	// we're now done with input file, so close

    // we're now done with any WavPack blocks, so flush any remaining data

    if (result == NO_ERROR && !WavpackFlushSamples (wpc)) {
	error_line ("%s", wpc->error_message);
	result = HARD_ERROR;
    }

    // if still no errors, check to see if we need to create & write a tag
    // (which is NOT stored in regular WavPack blocks)

    if (result == NO_ERROR && config->num_tag_strings) {
	int i;

	for (i = 0; i < config->num_tag_strings; ++i) {
	    int item_len = strchr (config->tag_strings [i], '=') - config->tag_strings [i];
	    int value_len = strlen (config->tag_strings [i]) - item_len - 1;

	    if (value_len) {
		char *item = malloc (item_len + 1);
		char *value = malloc (value_len * 2 + 1);

		strncpy (item, config->tag_strings [i], item_len);
		item [item_len] = 0;
		strcpy (value, config->tag_strings [i] + item_len + 1);
		AnsiToUTF8 (value, value_len * 2 + 1);
		WavpackAppendTagItem (wpc, item, value);
		free (value);
		free (item);
	    }
	}

	if (!WavpackWriteTag (wpc)) {
	    error_line ("%s", wpc->error_message);
	    result = HARD_ERROR;
	}
    }

    // At this point we're done writing to the output files. However, in some
    // situations we might have to back up and re-write the initial blocks.
    // Currently the only case is if we're ignoring length.

    if (result == NO_ERROR && WavpackGetNumSamples (wpc) != WavpackGetSampleIndex (wpc)) {
	if (loc_config.flags & CONFIG_IGNORE_LENGTH) {
	    char *block_buff = malloc (wv_file.first_block_size);

	    if (block_buff && !DoSetFilePositionAbsolute (wv_file.file, 0) &&
		DoReadFile (wv_file.file, block_buff, wv_file.first_block_size, &bcount) &&
		bcount == wv_file.first_block_size && !strncmp (block_buff, "wvpk", 4)) {

		    WavpackUpdateNumSamples (wpc, block_buff);

		    if (WavpackGetWrapperLocation (block_buff)) {
			RiffChunkHeader *riffhdr = WavpackGetWrapperLocation (block_buff);
			ChunkHeader *datahdr = (ChunkHeader *)((char *) riffhdr + wrapper_size - sizeof (ChunkHeader));
			uint32_t data_size = WavpackGetSampleIndex (wpc) * WavpackGetNumChannels (wpc) * WavpackGetBytesPerSample (wpc);

			if (!strncmp (riffhdr->ckID, "RIFF", 4)) {
			    little_endian_to_native (riffhdr, ChunkHeaderFormat);
			    riffhdr->ckSize = wrapper_size + data_size;
			    native_to_little_endian (riffhdr, ChunkHeaderFormat);
			}

			if (!strncmp (datahdr->ckID, "data", 4)) {
			    little_endian_to_native (datahdr, ChunkHeaderFormat);
			    datahdr->ckSize = data_size;
			    native_to_little_endian (datahdr, ChunkHeaderFormat);
			}
		    }

		    if (DoSetFilePositionAbsolute (wv_file.file, 0) || 
			!DoWriteFile (wv_file.file, block_buff, wv_file.first_block_size, &bcount) ||
			bcount != wv_file.first_block_size) {
			    error_line ("couldn't update WavPack header with actual length!!");
			    result = SOFT_ERROR;
		    }

		    free (block_buff);
	    }
	    else {
		error_line ("couldn't update WavPack header with actual length!!");
		result = SOFT_ERROR;
	    }

	    if (result == NO_ERROR && wvc_file.file) {
		block_buff = malloc (wvc_file.first_block_size);

		if (block_buff && !DoSetFilePositionAbsolute (wvc_file.file, 0) &&
		    DoReadFile (wvc_file.file, block_buff, wvc_file.first_block_size, &bcount) &&
		    bcount == wvc_file.first_block_size && !strncmp (block_buff, "wvpk", 4)) {

			WavpackUpdateNumSamples (wpc, block_buff);

			if (DoSetFilePositionAbsolute (wvc_file.file, 0) ||
			    !DoWriteFile (wvc_file.file, block_buff, wvc_file.first_block_size, &bcount) ||
			    bcount != wvc_file.first_block_size) {
				error_line ("couldn't update WavPack header with actual length!!");
				result = SOFT_ERROR;
			}
		}
		else {
		    error_line ("couldn't update WavPack header with actual length!!");
		    result = SOFT_ERROR;
		}

		free (block_buff);
	    }

	    if (result == NO_ERROR)
		error_line ("warning: length was %s by %d samples, corrected",
		    WavpackGetSampleIndex (wpc) < total_samples ? "short" : "long",
		    abs (total_samples - WavpackGetSampleIndex (wpc)));
	}
	else {
	    error_line ("couldn't read all samples, file may be corrupt!!");
	    result = SOFT_ERROR;
	}
    }

    // at this point we're done with the files, so close 'em whether there
    // were any other errors or not

    if (!DoCloseHandle (wv_file.file)) {
	error_line ("can't close WavPack file!");

	if (result == NO_ERROR)
	    result = SOFT_ERROR;
    }

    if (out2filename && !DoCloseHandle (wvc_file.file)) {
	error_line ("can't close correction file!");

	if (result == NO_ERROR)
	    result = SOFT_ERROR;
    }

    // if there were any errors, delete the output files, close the context,
    // and return the error

    if (result != NO_ERROR) {
	DoDeleteFile (outfilename);

	if (out2filename)
	    DoDeleteFile (out2filename);

	WavpackCloseFile (wpc);
	return result;
    }

#if defined (WIN32)
    if (result == NO_ERROR && (loc_config.flags & CONFIG_COPY_TIME))
	if (!copy_timestamp (infilename, outfilename) ||
	    (out2filename && !copy_timestamp (infilename, out2filename)))
		error_line ("failure copying time stamp!");
#endif

    // compute and display the time consumed along with some other details of
    // the packing operation, and then return NO_ERROR

#ifdef __BORLANDC__
    gettime (&time2);
    dtime = time2.ti_sec * 100.0 + time2.ti_hund + time2.ti_min * 6000.0 + time2.ti_hour * 360000.00;
    dtime -= time1.ti_sec * 100.0 + time1.ti_hund + time1.ti_min * 6000.0 + time1.ti_hour * 360000.00;

    if ((dtime /= 100.0) < 0.0)
	dtime += 86400.0;
#elif defined(WIN32)
    _ftime (&time2);
    dtime = time2.time + time2.millitm / 1000.0;
    dtime -= time1.time + time1.millitm / 1000.0;
#else
    gettimeofday(&time2,&timez);
    dtime = time2.tv_sec + time2.tv_usec / 1000000.0;
    dtime -= time1.tv_sec + time1.tv_usec / 1000000.0;
#endif

    if ((loc_config.flags & CONFIG_CALC_NOISE) && pack_noise (wpc, NULL) > 0.0) {
	int full_scale_bits = WavpackGetBitsPerSample (wpc);
	double full_scale_rms = 0.5, sum, peak;

	while (full_scale_bits--)
	    full_scale_rms *= 2.0;

	full_scale_rms = full_scale_rms * (full_scale_rms - 1.0) * 0.5;
	sum = pack_noise (wpc, &peak);

	error_line ("ave noise = %.2f dB, peak noise = %.2f dB",
	    log10 (sum / WavpackGetNumSamples (wpc) / full_scale_rms) * 10,
	    log10 (peak / full_scale_rms) * 10);
    }

    if (!(loc_config.flags & CONFIG_QUIET_MODE)) {
	char *file, *fext, *oper, *cmode, cratio [16] = "";

	if (outfilename && *outfilename != '-') {
	    file = FN_FIT (outfilename);
	    fext = wvc_file.bytes_written ? " (+.wvc)" : "";
	    oper = "created";
	}
	else {
	    file = (*infilename == '-') ? "stdin" : FN_FIT (infilename);
	    fext = "";
	    oper = "packed";
	}

	if (WavpackLossyBlocks (wpc)) {
	    cmode = "lossy";

	    if (WavpackGetAverageBitrate (wpc, TRUE) != 0.0)
		sprintf (cratio, ", %d kbps", (int) (WavpackGetAverageBitrate (wpc, TRUE) / 1000.0));
	}
	else {
	    cmode = "lossless";

	    if (WavpackGetRatio (wpc) != 0.0)
		sprintf (cratio, ", %.2f%%", 100.0 - WavpackGetRatio (wpc) * 100.0);
	}

	error_line ("%s %s%s in %.2f secs (%s%s)", oper, file, fext, dtime, cmode, cratio);
    }

    WavpackCloseFile (wpc);
    return NO_ERROR;
}

// This function handles the actual audio data compression. It assumes that the
// input file is positioned at the beginning of the audio data and that the
// WavPack configuration has been set. This is where the conversion from RIFF
// little-endian standard the executing processor's format is done and where
// (if selected) the MD5 sum is calculated and displayed.

#define INPUT_SAMPLES 65536

static int pack_audio (WavpackContext *wpc, FILE *infile)
{
    uint32_t samples_remaining, samples_read = 0;
    double progress = -1.0;
    int bytes_per_sample;
    int32_t *sample_buffer;
    uchar *input_buffer;
    MD5_CTX md5_context;

    if (wpc->config.flags & CONFIG_MD5_CHECKSUM)
	MD5Init (&md5_context);

    WavpackPackInit (wpc);
    bytes_per_sample = WavpackGetBytesPerSample (wpc) * WavpackGetNumChannels (wpc);
    input_buffer = malloc (INPUT_SAMPLES * bytes_per_sample);
    sample_buffer = malloc (INPUT_SAMPLES * sizeof (int32_t) * WavpackGetNumChannels (wpc));
    samples_remaining = WavpackGetNumSamples (wpc);

    while (1) {
	uint32_t bytes_to_read, bytes_read = 0;
	uint sample_count;

	if ((wpc->config.flags & CONFIG_IGNORE_LENGTH) || samples_remaining > INPUT_SAMPLES)
	    bytes_to_read = INPUT_SAMPLES * bytes_per_sample;
	else
	    bytes_to_read = samples_remaining * bytes_per_sample;

	samples_remaining -= bytes_to_read / bytes_per_sample;
	DoReadFile (infile, input_buffer, bytes_to_read, &bytes_read);
	samples_read += sample_count = bytes_read / bytes_per_sample;

	if (wpc->config.flags & CONFIG_MD5_CHECKSUM)
	    MD5Update (&md5_context, input_buffer, bytes_read);

	if (!sample_count)
	    break;

	if (sample_count) {
	    uint cnt = sample_count * WavpackGetNumChannels (wpc);
	    uchar *sptr = input_buffer;
	    int32_t *dptr = sample_buffer;

	    switch (WavpackGetBytesPerSample (wpc)) {

		case 1:
		    while (cnt--)
			*dptr++ = *sptr++ - 128;

		    break;

		case 2:
		    while (cnt--) {
			*dptr++ = sptr [0] | ((int32_t)(char) sptr [1] << 8);
			sptr += 2;
		    }

		    break;

		case 3:
		    while (cnt--) {
			*dptr++ = sptr [0] | ((int32_t) sptr [1] << 8) | ((int32_t)(char) sptr [2] << 16);
			sptr += 3;
		    }

		    break;

		case 4:
		    while (cnt--) {
			*dptr++ = sptr [0] | ((int32_t) sptr [1] << 8) | ((int32_t) sptr [2] << 16) | ((int32_t)(char) sptr [3] << 24);
			sptr += 4;
		    }

		    break;
	    }
	}

	if (!WavpackPackSamples (wpc, sample_buffer, sample_count)) {
	    error_line ("%s", wpc->error_message);
	    free (sample_buffer);
	    free (input_buffer);
	    return HARD_ERROR;
	}

	if (check_break ()) {
	    fprintf (stderr, "^C\n");
	    free (sample_buffer);
	    free (input_buffer);
	    return SOFT_ERROR;
	}

	if (WavpackGetProgress (wpc) != -1.0 &&
	    progress != floor (WavpackGetProgress (wpc) * 100.0 + 0.5)) {
		int nobs = progress == -1.0;

		progress = WavpackGetProgress (wpc);
		display_progress (progress);
		progress = floor (progress * 100.0 + 0.5);

		if (!(wpc->config.flags & CONFIG_QUIET_MODE))
		    fprintf (stderr, "%s%3d%% done...",
			nobs ? " " : "\b\b\b\b\b\b\b\b\b\b\b\b", (int) progress);
	}
    }

    free (sample_buffer);
    free (input_buffer);

    if (!WavpackFlushSamples (wpc)) {
	error_line ("%s", wpc->error_message);
	return HARD_ERROR;
    }

    if (wpc->config.flags & CONFIG_MD5_CHECKSUM) {
	char md5_string [] = "original md5 signature: 00000000000000000000000000000000";
	uchar md5_digest [16];
	int i;

	MD5Final (md5_digest, &md5_context);

	for (i = 0; i < 16; ++i)
	    sprintf (md5_string + 24 + (i * 2), "%02x", md5_digest [i]);

	if (!(wpc->config.flags & CONFIG_QUIET_MODE))
	    error_line (md5_string);

	WavpackStoreMD5Sum (wpc, md5_digest);
    }

    return NO_ERROR;
}

//////////////////////////////////////////////////////////////////////////////
// This function displays the progress status on the title bar of the DOS   //
// window that WavPack is running in. The "file_progress" argument is for   //
// the current file only and ranges from 0 - 1; this function takes into    //
// account the total number of files to generate a batch progress number.   //
//////////////////////////////////////////////////////////////////////////////

static void display_progress (double file_progress)
{
    char title [40];

    file_progress = (file_index + file_progress) / num_files;
    sprintf (title, "%d%% (WavPack)", (int) ((file_progress * 100.0) + 0.5));
    SetConsoleTitle (title);
}