//	VirtualDub - Video processing and capture application
//	Copyright (C) 1998-2001 Avery Lee
//
//	This program is free software; you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation; either version 2 of the License, or
//	(at your option) any later version.
//
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with this program; if not, write to the Free Software
//	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

#include "stdafx.h"

#include <windows.h>

#include "gui.h"
#include "crash.h"

#include <vd2/system/error.h>
#include <vd2/system/strutil.h>
#include <vd2/system/fraction.h>
#include "AudioFilterSystem.h"
#include "AudioSource.h"
#include "af_sink.h"

#include "audio.h"

AudioFormatConverter AudioPickConverter(WAVEFORMATEX *src, BOOL to_16bit, BOOL to_stereo);

//////////////// no change converters /////////////////////////////////////

static void convert_audio_nochange8(void *dest, void *src, long count) {
	memcpy(dest, src, count);
}

static void convert_audio_nochange16(void *dest, void *src, long count) {
	memcpy(dest, src, count*2);
}

static void convert_audio_nochange32(void *dest, void *src, long count) {
	memcpy(dest, src, count*4);
}

//////////////// regular converters /////////////////////////////////////

static void convert_audio_mono8_to_mono16(void *dest, void *src, long count) {
	unsigned char *s = (unsigned char *)src;
	signed short *d = (signed short *)dest;

	do {
		*d++ = (signed short)((unsigned long)(*s++-0x80)<<8);
	} while(--count);
}

static void convert_audio_mono8_to_stereo8(void *dest, void *src, long count) {
	unsigned char c,*s = (unsigned char *)src;
	unsigned char *d = (unsigned char *)dest;

	do {
		c = *s++;
		*d++ = c;
		*d++ = c;
	} while(--count);
}

static void convert_audio_mono8_to_stereo16(void *dest, void *src, long count) {
	unsigned char *s = (unsigned char *)src;
	unsigned long c, *d = (unsigned long *)dest;

	do {
		c = ((*s++-0x80)&0xff) << 8;
		*d++ = c | (c<<16);
	} while(--count);
}

static void convert_audio_mono16_to_mono8(void *dest, void *src, long count) {
	signed short *s = (signed short *)src;
	unsigned char *d = (unsigned char *)dest;

	do {
		*d++ = (unsigned char)((((unsigned long)*s++)+0x8000)>>8);
	} while(--count);
}

static void convert_audio_mono16_to_stereo8(void *dest, void *src, long count) {
	signed short *s = (signed short *)src;
	unsigned char c, *d = (unsigned char *)dest;

	do {
		c = (unsigned char)((((unsigned long)*s++)+0x8000)>>8);
		*d++ = c;
		*d++ = c;
	} while(--count);
}

static void convert_audio_mono16_to_stereo16(void *dest, void *src, long count) {
	signed short *s = (signed short *)src;
	unsigned long *d = (unsigned long *)dest, c;

	do {
		c = 0xffff & *s++;
		*d++ = (c | (c<<16));
	} while(--count);
}

static void convert_audio_stereo8_to_mono8(void *dest, void *src, long count) {
	unsigned short *s = (unsigned short *)src;
	unsigned char *d = (unsigned char *)dest;
	unsigned long c;

	do {
		c = *s++;
		*d++ = (unsigned char)(((c&0xff) + (c>>8))/2);
	} while(--count);
}

static void convert_audio_stereo8_to_mono16(void *dest, void *src, long count) {
	unsigned short *s = (unsigned short *)src;
	signed short *d = (signed short *)dest;
	unsigned long c;

	do {
		c = *s++;
		*d++ = (signed short)((((c&0xff) + (c>>8))<<7)-0x8000);
	} while(--count);
}

static void convert_audio_stereo8_to_stereo16(void *dest, void *src, long count) {
	unsigned short c,*s = (unsigned short *)src;
	unsigned long *d = (unsigned long *)dest;

	do {
		c = *s++;
		*d++ = ((unsigned long)((c-0x80)&0xff)<<8) | ((unsigned long)((c&0xff00)-0x8000)<<16);
	} while(--count);
}

static void convert_audio_stereo16_to_mono8(void *dest, void *src, long count) {
	unsigned long c, *s = (unsigned long *)src;
	unsigned char *d = (unsigned char *)dest;

	do {
		c = *s++;
		*d++ = (unsigned char)(((((c&0xffff)+0xffff8000)^0xffff8000) + ((signed long)c>>16) + 0x10000)>>9);
	} while(--count);
}

static void convert_audio_stereo16_to_mono16(void *dest, void *src, long count) {
	unsigned long c, *s = (unsigned long *)src;
	signed short *d = (signed short *)dest;

	do {
		c = *s++;
		*d++ = (signed short)(((((c&0xffff)+0xffff8000)^0xffff8000) + ((signed long)c>>16))/2);
	} while(--count);
}

static void convert_audio_stereo16_to_stereo8(void *dest, void *src, long count) {
	unsigned long c,*s = (unsigned long *)src;
	unsigned char *d = (unsigned char *)dest;

	do {
		c = *s++;
		*d++ = (unsigned char)((((unsigned long)(c & 0xffff))+0x8000)>>8);
		*d++ = (unsigned char)((((unsigned long)(c>>16))+0x8000)>>8);
	} while(--count);
}

static void convert_audio_dual8_to_mono8(void *dest, void *src, long count) {
	const unsigned char *s = (unsigned char *)src;
	unsigned char *d = (unsigned char *)dest;

	do {
		*d++ = *s;
		s+=2;
	} while(--count);
}

static void convert_audio_dual8_to_mono16(void *dest, void *src, long count) {
	unsigned char *s = (unsigned char *)src;
	signed short *d = (signed short *)dest;

	do {
		*d++ = (signed short)((unsigned long)(*s-0x80)<<8);
		s += 2;
	} while(--count);
}

static void convert_audio_dual16_to_mono8(void *dest, void *src, long count) {
	signed short *s = (signed short *)src;
	unsigned char *d = (unsigned char *)dest;

	do {
		*d++ = (unsigned char)((((unsigned long)*s)+0x8000)>>8);
		s += 2;
	} while(--count);
}

static void convert_audio_dual16_to_mono16(void *dest, void *src, long count) {
	const signed short *s = (signed short *)src;
	signed short *d = (signed short *)dest;

	do {
		*d++ = *s;
		s+=2;
	} while(--count);
}

////////////////////////////////////////////

static const AudioFormatConverter acv[]={
	convert_audio_nochange8,
	convert_audio_mono8_to_mono16,
	convert_audio_mono8_to_stereo8,
	convert_audio_mono8_to_stereo16,
	convert_audio_mono16_to_mono8,
	convert_audio_nochange16,
	convert_audio_mono16_to_stereo8,
	convert_audio_mono16_to_stereo16,
	convert_audio_stereo8_to_mono8,
	convert_audio_stereo8_to_mono16,
	convert_audio_nochange16,
	convert_audio_stereo8_to_stereo16,
	convert_audio_stereo16_to_mono8,
	convert_audio_stereo16_to_mono16,
	convert_audio_stereo16_to_stereo8,
	convert_audio_nochange32,
};

static const AudioFormatConverter acv2[]={
	convert_audio_nochange8,
	convert_audio_mono8_to_mono16,
	convert_audio_mono16_to_mono8,
	convert_audio_nochange16,
	convert_audio_dual8_to_mono8,
	convert_audio_dual8_to_mono16,
	convert_audio_dual16_to_mono8,
	convert_audio_dual16_to_mono16,
};

AudioFormatConverter AudioPickConverter(WAVEFORMATEX *src, BOOL to_16bit, BOOL to_stereo) {
	return acv[
			  (src->nChannels>1 ? 8 : 0)
			 +(src->wBitsPerSample>8 ? 4 : 0)
			 +(to_stereo ? 2 : 0)
			 +(to_16bit ? 1 : 0)
		];
}

AudioFormatConverter AudioPickConverterSingleChannel(WAVEFORMATEX *src, bool to_16bit) {
	return acv2[
			  (src->nChannels>1 ? 4 : 0)
			 +(src->wBitsPerSample>8 ? 2 : 0)
			 +(to_16bit ? 1 : 0)
		];
}

///////////////////////////////////

AudioStream::AudioStream() {
	format = NULL;
	format_len = 0;
	samples_read = 0;
	stream_limit = 0x7FFFFFFF;
}

AudioStream::~AudioStream() {
	freemem(format);
}

WAVEFORMATEX *AudioStream::AllocFormat(long len) {
	if (format) { freemem(format); format = 0; }

	if (!(format = (WAVEFORMATEX *)allocmem(len)))
		throw MyError("AudioStream: Out of memory");

    format_len = len;

	return format;
}

WAVEFORMATEX *AudioStream::GetFormat() {
	return format;
}

long AudioStream::GetFormatLen() {
	return format_len;
}

long AudioStream::GetSampleCount() {
	return samples_read;
}

long AudioStream::GetLength() {
	return stream_limit < stream_len ? stream_limit : stream_len;
}

long AudioStream::Read(void *buffer, long max_samples, long *lplBytes) {
	long actual;

	if (max_samples <= 0) {
		*lplBytes = 0;
		return 0;
	}

	if (samples_read >= stream_limit) {
		*lplBytes = 0;
		return 0;
	}

    if (samples_read + max_samples > stream_limit)
		max_samples = stream_limit - samples_read;

	actual = _Read(buffer, max_samples, lplBytes);

	_ASSERT(actual >= 0 && actual <= max_samples);

	samples_read += actual;

	return actual;
}

bool AudioStream::Skip(long samples) {
	return false;
}

void AudioStream::SetLimit(long limit) {
	_RPT1(0,"AudioStream: limit set to %ld\n",limit);
	stream_limit = limit;
}

void AudioStream::SetSource(AudioStream *src) {
	source = src;
	stream_len = src->GetLength();
}

BOOL AudioStream::isEnd() {
	return samples_read >= stream_limit || _isEnd();
}

long AudioStream::_Read(void *buffer, long max_samples, long *lplBytes) {
	*lplBytes = 0;

	return 0;
}

BOOL AudioStream::_isEnd() {
	return FALSE;
}

////////////////////

AudioStreamSource::AudioStreamSource(AudioSource *src, long first_samp, long max_samples, BOOL allow_decompression) : AudioStream() {
	WAVEFORMATEX *iFormat = src->getWaveFormat();
	WAVEFORMATEX *oFormat;
	MMRESULT res;

	hACStream = NULL;
	inputBuffer = outputBuffer = NULL;
	fZeroRead = false;
	fStart = true;
	pwfexTempInput = NULL;
	lPreskip = 0;

	if (max_samples < 0)
		max_samples = 0;

	if (iFormat->wFormatTag != WAVE_FORMAT_PCM && allow_decompression) {
		DWORD dwOutputBufferSize;
		DWORD dwOutputFormatSize;

		if (!AllocFormat(sizeof(PCMWAVEFORMAT)))
			throw MyMemoryError();

		if (acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FORMAT, (LPVOID)&dwOutputFormatSize))
			throw MyError("Couldn't get ACM's max format size");

		oFormat = (WAVEFORMATEX *)allocmem(dwOutputFormatSize); //AllocFormat(dwOutputFormatSize);
		if (!oFormat) throw MyMemoryError();
		oFormat->wFormatTag			= WAVE_FORMAT_PCM;

		if (acmFormatSuggest(NULL, iFormat, oFormat, dwOutputFormatSize, ACM_FORMATSUGGESTF_WFORMATTAG)) {
			freemem(oFormat);
			throw MyError("No audio decompressor could be found to decompress the source audio format.");
		}

		if (oFormat->wBitsPerSample!=8 && oFormat->wBitsPerSample!=16)
				oFormat->wBitsPerSample=16;

		if (oFormat->nChannels!=1 && oFormat->nChannels!=2)
			oFormat->nChannels = 2;

		oFormat->nBlockAlign		= (oFormat->wBitsPerSample/8) * oFormat->nChannels;
		oFormat->nAvgBytesPerSec	= oFormat->nBlockAlign * oFormat->nSamplesPerSec;
		oFormat->cbSize				= 0;

		memcpy(GetFormat(), oFormat, sizeof(PCMWAVEFORMAT));
		freemem(oFormat);
		oFormat = GetFormat();

		memset(&ashBuffer, 0, sizeof ashBuffer);

		res = acmStreamOpen(&hACStream, NULL, iFormat, oFormat, NULL, 0, 0, ACM_STREAMOPENF_NONREALTIME);

		if (res) {
			if (res == ACMERR_NOTPOSSIBLE) {
				throw MyError(
							"Error initializing audio stream decompression:\n"
							"The requested conversion is not possible.\n"
							"\n"
							"Check to make sure you have the required codec%s."
							,
							(iFormat->wFormatTag&~1)==0x160 ? " (Microsoft Audio Codec)" : ""
						);
			} else
				throw MyError("Error initializing audio stream decompression.");
		}

		if (acmStreamSize(hACStream, INPUT_BUFFER_SIZE, &dwOutputBufferSize, ACM_STREAMSIZEF_SOURCE))
			throw MyError("Error initializing audio stream output size.");

		if (!(inputBuffer = allocmem(INPUT_BUFFER_SIZE))
			|| !(outputBuffer = allocmem(dwOutputBufferSize)))

			throw MyMemoryError();

		ashBuffer.cbStruct		= sizeof(ACMSTREAMHEADER);
		ashBuffer.pbSrc			= (LPBYTE)inputBuffer;
		ashBuffer.cbSrcLength	= INPUT_BUFFER_SIZE;
		ashBuffer.pbDst			= (LPBYTE)outputBuffer;
		ashBuffer.cbDstLength	= dwOutputBufferSize;

		if (acmStreamPrepareHeader(hACStream, &ashBuffer, 0))
			throw MyError("Error preparing audio decompression buffers.");

		ashBuffer.cbSrcLength = 0;
		ashBuffer.cbDstLengthUsed = 0;

		// try to get driver name
		mDriverName[0] = 0;

		HACMDRIVERID hDriverID;
		if (!acmDriverID((HACMOBJ)hACStream, &hDriverID, 0)) {
			ACMDRIVERDETAILS add = { sizeof(ACMDRIVERDETAILS) };
			if (!acmDriverDetails(hDriverID, &add, 0)) {
				strncpyz(mDriverName, add.szLongName, sizeof mDriverName);
			}
		}

	} else {

		// FIX: If we have a PCMWAVEFORMAT stream, artificially cut the format size
		//		to sizeof(PCMWAVEFORMAT).  LSX-MPEG Encoder doesn't like large PCM
		//		formats!

		if (iFormat->wFormatTag == WAVE_FORMAT_PCM) {
			oFormat = AllocFormat(sizeof(PCMWAVEFORMAT));
			memcpy(oFormat, iFormat, sizeof(PCMWAVEFORMAT));
		} else {
			oFormat = AllocFormat(src->getFormatLen());
			memcpy(oFormat, iFormat, GetFormatLen());
		}
	}

	aSrc = src;
	stream_len = std::min<long>(max_samples, aSrc->lSampleLast - first_samp);

	if (hACStream) {
		stream_len = MulDiv(stream_len, GetFormat()->nSamplesPerSec * aSrc->getWaveFormat()->nBlockAlign, aSrc->getWaveFormat()->nAvgBytesPerSec);
	}

	cur_samp = first_samp;
	end_samp = first_samp + max_samples;

}

AudioStreamSource::~AudioStreamSource() {
	if (hACStream) {
		if (ashBuffer.fdwStatus & ACMSTREAMHEADER_STATUSF_PREPARED) {
			ashBuffer.cbSrcLength = INPUT_BUFFER_SIZE;
			acmStreamUnprepareHeader(hACStream, &ashBuffer, 0);
		}
		acmStreamClose(hACStream, 0);
	}
	if (inputBuffer)	freemem(inputBuffer);
	if (outputBuffer)	freemem(outputBuffer);
	delete[] pwfexTempInput;
}

long AudioStreamSource::_Read(void *buffer, long max_samples, long *lplBytes) {
	LONG lSamples=0;
	LONG lAddedBytes=0;
	LONG lAddedSamples=0;
	int err;
	MMRESULT res;

	// add filler samples as necessary

	if (cur_samp < 0) {
		long tc = -cur_samp;
		const int nBlockAlign = aSrc->getWaveFormat()->nBlockAlign;

		if (tc > max_samples)
			tc = max_samples;

		if (GetFormat()->nChannels > 1)
			memset(buffer, 0, nBlockAlign*tc);
		else
			memset(buffer, 0x80, nBlockAlign*tc);

		buffer = (char *)buffer + nBlockAlign*tc;

		max_samples -= tc;
		lAddedBytes = tc*nBlockAlign;
		lAddedSamples = tc;
		cur_samp += tc;
	}

	// read actual samples

	if (hACStream) {
		LONG ltActualBytes, ltActualSamples;
		LONG lBytesLeft = max_samples * GetFormat()->nBlockAlign;
		LONG lTotalBytes = lBytesLeft;
		const int nBlockAlign = aSrc->getWaveFormat()->nBlockAlign;

		while(lBytesLeft > 0) {
			// hmm... data still in the output buffer?

			if (ashBuffer.cbDstLengthUsed>0) {
				long tc = std::min<long>(lBytesLeft, ashBuffer.cbDstLengthUsed);

				if (lPreskip) {
					if (tc > lPreskip)
						tc = lPreskip;

					lPreskip -= tc;
				} else {
					memcpy(buffer, outputBufferPtr, tc);
					buffer = (void *)((char *)buffer + tc);
					lBytesLeft -= tc;
				}

				outputBufferPtr += tc;
				ashBuffer.cbDstLengthUsed -= tc;
				continue;
			}

			// fill the input buffer up... if we haven't gotten a zero yet.

			if (ashBuffer.cbSrcLength < INPUT_BUFFER_SIZE && !fZeroRead) {
				LONG lBytes=0;

				do {
					LONG to_read = (INPUT_BUFFER_SIZE - ashBuffer.cbSrcLength)/nBlockAlign;

					if (to_read > end_samp - cur_samp)
						to_read = end_samp - cur_samp;

					err = aSrc->read(cur_samp, to_read, (char *)inputBuffer + ashBuffer.cbSrcLength, INPUT_BUFFER_SIZE - ashBuffer.cbSrcLength, &ltActualBytes, &ltActualSamples);

					if (err != AVIERR_OK && err != AVIERR_BUFFERTOOSMALL) {
						if (err == AVIERR_FILEREAD)
							throw MyError("Audio samples %lu-%lu could not be read in the source.  The file may be corrupted.", cur_samp, cur_samp+to_read-1);
						else
							throw MyAVIError("AudioStreamSource", err);
					}

					cur_samp += ltActualSamples;

//					_RPT1(0,"Read to %ld\n", cur_samp);

					ashBuffer.cbSrcLength += ltActualBytes;

					lBytes += ltActualBytes;

				} while(err != AVIERR_BUFFERTOOSMALL && ashBuffer.cbSrcLength < INPUT_BUFFER_SIZE && ltActualBytes && cur_samp < end_samp);

				if (!lBytes) fZeroRead = true;
			}

			// ask ACM to convert for us

			ashBuffer.cbSrcLengthUsed = 0;
			ashBuffer.cbDstLengthUsed = 0;

	VDCHECKPOINT;
			vdprotected1("decompressing audio using \"%s\"", const char *, mDriverName) {
				if (ashBuffer.cbSrcLength)
					if (res = acmStreamConvert(hACStream, &ashBuffer, (fStart ? ACM_STREAMCONVERTF_START : 0) | ACM_STREAMCONVERTF_BLOCKALIGN))
						throw MyError("ACM reported error on audio decompress (%lx)", res);
			}
	VDCHECKPOINT;

			fStart = false;

			_RPT2(0,"Converted %ld bytes to %ld\n", ashBuffer.cbSrcLengthUsed, ashBuffer.cbDstLengthUsed);

			if (!ashBuffer.cbSrcLengthUsed && fZeroRead)
				break;

			// Check for a jam condition.

			if (!ashBuffer.cbSrcLengthUsed && !ashBuffer.cbDstLengthUsed) {
				// The codec didn't do anything.  Uh oh.

				if (lBytesLeft > 0) {
					const WAVEFORMATEX& wfsrc = *aSrc->getWaveFormat();
					const WAVEFORMATEX& wfdst = *GetFormat();

					throw MyError("The operation cannot continue as the source audio codec has jammed and is not decompressing data.\n"
									"Codec state for driver \"%s\":\n"
									"    source buffer size: %d bytes\n"
									"    destination buffer size: %d bytes\n"
									"    source format: tag %04x, %dHz/%dch/%d-bit, %d bytes/sec\n"
									"    destination format: tag %04x, %dHz/%dch/%d-bit, %d bytes/sec\n"
									, mDriverName
									, ashBuffer.cbSrcLength
									, ashBuffer.cbDstLength
									, wfsrc.wFormatTag, wfsrc.nSamplesPerSec, wfsrc.nChannels, wfsrc.wBitsPerSample, wfsrc.nAvgBytesPerSec
									, wfdst.wFormatTag, wfdst.nSamplesPerSec, wfdst.nChannels, wfdst.wBitsPerSample, wfdst.nAvgBytesPerSec);
				}
			}

			// if ACM didn't use all the source data, copy the remainder down

			if (ashBuffer.cbSrcLengthUsed < ashBuffer.cbSrcLength) {
				long left = ashBuffer.cbSrcLength - ashBuffer.cbSrcLengthUsed;

				memmove(inputBuffer, (char *)inputBuffer + ashBuffer.cbSrcLengthUsed, left);

				ashBuffer.cbSrcLength = left;
			} else
				ashBuffer.cbSrcLength = 0;

			outputBufferPtr = (char *)outputBuffer;
		};

		*lplBytes = (lTotalBytes - lBytesLeft) + lAddedBytes;

		return *lplBytes / GetFormat()->nBlockAlign + lAddedSamples;
	} else {
		if (max_samples > end_samp - cur_samp)
			max_samples = end_samp - cur_samp;

		if (max_samples > 0) {
			if (AVIERR_OK != (err = aSrc->read(cur_samp, max_samples, buffer, 0x7FFFFFFFL, lplBytes, &lSamples))) {
				if (err == AVIERR_FILEREAD)
					throw MyError("Audio samples %lu-%lu could not be read in the source.  The file may be corrupted.", cur_samp, cur_samp+max_samples-1);
				else
					throw MyAVIError("AudioStreamSource", err);
			}

			if (!lSamples) fZeroRead = true;
		} else
			lSamples = *lplBytes = 0;

		*lplBytes += lAddedBytes;

		cur_samp += lSamples;

		return lSamples + lAddedSamples;
	}
}

bool AudioStreamSource::Skip(long samples) {

	// nBlockAlign = bytes per block.
	//
	// nAvgBytesPerSec / nBlockAlign = blocks per second.
	// nSamplesPerSec * nBlockAlign / nAvgBytesPerSec = samples per block.

	if (hACStream) {
		const WAVEFORMATEX *pwfex = aSrc->getWaveFormat();

		if (samples < MulDiv(4*pwfex->nBlockAlign, pwfex->nSamplesPerSec, pwfex->nAvgBytesPerSec)) {
			lPreskip += samples*GetFormat()->nBlockAlign;
			return true;
		}

		// Flush input and output buffers.

		ashBuffer.cbSrcLength = 0;
		ashBuffer.cbDstLengthUsed = 0;

		// Trigger a reseek.

		long new_pos = ((samples_read + samples) * (__int64)pwfex->nAvgBytesPerSec) / ((__int64)pwfex->nBlockAlign*pwfex->nSamplesPerSec);

		if (new_pos > cur_samp)
			cur_samp = new_pos;

		fStart = true;

		// Skip fractional samples.

		long samp_start = (new_pos * (__int64)pwfex->nSamplesPerSec*pwfex->nBlockAlign) / pwfex->nAvgBytesPerSec;

		lPreskip = ((samples_read + samples) - samp_start)*GetFormat()->nBlockAlign;

		samples_read = samp_start;

		return true;

	} else {
		cur_samp += samples;
		samples_read += samples;

		return true;
	}
}

BOOL AudioStreamSource::_isEnd() {
	return (cur_samp >= end_samp || fZeroRead) && (!hACStream || !ashBuffer.cbDstLengthUsed);
}



///////////////////////////////////////////////////////////////////////////
//
//		AudioStreamConverter
//
//		This audio filter handles changes in format between 8/16-bit
//		and mono/stereo.
//
///////////////////////////////////////////////////////////////////////////



AudioStreamConverter::AudioStreamConverter(AudioStream *src, bool to_16bit, bool to_stereo_or_right, bool single_only) {
	WAVEFORMATEX *iFormat = src->GetFormat();
	WAVEFORMATEX *oFormat;
	bool to_stereo = single_only ? false : to_stereo_or_right;

	memcpy(oFormat = AllocFormat(src->GetFormatLen()), iFormat, src->GetFormatLen());

	oFormat->nChannels = to_stereo ? 2 : 1;
	oFormat->wBitsPerSample = to_16bit ? 16 : 8;

	bytesPerInputSample = (iFormat->nChannels>1 ? 2 : 1)
						* (iFormat->wBitsPerSample>8 ? 2 : 1);

	bytesPerOutputSample = (to_stereo ? 2 : 1)
						 * (to_16bit ? 2 : 1);

	offset = 0;

	if (single_only) {
		convRout = AudioPickConverterSingleChannel(iFormat, to_16bit);

		if (to_stereo_or_right && iFormat->nChannels>1) {
			offset = 1;

			if (iFormat->wBitsPerSample>8)
				offset = 2;
		}
	} else
		convRout = AudioPickConverter(iFormat, to_16bit, to_stereo);
	SetSource(src);

	oFormat->nAvgBytesPerSec = oFormat->nSamplesPerSec * bytesPerOutputSample;
	oFormat->nBlockAlign = bytesPerOutputSample;


	if (!(cbuffer = allocmem(bytesPerInputSample * BUFFER_SIZE)))
		throw MyError("AudioStreamConverter: out of memory");
}

AudioStreamConverter::~AudioStreamConverter() {
	freemem(cbuffer);
}

long AudioStreamConverter::_Read(void *buffer, long samples, long *lplBytes) {
	long lActualSamples=0;

	while(samples>0) {
		long srcSamples;
		long lBytes;

		// figure out how many source samples we need

		srcSamples = samples;

		if (srcSamples > BUFFER_SIZE) srcSamples = BUFFER_SIZE;

		srcSamples = source->Read(cbuffer, srcSamples, &lBytes);

		if (!srcSamples) break;

		convRout(buffer, (char *)cbuffer + offset, srcSamples);

		buffer = (void *)((char *)buffer + bytesPerOutputSample * srcSamples);
		lActualSamples += srcSamples;
		samples -= srcSamples;

	}

	*lplBytes = lActualSamples * bytesPerOutputSample;

	return lActualSamples;
}

BOOL AudioStreamConverter::_isEnd() {
	return source->isEnd();
}

bool AudioStreamConverter::Skip(long samples) {
	return source->Skip(samples);
}



///////////////////////////////////////////////////////////////////////////
//
//		AudioStreamResampler
//
//		This audio filter handles changes in sampling rate.
//
///////////////////////////////////////////////////////////////////////////

static long audio_pointsample_8(void *dst, void *src, long accum, long samp_frac, long cnt) {
	unsigned char *d = (unsigned char *)dst;
	unsigned char *s = (unsigned char *)src;

	do {
		*d++ = s[accum>>19];
		accum += samp_frac;
	} while(--cnt);

	return accum;
}

static long audio_pointsample_16(void *dst, void *src, long accum, long samp_frac, long cnt) {
	unsigned short *d = (unsigned short *)dst;
	unsigned short *s = (unsigned short *)src;

	do {
		*d++ = s[accum>>19];
		accum += samp_frac;
	} while(--cnt);

	return accum;
}

static long audio_pointsample_32(void *dst, void *src, long accum, long samp_frac, long cnt) {
	unsigned long *d = (unsigned long *)dst;
	unsigned long *s = (unsigned long *)src;

	do {
		*d++ = s[accum>>19];
		accum += samp_frac;
	} while(--cnt);

	return accum;
}

static long audio_downsample_mono8(void *dst, void *src, long *filter_bank, int filter_width, long accum, long samp_frac, long cnt) {
	unsigned char *d = (unsigned char *)dst;
	unsigned char *s = (unsigned char *)src;

	do {
		long sum = 0;
		int w;
		long *fb_ptr;
		unsigned char *s_ptr;

		w = filter_width;
		fb_ptr = filter_bank + filter_width * ((accum>>11)&0xff);
		s_ptr = s + (accum>>19);
		do {
			sum += *fb_ptr++ * (int)*s_ptr++;
		} while(--w);

		if (sum < 0)
			*d++ = 0;
		else if (sum > 0x3fffff)
			*d++ = 0xff;
		else
			*d++ = ((sum + 0x2000)>>14);

		accum += samp_frac;
	} while(--cnt);

	return accum;
}

static long audio_downsample_mono16(void *dst, void *src, long *filter_bank, int filter_width, long accum, long samp_frac, long cnt) {
	signed short *d = (signed short *)dst;
	signed short *s = (signed short *)src;

	do {
		long sum = 0;
		int w;
		long *fb_ptr;
		signed short *s_ptr;

		w = filter_width;
		fb_ptr = filter_bank + filter_width * ((accum>>11)&0xff);
		s_ptr = s + (accum>>19);
		do {
			sum += *fb_ptr++ * (int)*s_ptr++;
		} while(--w);

		if (sum < -0x20000000)
			*d++ = -0x8000;
		else if (sum > 0x1fffffff)
			*d++ = 0x7fff;
		else
			*d++ = ((sum + 0x2000)>>14);

		accum += samp_frac;
	} while(--cnt);

	return accum;
}

static long audio_downsample_stereo8(void *dst, void *src, long *filter_bank, int filter_width, long accum, long samp_frac, long cnt) {
	unsigned char *d = (unsigned char *)dst;
	unsigned char *s = (unsigned char *)src;

	do {
		long sum_l = 0, sum_r = 0;
		int w;
		long *fb_ptr;
		unsigned char *s_ptr;

		w = filter_width;
		fb_ptr = filter_bank + filter_width * ((accum>>11)&0xff);
		s_ptr = s + (accum>>19)*2;
		do {
			long f = *fb_ptr++;

			sum_l += f * (int)*s_ptr++;
			sum_r += f * (int)*s_ptr++;
		} while(--w);

		if (sum_l < 0)
			*d++ = 0;
		else if (sum_l > 0x3fffff)
			*d++ = 0xff;
		else
			*d++ = ((sum_l + 0x2000)>>14);

		if (sum_r < 0)
			*d++ = 0;
		else if (sum_r > 0x3fffff)
			*d++ = 0xff;
		else
			*d++ = ((sum_r + 0x2000)>>14);

		accum += samp_frac;
	} while(--cnt);

	return accum;
}

static long audio_downsample_stereo16(void *dst, void *src, long *filter_bank, int filter_width, long accum, long samp_frac, long cnt) {
	signed short *d = (signed short *)dst;
	signed short *s = (signed short *)src;

	do {
		long sum_l = 0, sum_r = 0;
		int w;
		long *fb_ptr;
		signed short *s_ptr;

		w = filter_width;
		fb_ptr = filter_bank + filter_width * ((accum>>11)&0xff);
		s_ptr = s + (accum>>19)*2;
		do {
			long f = *fb_ptr++;

			sum_l += f * (int)*s_ptr++;
			sum_r += f * (int)*s_ptr++;
		} while(--w);

		if (sum_l < -0x20000000)
			*d++ = -0x8000;
		else if (sum_l > 0x1fffffff)
			*d++ = 0x7fff;
		else
			*d++ = ((sum_l + 0x2000)>>14);

		if (sum_r < -0x20000000)
			*d++ = -0x8000;
		else if (sum_r > 0x1fffffff)
			*d++ = 0x7fff;
		else
			*d++ = ((sum_r + 0x2000)>>14);

		accum += samp_frac;
	} while(--cnt);

	return accum;
}

static long audio_upsample_mono8(void *dst, void *src, long accum, long samp_frac, long cnt) {
	unsigned char *d = (unsigned char *)dst;
	unsigned char *s = (unsigned char *)src;

	do {
		unsigned char *s_ptr = s + (accum>>19);
		long frac = (accum>>3) & 0xffff;

		*d++ = ((int)s_ptr[0] * (0x10000 - frac) + (int)s_ptr[1] * frac) >> 16;
		accum += samp_frac;
	} while(--cnt);

	return accum;
}

static long audio_upsample_mono16(void *dst, void *src, long accum, long samp_frac, long cnt) {
	signed short *d = (signed short *)dst;
	signed short *s = (signed short *)src;

	do {
		signed short *s_ptr = s + (accum>>19);
		long frac = (accum>>3) & 0xffff;

		*d++ = ((int)s_ptr[0] * (0x10000 - frac) + (int)s_ptr[1] * frac) >> 16;
		accum += samp_frac;
	} while(--cnt);

	return accum;
}

static long audio_upsample_stereo8(void *dst, void *src, long accum, long samp_frac, long cnt) {
	unsigned char *d = (unsigned char *)dst;
	unsigned char *s = (unsigned char *)src;

	do {
		unsigned char *s_ptr = s + (accum>>19)*2;
		long frac = (accum>>3) & 0xffff;

		*d++ = ((int)s_ptr[0] * (0x10000 - frac) + (int)s_ptr[2] * frac) >> 16;
		*d++ = ((int)s_ptr[1] * (0x10000 - frac) + (int)s_ptr[3] * frac) >> 16;
		accum += samp_frac;
	} while(--cnt);

	return accum;
}

static long audio_upsample_stereo16(void *dst, void *src, long accum, long samp_frac, long cnt) {
	signed short *d = (signed short *)dst;
	signed short *s = (signed short *)src;

	do {
		signed short *s_ptr = s + (accum>>19)*2;
		long frac = (accum>>3) & 0xffff;

		*d++ = ((int)s_ptr[0] * (0x10000 - frac) + (int)s_ptr[2] * frac) >> 16;
		*d++ = ((int)s_ptr[1] * (0x10000 - frac) + (int)s_ptr[3] * frac) >> 16;
		accum += samp_frac;
	} while(--cnt);

	return accum;
}

static int permute_index(int a, int b) {
	return (b-(a>>8)-1) + (a&255)*b;
}

static void make_downsample_filter(long *filter_bank, int filter_width, long samp_frac) {
	int i, j, v;
	double filt_max;
	double filtwidth_frac;

	filtwidth_frac = samp_frac/2048.0;

	filter_bank[filter_width-1] = 0;

	filt_max = (16384.0 * 524288.0) / samp_frac;

	for(i=0; i<128*filter_width; i++) {
		int y = 0;
		double d = i / filtwidth_frac;

		if (d<1.0)
			y = VDRoundToInt(filt_max*(1.0 - d));

		filter_bank[permute_index(128*filter_width + i, filter_width)]
			= filter_bank[permute_index(128*filter_width - i, filter_width)]
			= y;
	}

	// Normalize the filter to correct for integer roundoff errors

	for(i=0; i<256*filter_width; i+=filter_width) {
		v=0;
		for(j=0; j<filter_width; j++)
			v += filter_bank[i+j];

//		_RPT2(0,"error[%02x] = %04x\n", i/filter_width, 0x4000 - v);

		v = (0x4000 - v)/filter_width;
		for(j=0; j<filter_width; j++)
			filter_bank[i+j] += v;
	}
}

AudioStreamResampler::AudioStreamResampler(AudioStream *src, long new_rate, bool integral_conversion, bool hi_quality) : AudioStream() {
	WAVEFORMATEX *iFormat = src->GetFormat();
	WAVEFORMATEX *oFormat;

	memcpy(oFormat = AllocFormat(src->GetFormatLen()), iFormat, src->GetFormatLen());

	if (oFormat->nChannels>1)
		if (oFormat->wBitsPerSample>8) {
			ptsampleRout = audio_pointsample_32;
			upsampleRout = audio_upsample_stereo16;
			dnsampleRout = audio_downsample_stereo16;
		} else {
			ptsampleRout = audio_pointsample_16;
			upsampleRout = audio_upsample_stereo8;
			dnsampleRout = audio_downsample_stereo8;
		}
	else
		if (oFormat->wBitsPerSample>8) {
			ptsampleRout = audio_pointsample_16;
			upsampleRout = audio_upsample_mono16;
			dnsampleRout = audio_downsample_mono16;
		} else {
			ptsampleRout = audio_pointsample_8;
			upsampleRout = audio_upsample_mono8;
			dnsampleRout = audio_downsample_mono8;
		}

	SetSource(src);

	bytesPerSample = (iFormat->nChannels>1 ? 2 : 1)
						* (iFormat->wBitsPerSample>8 ? 2 : 1);

	_RPT2(0,"AudioStreamResampler: converting from %ldHz to %ldHz\n", iFormat->nSamplesPerSec, new_rate);

	if (integral_conversion)
		if (new_rate > iFormat->nSamplesPerSec)
//			samp_frac = MulDiv(0x10000, new_rate + iFormat->nSamplesPerSec/2, iFormat->nSamplesPerSec);
			samp_frac = 0x80000 / ((new_rate + iFormat->nSamplesPerSec/2) / iFormat->nSamplesPerSec); 
		else
			samp_frac = 0x80000 * ((iFormat->nSamplesPerSec + new_rate/2) / new_rate);
	else
		samp_frac = MulDiv(iFormat->nSamplesPerSec, 0x80000L, new_rate);

	stream_len = MulDiv(stream_len, 0x80000L, samp_frac);

	oFormat->nSamplesPerSec = MulDiv(iFormat->nSamplesPerSec, 0x80000L, samp_frac);
	oFormat->nAvgBytesPerSec = oFormat->nSamplesPerSec * bytesPerSample;
	oFormat->nBlockAlign = bytesPerSample;

	holdover = 0;
	filter_bank = NULL;
	filter_width = 1;
	accum=0;
	fHighQuality = hi_quality;

	if (!(cbuffer = allocmem(bytesPerSample * BUFFER_SIZE)))
		throw MyMemoryError();

	// Initialize the buffer.

	if (oFormat->wBitsPerSample>8)
		memset(cbuffer, 0x00, bytesPerSample * BUFFER_SIZE);
	else
		memset(cbuffer, 0x80, bytesPerSample * BUFFER_SIZE);

	// If this is a high-quality downsample, allocate memory for the filter bank

	if (hi_quality) {
		if (samp_frac>0x80000) {

			// HQ downsample: allocate filter bank

			filter_width = ((samp_frac + 0x7ffff)>>19)<<1;

			if (!(filter_bank = new long[filter_width * 256])) {
				freemem(cbuffer);
				throw MyMemoryError();
			}

			make_downsample_filter(filter_bank, filter_width, samp_frac);

			// Clear lower samples

			if (oFormat->wBitsPerSample>8)
				memset(cbuffer, 0, bytesPerSample*filter_width);
			else
				memset(cbuffer, 0x80, bytesPerSample*filter_width);

			holdover = filter_width/2;
		}
	}
}

AudioStreamResampler::~AudioStreamResampler() {
	freemem(cbuffer);
	delete filter_bank;
}

long AudioStreamResampler::_Read(void *buffer, long samples, long *lplBytes) {

	if (samp_frac == 0x80000)
		return source->Read(buffer, samples, lplBytes);

	if (samp_frac < 0x80000)
		return Upsample(buffer, samples, lplBytes);
	else
		return Downsample(buffer, samples, lplBytes);
}


long AudioStreamResampler::Upsample(void *buffer, long samples, long *lplBytes) {
	long lActualSamples=0;

	// Upsampling: producing more output samples than input
	//
	// There are two issues we need to watch here:
	//
	//	o  An input sample can be read more than once.  In particular, even
	//	   when point sampling, we may need the last input sample again.
	//
	//	o  When interpolating (HQ), we need one additional sample.

	while(samples>0) {
		long srcSamples, dstSamples;
		long lBytes;
		int holdover = 0;

		// A negative accum value indicates that we need to reprocess a sample.
		// The last iteration should have left it at the bottom of the buffer
		// for us.  In interpolation mode, we'll always have at least a 1
		// sample overlap.

		if (accum<0) {
			holdover = 1;
			accum += 0x80000;
		}

		if (fHighQuality)
			++holdover;

		// figure out how many source samples we need

		srcSamples = (long)(((__int64)samp_frac*(samples-1) + accum) >> 19) + 1 - holdover;

		if (fHighQuality)
			++srcSamples;

		if (srcSamples > BUFFER_SIZE-holdover) srcSamples = BUFFER_SIZE-holdover;

		srcSamples = source->Read((char *)cbuffer + holdover * bytesPerSample, srcSamples, &lBytes);

		if (!srcSamples) break;

		srcSamples += holdover;

		// figure out how many destination samples we'll get out of what we read

		if (fHighQuality)
			dstSamples = ((srcSamples<<19) - accum - 0x80001)/samp_frac + 1;
		else
			dstSamples = ((srcSamples<<19) - accum - 1)/samp_frac + 1;

		if (dstSamples > samples)
			dstSamples = samples;

		if (dstSamples>=1) {

			if (fHighQuality)
				accum = upsampleRout(buffer, cbuffer, accum, samp_frac, dstSamples);
			else
				accum = ptsampleRout(buffer, cbuffer, accum, samp_frac, dstSamples);

			buffer = (void *)((char *)buffer + bytesPerSample * dstSamples);
			lActualSamples += dstSamples;
			samples -= dstSamples;
		}

		if (fHighQuality)
			accum -= ((srcSamples-1)<<19);
		else
			accum -= (srcSamples<<19);

		// do we need to hold a sample over?

		if (fHighQuality)
			if (accum<0)
				memcpy(cbuffer, (char *)cbuffer + (srcSamples-2)*bytesPerSample, bytesPerSample*2);
			else
				memcpy(cbuffer, (char *)cbuffer + (srcSamples-1)*bytesPerSample, bytesPerSample);
		else if (accum<0)
			memcpy(cbuffer, (char *)cbuffer + (srcSamples-1)*bytesPerSample, bytesPerSample);
	}

	*lplBytes = lActualSamples * bytesPerSample;

//	_RPT2(0,"Converter: %ld samples, %ld bytes\n", lActualSamples, *lplBytes);

	return lActualSamples;
}

long AudioStreamResampler::Downsample(void *buffer, long samples, long *lplBytes) {
	long lActualSamples=0;

	// Downsampling is even worse because we have overlap to the left and to the
	// right of the interpolated point.
	//
	// We need (n/2) points to the left and (n/2-1) points to the right.

	while(samples>0) {
		long srcSamples, dstSamples;
		long lBytes;
		int nhold;

		// Figure out how many source samples we need.
		//
		// To do this, compute the highest fixed-point accumulator we'll reach.
		// Truncate that, and add the filter width.  Then subtract however many
		// samples are sitting at the bottom of the buffer.

		srcSamples = (long)(((__int64)samp_frac*(samples-1) + accum) >> 19) + filter_width - holdover;

		// Don't exceed the buffer (BUFFER_SIZE - holdover).

		if (srcSamples > BUFFER_SIZE - holdover)
			srcSamples = BUFFER_SIZE - holdover;

		// Read into buffer.

		srcSamples = source->Read((char *)cbuffer + holdover*bytesPerSample, srcSamples, &lBytes);

		if (!srcSamples) break;

		// Figure out how many destination samples we'll get out of what we
		// read.  We'll have (srcSamples+holdover) bytes, so the maximum
		// fixed-pt accumulator we can hit is
		// (srcSamples+holdover-filter_width)<<16 + 0xffff.

		dstSamples = (((srcSamples+holdover-filter_width)<<19) + 0x7ffff - accum) / samp_frac + 1;

		if (dstSamples > samples)
			dstSamples = samples;

		if (dstSamples>=1) {
			if (filter_bank)
				accum = dnsampleRout(buffer, cbuffer, filter_bank, filter_width, accum, samp_frac, dstSamples);
			else
				accum = ptsampleRout(buffer, cbuffer, accum, samp_frac, dstSamples);

			buffer = (void *)((char *)buffer + bytesPerSample * dstSamples);
			lActualSamples += dstSamples;
			samples -= dstSamples;
		}

		// We're "shifting" the new samples down to the bottom by discarding
		// all the samples in the buffer, so adjust the fixed-pt accum
		// accordingly.

		accum -= ((srcSamples+holdover)<<19);

		// Oops, did we need some of those?
		//
		// If accum=0, we need (n/2) samples back.  accum>=0x10000 is fewer,
		// accum<0 is more.

		nhold = - (accum>>19);

//		_ASSERT(nhold<=(filter_width/2));

		if (nhold>0) {
			memmove(cbuffer, (char *)cbuffer+bytesPerSample*(srcSamples+holdover-nhold), bytesPerSample*nhold);
			holdover = nhold;
			accum += nhold<<19;
		} else
			holdover = 0;

		_ASSERT(accum>=0);
	}

	*lplBytes = lActualSamples * bytesPerSample;

	return lActualSamples;
}

BOOL AudioStreamResampler::_isEnd() {
	return accum>=0 && source->isEnd();
}



///////////////////////////////////////////////////////////////////////////
//
//		AudioCompressor
//
//		This audio filter handles audio compression.
//
///////////////////////////////////////////////////////////////////////////


struct FrustratedACMOpenData {
	HACMSTREAM *pp;
	HACMDRIVER hdrv;
	WAVEFORMATEX *iFormat;
	WAVEFORMATEX *oFormat;
	bool success;
};


BOOL CALLBACK ACMStreamOpenCallback(HACMDRIVERID hadid, DWORD dwInstance, DWORD fdwSupport) {
	FrustratedACMOpenData *pfad = (FrustratedACMOpenData *)dwInstance;

	// Ignore drivers that don't do format conversion.

	if (fdwSupport & ACMDRIVERDETAILS_SUPPORTF_CODEC) {
		MMRESULT res;

		// Attempt to open driver.

		res = acmDriverOpen(&pfad->hdrv, hadid, 0);

		if (!res) {

			// Attempt our stream open!

			res = acmStreamOpen(pfad->pp, pfad->hdrv, pfad->iFormat, pfad->oFormat, NULL, 0, 0, ACM_STREAMOPENF_NONREALTIME);
			if (!res)
				pfad->success = true;
			else {
				res = acmStreamOpen(pfad->pp, pfad->hdrv, pfad->iFormat, pfad->oFormat, NULL, 0, 0, 0);
				if (!res)
					pfad->success = true;
				else
					acmDriverClose(pfad->hdrv, 0);
			}
		}
	}

	return !pfad->success;
}


AudioCompressor::AudioCompressor(AudioStream *src, WAVEFORMATEX *dst_format, long dst_format_len) : AudioStream() {
	WAVEFORMATEX *iFormat = src->GetFormat();
	WAVEFORMATEX *oFormat;
	DWORD dwOutputBufferSize;
	MMRESULT err;

	hADriver = NULL;
	hACStream = NULL;
	pwfexTempOutput = NULL;
	inputBuffer = outputBuffer = NULL;

	// Stupid Microsoft Audio Codec.

	oFormat = AllocFormat(dst_format_len);
	memcpy(oFormat, dst_format, dst_format_len);

	SetSource(src);

	memset(&ashBuffer, 0, sizeof ashBuffer);

	do {
		// Try opening with ACM_STREAMOPENF_NONREALTIME.

		if (!(err = acmStreamOpen(&hACStream, NULL, iFormat, oFormat, NULL, 0, 0, ACM_STREAMOPENF_NONREALTIME)))
			break;

		// Still didn't work, try every f*cking driver.

		struct FrustratedACMOpenData fad;

		fad.pp		= &hACStream;
		fad.hdrv	= NULL;
		fad.iFormat = iFormat;
		fad.oFormat = oFormat;
		fad.success = false;

		if (!acmDriverEnum(ACMStreamOpenCallback, (DWORD)&fad, 0) && fad.success) {
			hADriver = fad.hdrv;
			break;
		}

		// Damn!

		if (err == ACMERR_NOTPOSSIBLE)
			throw MyError("Error initializing audio stream compression:\nThe requested conversion is not possible.");
		else
			throw MyError("Error initializing audio stream compression.");

	} while(false);

	ACMDRIVERDETAILS add;

	memset(&add, 0, sizeof add);
	add.cbStruct = sizeof add;
	mDriverName[0] = 0;
	HACMDRIVERID hadid;
	if (!acmDriverID((HACMOBJ)hACStream, &hadid, 0)) {
		if (!acmDriverDetails(hadid, &add, 0)) {
			strncpyz(mDriverName, add.szLongName, sizeof mDriverName);
		}
	}

	if (acmStreamSize(hACStream, INPUT_BUFFER_SIZE, &dwOutputBufferSize, ACM_STREAMSIZEF_SOURCE))
		throw MyError("Error querying audio compression.");

	if (!(inputBuffer = allocmem(INPUT_BUFFER_SIZE))
		|| !(outputBuffer = allocmem(dwOutputBufferSize)))

		throw MyMemoryError();

	ashBuffer.cbStruct		= sizeof(ACMSTREAMHEADER);
	ashBuffer.pbSrc			= (LPBYTE)inputBuffer;
	ashBuffer.cbSrcLength	= INPUT_BUFFER_SIZE;
	ashBuffer.pbDst			= (LPBYTE)outputBuffer;
	ashBuffer.cbDstLength	= dwOutputBufferSize;

	if (acmStreamPrepareHeader(hACStream, &ashBuffer, 0))
		throw MyError("Error preparing audio compression buffers.");

	ashBuffer.cbSrcLength = 0;

	bytesPerInputSample = iFormat->nBlockAlign;
	bytesPerOutputSample = oFormat->nBlockAlign;

	fStreamEnded = FALSE;

	mReadOffset = 0;
}

AudioCompressor::~AudioCompressor() {
	if (hACStream) {
		if (ashBuffer.fdwStatus & ACMSTREAMHEADER_STATUSF_PREPARED)
			acmStreamUnprepareHeader(hACStream, &ashBuffer, 0);
		acmStreamClose(hACStream, 0);
	}
	if (hADriver)
		acmDriverClose(hADriver, 0);

	if (inputBuffer)	freemem(inputBuffer);
	if (outputBuffer)	freemem(outputBuffer);

	delete[] pwfexTempOutput;
}

void AudioCompressor::CompensateForMP3() {

	// Fraunhofer-IIS's MP3 codec has a compression delay that we need to
	// compensate for.  Comparison of PCM input, F-IIS output, and
	// WinAmp's Nitrane output reveals that the decompressor half of the
	// ACM codec is fine, but the compressor inserts a delay of 1373
	// (0x571) samples at the start.  This is a lag of 2 frames at
	// 30fps and 22KHz, so it's significant enough to be noticed.  At
	// 11KHz, this becomes a tenth of a second.  Needless to say, the
	// F-IIS MP3 codec is a royal piece of sh*t.
	//
	// By coincidence, the MPEGLAYER3WAVEFORMAT struct has a field
	// called nCodecDelay which is set to this value...

	if (GetFormat()->wFormatTag == WAVE_FORMAT_MPEGLAYER3) {
		long lSamplesToRead = ((MPEGLAYER3WAVEFORMAT *)GetFormat())->nCodecDelay;

		// Note: LameACM does not have a codec delay!

		if (lSamplesToRead) {
			long ltActualBytes, ltActualSamples;
			int nBlockAlign = source->GetFormat()->nBlockAlign;

			do {
				long tc;

				tc = lSamplesToRead;
				if (tc > INPUT_BUFFER_SIZE / nBlockAlign)
					tc = INPUT_BUFFER_SIZE / nBlockAlign;

				ltActualSamples = source->Read((char *)inputBuffer, tc,
							&ltActualBytes);

				lSamplesToRead -= ltActualSamples;
			} while(lSamplesToRead>0 && ltActualBytes);

			if (!ltActualBytes || source->isEnd())
				fStreamEnded = TRUE;
		}
	}
}

long AudioCompressor::_Read(void *buffer, long samples, long *lplBytes) {
	long bytesToRead = samples * bytesPerOutputSample;
	long actualBytes = 0;

	while(bytesToRead > 0) {
		long tc = std::min<long>(bytesToRead, ashBuffer.cbDstLengthUsed - mReadOffset);

		if (tc > 0) {
			memcpy(buffer, (char *)outputBuffer + mReadOffset, tc);
			buffer = (char *)buffer + tc;
			bytesToRead -= tc;
			mReadOffset += tc;
			actualBytes += tc;
			continue;
		}

		if (!Process())
			break;
	}

	VDASSERT(!(actualBytes % bytesPerOutputSample));	// should always be true, since we trim runts in Process()

	if (lplBytes)
		*lplBytes = actualBytes;

	return actualBytes / bytesPerOutputSample;
}

bool AudioCompressor::Process() {
	VDASSERT(mReadOffset >= ashBuffer.cbDstLengthUsed);

	ashBuffer.cbDstLengthUsed = 0;
	mReadOffset = 0;

	while(!fStreamEnded && !ashBuffer.cbDstLengthUsed) {
		// fill the input buffer up!
		if (ashBuffer.cbSrcLength < INPUT_BUFFER_SIZE) {
			LONG lBytes=0;
			LONG ltActualSamples;
			LONG ltActualBytes;
			long lSamplesToRead;

			do {
				lSamplesToRead = (INPUT_BUFFER_SIZE - ashBuffer.cbSrcLength)/bytesPerInputSample;

				ltActualSamples = source->Read((char *)inputBuffer + ashBuffer.cbSrcLength, lSamplesToRead,
							&ltActualBytes);

				ashBuffer.cbSrcLength += ltActualBytes;

				lBytes += ltActualBytes;
			} while(ashBuffer.cbSrcLength < INPUT_BUFFER_SIZE && ltActualBytes);

			if (!ltActualBytes || source->isEnd())
				fStreamEnded = TRUE;
		}

		if (ashBuffer.cbSrcLength <= 0)
			return false;

		// ask ACM to convert for us
		ashBuffer.cbSrcLengthUsed = 0;
		ashBuffer.cbDstLengthUsed = 0;

		vdprotected1("compressing audio using \"%.64s\"", const char *, mDriverName) {
			if (acmStreamConvert(hACStream, &ashBuffer, fStreamEnded ? ACM_STREAMCONVERTF_END : ACM_STREAMCONVERTF_BLOCKALIGN))
				throw MyError("Audio Compression Manager (ACM) failure on compress");
		}

		// Check for a jam condition to try to trap that damned 9995 frame
		// hang problem.

		if (!ashBuffer.cbSrcLengthUsed && !ashBuffer.cbDstLengthUsed) {
			// The codec didn't do anything.  Uh oh.

			if (!fStreamEnded && ashBuffer.cbSrcLength >= INPUT_BUFFER_SIZE) {
				const WAVEFORMATEX& wfsrc = *source->GetFormat();
				const WAVEFORMATEX& wfdst = *GetFormat();

				throw MyError("The operation cannot continue as the target audio codec has jammed and is not compressing data.\n"
								"Codec state for driver \"%.64s\":\n"
								"    source buffer size: %d bytes\n"
								"    destination buffer size: %d bytes\n"
								"    source format: tag %04x, %dHz/%dch/%d-bit, %d bytes/sec\n"
								"    destination format: tag %04x, %dHz/%dch/%d-bit, %d bytes/sec\n"
								, mDriverName
								, ashBuffer.cbSrcLength
								, ashBuffer.cbDstLength
								, wfsrc.wFormatTag, wfsrc.nSamplesPerSec, wfsrc.nChannels, wfsrc.wBitsPerSample, wfsrc.nAvgBytesPerSec
								, wfdst.wFormatTag, wfdst.nSamplesPerSec, wfdst.nChannels, wfdst.wBitsPerSample, wfdst.nAvgBytesPerSec);
			}
		}

		// if ACM didn't use all the source data, copy the remainder down

		if (ashBuffer.cbSrcLengthUsed < ashBuffer.cbSrcLength) {
			long left = ashBuffer.cbSrcLength - ashBuffer.cbSrcLengthUsed;

			memmove(inputBuffer, (char *)inputBuffer + ashBuffer.cbSrcLengthUsed, left);

			ashBuffer.cbSrcLength = left;
		} else
			ashBuffer.cbSrcLength = 0;

		// NOTE: Microsoft ADPCM appears to write a partial block when the
		//       last part of the input stream is insufficient to end with
		//		 a full block.  We can't write partial blocks to the AVI
		//		 file, as AVIFile will ignore it.  So we cut it off.

		VDASSERT(fStreamEnded || !(ashBuffer.cbDstLengthUsed % format->nBlockAlign));

		ashBuffer.cbDstLengthUsed -= ashBuffer.cbDstLengthUsed % format->nBlockAlign;
	}

	return mReadOffset < ashBuffer.cbDstLengthUsed;
}

BOOL AudioCompressor::isEnd() {
	return fStreamEnded && mReadOffset >= ashBuffer.cbDstLengthUsed;
}

///////////////////////////////////////////////////////////////////////////
//
//	Corrects the nAvgBytesPerFrame for that stupid Fraunhofer-IIS
//	codec.


AudioL3Corrector::AudioL3Corrector() {
	samples = frame_bytes = 0;
	read_left = 4;
	header_mode = true;
}

long AudioL3Corrector::ComputeByterate(long sample_rate) {
	return MulDiv(frame_bytes, sample_rate, samples);
}

void AudioL3Corrector::Process(void *buffer, long bytes) {
	static const int bitrates[2][16]={
		{0, 8,16,24,32,40,48,56, 64, 80, 96,112,128,144,160,0},
		{0,32,40,48,56,64,80,96,112,128,160,192,224,256,320,0}
	};
	static const long samp_freq[4] = {44100, 48000, 32000, 0};

	int cnt=0;
	int tc;

	while(cnt < bytes) {
		tc = bytes - cnt;
		if (tc > read_left)
			tc = read_left;

		if (header_mode)
			memcpy(&hdr_buffer[4-read_left], buffer, tc);

		buffer = (char *)buffer + tc;
		cnt += tc;
		read_left -= tc;

		if (read_left <= 0)
			if (header_mode) {
				// We've got a header!  Process it...

				long hdr = *(long *)hdr_buffer;
				long samp_rate, framelen;

				if ((hdr & 0xE0FF) != 0xE0FF)
					throw MyError("MPEG audio sync error: try disabling MPEG audio time correction");

				samp_rate = samp_freq[(hdr>>18)&3];

				if (!((hdr>>11)&1)) {
					samp_rate /= 2;
					samples += 576;
				} else
					samples += 1152;

				if (!(hdr & 0x1000))
					samp_rate /= 2;

				framelen = (bitrates[(hdr>>11)&1][(hdr>>20)&15] * (((hdr>>11)&1) ? 144000 : 72000)) / samp_rate;

				if (hdr&0x20000) ++framelen;

				// update statistics

				frame_bytes += framelen;

				// start skipping the remainder

				read_left = framelen - 4;
				header_mode = false;

			} else {

				// Done skipping frame data; collect the next header

				read_left = 4;
				header_mode = true;
			}
	}
}

///////////////////////////////////////////////////////////////////////////

long AudioTranslateVideoSubset(FrameSubset& dst, FrameSubset& src, const VDFraction& videoFrameRate, WAVEFORMATEX *pwfex) {
	FrameSubsetNode *fsn = src.getFirstFrame();
	const long lSampPerSec = pwfex->nSamplesPerSec;
	const long nBytesPerSec = pwfex->nAvgBytesPerSec;
	const int nBlockAlign = pwfex->nBlockAlign;
	long total = 0;

	// I like accuracy, so let's strive for accuracy.  Accumulate errors as we go;
	// use them to offset the starting points of subsequent segments, never being
	// more than 1/2 segment off.
	//
	// The conversion equation is in units of (1000000*nBlockAlign).

	sint64 nError		= 0;
	sint64 nMultiplier	= (sint64)videoFrameRate.getLo() * nBytesPerSec;
	sint64 nDivisor		= (sint64)videoFrameRate.getHi() * nBlockAlign;
	sint64 nRound		= nDivisor/2;
	long nTotalFramesAccumulated = 0;

	while(fsn) {
		long start, end;

		// Compute error.
		//
		// Ideally, we want the audio and video streams to be of the exact length.
		//
		// Audiolen = (videolen * usPerFrame * nBytesPerSec) / (1000000*nBlockAlign);

		nError = total*nDivisor - (nTotalFramesAccumulated * nMultiplier);

		VDDEBUG("nError = %I64d\n", nError);

		// Add a block.

		start = ((__int64)fsn->start * nMultiplier + nRound + nError) / nDivisor;
		end = ((__int64)(fsn->start + fsn->len) * nMultiplier + nRound) / nDivisor;

		nTotalFramesAccumulated += fsn->len;

		dst.addRange(start, end-start, false);

		total += end-start;

		fsn = src.getNextFrame(fsn);
	}

	return total;
}

AudioSubset::AudioSubset(AudioStream *src, FrameSubset *pfs, const VDFraction& videoFrameRate, sint64 preskew) : AudioStream() {
	memcpy(AllocFormat(src->GetFormatLen()), src->GetFormat(), src->GetFormatLen());

	SetSource(src);

	long total = AudioTranslateVideoSubset(subset, *pfs, videoFrameRate, src->GetFormat());

	const WAVEFORMATEX *pSrcFormat = src->GetFormat();

	subset.deleteRange(0, (preskew*pSrcFormat->nAvgBytesPerSec) / ((sint64)1000000*pSrcFormat->nBlockAlign));

	stream_len = total;

	SetLimit(total);

	pfsnCur = subset.getFirstFrame();
	iOffset = 0;
	lSrcPos = 0;
	lSkipSize = sizeof skipBuffer / pSrcFormat->nBlockAlign;
}

AudioSubset::~AudioSubset() {
}

long AudioSubset::_Read(void *buffer, long samples, long *lplBytes) {
	int offset, actual;

	if (!pfsnCur) {
		*lplBytes = 0;
		return 0;
	}

	while ((offset = pfsnCur->start - lSrcPos) > 0) {
		long t;

		if (source->Skip(offset)) {
			lSrcPos += offset;
			break;
		}

		if (offset > lSkipSize) offset = lSkipSize;

		actual = source->Read(skipBuffer, offset, &t);

		if (!actual) {
			*lplBytes = 0;
			return 0;
		}

		lSrcPos += actual;
	}

	if (samples > pfsnCur->len - iOffset)
		samples = pfsnCur->len - iOffset;

	samples = source->Read(buffer, samples, lplBytes);

	iOffset += samples;
	lSrcPos += samples;
	while (pfsnCur && iOffset >= pfsnCur->len) {
		iOffset -= pfsnCur->len;
		pfsnCur = subset.getNextFrame(pfsnCur);
	}

	return samples;
}

BOOL AudioSubset::_isEnd() {
	return !pfsnCur || source->isEnd();
}

///////////////////////////////////////////////////////////////////////////
//
//	AudioAmplifier
//
///////////////////////////////////////////////////////////////////////////

static void amplify8(unsigned char *dst, int count, long lFactor) {
	long lBias = 0x8080 - 0x80*lFactor;

	if (count)
		do {
			int y = ((long)*dst++ * lFactor + lBias) >> 8;

			if (y<0) y=0; else if (y>255) y=255;

			dst[-1] = (unsigned char)y;
		} while(--count);
}

static void amplify16(signed short *dst, int count, long lFactor) {
	if (count)
		do {
			int y = ((long)*dst++ * lFactor + 0x80) >> 8;

			if (y<-0x7FFF) y=-0x7FFF; else if (y>0x7FFF) y=0x7FFF;

			dst[-1] = (signed short)y;
		} while(--count);
}

AudioStreamAmplifier::AudioStreamAmplifier(AudioStream *src, long _lFactor)
: lFactor(_lFactor) {

	WAVEFORMATEX *iFormat = src->GetFormat();
	WAVEFORMATEX *oFormat;

	memcpy(oFormat = AllocFormat(src->GetFormatLen()), iFormat, src->GetFormatLen());

	SetSource(src);
}

AudioStreamAmplifier::~AudioStreamAmplifier() {
}

long AudioStreamAmplifier::_Read(void *buffer, long samples, long *lplBytes) {
	long lActualSamples=0;
	long lBytes;

	lActualSamples = source->Read(buffer, samples, &lBytes);

	if (lActualSamples) {
		if (GetFormat()->wBitsPerSample > 8)
			amplify16((signed short *)buffer, lBytes/2, lFactor);
		else
			amplify8((unsigned char *)buffer, lBytes, lFactor);
	}

	if (lplBytes)
		*lplBytes = lBytes;

	return lActualSamples;
}

BOOL AudioStreamAmplifier::_isEnd() {
	return source->isEnd();
}

bool AudioStreamAmplifier::Skip(long samples) {
	return source->Skip(samples);
}

///////////////////////////////////////////////////////////////////////////
//
//	AudioFilterSystemStream
//
///////////////////////////////////////////////////////////////////////////

AudioFilterSystemStream::AudioFilterSystemStream(const VDAudioFilterGraph& graph, sint64 start_us) {
	const unsigned nFilters = graph.mFilters.size();
	int nOutputFilter = -1;

	VDAudioFilterGraph graph2(graph);
	VDAudioFilterGraph::FilterList::iterator it(graph2.mFilters.begin()), itEnd(graph2.mFilters.end());

	for(unsigned i=0; it!=itEnd; ++it, ++i) {
		if ((*it).mFilterName == L"output") {
			if (nOutputFilter >= 0)
				throw MyError("Audio filter graph contains more than one output node.");

			nOutputFilter = i;
			(*it).mFilterName = L"*sink";
		}
	}

	if (nOutputFilter < 0)
		throw MyError("Audio filter graph lacks an output node.");

	std::vector<IVDAudioFilterInstance *> filterPtrs;
	mFilterSystem.LoadFromGraph(graph2, filterPtrs);
	mFilterSystem.Start();

	mpFilterIF = VDGetAudioFilterSinkInterface(filterPtrs[nOutputFilter]->GetObject());

	mFilterSystem.Seek(start_us);

	int len = mpFilterIF->GetFormatLen();
	const WAVEFORMATEX *pFormat = (const WAVEFORMATEX *)mpFilterIF->GetFormat();

	if (pFormat->wFormatTag == WAVE_FORMAT_PCM)
		len = sizeof(PCMWAVEFORMAT);

	memcpy(AllocFormat(len), pFormat, len);

	stream_len = ((mpFilterIF->GetLength() - start_us)*pFormat->nAvgBytesPerSec) / (pFormat->nBlockAlign*(sint64)1000000);

	mStartTime = start_us;
	mSamplePos = 0;
}

AudioFilterSystemStream::~AudioFilterSystemStream() {
	mFilterSystem.Stop();
}

long AudioFilterSystemStream::_Read(void *buffer, long samples, long *lplBytes) {
	uint32 total_samples = 0;
	uint32 total_bytes = 0;

	if (samples>0) {
		char *dst = (char *)buffer;

		while(!mpFilterIF->IsEnded()) {
			uint32 actual = mpFilterIF->Read(dst, samples - total_samples);

			if (actual) {
				total_samples += actual;
				total_bytes += format->nBlockAlign*actual;
				dst += format->nBlockAlign*actual;
			}

			if (total_samples >= samples)
				break;

			mFilterSystem.Run();
		}
	}

	if (lplBytes)
		*lplBytes = total_bytes;

	mSamplePos += total_samples;

	return total_samples;
}

BOOL AudioFilterSystemStream::_isEnd() {
	return mpFilterIF->IsEnded();
}

bool AudioFilterSystemStream::Skip(long samples) {
	const WAVEFORMATEX *pFormat = GetFormat();

	// for short skips (<1 sec), just read through
	if (samples < pFormat->nAvgBytesPerSec / pFormat->nBlockAlign)
		return false;

	// reseek
	mSamplePos += samples;

	mFilterSystem.Seek(mStartTime + (mSamplePos * pFormat->nBlockAlign * 1000000) / pFormat->nAvgBytesPerSec);

	return true;
}
