#include <windows.h>
#include <stdio.h>

#define VERSION "1.04"
/*
1.04: added support for floating-point samples (thanks to tebasuna51)
1.04: added support fot files > 4G; use Win32 API
1.03: fixed size field in data chunk header (thanks to tebasuna51)
1.03: removed annoying debug message
*/

#define WAVE_FORMAT_IEEE_FLOAT 3

int main (int argc, char * argv[])
{
	WAVEFORMATEX waveFormat;
	double       length = 1.0;
	LPSTR        outputFile = NULL;
	BOOL         bitsPerSampleGiven = FALSE;
	double       sampleCount;
	DWORD        noOfSamples;
	DWORD        sampleSize;
	DWORDLONG    fileSize;
	BYTE         wavHeader[44];
	DWORD        headerPos = 0;
	HANDLE       outputHandle;
	DWORD        bufferSize;
	DWORD        samplesInBuffer;
	DWORD        nextSample = 0;
	LPVOID       buffer = NULL;
	DWORD        bytesWritten;
	
	waveFormat.wFormatTag     = WAVE_FORMAT_PCM;
	waveFormat.nChannels      = 1;
	waveFormat.nSamplesPerSec = 48000;
	waveFormat.wBitsPerSample = 16;
	waveFormat.cbSize         = 0;
	
	if (argc < 2) {
		fprintf(stderr, 
			"SILENCE v" VERSION " - Create silent WAV files\n"
			"(c) 2007 Tamas Kurucsai\n"
			"Licensed under the terms of the GNU General Public License\n"
			"\n"
			"Usage: SILENCE <wav-file> -f -l <length> -c <channels>\n"
			"               -s <sample-rate> -b <bits-per-sample>\n"
			"-f tells that that floating-point samples shall be used.\n"
			"\n"
			"If <wav-file> is not given, the WAV file will be written\n"
			"to the standard output.\n"
			"\n"
			"Defaults for options are: -l 1.0 -c 1 -s 48000 -b 16\n");
		exit(1);
	} else {
		int arg = 0;
		while (argv[++arg]) {
			if (argv[arg][0] != '-') {
				if (outputFile) {
					fprintf(stderr, "Too many parameters\n");
					exit(1);
				} else {
					outputFile = argv[arg];
				}
			} else {
				switch(argv[arg][1]) {
				case 'f':
				case 'F':
					waveFormat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
					if (! bitsPerSampleGiven) {
						waveFormat.wBitsPerSample = 32;
					}
					break;
				case 'l':
				case 'L':
					if (argv[++arg]) {
						length = atof(argv[arg]);
						if (length <= 0.0) {
							fprintf(stderr, "Invalid length given after -l\n");
							exit(1);
						}
					} else {
						fprintf(stderr, "Missing length after -l\n");
						exit(1);
					}
					break;
				case 'c':
				case 'C':
					if (argv[++arg]) {
						int channels;
						channels = atoi(argv[arg]);
						if (channels <= 0 || 65535 < channels) {
							fprintf(stderr, "Invalid no. of channels given after -c\n");
							exit(1);
						}
						waveFormat.nChannels = channels;
					} else {
						fprintf(stderr, "Missing no. of channels after -c\n");
						exit(1);
					}
					break;
				case 's':
				case 'S':
					if (argv[++arg]) {
						int samplesPerSec;
						samplesPerSec = atoi(argv[arg]);
						if (samplesPerSec <= 0) {
							fprintf(stderr, "Invalid sample rate given after -s\n");
							exit(1);
						}
						waveFormat.nSamplesPerSec = samplesPerSec;						
					} else {
						fprintf(stderr, "Missing sample rate after -s\n");
						exit(1);
					}
					break;
				case 'b':
				case 'B':
					if (argv[++arg]) {
						int bitsPerSample;
						bitsPerSample = atoi(argv[arg]);
						if (bitsPerSample <= 0 || 65535 < bitsPerSample) {
							fprintf(stderr, "Invalid bits per sample given after -b\n");
							exit(1);
						}
						waveFormat.wBitsPerSample = bitsPerSample;
						bitsPerSampleGiven = TRUE;
					} else {
						fprintf(stderr, "Missing bits per sample after -b\n");
						exit(1);
					}
					break;
				case 0:
					break;
				default:
					fprintf(stderr, "Invalid option -%c\n", argv[arg][1]);
					exit(1);
				}
			}
		}
	}

	sampleCount = length * waveFormat.nSamplesPerSec;
	if (sampleCount > 1.0 * 0xFFFFFFFF) {
		fprintf(stderr, "Error: WAV file would be too long.\n");
		exit(1);
	}
	noOfSamples = (DWORD) (sampleCount);
	sampleSize = waveFormat.nChannels * ((waveFormat.wBitsPerSample + 7) / 8);
	fileSize = UInt32x32To64(sampleSize, noOfSamples);
	if (fileSize > 0xFFFFFFFF - 36) {
		fprintf(stderr, "Warning: WAV file will be larger than 4 GB.\n");
		fileSize = sampleSize * ((0xFFFFFFFF - 36) / sampleSize);
	}
	waveFormat.nAvgBytesPerSec = sampleSize * waveFormat.nSamplesPerSec;
	waveFormat.nBlockAlign     = sampleSize;
	
	wavHeader[headerPos++] = 'R';
	wavHeader[headerPos++] = 'I';
	wavHeader[headerPos++] = 'F';
	wavHeader[headerPos++] = 'F';
	*(LPDWORD)(wavHeader + headerPos) = (DWORD) fileSize + 36;
	headerPos += 4;
	wavHeader[headerPos++] = 'W';
	wavHeader[headerPos++] = 'A';
	wavHeader[headerPos++] = 'V';
	wavHeader[headerPos++] = 'E';
	wavHeader[headerPos++] = 'f';
	wavHeader[headerPos++] = 'm';
	wavHeader[headerPos++] = 't';
	wavHeader[headerPos++] = ' ';
	*(LPDWORD)(wavHeader + headerPos) = 16;
	headerPos += 4;
	*(LPWORD)(wavHeader + headerPos)  = waveFormat.wFormatTag;
	headerPos += 2;
	*(LPWORD)(wavHeader + headerPos)  = waveFormat.nChannels;
	headerPos += 2;
	*(LPDWORD)(wavHeader + headerPos) = waveFormat.nSamplesPerSec;
	headerPos += 4;
	*(LPDWORD)(wavHeader + headerPos) = waveFormat.nAvgBytesPerSec;
	headerPos += 4;
	*(LPWORD)(wavHeader + headerPos)  = waveFormat.nBlockAlign;
	headerPos += 2;
	*(LPWORD)(wavHeader + headerPos)  = waveFormat.wBitsPerSample;
	headerPos += 2;
	wavHeader[headerPos++] = 'd';
	wavHeader[headerPos++] = 'a';
	wavHeader[headerPos++] = 't';
	wavHeader[headerPos++] = 'a';
	*(LPDWORD)(wavHeader + headerPos) = (DWORD) fileSize;
	headerPos += 4;

	if (! outputFile) {
		outputHandle = GetStdHandle(STD_OUTPUT_HANDLE);
		fprintf(stderr, "Writing WAV file...\n");
	} else {
		outputHandle = CreateFile(outputFile, GENERIC_WRITE, FILE_SHARE_READ, 
			NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
		if (outputHandle == INVALID_HANDLE_VALUE) {
			fprintf(stderr, "Error: Could not create file \"%s\".\n", outputFile);
			exit(1);
		}
		fprintf(stderr, "Writing WAV file \"%s\"...\n", outputFile);
	}
		
	if (! WriteFile(outputHandle, wavHeader, headerPos, & bytesWritten, NULL) || (bytesWritten != headerPos)) {
		if (! outputFile) {
			fprintf(stderr, "Error: Could not write to the standard output.\n");
		} else {
			fprintf(stderr, "Error: Could not write file \"%s\".\n", outputFile);
			CloseHandle(outputHandle);
		}
		exit(1);
	}
	
	bufferSize = 1024 * 1024;
	samplesInBuffer = bufferSize / sampleSize;
	if (! samplesInBuffer) {
		samplesInBuffer = 1;
		bufferSize = sampleSize * samplesInBuffer;
	}
	buffer = (LPVOID) LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, bufferSize);
	if (! buffer) {
		fprintf(stderr, "Error: Out of memory.\n");
		if (outputFile) CloseHandle(outputHandle);
		exit(1);
	}

	while (nextSample < noOfSamples) {
		if (noOfSamples - nextSample < samplesInBuffer) {
			bufferSize = (noOfSamples - nextSample) * sampleSize;
		}
		if (! WriteFile(outputHandle, buffer, bufferSize, & bytesWritten, NULL) || (bytesWritten != bufferSize)) {
			LocalFree((HLOCAL) buffer);
			if (! outputFile) {
				fprintf(stderr, "Error: Could not write to the standard output.\n");
			} else {
				fprintf(stderr, "Error: Could not write file \"%s\".\n", outputFile);
				CloseHandle(outputHandle);
			}
			exit(1);
		}
		nextSample += samplesInBuffer;
	}
	
	LocalFree((HLOCAL) buffer);
	
	if (outputFile && ! CloseHandle(outputHandle)) {
		fprintf(stderr, "Error: Could not write file \"%s\".\n", outputFile);
		exit(1);
	}
	
	fprintf(stderr, "WAV file written successfully.\n");
	return 0;
}


