/*
 * DWAV
 * Convert DMX EPROM binary file to WAV file (command line)
 *
 * to compile under FreeBSD (and probably other Unices as well): 
 *     gcc dwav.c -o dwav -lm
 * requires math library for EXP function
 *
 * Copyright (c) 2002 Paul J. White.  All rights reserved.
 *      Comments and corrections to: pjwhite@electrongate.com
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

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

typedef unsigned char       BYTE;
typedef unsigned short      WORD;
typedef unsigned long       DWORD;

#define VERSION_STRING		"1.00"
#define STRING_SIZE		128
#define DEFAULT_SAMPLE_RATE	24000.0

struct RIFFhdr {
	BYTE IDstr[4];
	DWORD RIFFsize;
	BYTE TypeStr[4];
};

struct CHUNKhdr {
	BYTE Cname[4];
	DWORD Csize;
};

struct WAVEfmt {
	WORD wFormatTag;
	WORD wChannels;
	DWORD wSamplesPerSec;
	DWORD wAvgBytesPerSec;
	WORD wBlockAlign;
};

/*
Note: On some systems, alignment will make sizeof(struct WAVEfmt) == 16
instead of 14, which is what it should be.  I have explicitly used 14
here, instead of sizeof(struct WAVEfmt).
*/

struct PCMfmt {			// Valid for WAVE_FORMAT_PCM format only
	WORD wBitsPerSample;
};

struct DATAfmt16 {
	WORD left_channel;
	WORD right_channel;
};

struct DATAfmt8 {
	BYTE left_channel;
	BYTE right_channel;
};

/*
Format of our WAV file:

struct WAVFILE {
	struct RIFFhdr header;
	struct CHUNKhdr fmtchunkheader;
	BYTE chunkdata[];
	struct CHUNKhdr datachunkheader;
	WORD datadata[];
};
*/

/*********************************************************
 Function: Usage()
 Display a program usage message
 Inputs: none
 Output: none
 *********************************************************/
void Usage(void)
{
	fprintf(stderr, "dwav version " VERSION_STRING " Copyright 2002, Paul J. White (Electrongate)\n"
			"This program converts a DMX EPROM image file to a WAV audio file.\n"
			"The input file should be in raw binary format.\n\n"
			"Usage:\n"
			"dwav input_filename [-o output_filename][-s sample_rate][-v]\n\n"
			"Where: optional parameters are shown in [brackets].\n"
			"       -o specifies an output filename explicitly.  By default,\n"
			"          the output filename is \"input_filename.wav\"\n"
			"       -s specifies the output sample rate in samples/second.\n"
			"          Typical for DMX files is 22000 to 32000.\n"
			"       -v sets verbose mode to provide more detailed information.\n\n");
	fprintf(stderr, "Example:\n"
			"dwav drum.bin -s 24000 -o drum7.wav -v\n");
}

/*********************************************************
 Function: u255lawToLinear()
  Convert byte in companded format to 16 bit word
 Inputs: companded value to convert, scaling value
 Output: linear value
 *********************************************************/
long int u255lawToLinear(int x, long int scale)
{
	double AX;
	double signbit;
	int y;

	if (x & 0x80) {
		signbit = 1.0;
	} else {
		signbit = -1.0;
	}
	x = x & 0x7F;

	AX = (double) x;

	AX = signbit * scale * exp(AX / 23.08) / 255.0;

	y =  (int) AX;

	return y;
}

/*********************************************************
 Function: main()
  Main program
 Inputs: argument count, argument list
 Output: return code. 0 for success
 *********************************************************/
int main(int argc, char ** argv)
{

	double sample_rate = DEFAULT_SAMPLE_RATE;
	double A, output_time;

	char * infile = NULL;
	char * outfile = NULL;
	char * p;
	FILE *ip, *op;

	char string[STRING_SIZE];
	
	int i, k, bytes_per_sample, f = 0, verbose = 0;
	long int number_of_samples;
	
	struct CHUNKhdr chunkheader;
	struct WAVEfmt waveformat;
	struct PCMfmt pcmformat;

	if (argc < 2) {
		Usage();
		return 1;
	}

	// Collect all command line arguments before starting.
	while (++f < argc) {
		if (argv[f][0] == '-') {
			p = argv[f];
			p++;
			switch (*p) {
				case '?':
					Usage();
					break;

				case 'i':
				case 'I':
					f++;
					if (f < argc) infile = argv[f];
					else fprintf(stderr, "Missing input file spec after -%c\n", *p);
					break;

				case 'o':
				case 'O':
					f++;
					if (f < argc) outfile = argv[f];
					else fprintf(stderr, "Missing output file spec after -%c\n", *p);
					break;

				case 's':
				case 'S':
					f++;
					if (f < argc) {
						sample_rate = atof(argv[f]);
						if (sample_rate < 1.0) {
							fprintf(stderr, "Bad value for sample rate (%f)\n", sample_rate);
							sample_rate = DEFAULT_SAMPLE_RATE;
						}
					} else {
						fprintf(stderr, "Missing output file spec after -%c\n", *p);
					}
					break;

				case 'v':
				case 'V':
					verbose++;
					break;

				default:
					fprintf(stderr, "Unknown argument: -%s\n", p);
					break;
			}
		} else {
			infile = argv[f];
		}
	}

	// Check given arguments
	if (infile == NULL) {
		fprintf(stderr, "No input file specified.\n");
		return 2;
	}

	if (outfile == NULL) {
		strncpy(string, infile, STRING_SIZE - 1);
		string[STRING_SIZE - 1] = 0;

		// change name to XXX.wav
		p = strstr(string, ".");
		if (p != NULL) *p = 0;
		strcat(string, ".wav");
		outfile = string;
	}
	
//	Here is where we start the conversion...

	ip = fopen(infile, "rb");
	if (ip == NULL) {
		fprintf(stderr, "Unable to open input file: %s\n", infile);
		return 3;
	} else {
		fprintf(stderr, "Input file: %s\n", infile);
	}

	op = fopen(outfile, "wb");
	if (op == NULL) {
		fprintf(stderr, "Unable to open output file: %s\n", outfile);
		fclose(ip);
		return 1;
	} else {
		fprintf(stderr, "Output file: %s\n", outfile);
	}

	// Get length of input file
	if (fseek(ip, 0, SEEK_END) == 0) {
		number_of_samples = ftell(ip);
	} else {
		fprintf(stderr, "Unable to determine length of input file: %s\n", infile);
		fclose(ip);
		fclose(op);
		return 1;
	}
	rewind(ip);

	A = (double) number_of_samples;
	output_time = A / sample_rate;

	if (verbose) {
		fprintf(stderr, "Input file length = %ld\n", number_of_samples);
		fprintf(stderr, "Sample rate = %.0f Hz, duration = %.3f seconds\n",
			sample_rate, output_time);
	}

	// Write WAV file header
	bytes_per_sample = 2;

	//---------------------------------
	chunkheader.Cname[0] = 'R';
	chunkheader.Cname[1] = 'I';
	chunkheader.Cname[2] = 'F';
	chunkheader.Cname[3] = 'F';

	chunkheader.Csize =
		strlen("WAVE") +
		sizeof(struct CHUNKhdr) +
/*		sizeof(struct WAVEfmt) */  14 +	
		sizeof(struct PCMfmt) +
		sizeof(struct CHUNKhdr) +
		(number_of_samples * bytes_per_sample);

	fwrite(&chunkheader, sizeof(struct CHUNKhdr), 1, op);

	//---------------------------------
	fprintf(op, "WAVE");

	//---------------------------------
	chunkheader.Cname[0] = 'f';
	chunkheader.Cname[1] = 'm';
	chunkheader.Cname[2] = 't';
	chunkheader.Cname[3] = ' ';

	chunkheader.Csize =
/*		sizeof(struct WAVEfmt) */  14 +	
		sizeof(struct PCMfmt);

	fwrite(&chunkheader, sizeof(struct CHUNKhdr), 1, op);

	//---------------------------------
	waveformat.wFormatTag = 0x0001;		// WAVE_FORMAT_PCM
	waveformat.wChannels = 1;
	waveformat.wSamplesPerSec = (DWORD) (long int) sample_rate;
	waveformat.wAvgBytesPerSec = waveformat.wSamplesPerSec * bytes_per_sample;
	waveformat.wBlockAlign = (WORD) bytes_per_sample;

	fwrite(&waveformat, /* sizeof(struct WAVEfmt) */ 14, 1, op);

	//--------------------------------- format specific info
	pcmformat.wBitsPerSample = (WORD) (bytes_per_sample * 8);
	fwrite(&pcmformat, sizeof(struct PCMfmt), 1, op);

	//---------------------------------
	chunkheader.Cname[0] = 'd';
	chunkheader.Cname[1] = 'a';
	chunkheader.Cname[2] = 't';
	chunkheader.Cname[3] = 'a';

	chunkheader.Csize = number_of_samples * bytes_per_sample;

	fwrite(&chunkheader, sizeof(struct CHUNKhdr), 1, op);

	while (!feof(ip)) {
		i = fgetc(ip);
		if (feof(ip)) continue;

		k = (int) u255lawToLinear(i, 32767);
		fwrite(&k, sizeof(WORD), 1, op);
	}

	fclose(ip);
	fclose(op);

	if (verbose) fprintf(stderr, "Finished.\n");

	return 0;
}













