//	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 "VirtualDub.h"
#include <crtdbg.h>
#include <math.h>
#include <windows.h>
#include <commctrl.h>
#include <vfw.h>

#include "resource.h"
#include "oshelper.h"
#include "helpfile.h"
#include "MonoBitmap.h"
#include "FHT.h"
#include "Error.h"


#define BALANCE_DEADZONE		(2048)

///////////////////////////////////////////////////////////////////////////
//
//	Volume meter
//
///////////////////////////////////////////////////////////////////////////

enum {
	VMMODE_VUMETER,
	VMMODE_SCOPE,
	VMMODE_ANALYZER,
};

typedef struct VumeterDlgData {
	RECT rect[2];
	LONG last[2], peak[2];
	void *buffer;
	char *scrollBuffer;
	int scrollSize;
	int bufCnt;
	WAVEFORMATEX wfex;
	HWAVEIN hWaveIn;
	WAVEHDR bufs[2];
	char *pBitmap;
	MonoBitmap *pbm;
	int mode;
	Fht *pfht_left, *pfht_right;
	HWND hwndVolume, hwndBalance;
	long lVolume, lBalance;
	MIXERCONTROLDETAILS mcdVolume;
	MIXERCONTROLDETAILS_UNSIGNED mcdVolumeData[2];

	BOOL fOpened;
	BOOL fRecording;
	BOOL fStereo;
	BOOL fOddByte;
} VumeterDlgData;

static void CaptureVumeterDestruct(VumeterDlgData *vdd) {
	if (vdd->fRecording) waveInReset(vdd->hWaveIn);

	while(vdd->bufCnt--) {
		--vdd->bufCnt;
		waveInUnprepareHeader(vdd->hWaveIn, &vdd->bufs[vdd->bufCnt], sizeof(WAVEHDR));
	}
	GdiFlush();
	delete vdd->pbm;
	delete vdd->pfht_left;
	delete vdd->pfht_right;
	if (vdd->fOpened) waveInClose(vdd->hWaveIn);
	if (vdd->buffer) freemem(vdd->buffer);
	freemem(vdd);
}

static void CaptureVumeterRepaintVumeter(VumeterDlgData *vdd, HDC hDC) {
	HBRUSH hbr, hbrp;
	int i;

	for(i=0; i<(vdd->fStereo?2:1); i++)
		Draw3DRect(hDC,
				vdd->rect[i].left,
				vdd->rect[i].top,
				vdd->rect[i].right  - vdd->rect[i].left,
				vdd->rect[i].bottom - vdd->rect[i].top,
				TRUE);

	if ((hbr = CreateSolidBrush(RGB(0x00,0x00,0xff))) && (hbrp = CreateSolidBrush(RGB(0xff,0x00,0x00)))) {
		RECT r;

		for(i=0; i<(vdd->fStereo?2:1); i++) {
			r.top		= vdd->rect[i].top+1;
			r.bottom	= vdd->rect[i].bottom-1;

			if (vdd->peak[i]) {
				r.left		= vdd->rect[i].left+1 + vdd->last[i];
				r.right		= vdd->rect[i].left+1 + vdd->peak[i];
				FillRect(hDC, &r, hbrp);
			}

			if (vdd->last[i]) {
				r.left		= vdd->rect[i].left+1;
				r.right		= vdd->rect[i].left+1 + vdd->last[i];
				FillRect(hDC, &r, hbr);
			}

			r.left		= vdd->rect[i].left+1 + vdd->peak[i];
			r.right		= vdd->rect[i].right-1;
			FillRect(hDC, &r, (HBRUSH)GetStockObject(LTGRAY_BRUSH));
		}

	}
	if (hbr) DeleteObject(hbr);
	if (hbrp) DeleteObject(hbrp);
}

static void CaptureVumeterDoVumeter(VumeterDlgData *vdd, HDC hdc, unsigned long *total, unsigned long *peak, DWORD dwBytes) {
	HBRUSH hbr = CreateSolidBrush(RGB(0x00,0x00,0xff));
	HBRUSH hbrp = CreateSolidBrush(RGB(0xff,0x00,0x00));
	LONG lvl, plvl;
	RECT r;
	int i;

	for(i=0; i<(vdd->fStereo?2:1); i++) {
		lvl = MulDiv(total[i],vdd->rect[i].right-vdd->rect[i].left-2,dwBytes*128);
		plvl = MulDiv(peak[i],vdd->rect[i].right-vdd->rect[i].left-2,128);

		r.top		= vdd->rect[i].top+1;
		r.bottom	= vdd->rect[i].bottom-1;

		if (lvl < vdd->last[i] && hbrp) {
			r.left		= vdd->rect[i].left+1 + max(lvl, plvl);
			r.right		= vdd->rect[i].left+1 + min(vdd->peak[i], vdd->last[i]);
			FillRect(hdc, &r, hbrp);
		} else if (lvl > vdd->last[i] && hbr) {
			r.left		= vdd->rect[i].left+1 + vdd->last[i];
			r.right		= vdd->rect[i].left+1 + min(lvl, plvl);
			FillRect(hdc, &r, hbr);
		}

		if (plvl < vdd->peak[i]) {
			r.left		= vdd->rect[i].left+1 + plvl;
			r.right		= vdd->rect[i].left+1 + vdd->peak[i];
			FillRect(hdc, &r, (HBRUSH)GetStockObject(LTGRAY_BRUSH));
		} else if (plvl > vdd->peak[i] && hbrp) {
			r.left		= vdd->rect[i].left+1 + max(lvl, vdd->peak[i]);
			r.right		= vdd->rect[i].left+1 + plvl;
			FillRect(hdc, &r, hbrp);
		}

		vdd->last[i] = lvl;
		vdd->peak[i] = plvl;
	}

	if (hbr) DeleteObject(hbr);
	if (hbrp) DeleteObject(hbrp);
}

static void CaptureVumeterRepaintScopeAnalyzer(VumeterDlgData *vdd, HDC hDC) {
	vdd->pbm->BitBlt(hDC, vdd->rect[0].left, vdd->rect[0].top, 0, 0, vdd->rect[0].right-vdd->rect[0].left, vdd->rect[0].bottom-vdd->rect[0].top);
	if (vdd->fStereo)
		vdd->pbm->BitBlt(hDC, vdd->rect[1].left, vdd->rect[1].top, 0, vdd->rect[0].bottom-vdd->rect[0].top, vdd->rect[1].right-vdd->rect[1].left, vdd->rect[1].bottom-vdd->rect[1].top);
}

static void CaptureVumeterDoScope(VumeterDlgData *vdd, HDC hDC) {
	int w = vdd->rect[0].right - vdd->rect[0].left;
	if (w > 4096) w = 4096;

	unsigned char *src = (unsigned char *)vdd->scrollBuffer + vdd->scrollSize - (vdd->fStereo ? w*2 : w);
	unsigned char *dst_col = (unsigned char *)vdd->pbm->getBits(), *dst;
	int hl = vdd->rect[0].bottom - vdd->rect[0].top;
	int hr = vdd->rect[1].bottom - vdd->rect[1].top;
	int h1, h2, h;
	unsigned char mask, c;
	int iPitch = vdd->pbm->getPitch();
	int left_offset = hl*iPitch;

	GdiFlush();
	vdd->pbm->Clear();

	mask = 0x80;
	do {
		c = *src++;

		h1 = (c * hl) >> 8;
		h2 = hl>>1;

		if (h1 < h2)
			h = h2+1-h1;
		else {
			h = h1+1-h2;
			h1 = h2;
		}

		dst = dst_col + left_offset + iPitch * h1;
		do {
			*dst |= mask;
			dst += iPitch;
		} while(--h);

		if (vdd->fStereo) {
			c = *src++;

			h1 = (c * hr) >> 8;
			h2 = hr>>1;

			if (h1 < h2)
				h = h2+1-h1;
			else {
				h = h1+1-h2;
				h1 = h2;
			}

			dst = dst_col + iPitch * h1;
			do {
				*dst |= mask;
				dst += iPitch;
			} while(--h);
		}

		if (!(mask>>=1)) {
			mask = 0x80;
			++dst_col;
		}
	} while(--w);

	CaptureVumeterRepaintScopeAnalyzer(vdd, hDC);
}

static void CaptureVumeterDoAnalyzer(VumeterDlgData *vdd, HDC hDC) {
	int w = vdd->rect[0].right - vdd->rect[0].left;
	if (w > 1024) w = 1024;

	unsigned char *src = (unsigned char *)vdd->scrollBuffer + vdd->scrollSize - (vdd->fStereo ? w*2 : w);
	unsigned char *dst_col = (unsigned char *)vdd->pbm->getBits(), *dst;
	int hl = vdd->rect[0].bottom - vdd->rect[0].top;
	int hr = vdd->rect[1].bottom - vdd->rect[1].top;
	int h1;
	int x=0;
	unsigned char mask;
	int iPitch = vdd->pbm->getPitch();
	int left_offset = hl*iPitch;

	if (vdd->fStereo) {
		vdd->pfht_left->CopyInStereo8((unsigned char *)vdd->scrollBuffer, 1024);
		vdd->pfht_left->Transform(w);
		vdd->pfht_right->CopyInStereo8((unsigned char *)vdd->scrollBuffer + 1, 1024);
		vdd->pfht_right->Transform(w);
	} else {
		vdd->pfht_left->CopyInMono8((unsigned char *)vdd->scrollBuffer, 1024);
		vdd->pfht_left->Transform(w);
	}

	GdiFlush();
	vdd->pbm->Clear();

	mask = 0x80;
	do {
		h1 = floor(0.5 + vdd->pfht_left->GetIntensity(x) * 8 * hl);
		if (h1 > hl) h1 = hl;

		if (h1>0) {
			dst = dst_col + left_offset;
			do {
				*dst |= mask;
				dst += iPitch;
			} while(--h1);
		}

		if (vdd->fStereo) {
			h1 = floor(0.5 + vdd->pfht_right->GetIntensity(x) * 8 * hr);
			if (h1 > hr) h1 = hr;

			if (h1>0) {
				dst = dst_col;
				do {
					*dst |= mask;
					dst += iPitch;
				} while(--h1);
			}
		}

		++x;

		if (!(mask>>=1)) {
			mask = 0x80;
			++dst_col;
		}
	} while(--w);

	CaptureVumeterRepaintScopeAnalyzer(vdd, hDC);
}

static void CaptureVumeterSetupMixer(HWND hdlg, VumeterDlgData *vdd) {
	MMRESULT res;
	UINT mixerID;
	MIXERCAPS mixerCaps;
	MIXERLINE mainMixerLine;
	MIXERLINE lineinMixerLine;
	MIXERLINECONTROLS mixerLineControls;
	MIXERCONTROL control;
	bool fVolumeOk = false;

	//
	// I have only this to say:
	//
	//		I hate the Windows mixer architecture.
	//
	// We have to use this braindead code because there's no good way
	// to get the mixer controls associated with a wave handle.  If you
	// give the mixer functions a wave handle, you always get the direct
	// line to that channel, and can't get to any of the sources...
	//

	mainMixerLine.cbStruct		= sizeof(MIXERLINE);

	_RPT0(0,"CaptureVumeterSetupMixer() begin\n");

	// Find the mixer line associated with the wave input device

	if (MMSYSERR_NOERROR != (res = mixerGetID((HMIXEROBJ)vdd->hWaveIn, &mixerID, MIXER_OBJECTF_HWAVEIN)))
		return;

	if (MMSYSERR_NOERROR != (res = mixerGetDevCaps(mixerID, &mixerCaps, sizeof(MIXERCAPS))))
		return;

	mainMixerLine.cbStruct					= sizeof(MIXERLINE);
	mainMixerLine.Target.dwType				= MIXERLINE_TARGETTYPE_WAVEIN;
	mainMixerLine.Target.wMid				= mixerCaps.wMid;
	mainMixerLine.Target.wPid				= mixerCaps.wPid;
	mainMixerLine.Target.vDriverVersion		= mixerCaps.vDriverVersion;
	strcpy(mainMixerLine.Target.szPname,mixerCaps.szPname);

	if (MMSYSERR_NOERROR == (res = mixerGetLineInfo((HMIXEROBJ)vdd->hWaveIn, &mainMixerLine, MIXER_OBJECTF_HWAVEIN))) {
		int src;

		_RPT1(0,"Found input line - %d connections\n", mainMixerLine.cConnections);

		// Look for the line-in line that feeds to us

		for(src = 0; src < mainMixerLine.cConnections; src++) {

			lineinMixerLine.cbStruct		= sizeof(MIXERLINE);
			lineinMixerLine.dwDestination	= mainMixerLine.dwDestination;
			lineinMixerLine.dwSource		= src;

			if (MMSYSERR_NOERROR == (res = mixerGetLineInfo((HMIXEROBJ)mixerID, &lineinMixerLine, MIXER_OBJECTF_MIXER| MIXER_GETLINEINFOF_SOURCE))
				&& (lineinMixerLine.dwComponentType == MIXERLINE_COMPONENTTYPE_SRC_AUXILIARY
					|| lineinMixerLine.dwComponentType == MIXERLINE_COMPONENTTYPE_SRC_LINE)) {

				_RPT0(0,"Found Line-In for this input device\n");

				vdd->mcdVolume.paDetails = vdd->mcdVolumeData;

				// Look for a volume control

				mixerLineControls.cbStruct		= sizeof mixerLineControls;
				mixerLineControls.dwLineID		= lineinMixerLine.dwLineID;
				mixerLineControls.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
				mixerLineControls.cbmxctrl		= sizeof(MIXERCONTROL);
				mixerLineControls.pamxctrl		= &control;

				if (MMSYSERR_NOERROR == (res = mixerGetLineControls((HMIXEROBJ)mixerID, &mixerLineControls, MIXER_GETLINECONTROLSF_ONEBYTYPE | MIXER_OBJECTF_MIXER))) {
					_RPT0(0,"Got the control.\n");

					vdd->mcdVolume.cbStruct			= sizeof(MIXERCONTROLDETAILS);
					vdd->mcdVolume.dwControlID		= control.dwControlID;
					vdd->mcdVolume.cChannels		= lineinMixerLine.cChannels==2 ? 2 : 1;
					vdd->mcdVolume.cMultipleItems	= 0;
					vdd->mcdVolume.cbDetails		= sizeof(MIXERCONTROLDETAILS_UNSIGNED);

					if (MMSYSERR_NOERROR == (res = mixerGetControlDetails((HMIXEROBJ)mixerID, &vdd->mcdVolume, MIXER_GETCONTROLDETAILSF_VALUE | MIXER_OBJECTF_MIXER)))
						fVolumeOk = true;

				}

				break;

			}
		}
	}

	if (fVolumeOk) {
		vdd->hwndVolume = GetDlgItem(hdlg, IDC_VOLUME);

		if (vdd->mcdVolume.cChannels > 1) {
			long l, r;

			l = vdd->mcdVolumeData[0].dwValue;
			r = vdd->mcdVolumeData[1].dwValue;

			vdd->lVolume = max(l,r);
			if (vdd->lVolume)
				vdd->lBalance = MulDiv(r-l, 32768-BALANCE_DEADZONE, vdd->lVolume);
			else
				vdd->lBalance = 0;
		} else {
			vdd->lVolume = vdd->mcdVolumeData[0].dwValue;
			vdd->lBalance = 0;
		}

		EnableWindow(vdd->hwndVolume, TRUE);
		SendMessage(vdd->hwndVolume, TBM_SETRANGEMIN, TRUE, 0);
		SendMessage(vdd->hwndVolume, TBM_SETRANGEMAX, TRUE, 65535);
		SendMessage(vdd->hwndVolume, TBM_SETPOS, TRUE, vdd->lVolume);

		SendMessage(vdd->hwndVolume, TBM_SETTICFREQ, 8192, 0);

		EnableWindow(GetDlgItem(hdlg, IDC_STATIC_VOLUME), TRUE);

		if (vdd->mcdVolume.cChannels>1) {
			vdd->hwndBalance = GetDlgItem(hdlg, IDC_BALANCE);

			EnableWindow(vdd->hwndBalance, TRUE);
			SendMessage(vdd->hwndBalance, TBM_SETRANGEMIN, TRUE, 0);
			SendMessage(vdd->hwndBalance, TBM_SETRANGEMAX, TRUE, 65535);

			if (vdd->lBalance<0)
				SendMessage(vdd->hwndBalance, TBM_SETPOS, TRUE, vdd->lBalance + 32768 - BALANCE_DEADZONE);
			else if (vdd->lBalance>0)
				SendMessage(vdd->hwndBalance, TBM_SETPOS, TRUE, vdd->lBalance + 32768 + BALANCE_DEADZONE);
			else
				SendMessage(vdd->hwndBalance, TBM_SETPOS, TRUE, 32768);

			SendMessage(vdd->hwndBalance, TBM_SETTIC, 0, 0);
			SendMessage(vdd->hwndBalance, TBM_SETTIC, 0, 32768 - BALANCE_DEADZONE);
			SendMessage(vdd->hwndBalance, TBM_SETTIC, 0, 32768 + BALANCE_DEADZONE);
			SendMessage(vdd->hwndBalance, TBM_SETTIC, 0, 65536);

			EnableWindow(GetDlgItem(hdlg, IDC_STATIC_BALANCE), TRUE);
		}
	}

	_RPT0(0,"CaptureVumeterSetupMixer() end\n");
}

BOOL APIENTRY CaptureVumeterDlgProc( HWND hDlg, UINT message, UINT wParam, LONG lParam) {
	VumeterDlgData *vdd = (VumeterDlgData *)GetWindowLong(hDlg, DWL_USER);
	MMRESULT res;

	switch(message) {

		case WM_INITDIALOG:
			try {
				HWND hWndLeft, hWndRight;
				LONG fsize;
				WAVEFORMATEX *wf;

				////

				if (!(vdd = new VumeterDlgData)) throw MyError("Out of memory");
				memset(vdd, 0, sizeof(VumeterDlgData));

				if (fsize = capGetAudioFormatSize((HWND)lParam)) {
					if (wf = (WAVEFORMATEX *)allocmem(fsize)) {
						if (capGetAudioFormat((HWND)lParam, wf, fsize)) {
							vdd->fStereo = wf->nChannels>1 ? TRUE : FALSE;
						}
						freemem(wf);
					}
				}

				if (!(vdd->buffer = allocmem(vdd->fStereo?1024 + 2048 : 512 + 1024))) throw MyError("Out of memory");
				if (!(vdd->pfht_left = new Fht(1024, 11025))
					|| (vdd->fStereo && !(vdd->pfht_right = new Fht(1024, 11025))))
					throw MyMemoryError();

				vdd->scrollBuffer = (char *)vdd->buffer + (vdd->fStereo ? 1024 : 512);
				vdd->scrollSize = vdd->fStereo ? 2048 : 1024;

				SetWindowLong(hDlg, DWL_USER, (DWORD)vdd);

				GetWindowRect(hWndLeft = GetDlgItem(hDlg, IDC_VOLUME_LEFT), &vdd->rect[0]);
				GetWindowRect(hWndRight = GetDlgItem(hDlg, IDC_VOLUME_RIGHT), &vdd->rect[1]);

				if (!(vdd->pbm = new MonoBitmap(NULL, (vdd->rect[0].right - vdd->rect[0].left), (vdd->rect[0].bottom - vdd->rect[0].top) + (vdd->rect[1].bottom - vdd->rect[1].top), RGB(0x00, 0x80, 0xff), RGB(0,0,0))))
					throw MyMemoryError();

				ShowWindow(hWndLeft, SW_HIDE);
				ShowWindow(hWndRight, SW_HIDE);

				ScreenToClient(hDlg, (LPPOINT)&vdd->rect[0] + 0);
				ScreenToClient(hDlg, (LPPOINT)&vdd->rect[0] + 1);
				ScreenToClient(hDlg, (LPPOINT)&vdd->rect[1] + 0);
				ScreenToClient(hDlg, (LPPOINT)&vdd->rect[1] + 1);

				vdd->wfex.wFormatTag		= WAVE_FORMAT_PCM;
				vdd->wfex.nChannels			= vdd->fStereo?2:1;
				vdd->wfex.nSamplesPerSec	= 11025;
				vdd->wfex.nAvgBytesPerSec	= vdd->fStereo?22050:11025;
				vdd->wfex.nBlockAlign		= vdd->fStereo?2:1;
				vdd->wfex.wBitsPerSample	= 8;
				vdd->wfex.cbSize			= 0;

				if (MMSYSERR_NOERROR != (res = waveInOpen(&vdd->hWaveIn, WAVE_MAPPER, &vdd->wfex, (DWORD)hDlg, 0, CALLBACK_WINDOW)))
					throw MyError("MM system: error %ld", res);

				vdd->fOpened = TRUE;

				for(vdd->bufCnt=0; vdd->bufCnt<2; vdd->bufCnt++) {
					vdd->bufs[vdd->bufCnt].lpData			= (char *)vdd->buffer + (vdd->fStereo?512:256)*vdd->bufCnt;
					vdd->bufs[vdd->bufCnt].dwBufferLength	= vdd->fStereo?512:256;
					vdd->bufs[vdd->bufCnt].dwFlags			= NULL;

					if (MMSYSERR_NOERROR != (res = waveInPrepareHeader(vdd->hWaveIn,&vdd->bufs[vdd->bufCnt],sizeof(WAVEHDR))))
						throw MyError("MM system: error %ld", res);
					if (MMSYSERR_NOERROR != (res = waveInAddBuffer(vdd->hWaveIn,&vdd->bufs[vdd->bufCnt],sizeof(WAVEHDR))))
						throw MyError("MM system: error %ld", res);
				}

				CaptureVumeterSetupMixer(hDlg, vdd);

				if (MMSYSERR_NOERROR != (res = waveInStart(vdd->hWaveIn)))
					throw MyError("MM system: error %ld", res);

				vdd->fRecording = TRUE;
				vdd->mode = VMMODE_VUMETER;

				CheckDlgButton(hDlg, IDC_VMODE_VUMETER, BST_CHECKED);

			} catch(const MyError& e) {
				e.post(NULL,"Vumeter error");

				if (vdd) CaptureVumeterDestruct(vdd);

				EndDialog(hDlg, FALSE);
				return FALSE;
			}

			return TRUE;

		case WM_PAINT:
			{
				PAINTSTRUCT ps;
				HDC hDC;

				hDC = BeginPaint(hDlg, &ps);

				if (vdd->mode == VMMODE_VUMETER)
					CaptureVumeterRepaintVumeter(vdd, hDC);
				else
					CaptureVumeterRepaintScopeAnalyzer(vdd, hDC);

				EndPaint(hDlg, &ps);
			}
			return TRUE;

		case WM_SYSCOMMAND:
			if ((wParam & 0xFFF0) == SC_CONTEXTHELP) {
				HelpPopup(hDlg, IDH_CAPTURE_VUMETER);
				return TRUE;
			}
			break;

		case WM_COMMAND:
			switch(LOWORD(wParam)) {

			case IDOK:
			case IDCANCEL:
				if (vdd) CaptureVumeterDestruct(vdd);
				EndDialog(hDlg, FALSE);
				return TRUE;
			case IDC_VMODE_VUMETER:
				vdd->mode = VMMODE_VUMETER;
				InvalidateRect(hDlg, &vdd->rect[0], FALSE);
				InvalidateRect(hDlg, &vdd->rect[1], FALSE);
				return TRUE;
			case IDC_VMODE_OSCILLOSCOPE:
				vdd->mode = VMMODE_SCOPE;
				return TRUE;
			case IDC_VMODE_ANALYZER:
				vdd->mode = VMMODE_ANALYZER;
				return TRUE;
			}
			break;

		case WM_HSCROLL:
			if (!lParam) return FALSE;

			if ((HWND)lParam == vdd->hwndVolume || (HWND)lParam == vdd->hwndBalance) {
				if ((HWND)lParam == vdd->hwndVolume) {
					vdd->lVolume = SendMessage(vdd->hwndVolume, TBM_GETPOS, 0, 0);
				} else {
					long lPos;

					lPos = SendMessage(vdd->hwndBalance, TBM_GETPOS, 0, 0) - 32768;

					if (lPos < -BALANCE_DEADZONE)
						vdd->lBalance = lPos + BALANCE_DEADZONE;
					else if (lPos > BALANCE_DEADZONE)
						vdd->lBalance = lPos - BALANCE_DEADZONE;
					else
						vdd->lBalance = 0;
				}

				// compute L/R volume

				if (vdd->mcdVolume.cChannels == 1)
					vdd->mcdVolumeData[0].dwValue = vdd->lVolume;
				else if (vdd->lBalance < 0) {
					vdd->mcdVolumeData[0].dwValue = vdd->lVolume;
					vdd->mcdVolumeData[1].dwValue = MulDiv(vdd->lVolume, (32768-BALANCE_DEADZONE)+vdd->lBalance, 32768-BALANCE_DEADZONE);
				} else {
					vdd->mcdVolumeData[0].dwValue = MulDiv(vdd->lVolume, (32768-BALANCE_DEADZONE)-vdd->lBalance, 32768-BALANCE_DEADZONE);
					vdd->mcdVolumeData[1].dwValue =	vdd->lVolume;
				}
				mixerSetControlDetails(
						(HMIXEROBJ)vdd->hWaveIn,
						&vdd->mcdVolume,
						MIXER_GETCONTROLDETAILSF_VALUE | MIXER_OBJECTF_HWAVEIN);
			}
			return TRUE;

		case MM_WIM_DATA:
			{
				WAVEHDR *hdr = (WAVEHDR *)lParam;
				unsigned char c,*src = (unsigned char *)hdr->lpData;
				long dwBytes = hdr->dwBytesRecorded, len;

				if (dwBytes) {
					memmove(vdd->scrollBuffer, vdd->scrollBuffer + dwBytes, vdd->scrollSize - dwBytes);
					memcpy(vdd->scrollBuffer + vdd->scrollSize - dwBytes, src, dwBytes);
				}

				if (vdd->fStereo) {
					if (vdd->fOddByte) {
						++src;
						--dwBytes;
					}

					vdd->fOddByte = dwBytes & 1;

					dwBytes/=2;
				}
				if (len = dwBytes) {
					unsigned long total[2]={0,0}, peak[2]={0,0}, v;
					HDC hdc;

					if (!vdd->fStereo) {
						do {
							c = *src++;

							if (c>=0x80)	v = (c-0x80);
							else			v = (0x80-c);

							if (v>peak[0]) peak[0]=v;

							total[0] += v;

						} while(--len);
					} else {
						do {
							c = *src++;

							if (c>=0x80)	v = (c-0x80);
							else			v = (0x80-c);

							if (v>peak[0]) peak[0]=v;

							total[0] += v;

							//////

							c = *src++;

							if (c>=0x80)	v = (c-0x80);
							else			v = (0x80-c);

							if (v>peak[1]) peak[1]=v;

							total[1] += v;
						} while(--len);
					}

					if (hdc = GetDC(hDlg)) {
						if (vdd->mode == VMMODE_VUMETER)
							CaptureVumeterDoVumeter(vdd, hdc, total, peak, dwBytes);
						else if (vdd->mode == VMMODE_ANALYZER)
							CaptureVumeterDoAnalyzer(vdd, hdc);
						else
							CaptureVumeterDoScope(vdd, hdc);
						ReleaseDC(hDlg, hdc);
					}
				}

				hdr->dwFlags &= ~WHDR_DONE;

//				_RPT1(0,"Received buffer: %ld bytes\n", hdr->dwBytesRecorded);

				if (MMSYSERR_NOERROR != (res = waveInAddBuffer(vdd->hWaveIn,hdr,sizeof(WAVEHDR))))
					MyError("MM system: error %ld",res).post(hDlg,"Vumeter error");
			}
			break;
	}

	return FALSE;
}
