#include <windows.h>
#include <windowsx.h>
#include <win32.h>
#include <commdlg.h>
#include <vfw.h>
#define CLIPSTUFF
#ifdef CLIPSTUFF
#include <vfw.h>
#endif

#include <msacm.h>

#define TEST_FINDSAMPLE

#ifndef streamtypeTEXT
    #pragma message("streamtypeTEXT is not defined in AVIFMT.H")
    #define streamtypeTEXT          mmioFOURCC('t', 'x', 't', 's')
#endif
#include <memory.h>
#include "aviview.h"
#include "audplay.h"

#define GlobalSizePtr(lp)   GlobalSize(GlobalPtrHandle(lp))

#ifndef WIN32
extern LONG FAR PASCAL muldiv32(LONG, LONG, LONG);
#endif

extern BOOL RegisterObjects(void);
extern void RevokeObjects(void);

TCHAR gachFilter[512] = TEXT("");

#define FIXCC(fcc)  if (fcc == 0)       fcc = mmioFOURCC('N', 'o', 'n', 'e'); \
                    if (fcc == BI_RLE8) fcc = mmioFOURCC('R', 'l', 'e', '8');

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/
typedef LONG (FAR PASCAL *LPWNDPROC)(HWND, UINT, WPARAM, LPARAM); // pointer to a window procedure

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/
static  TCHAR        gszAppName[]=TEXT("AVIView");

static  HANDLE      ghInstApp;
static  HWND        ghwndApp;
static  HACCEL	    ghAccel;

#define SCROLLRANGE  10000

#define MAXNUMSTREAMS   50
int                 giCurrentStream;            // current stream;
PAVIFILE	    gpfile;			// the current file
int                 gaiStreamTop[MAXNUMSTREAMS];
PAVISTREAM          gapavi[MAXNUMSTREAMS];	// the current streams
PGETFRAME	    gapgf[MAXNUMSTREAMS];	// data for decompressing
						// video
HDRAWDIB	    ghdd[MAXNUMSTREAMS];	// drawdib handles
HIC		    ghic[MAXNUMSTREAMS];	// experimental: installable
						// draw handlers for non-video
						// streams
int		    gcpavi;			// # of streams

BOOL		    gfPlaying = FALSE;		// Are we playing right now?
LONG		    glPlayStartTime;		// When did we start playing?
LONG 		    glPlayStartPos;		// From what position?

PAVISTREAM          gpaviAudio;                 // 1st audio stream found
PAVISTREAM          gpaviVideo;                 // 1st video stream found

#define             gfVideoFound (gpaviVideo != NULL)
#define             gfAudioFound (gpaviAudio != NULL)

LONG                timeStart;			// cached start, end, length
LONG                timeEnd;
LONG                timeLength;
LONG		    timehscroll;	// how much arrows scroll HORZ bar
LONG		    vertSBLen;
LONG		    vertHeight;


DWORD		    gdwMicroSecPerPixel = 1000L;	// !!! x-stretch

TCHAR                gachFileName[MAX_PATH] = TEXT("");
TCHAR                gachSaveFileName[MAX_PATH] = TEXT("");
UINT		    gwZoom = 2;
AVICOMPRESSOPTIONS  gaAVIOptions[MAXNUMSTREAMS];
LPAVICOMPRESSOPTIONS  galpAVIOptions[MAXNUMSTREAMS];

HFONT               hfontApp;
TEXTMETRIC          tm;
				// !!! constants for painting
            #define VSPACE  8	// no one will ever know what this means :-(
            #define HSPACE  4	// space between frames
            #define TSPACE  (tm.tmHeight) // space for text area about each stream
            #define AUDIOVSPACE  64	// height of an audio stream
            #define FRAME_BORDER 2

void SaveSmall(PAVISTREAM ps, LPTSTR lpFilename);

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/

#define GetScrollTime(hwnd) \
    (timeStart + muldiv32(GetScrollPos(hwnd, SB_HORZ), timeLength, SCROLLRANGE))

#define SetScrollTime(hwnd, time) SetScrollPos(hwnd, SB_HORZ, \
    (int)muldiv32((time) - timeStart, SCROLLRANGE, timeLength), TRUE)

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/

LONG FAR PASCAL _export AppWndProc (HWND hwnd, unsigned uiMessage, WPARAM wParam, LPARAM lParam);
int  ErrMsg (LPTSTR sz,...);
BOOL fDialog(int id,HWND hwnd,FARPROC fpfn);

LONG NEAR PASCAL AppCommand(HWND hwnd, unsigned msg, WPARAM wParam, LPARAM lParam);

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/

HCURSOR hcurSave;
int     fWait = 0;

void StartWait()
{
    if (fWait++ == 0)
    {
        SetCursor(LoadCursor(NULL,IDC_WAIT));
    }
}

void EndWait()
{
    if (--fWait == 0)
    {
        SetCursor(LoadCursor(NULL,IDC_ARROW));
        InvalidateRect(ghwndApp, NULL, TRUE);
    }
}

BOOL WinYield()
{
    MSG msg;
    BOOL fAbort=FALSE;

    while(fWait > 0 && PeekMessage(&msg,NULL,0,0,PM_REMOVE))
    {
	if (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE)
            fAbort = TRUE;
	if (msg.message == WM_SYSCOMMAND && (msg.wParam & 0xFFF0) == SC_CLOSE)
	    fAbort = TRUE;
	TranslateMessage(&msg);
	DispatchMessage(&msg);
    }
    return fAbort;
}


/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/

//
// When we load a file or zoom changes, we re-set the scrollbars
//
void FixScrollbars(HWND hwnd)
{
    AVISTREAMINFO     avis;
    LONG		r, lHeight = 0;
    UINT		w;
    int			i;
    RECT		rc;

    //
    // Walk through all streams and determine how many pixels it will take to
    // draw it.
    //
    for (i = 0; i < gcpavi; i++) {

        AVIStreamInfo(gapavi[i], &avis, sizeof(avis));

        if (avis.fccType == streamtypeVIDEO) {

	    //
	    // Set the horizontal scrollbar scale to show every frame
	    // of the first video stream exactly once
	    //
	    if (gapavi[i] == gpaviVideo) {
		w = (avis.rcFrame.right - avis.rcFrame.left) * gwZoom / 4 +
								    HSPACE;
		r = (LONG)(avis.dwRate / avis.dwScale);
		gdwMicroSecPerPixel = muldiv32(1000000, 1, w * r);
		timehscroll = 1000 / r;	// msec per frame
	    }

	    lHeight +=	TSPACE + TSPACE / 2 + TSPACE +
			(avis.rcFrame.bottom - avis.rcFrame.top) * gwZoom / 4;
	} else if (avis.fccType == streamtypeAUDIO) {
	    lHeight += TSPACE + AUDIOVSPACE * gwZoom / 4;

	} else if (avis.fccType == streamtypeTEXT) {
	    lHeight += TSPACE + TSPACE;
	    ghic[i] = ICDrawOpen(avis.fccType, avis.fccHandler, NULL);
	}

	//
	// Every stream has this much space
	//
	lHeight += TSPACE + TSPACE + TSPACE + VSPACE;
    }

    //
    // Set vertical scrollbar for scrolling the visible area
    //
    GetClientRect(hwnd, &rc);
    vertHeight = lHeight;	// total height in pixels of entire display

    //
    // We won't fit in the window... need scrollbars
    //
    if (lHeight > rc.bottom) {
	vertSBLen = lHeight - rc.bottom;
	SetScrollRange(hwnd, SB_VERT, 0, (int)vertSBLen, TRUE);
	SetScrollPos(hwnd, SB_VERT, 0, TRUE);

    //
    // We will fit in the window!  No scrollbars necessary
    //
    } else {
	vertSBLen = 0;
	SetScrollRange(hwnd, SB_VERT, 0, 0, TRUE);
    }
}

//
// Initialize the streams of a loaded file -- the compression options, the
// DrawDIB handles, and the scroll bars
//
void InitStreams(HWND hwnd)
{
    AVISTREAMINFO     avis;
    LONG	lTemp;
    int		i;

    //
    // Start with bogus times
    //
    timeStart = 0x7FFFFFFF;
    timeEnd   = 0;

    //
    // Walk through and init all streams loaded
    //
    for (i = 0; i < gcpavi; i++) {

        AVIStreamInfo(gapavi[i], &avis, sizeof(avis));

	//
	// Save and SaveOptions code takes a pointer to our compression opts
	//
	galpAVIOptions[i] = &gaAVIOptions[i];

	//
	// clear options structure to zeroes
	//
	_fmemset(galpAVIOptions[i], 0, sizeof(AVICOMPRESSOPTIONS));

	//
 	// Initialize the compression options to some default stuff
	// !!! Pick something better
	//
	galpAVIOptions[i]->fccType = avis.fccType;

	switch(avis.fccType) {

	    case streamtypeVIDEO:		
		galpAVIOptions[i]->dwFlags = AVICOMPRESSF_VALID |
			AVICOMPRESSF_KEYFRAMES | AVICOMPRESSF_DATARATE;
		galpAVIOptions[i]->fccHandler = 0;
		galpAVIOptions[i]->dwQuality = (DWORD)ICQUALITY_DEFAULT;
		galpAVIOptions[i]->dwKeyFrameEvery = 7;	// !!! ask compressor?
		galpAVIOptions[i]->dwBytesPerSecond = 60000;
		break;

	    case streamtypeAUDIO:
		galpAVIOptions[i]->dwFlags |= AVICOMPRESSF_VALID;
		galpAVIOptions[i]->dwInterleaveEvery = 5;
		acmMetrics(NULL,
			      ACM_METRIC_MAX_SIZE_FORMAT,
			      (LPVOID) &galpAVIOptions[i]->cbFormat);

		galpAVIOptions[i]->lpFormat =
			GlobalAllocPtr(GHND, galpAVIOptions[i]->cbFormat);

		lTemp = galpAVIOptions[i]->cbFormat;
		// Use current format as default format
		AVIStreamReadFormat(gapavi[i], 0,
				    galpAVIOptions[i]->lpFormat,
				    &lTemp);
		break;

	    default:
		break;
	}

	//
	// We're finding the earliest and latest start and end points for
	// our scrollbar.
	//
        timeStart = min(timeStart, AVIStreamStartTime(gapavi[i]));
        timeEnd   = max(timeEnd, AVIStreamEndTime(gapavi[i]));

	//
	// Initialize video streams for getting decompressed frames to display
	//
        if (avis.fccType == streamtypeVIDEO) {

#if 1
	    gapgf[i] = AVIStreamGetFrameOpen(gapavi[i], NULL);
#else
	    // Alternate code for testing AVIStreamGetFrameOpen
	    BITMAPINFOHEADER bih;

	    bih.biSize = sizeof(bih);
	    bih.biClrUsed = 256;
	    bih.biBitCount = 8;
	    bih.biPlanes = 1;
	    bih.biWidth = 0;
	    bih.biHeight = 0;
	    bih.biCompression = BI_RGB;
	    bih.biSizeImage = 0;
	    bih.biClrImportant = 0;

	    gapgf[i] = AVIStreamGetFrameOpen(gapavi[i], &bih);
#endif

	    if (gapgf[i] == NULL)
		continue;
	
	    ghdd[i] = DrawDibOpen();
	    // !!! DrawDibBegin?
	
	    if (gpaviVideo == NULL) {

		//
		// Remember the first video stream --- treat it specially
		//
                gpaviVideo = gapavi[i];
	    }

	} else if (avis.fccType == streamtypeAUDIO) {

	    //
	    // Remember the first audio stream --- treat it specially
	    //
	    if (gpaviAudio == NULL)
	        gpaviAudio = gapavi[i];

	}

    }

    timeLength = timeEnd - timeStart;

    SetScrollRange(hwnd, SB_HORZ, 0, SCROLLRANGE, TRUE);
    SetScrollTime(hwnd, timeStart);

    FixScrollbars(hwnd);
}

//
// Update the window title to reflect what's loaded
//
void FixWindowTitle(HWND hwnd)
{
    TCHAR ach[80];

    wsprintf(ach, TEXT("%s %s"),
            (LPTSTR)gszAppName,
            (LPTSTR)gachFileName);

    SetWindowText(hwnd, ach);

    InvalidateRect(hwnd, NULL, TRUE);
}

void FreeDrawStuff(HWND hwnd)
{
    int	i;

    aviaudioStop();

    for (i = 0; i < gcpavi; i++) {
	if (gapgf[i]) {
	    AVIStreamGetFrameClose(gapgf[i]);
	    gapgf[i] = NULL;
	}
	if (ghdd[i]) {
	    DrawDibClose(ghdd[i]);
	    ghdd[i] = 0;
	}
	if (ghic[i]) {
	    ICClose(ghic[i]);
	    ghic[i] = 0;
	}
    }
    SetScrollRange(hwnd, SB_HORZ, 0, 0, TRUE);
    gpaviVideo = gpaviAudio = NULL;
}

void FreeAvi(HWND hwnd)
{
    int	i;

    FreeDrawStuff(hwnd);

    for (i = 0; i < gcpavi; i++) {
	AVIStreamClose(gapavi[i]);
	if (galpAVIOptions[i]->lpFormat) {
	    GlobalFreePtr(galpAVIOptions[i]->lpFormat);
	}
    }
    if (gpfile)
	AVIFileClose(gpfile);

    gpfile = NULL;
    gcpavi = 0;
    giCurrentStream = 0;
}

void InitBall(HWND hwnd)
{
    PAVISTREAM FAR PASCAL NewBall(void);

    FreeAvi(hwnd);

    gapavi[0] = NewBall();

    if (gapavi[0])
	gcpavi = 1;

    lstrcpy(gachFileName, TEXT("BALL"));
    InitStreams(hwnd);
    FixWindowTitle(hwnd);
}


void InsertAVIFile(PAVIFILE pfile, HWND hwnd, LPTSTR lpszFile)
{
    int		i;

    for (i = gcpavi; i < MAXNUMSTREAMS; i++) {
	gapavi[i] = NULL;
	
	if (AVIFileGetStream(pfile, &gapavi[i], 0L, i - gcpavi) != AVIERR_OK)
	    break;

	if (gapavi[i] == NULL)
	    break;
    }

    if (gcpavi == i)
    {
        ErrMsg(TEXT("Unable to open %s"), lpszFile);
	if (pfile)
	    AVIFileClose(pfile);
	goto exit;
    }

    gcpavi = i;

    if (gpfile) {
	AVIFileClose(pfile);
    } else
	gpfile = pfile;

exit:
    InitStreams(hwnd);
    FixWindowTitle(hwnd);
}


void InitAvi(HWND hwnd, LPTSTR szFile, UINT wMenu)
{
    HRESULT	hr;
    PAVIFILE	pfile;

    hr = AVIFileOpen(&pfile, szFile, OF_SHARE_DENY_WRITE, 0L);

    if (hr != 0)
    {
        ErrMsg(TEXT("Unable to open %s"), szFile);
        return;
    }

    if (wMenu == MENU_OPEN)
	FreeAvi(hwnd);
    else
	FreeDrawStuff(hwnd);

    InsertAVIFile(pfile, hwnd, szFile);
}

/*----------------------------------------------------------------------------*\
|   AppInit( hInst, hPrev)						       |
|									       |
|   Description:							       |
|	This is called when the application is first loaded into	       |
|	memory.  It performs all initialization that doesn't need to be done   |
|	once per instance.						       |
|									       |
|   Arguments:								       |
|	hInstance	instance handle of current instance		       |
|	hPrev		instance handle of previous instance		       |
|									       |
|   Returns:								       |
|	TRUE if successful, FALSE if not				       |
|									       |
\*----------------------------------------------------------------------------*/
BOOL AppInit(HINSTANCE hInst, HINSTANCE hPrev, int sw,LPSTR szCmdLine)
{
    WNDCLASS cls;
    int      dx,dy;

    /* Save instance handle for DialogBoxs */
    ghInstApp = hInst;

    ghAccel = LoadAccelerators(hInst, MAKEINTATOM(ID_APP));

    if (szCmdLine && szCmdLine[0]) {
#ifdef UNICODE
	// convert to unicode
	lstrcpy(gachFileName, GetCommandLine());
#else
    	lstrcpy(gachFileName, szCmdLine);
#endif
    }

    if (!hPrev) {
	/*
	 *  Register a class for the main application window
	 */
        cls.hCursor        = LoadCursor(NULL,IDC_ARROW);
        cls.hIcon          = LoadIcon(hInst,MAKEINTATOM(ID_APP));
        cls.lpszMenuName   = MAKEINTATOM(ID_APP);
        cls.lpszClassName  = MAKEINTATOM(ID_APP);
        cls.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        cls.hInstance      = hInst;
        cls.style          = CS_BYTEALIGNCLIENT | CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS;
        cls.lpfnWndProc    = (LPWNDPROC)AppWndProc;
        cls.cbWndExtra     = 0;
        cls.cbClsExtra     = 0;

        if (!RegisterClass(&cls))
	    return FALSE;
    }

    AVIStreamInit();
    RegisterObjects();

    {HDC hdc;
    hfontApp = GetStockObject(ANSI_VAR_FONT);
    hdc = GetDC(NULL);
    SelectObject(hdc, hfontApp);
    GetTextMetrics(hdc, &tm);
    ReleaseDC(NULL, hdc);
    }

    dx = GetSystemMetrics (SM_CXSCREEN);
    dy = GetSystemMetrics (SM_CYSCREEN);

    ghwndApp =
	CreateWindowEx(
#ifdef BIDI
	    WS_EX_BIDI_SCROLL  | WS_EX_BIDI_MENU |WS_EX_BIDI_NOICON,
#else
			    0,
#endif
			    MAKEINTATOM(ID_APP),    // Class name
                            gszAppName,             // Caption
                            WS_OVERLAPPEDWINDOW,    // Style bits
                            CW_USEDEFAULT, 0,       // Position
                            320,300,                // Size
                            (HWND)NULL,             // Parent window (no parent)
                            (HMENU)NULL,            // use class menu
                            (HANDLE)hInst,          // handle to window instance
                            (LPSTR)NULL             // no params to pass on
                           );
    ShowWindow(ghwndApp,sw);

    return TRUE;
}

/*----------------------------------------------------------------------------*\
|   WinMain( hInst, hPrev, lpszCmdLine, cmdShow )			       |
|                                                                              |
|   Description:                                                               |
|       The main procedure for the App.  After initializing, it just goes      |
|       into a message-processing loop until it gets a WM_QUIT message         |
|       (meaning the app was closed).                                          |
|                                                                              |
|   Arguments:                                                                 |
|	hInst		instance handle of this instance of the app	       |
|	hPrev		instance handle of previous instance, NULL if first    |
|       szCmdLine       ->null-terminated command line                         |
|       cmdShow         specifies how the window is initially displayed        |
|                                                                              |
|   Returns:                                                                   |
|       The exit code as specified in the WM_QUIT message.                     |
|                                                                              |
\*----------------------------------------------------------------------------*/
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw)
{
    MSG     msg;

    /* Call initialization procedure */
    if (!AppInit(hInst,hPrev,sw,szCmdLine))
        return FALSE;

    /*
     * Polling messages from event queue
     */
    for (;;)
    {
        while (PeekMessage(&msg, NULL, 0, 0,PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                return msg.wParam;

	    if (TranslateAccelerator(ghwndApp, ghAccel, &msg))
		continue;
	
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

	//
	// If we have no messages to dispatch, we do our background task...
	// If we're playing a file, we set the scroll bar to show the video
	// frames corresponding with the current playing audio sample
	//
        if (gfPlaying) {
	    LONG    l;

	    //
	    // Use the audio clock to tell how long we've been playing.  To
	    // maintain sync, it's important we use this clock.
	    //
	    l = aviaudioTime(); 	// returns -1 if no audio playing

	    //
	    // If we can't use the audio clock to tell us how long we've been
	    // playing, calculate it ourself
	    //
	    if (l == -1)
		l = timeGetTime() - glPlayStartTime + glPlayStartPos;

	    if (l != GetScrollTime(ghwndApp)) {
	        if (l < timeStart)	// make sure number isn't out of bounds
		    l = timeStart;
	        if (l > timeEnd)	// looks like we're all done!
		    gfPlaying = FALSE;
		SetScrollTime(ghwndApp, l);
		InvalidateRect(ghwndApp, NULL, FALSE);
		UpdateWindow(ghwndApp);
		continue;
	    }
	}
	
	WaitMessage();
    }

    return msg.wParam;
}

typedef BYTE _huge * HPBYTE;
typedef int _huge *  HPINT;

void PaintAudio(HDC hdc, PRECT prc, PAVISTREAM pavi, LONG lStart, LONG lLen)
{
    PCMWAVEFORMAT wf;
    int i;
    int x,y;
    int w,h;
    BYTE b;
    HBRUSH hbr;
    RECT rc = *prc;
    LONG    lBytes;
    LONG    l, lLenOrig = lLen;
    LONG    lWaveBeginTime = AVIStreamStartTime(pavi);
    LONG    lWaveEndTime   = AVIStreamEndTime(pavi);

    static LPVOID lpAudio = NULL;

    // We've been told to draw some times that don't exist
    if (lStart < lWaveBeginTime) {
	lLen -= lWaveBeginTime - lStart;
	lStart = lWaveBeginTime;
	rc.left = rc.right - (int)muldiv32(rc.right - rc.left, lLen, lLenOrig);
    }

    if (lStart + lLen > lWaveEndTime) {
	lLenOrig = lLen;
	lLen = lWaveEndTime - lStart;
	rc.right = rc.left + (int)muldiv32(rc.right - rc.left, lLen, lLenOrig);
    }

    /* Now change and work with samples, not time. */
    l = lStart;
    lStart = AVIStreamTimeToSample(pavi, lStart);
    lLen = AVIStreamTimeToSample(pavi, l + lLen) - lStart;

    l = sizeof(wf);
    AVIStreamReadFormat(pavi, lStart, &wf, &l);
    if (!l)
        return;

    w = rc.right - rc.left;
    h = rc.bottom - rc.top;

    if (rc.left > prc->left) {
        SelectObject(hdc, GetStockObject(DKGRAY_BRUSH));
	PatBlt(hdc, prc->left, rc.top, rc.left - prc->left,
						rc.bottom - rc.top, PATCOPY);
    }

    if (rc.right < prc->right) {
        SelectObject(hdc, GetStockObject(DKGRAY_BRUSH));
	PatBlt(hdc, rc.right, rc.top, prc->right - rc.right,
						rc.bottom - rc.top, PATCOPY);
    }

#ifdef WIN32
#define BACKBRUSH  (RGB(0, 0, 0))
#define MONOBRUSH  (RGB(0,255,0))
#define LEFTBRUSH  (RGB(0,0,255))
#define RIGHTBRUSH (RGB(0,255,0))
#define HPOSBRUSH  (RGB(255,0,0))
#else
#define BACKBRUSH  (GetSysColor(COLOR_3DFACE))
#define MONOBRUSH  (GetSysColor(COLOR_3DSHADOW))
#define LEFTBRUSH  (RGB(0,0,255))
#define RIGHTBRUSH (RGB(0,255,0))
#define HPOSBRUSH  (RGB(255,0,0))
#endif

    hbr = SelectObject(hdc, CreateSolidBrush(BACKBRUSH));
    PatBlt(hdc, rc.left, rc.top, w, h, PATCOPY);
    DeleteObject(SelectObject(hdc, hbr));

    //
    // !!! we can only paint PCM data
    //
    if (wf.wf.wFormatTag != WAVE_FORMAT_PCM)
        return;

    lBytes = lLen * wf.wf.nChannels * wf.wBitsPerSample / 8;

    if (!lpAudio)
        lpAudio = GlobalAllocPtr (GHND, lBytes);
    else if ((LONG)GlobalSizePtr(lpAudio) < lBytes)
        lpAudio = GlobalReAllocPtr(lpAudio, lBytes, GMEM_MOVEABLE);

    if (!lpAudio)
        return;

    AVIStreamRead(pavi, lStart, lLen, lpAudio, lBytes, NULL, &l);

    if (l != lLen)
        ; // FatalAppExit(0, "BLAH!!!");

    lLen = l;

    if (lLen == 0)
	return;

#define MulDiv(a,b,c) (UINT)((DWORD)(UINT)(a) * (DWORD)(UINT)(b) / (UINT)(c))

    // !!! Flickers less painting it NOW or LATER?
    // First show the current position as a bar
    hbr = SelectObject(hdc, CreateSolidBrush(HPOSBRUSH));
    PatBlt(hdc, prc->right / 2, prc->top, 1, prc->bottom - prc->top, PATCOPY);
    DeleteObject(SelectObject(hdc, hbr));

    // Mono
    if (wf.wf.nChannels == 1) {

        hbr = SelectObject(hdc, CreateSolidBrush(MONOBRUSH));
        y = rc.top + h/2;
        PatBlt(hdc, rc.left, y, w, 1, PATCOPY);

        if (wf.wBitsPerSample == 8) {
            for (x=0; x<w; x++) {
                b = *((HPBYTE)lpAudio + muldiv32(x,lLen,w));

                if (b > 0x80) {
                    i = y - MulDiv(b-0x80,(h/2),128);
                    PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
                }
                else {
                    i = y + MulDiv(0x80-b,(h/2),128);
                    PatBlt(hdc, rc.left+x, y, 1, i-y, PATCOPY);
                }
            }
        }
        else if (wf.wBitsPerSample == 16) {
            for (x=0; x<w; x++) {
                i = *((HPINT)lpAudio + muldiv32(x,lLen,w));
                if (i > 0) {
                   i = y - (int) ((LONG)i * (h/2) / 32768);
                   PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
                }
                else {
                   i = (int) ((LONG)i * (h/2) / 32768);
                   PatBlt(hdc, rc.left+x, y, 1, -i, PATCOPY);
                }
            }
        }
        DeleteObject(SelectObject(hdc, hbr));
    } // endif mono

    // Stereo
    else if (wf.wf.nChannels == 2) {

        if (wf.wBitsPerSample == 8) {

            // Left channel
            hbr = SelectObject(hdc, CreateSolidBrush(LEFTBRUSH));
            y = rc.top + h/4;
            PatBlt(hdc, rc.left, y, w, 1, PATCOPY);

            for (x=0; x<w; x++) {
                b = *((HPBYTE)lpAudio + muldiv32(x,lLen,w) * 2);

                if (b > 0x80) {
                    i = y - MulDiv(b-0x80,(h/4),128);
                    PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
                }
                else {
                    i = y + MulDiv(0x80-b,(h/4),128);
                    PatBlt(hdc, rc.left+x, y, 1, i-y, PATCOPY);
                }
            }

            // Right channel
            DeleteObject(SelectObject(hdc, hbr));
            hbr = SelectObject(hdc, CreateSolidBrush(RIGHTBRUSH));
            y = rc.top + h * 3 / 4;
            PatBlt(hdc, rc.left, y, w, 1, PATCOPY);

            for (x=0; x<w; x++) {
                b = *((HPBYTE)lpAudio + muldiv32(x,lLen,w) * 2 + 1);

                if (b > 0x80) {
                    i = y - MulDiv(b-0x80,(h/4),128);
                    PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
                }
                else {
                    i = y + MulDiv(0x80-b,(h/4),128);
                    PatBlt(hdc, rc.left+x, y, 1, i-y, PATCOPY);
                }
            }
            DeleteObject(SelectObject(hdc, hbr));
        }

        else if (wf.wBitsPerSample == 16) {

            // Left channel
            hbr = SelectObject(hdc, CreateSolidBrush(LEFTBRUSH));
            y = rc.top + h/4;
            PatBlt(hdc, rc.left, y, w, 1, PATCOPY);

            for (x=0; x<w; x++) {
                i = *((HPINT)lpAudio + muldiv32(x,lLen,w) * 2);
                if (i > 0) {
                    i = y - (int) ((LONG)i * (h/4) / 32768);
                    PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
                }
                else {
                    i = (int) ((LONG)i * (h/4) / 32768);
                    PatBlt(hdc, rc.left+x, y, 1, -i, PATCOPY);
                }
            }

            // Right channel
            DeleteObject(SelectObject(hdc, hbr));

            hbr = SelectObject(hdc, CreateSolidBrush(RIGHTBRUSH));
            y = rc.top + h * 3 / 4;
            PatBlt(hdc, rc.left, y, w, 1, PATCOPY);

            for (x=0; x<w; x++) {
                i = *((HPINT)lpAudio + muldiv32(x,lLen,w) * 2 + 1);
                if (i > 0) {
                   i = y - (int) ((LONG)i * (h/4) / 32768);
                   PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
                }
                else {
                   i = (int) ((LONG)i * (h/4) / 32768);
                   PatBlt(hdc, rc.left+x, y, 1, -i, PATCOPY);
                }
            }
            DeleteObject(SelectObject(hdc, hbr));
        }
    } // endif stereo
}

/*----------------------------------------------------------------------------*\
|   AppWndProc( hwnd, uiMessage, wParam, lParam )			       |
|                                                                              |
|   Description:                                                               |
|       The window proc for the app's main (tiled) window.  This processes all |
|       of the parent window's messages.                                       |
|                                                                              |
|   Arguments:                                                                 |
|	hwnd		window handle for the window			       |
|       uiMessage       message number                                         |
|       wParam          message-dependent                                      |
|       lParam          message-dependent                                      |
|                                                                              |
|   Returns:                                                                   |
|       0 if processed, nonzero if ignored                                     |
|                                                                              |
\*----------------------------------------------------------------------------*/
LONG FAR PASCAL _export AppWndProc(hwnd, msg, wParam, lParam)
    HWND     hwnd;
    unsigned msg;
    WPARAM     wParam;
    LPARAM     lParam;
{
    PAINTSTRUCT ps;
    BOOL        f;
    HDC         hdc;
    TCHAR        ach[200];
    int	        iFrameWidth;
    int		iLen;
    LONG        lFrame, lCurFrame;
    int		n;
    int         nFrames;
    LPBITMAPINFOHEADER lpbi;
    static      BYTE abFormat[1024];
    LONG        l;
    LONG	lTime;
    LONG        lSize = 0;
    LONG	lAudioStart;
    LONG	lAudioLen;
    RECT        rcFrame, rcC;
    int		yStreamTop;
    int         i;
    HBRUSH      hbr;
    RECT	rc;
    HRESULT	hr;

    switch (msg) {
        case WM_CREATE:
            if (gachFileName[0])
                InitAvi(hwnd, gachFileName, MENU_OPEN);
	    break;

        case WM_COMMAND:
            return AppCommand(hwnd,msg,wParam,lParam);

        case WM_INITMENU:
            f = gcpavi > 0;
            EnableMenuItem((HMENU)wParam, MENU_SAVE,   MF_GRAYED);
            EnableMenuItem((HMENU)wParam, MENU_SAVEAS, f ? MF_ENABLED : MF_GRAYED);
            EnableMenuItem((HMENU)wParam, MENU_SAVERAW,f ? MF_ENABLED : MF_GRAYED);
            EnableMenuItem((HMENU)wParam, MENU_OPTIONS,f ? MF_ENABLED : MF_GRAYED);

            f = gcpavi > 0;
            EnableMenuItem((HMENU)wParam, MENU_NEW,    f ? MF_ENABLED : MF_GRAYED);
            EnableMenuItem((HMENU)wParam, MENU_ADD,    f ? MF_ENABLED : MF_GRAYED);
	
            EnableMenuItem((HMENU)wParam, MENU_COPY,   f ? MF_ENABLED : MF_GRAYED);

            EnableMenuItem((HMENU)wParam, MENU_PLAY_STREAM,f ? MF_ENABLED : MF_GRAYED);

	    f = gpfile != 0;
            EnableMenuItem((HMENU)wParam, MENU_CFILE,  f ? MF_ENABLED : MF_GRAYED);
            EnableMenuItem((HMENU)wParam, MENU_PLAY_FILE,  f ? MF_ENABLED : MF_GRAYED);

	    f = TRUE;
	    EnableMenuItem((HMENU)wParam, MENU_PASTE,  f ? MF_ENABLED : MF_GRAYED);
	
            f = FALSE;
            EnableMenuItem((HMENU)wParam, MENU_CUT,    f ? MF_ENABLED : MF_GRAYED);
            EnableMenuItem((HMENU)wParam, MENU_DELETE, f ? MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_MARK,   f ? MF_ENABLED : MF_GRAYED);
	
	    f = gfAudioFound;
            EnableMenuItem((HMENU)wParam, MENU_SAVEWAVE,f ? MF_ENABLED : MF_GRAYED);
	
	    f = gfVideoFound || gfAudioFound;
            EnableMenuItem((HMENU)wParam, MENU_PLAY,
			(f && !gfPlaying) ? MF_ENABLED : MF_GRAYED);
            EnableMenuItem((HMENU)wParam, MENU_STOP,
                        (f && gfPlaying) ? MF_ENABLED : MF_GRAYED);

	    CheckMenuItem((HMENU)wParam, MENU_ZOOMQUARTER,
		    (gwZoom == 1) ? MF_CHECKED : MF_UNCHECKED);
	    CheckMenuItem((HMENU)wParam, MENU_ZOOMHALF,
		    (gwZoom == 2) ? MF_CHECKED : MF_UNCHECKED);
	    CheckMenuItem((HMENU)wParam, MENU_ZOOM1,
		    (gwZoom == 4) ? MF_CHECKED : MF_UNCHECKED);
	    CheckMenuItem((HMENU)wParam, MENU_ZOOM2,
		    (gwZoom == 8) ? MF_CHECKED : MF_UNCHECKED);
	    CheckMenuItem((HMENU)wParam, MENU_ZOOM4,
		    (gwZoom == 16) ? MF_CHECKED : MF_UNCHECKED);
	    	
            break;

	case WM_NCHITTEST:
	    if (fWait)
	    {
		lParam = DefWindowProc(hwnd,msg,wParam,lParam);

		if (lParam == HTMENU)
		    lParam = HTCLIENT;

		return lParam;
	    }
	    break;

	case WM_SIZE:
	    // Set vertical scrollbar for scrolling streams
	    GetClientRect(hwnd, &rc);
	    if (vertHeight > rc.bottom) {
	        vertSBLen = vertHeight - rc.bottom;
	        SetScrollRange(hwnd, SB_VERT, 0, (int)vertSBLen, TRUE);
	    } else {
	        vertSBLen = 0;
	        SetScrollRange(hwnd, SB_VERT, 0, 0, TRUE);
	    }
	    break;

        case WM_SETCURSOR:
            if (fWait && LOWORD(lParam) == HTCLIENT)
            {
                SetCursor(LoadCursor(NULL, IDC_WAIT));
                return TRUE;
            }
            break;

        case WM_DESTROY:
            FreeAvi(hwnd);
	    RevokeObjects();
	    AVIStreamExit();
	    PostQuitMessage(0);
	    break;

        case WM_CLOSE:
	    if (fWait)
		return 0;
            break;

	case WM_SYSCOMMAND:
	    switch (wParam & 0xFFF0) {
		case SC_KEYMENU:
		    if (fWait)	    // block keyboard access to menus if waiting
			return 0;
		    break;
	    }
	    break;

        case WM_PALETTECHANGED:
            if ((HWND)wParam == hwnd)
                break;

	case WM_QUERYNEWPALETTE:
            hdc = GetDC(hwnd);

            if (f = DrawDibRealize(ghdd[0], hdc, FALSE)) // !!! stream #
                InvalidateRect(hwnd,NULL,TRUE);

            ReleaseDC(hwnd,hdc);

            return f;
	
        case WM_ERASEBKGND:
            break;

        case WM_PAINT:
            hdc = BeginPaint(hwnd,&ps);

            SelectObject(hdc, hfontApp);

            #define PRINT(sz) \
                (TextOut(hdc, TSPACE, yStreamTop, sz, lstrlen(sz)), \
                yStreamTop += tm.tmHeight+1)

            #define PF1(sz,a)                   (wsprintf(ach, sz, a), PRINT(ach))
            #define PF2(sz,a,b)                 (wsprintf(ach, sz, a, b), PRINT(ach))
            #define PF3(sz,a,b,c)               (wsprintf(ach, sz, a, b, c), PRINT(ach))
            #define PF4(sz,a,b,c,d)             (wsprintf(ach, sz, a, b, c, d), PRINT(ach))
            #define PF5(sz,a,b,c,d,e)           (wsprintf(ach, sz, a, b, c, d, e), PRINT(ach))
            #define PF6(sz,a,b,c,d,e,f)         (wsprintf(ach, sz, a, b, c, d, e, f), PRINT(ach))
            #define PF7(sz,a,b,c,d,e,f,g)       (wsprintf(ach, sz, a, b, c, d, e, f, g), PRINT(ach))
            #define PF8(sz,a,b,c,d,e,f,g,h)     (wsprintf(ach, sz, a, b, c, d, e, f, g, h), PRINT(ach))
            #define PF9(sz,a,b,c,d,e,f,g,h,i)   (wsprintf(ach, sz, a, b, c, d, e, f, g, h, i), PRINT(ach))

	    GetClientRect(hwnd, &rcC);

	    lTime = GetScrollTime(hwnd);
	    yStreamTop = -GetScrollPos(hwnd, SB_VERT);

	    for (i=0; i<gcpavi; i++) {
		AVISTREAMINFO		avis;
                BOOL                    fFirstFrame = TRUE;

                LONG                    lPos;

                LONG                    lNearKey;
                LONG                    lPrevKey;
                LONG                    lNextKey;

                LONG                    lNearAny;
                LONG                    lPrevAny;
                LONG                    lNextAny;

                LONG                    lNearFmt;
                LONG                    lPrevFmt;
                LONG                    lNextFmt;

		LONG			lEnd, lEndTime;

                // the idea is to allow stream select!
                gaiStreamTop[i] = yStreamTop;
			
                hr = AVIStreamInfo(gapavi[i], &avis, sizeof(avis));
                FIXCC(avis.fccHandler);
                FIXCC(avis.fccType);

		l = sizeof(abFormat);
                hr = AVIStreamReadFormat(gapavi[i],0, &abFormat, &l);

#ifdef WIN32
                PF7(TEXT("Stream%d [%4.4hs/%4.4hs] Start: %ld Length: %ld (%ld.%03ld sec)             "),
			    i,
                            (LPSTR)&avis.fccType,
                            (LPSTR)&avis.fccHandler,
			    AVIStreamStart(gapavi[i]),
                            AVIStreamLength(gapavi[i]),
                            AVIStreamLengthTime(gapavi[i]) / 1000,
			    AVIStreamLengthTime(gapavi[i]) % 1000);
#else
                PF7(TEXT("Stream%d [%4.4ls/%4.4ls] Start: %ld Length: %ld (%ld.%03ld sec)             "),
			    i,
                            (LPSTR)&avis.fccType,
                            (LPSTR)&avis.fccHandler,
			    AVIStreamStart(gapavi[i]),
                            AVIStreamLength(gapavi[i]),
                            AVIStreamLengthTime(gapavi[i]) / 1000,
			    AVIStreamLengthTime(gapavi[i]) % 1000);
#endif

                lPos = AVIStreamTimeToSample(gapavi[i], lTime);
                AVIStreamSampleSize(gapavi[i], lPos, &lSize);

                lNearKey = AVIStreamFindSample(gapavi[i], lPos, FIND_PREV|FIND_KEY);

#ifdef TEST_FINDSAMPLE
                lNearAny = AVIStreamFindSample(gapavi[i], lPos, FIND_PREV|FIND_ANY);
                lNearFmt = AVIStreamFindSample(gapavi[i], lPos, FIND_PREV|FIND_FORMAT);

                lPrevKey = AVIStreamFindSample(gapavi[i], lPos-1, FIND_PREV|FIND_KEY);
                lPrevAny = AVIStreamFindSample(gapavi[i], lPos-1, FIND_PREV|FIND_ANY);
                lPrevFmt = AVIStreamFindSample(gapavi[i], lPos-1, FIND_PREV|FIND_FORMAT);

                lNextKey = AVIStreamFindSample(gapavi[i], lPos+1, FIND_NEXT|FIND_KEY);
                lNextAny = AVIStreamFindSample(gapavi[i], lPos+1, FIND_NEXT|FIND_ANY);
                lNextFmt = AVIStreamFindSample(gapavi[i], lPos+1, FIND_NEXT|FIND_FORMAT);
#endif
                PF5(TEXT("Pos:%ld Time:%ld.%03ld sec Size:%ld bytes %s                                 "),
                        lPos, lTime/1000, lTime%1000, lSize,
                        (LPTSTR)(lPos == lNearKey ? TEXT("Key") : TEXT("")));

#ifdef TEST_FINDSAMPLE
                PF9(TEXT("PrevKey=%ld NearKey=%ld NextKey=%ld, PrevAny=%ld NearAny=%ld NextAny=%ld, PrevFmt=%ld NearFmt=%ld NextFmt=%ld                      "),
                    lPrevKey, lNearKey, lNextKey,
                    lPrevAny, lNearAny, lNextAny,
                    lPrevFmt, lNearFmt, lNextFmt);
#endif

                if (avis.fccType == streamtypeVIDEO) {

                    lpbi = (LPBITMAPINFOHEADER)abFormat;
                    FIXCC(lpbi->biCompression);

                    //
                    // display video format
                    //
                    //  Video: 160x120x8 (cram)
                    //
#ifdef WIN32
                    PF4(TEXT("Format: %dx%dx%d (%4.4hs)"),
                        (int)lpbi->biWidth,
                        (int)lpbi->biHeight,
                        (int)lpbi->biBitCount,
                        (LPSTR)&lpbi->biCompression);
#else
                    PF4(TEXT("Format: %dx%dx%d (%4.4s)"),
                        (int)lpbi->biWidth,
                        (int)lpbi->biHeight,
                        (int)lpbi->biBitCount,
                        (LPSTR)&lpbi->biCompression);
#endif

		    //
		    // Which frame belongs at this time?
		    //
		    lEndTime = AVIStreamEndTime(gapavi[i]);
		    if (lTime <= lEndTime)
		        lFrame = AVIStreamTimeToSample(gapavi[i], lTime);
		    else {	// we've scrolled past the end of this stream
		        lEnd = AVIStreamEnd(gapavi[i]);
		        lFrame = lEnd + AVIStreamTimeToSample(
				gapavi[i], lTime - lEndTime);
		    }

                    yStreamTop += TSPACE/2;

		    // how wide is each frame to paint?
		    iFrameWidth = (avis.rcFrame.right - avis.rcFrame.left) *
			gwZoom / 4 + HSPACE;

		    //
		    // how many frames can we fit on each half of the screen?
		    //
		    nFrames = (rcC.right - iFrameWidth) / (2 * iFrameWidth);
		    if (nFrames < 0)
		        nFrames = 0;    // at least draw *something*

		    // Step through all the frames we'll draw
		    for (n=-nFrames; n<=nFrames; n++)
		    {
			// !!! If this code didn't have awful rounding errors,
			// I wouldn't need to special case the first stream.
			if (gapavi[i] == gpaviVideo) {
			    // by definition, we know what frame we're drawing.
			    lCurFrame = lFrame + n;

			    // what time is it at that frame?
			    l = AVIStreamSampleToTime(gapavi[i], lCurFrame);
			} else {
			    // What time is it at this pixel?
			    l = lTime + muldiv32(n * iFrameWidth, gdwMicroSecPerPixel, 1000);

			    // Get the frame
			    lCurFrame = AVIStreamTimeToSample(gapavi[i], l);
			}
			
			// !!!
			// Could actually return an LPBI for invalid frames
			// so we better force it to NULL.
			//
			if (gapgf[i] && lCurFrame >= AVIStreamStart(gapavi[i]))
		            lpbi = AVIStreamGetFrame(gapgf[i], lCurFrame);
			else
			    lpbi = NULL;

		        // Location of the current frame
		        rcFrame.left   = rcC.right / 2 -
			    ((avis.rcFrame.right - avis.rcFrame.left) *
				gwZoom/4)/2+ (n * iFrameWidth);
		        rcFrame.top    = yStreamTop;
		        rcFrame.right  = rcFrame.left +
				(avis.rcFrame.right - avis.rcFrame.left)*gwZoom/4;
		        rcFrame.bottom = rcFrame.top +
				(avis.rcFrame.bottom - avis.rcFrame.top)*gwZoom/4;

		        //
		        // draw frame around current frame.
		        //
		        if (n == 0)
			    hbr = CreateSolidBrush(RGB(255,0,0));
		        else
			    hbr = CreateSolidBrush(RGB(255,255,255));

		        InflateRect(&rcFrame, 1, 1);
		        FrameRect(hdc, &rcFrame, hbr);
		        InflateRect(&rcFrame, -1, -1);
		        DeleteObject (hbr);

		        if (lpbi)
		        {
			    DrawDibDraw(ghdd[i],hdc,
			        rcFrame.left, rcFrame.top,
			        rcFrame.right - rcFrame.left,
			        rcFrame.bottom - rcFrame.top,
			        lpbi, NULL,
				0, 0, -1, -1,
				((gapavi[i] == gpaviVideo) && fFirstFrame) ?
					0 : DDF_BACKGROUNDPAL);

			    fFirstFrame = FALSE;

			    iLen = wsprintf(ach, TEXT("%ld %ld.%03lds"),
			        lCurFrame, l/1000, l%1000);
		        }
		        else
		        {
			    if (gapgf[i])
  			        SelectObject(hdc,GetStockObject(DKGRAY_BRUSH));
  			    else
  			        SelectObject(hdc,GetStockObject(LTGRAY_BRUSH));

			    PatBlt(hdc,
			        rcFrame.left, rcFrame.top,
			        rcFrame.right - rcFrame.left,
			        rcFrame.bottom - rcFrame.top,
			        PATCOPY);
			    iLen = 0;
			    ach[0] = TEXT('\0');
		        }

			rc.left = rcFrame.left;
			rc.right = rcFrame.right + HSPACE;
			rc.top = rcFrame.bottom + 2;
			rc.bottom = rc.top + TSPACE;
			ExtTextOut(hdc, rc.left, rc.top, ETO_OPAQUE,
				   &rc, ach, iLen, NULL);
		    }
		
                    yStreamTop += TSPACE + avis.rcFrame.bottom*gwZoom/4;
		}
		else if (avis.fccType == streamtypeAUDIO)
                {
                    LPWAVEFORMAT pwf = (LPWAVEFORMAT)abFormat;
                    TCHAR *szFmt;

                    if (pwf->wFormatTag == 1) {  // PCM
                        if (pwf->nChannels == 1)
                            szFmt = TEXT("Format: Mono %dHz %dbit");
                        else
                            szFmt = TEXT("Format: Stereo %dHz %dbit");
                    }
                    else if (pwf->wFormatTag == 2) {  // ADPCM
                        if (pwf->nChannels == 1)
                            szFmt = TEXT("Format: ADPCM Mono %dHz %dbit");
                        else
                            szFmt = TEXT("Format: ADPCM Stereo %dHz %dbit");
                    }
                    else {
                        if (pwf->nChannels == 1)
                            szFmt = TEXT("Format: Compressed Mono %dHz %dbit");
                        else
                            szFmt = TEXT("Format: Compressed Stereo %dHz %dbit");
                    }

                    PF2(szFmt,(int)pwf->nSamplesPerSec,
                        (int)(pwf->nAvgBytesPerSec * 8 / pwf->nSamplesPerSec));

		    lAudioStart = lTime - muldiv32(rcC.right / 2,
						gdwMicroSecPerPixel, 1000);
		    lAudioLen = 2 * (lTime - lAudioStart);

                    // !!! Fix the SAMPLE field for short audio clips !!!
                    //PF2("Sample:%ld %ld                        ",
                    //            AVIStreamTimeToSample(gapavi[i], lAudioStart),
                    //            AVIStreamTimeToSample(gapavi[i], lAudioLen));

		    /* Make rectangle to draw audio into */
		    rc.left = rcC.left;
		    rc.right = rcC.right;
                    rc.top = yStreamTop;
		    rc.bottom = rc.top + AUDIOVSPACE * gwZoom / 4;

		    PaintAudio(hdc, &rc, gapavi[i], lAudioStart, lAudioLen);

                    yStreamTop += rc.bottom - rc.top;
		}
		else if (avis.fccType == streamtypeTEXT)
		{
		    LONG    lPos;
		    int	    iLeft;

		    lPos = AVIStreamTimeToSample(gapavi[i],
						 lTime -
						 muldiv32((rcC.right - rcC.left),
							gdwMicroSecPerPixel,
							1000));

		    if (lPos < 0)
			lPos = 0;

		    PatBlt(hdc, rcC.left, yStreamTop,
			   rcC.right - rcC.left, TSPACE + TSPACE,
			   WHITENESS);
		
                    while (lPos < AVIStreamEnd(gapavi[i]) - 1) {

			// What pixel is it at this time?
			iLeft = (rcC.right + rcC.left) / 2 +
				    (int) muldiv32(AVIStreamSampleToTime(gapavi[i], lPos) - lTime,
					     1000,  gdwMicroSecPerPixel);

			if (iLeft >= rcC.right)
			    break;

                        AVIStreamRead(gapavi[i], lPos, 1, ach, sizeof(ach), &l, NULL);

			if (l)
			    TextOut(hdc, iLeft, yStreamTop, ach, (int) l - 1);

			iLen = wsprintf(ach, TEXT("%ld"), lPos);
			TextOut(hdc, iLeft, yStreamTop + TSPACE, ach, iLen);

#if 0
			if (ghic[i]) {
			    ICDrawBegin(ghic[i],
					0,
					0,
					hwnd,
					hdc,
					iLeft,
					yStreamTop,
					100,
					50,
					NULL,
					0,
					0,
					-1,
					-1,
					1,
					1);
	
			    ICDraw(ghic[i], 0,
				   NULL, ach, l, lPos);
			    ICDrawEnd(ghic[i]);
			}
#endif		
			lPos += 1;
                    }

		    yStreamTop += TSPACE + TSPACE;
		}
		else
		{
		}

                yStreamTop += VSPACE;

		if (yStreamTop >= rcC.bottom)
		    break;
	    }

            EndPaint(hwnd,&ps);
            break;

	case WM_KEYDOWN:
            switch (wParam)
            {
                case VK_UP:    PostMessage (hwnd,WM_VSCROLL,SB_LINEUP,0L);   break;
                case VK_DOWN:  PostMessage (hwnd,WM_VSCROLL,SB_LINEDOWN,0L); break;
                case VK_PRIOR: PostMessage (hwnd,WM_HSCROLL,SB_PAGEUP,0L);   break;
                case VK_NEXT:  PostMessage (hwnd,WM_HSCROLL,SB_PAGEDOWN,0L); break;
                case VK_HOME:  PostMessage (hwnd,WM_HSCROLL,SB_THUMBPOSITION,0L);     break;
                case VK_END:   PostMessage (hwnd,WM_HSCROLL,SB_THUMBPOSITION,0x7FFF); break;
                case VK_LEFT:  PostMessage (hwnd,WM_HSCROLL,SB_LINEUP,0L);   break;
                case VK_RIGHT: PostMessage (hwnd,WM_HSCROLL,SB_LINEDOWN,0L); break;
	    }
	    break;

        case WM_HSCROLL:
            l = GetScrollTime(hwnd);

            switch (GET_WM_HSCROLL_CODE(wParam, lParam)) {
                case SB_LINEDOWN:      l += timehscroll;  break;
                case SB_LINEUP:        l -= timehscroll;  break;
                case SB_PAGEDOWN:      l += timeLength/10; break;
                case SB_PAGEUP:        l -= timeLength/10; break;
                case SB_THUMBTRACK:
                case SB_THUMBPOSITION:
			l = GET_WM_HSCROLL_POS(wParam, lParam);
			l = timeStart + muldiv32(l, timeLength, SCROLLRANGE);
			break;
            }

	    if (l < timeStart)
		l = timeStart;

	    if (l > timeEnd)
		l = timeEnd;

	    if (l == GetScrollTime(hwnd))
		break;
	
	    SetScrollTime(hwnd, l);
            InvalidateRect(hwnd, NULL, FALSE);
            UpdateWindow(hwnd);
            break;

        case WM_VSCROLL:
            l = GetScrollPos(hwnd, SB_VERT);
	    GetClientRect(hwnd, &rc);

            switch (GET_WM_VSCROLL_CODE(wParam, lParam)) {
                case SB_LINEDOWN:      l += 10;  break;
                case SB_LINEUP:        l -= 10;  break;
                case SB_PAGEDOWN:      l += rc.bottom; break;
                case SB_PAGEUP:        l -= rc.bottom; break;
                case SB_THUMBTRACK:
                case SB_THUMBPOSITION:
		    l = GET_WM_VSCROLL_POS(wParam, lParam);
		    break;
            }

	    if (l < 0)
		l = 0;

	    if (l > vertSBLen)
		l = vertSBLen;

	    if (l == GetScrollPos(hwnd, SB_VERT))
		break;
	
	    SetScrollPos(hwnd, SB_VERT, (int)l, TRUE);
            InvalidateRect(hwnd, NULL, TRUE);
            UpdateWindow(hwnd);
            break;

	case MM_WOM_OPEN:
	case MM_WOM_DONE:
	case MM_WOM_CLOSE:
	    aviaudioMessage(hwnd, msg, wParam, lParam);
	    break;
    }
    return DefWindowProc(hwnd,msg,wParam,lParam);
}

BOOL FAR PASCAL _export SaveCallback(int iProgress)
{
    TCHAR    ach[128];

    wsprintf(ach, TEXT("%s - Saving %s: %d%%"),
        (LPTSTR) gszAppName, (LPTSTR) gachSaveFileName, iProgress);

    SetWindowText(ghwndApp, ach);
    return WinYield();
}

LONG NEAR PASCAL AppCommand (hwnd, msg, wParam, lParam)
    HWND     hwnd;
    unsigned msg;
    WPARAM     wParam;
    LPARAM     lParam;
{
    OPENFILENAME ofn;
    TCHAR	achFilter[128];

    switch(GET_WM_COMMAND_ID(wParam, lParam))
    {
	case MENU_EXIT:
	    PostMessage(hwnd,WM_CLOSE,0,0L);
            break;

	// Set the compression options for each stream - pass an array of
	// streams and an array of compression options structures
        case MENU_OPTIONS:
            AVISaveOptions(hwnd, ICMF_CHOOSE_KEYFRAME | ICMF_CHOOSE_DATARATE
			| ICMF_CHOOSE_PREVIEW,
		gcpavi, gapavi, galpAVIOptions);
	    break;
	
        case MENU_SAVEAS:
	case MENU_SAVERAW:
	case MENU_SAVESMALL:

            gachSaveFileName[0] = 0;

            /* prompt user for file to open */
            ofn.lStructSize = sizeof(OPENFILENAME);
            ofn.hwndOwner = hwnd;
            ofn.hInstance = NULL;
	    AVIBuildFilter(achFilter, sizeof(achFilter)/sizeof(TCHAR), TRUE);
            ofn.lpstrFilter = achFilter;
            ofn.lpstrCustomFilter = NULL;
            ofn.nMaxCustFilter = 0;
            ofn.nFilterIndex = 0;
            ofn.lpstrFile = gachSaveFileName;
            ofn.nMaxFile = sizeof(gachSaveFileName)/sizeof(TCHAR);
            ofn.lpstrFileTitle = NULL;
            ofn.nMaxFileTitle = 0;
            ofn.lpstrInitialDir = NULL;
	    if (GET_WM_COMMAND_ID(wParam, lParam) == MENU_SAVEAS)
                ofn.lpstrTitle = TEXT("Save AVI File");
	    else if (GET_WM_COMMAND_ID(wParam, lParam) == MENU_SAVERAW)
                ofn.lpstrTitle = TEXT("Save Raw");
	    else if (GET_WM_COMMAND_ID(wParam, lParam) == MENU_SAVESMALL)
                ofn.lpstrTitle = TEXT("Save Small");
	    else
                ofn.lpstrTitle = TEXT("Please give me a title");
            ofn.Flags = OFN_PATHMUSTEXIST | OFN_HIDEREADONLY |
			    OFN_OVERWRITEPROMPT;
            ofn.nFileOffset = 0;
            ofn.nFileExtension = 0;
            ofn.lpstrDefExt = TEXT("avi");
            ofn.lCustData = 0;
            ofn.lpfnHook = NULL;
            ofn.lpTemplateName = NULL;

            if (GetSaveFileName(&ofn))
            {
                FARPROC lpfn = MakeProcInstance(SaveCallback, ghInstApp);

		if (lpfn)
		{
		    DWORD	fccHandler[MAXNUMSTREAMS];
		    int		i;
		
		    StartWait();

		    for (i = 0; i < gcpavi; i++)
		        fccHandler[i] = galpAVIOptions[i]->fccHandler;

		    // We only want to save the first video stream
		    if (GET_WM_COMMAND_ID(wParam, lParam) == MENU_SAVESMALL) {
			SaveSmall(gpaviVideo, gachSaveFileName);
			EndWait();
			break;
		    }
		
		    // !!! This won't take away audio compression !!!
		    // We want to save raw -- don't use any video compression
		    if (GET_WM_COMMAND_ID(wParam, lParam) == MENU_SAVERAW)
		        for (i = 0; i < gcpavi; i++)
			    galpAVIOptions[i]->fccHandler = 0;


		    AVISaveV(gachSaveFileName,
			     NULL,
			     (AVISAVECALLBACK) lpfn,
			     gcpavi,
			     gapavi,
			     galpAVIOptions);
		    // !!! error check?

		    // Now put the video compressors back that we stole
		    for (i = 0; i < gcpavi; i++)
		        galpAVIOptions[i]->fccHandler = fccHandler[i];
		
		    EndWait();
		    FreeProcInstance(lpfn);
		    FixWindowTitle(hwnd);
		}
            }
	    break;

	case MENU_NEW:
	    FreeAvi(hwnd);
	    gachFileName[0] = TEXT('\0');
	    FixWindowTitle(hwnd);
	    break;
	
        case MENU_OPEN:
	case MENU_ADD:
            gachFileName[0] = 0;

            /* prompt user for file to open */
            ofn.lStructSize = sizeof(OPENFILENAME);
            ofn.hwndOwner = hwnd;
            ofn.hInstance = NULL;
	    if (wParam == MENU_ADD)
	    {
		ofn.lpstrTitle = TEXT("Merge With");
	    }
	    else
	    {
		ofn.lpstrTitle = TEXT("Open AVI");
	    }

	    if (gachFilter[0] == TEXT('\0'))
		AVIBuildFilter(gachFilter, sizeof(gachFilter)/sizeof(TCHAR), FALSE);
	
	    ofn.lpstrFilter = gachFilter;
            ofn.lpstrCustomFilter = NULL;
            ofn.nMaxCustFilter = 0;
            ofn.nFilterIndex = 0;
            ofn.lpstrFile = gachFileName;
            ofn.nMaxFile = sizeof(gachFileName)/sizeof(TCHAR);
            ofn.lpstrFileTitle = NULL;
            ofn.nMaxFileTitle = 0;
            ofn.lpstrInitialDir = NULL;
            ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
            ofn.nFileOffset = 0;
            ofn.nFileExtension = 0;
            ofn.lpstrDefExt = NULL;
            ofn.lCustData = 0;
            ofn.lpfnHook = NULL;
            ofn.lpTemplateName = NULL;

            if (GetOpenFileNamePreview(&ofn))
            {
		InitAvi(hwnd, gachFileName, wParam);
            }
	    break;

	case MENU_BALL:
	    InitBall(hwnd);
	    break;
	
	case MENU_ZOOMQUARTER:
	    gwZoom = 1;
	    FixScrollbars(hwnd);
            InvalidateRect(hwnd, NULL, TRUE);
	    break;
	
	case MENU_ZOOMHALF:
	    gwZoom = 2;
	    FixScrollbars(hwnd);
            InvalidateRect(hwnd, NULL, TRUE);
	    break;
	
	case MENU_ZOOM1:
	    gwZoom = 4;
	    FixScrollbars(hwnd);
            InvalidateRect(hwnd, NULL, TRUE);
	    break;
	
	case MENU_ZOOM2:
	    gwZoom = 8;
	    FixScrollbars(hwnd);
            InvalidateRect(hwnd, NULL, TRUE);
	    break;
	
	case MENU_ZOOM4:
	    gwZoom = 16;
	    FixScrollbars(hwnd);
            InvalidateRect(hwnd, NULL, TRUE);
            break;

	//
        // readly play the file, via MCI
	//
        case MENU_PLAY_STREAM:
        case MENU_PLAY_FILE:
            {
                TCHAR ach[80];
                HWND hwndMci = MCIWndCreate(NULL, ghInstApp,
                        MCIWNDF_SHOWNAME | MCIWNDF_SHOWMODE |
                        WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                        NULL);

                if (hwndMci) {
                    if (GET_WM_COMMAND_ID(wParam,lParam) == MENU_PLAY_FILE)
                        wsprintf(ach, TEXT("AVIVideo!@%ld"), gpfile);
                    else
                        wsprintf(ach, TEXT("AVIVideo!@%ld"), gapavi[giCurrentStream]);

                    MCIWndOpen(hwndMci, ach, 0);
                    MCIWndPlay(hwndMci);
                }
            }
	    break;

	//
	// Simulate playing the file.  We just play the 1st audio stream and let
	// our main message loop scroll the video by whenever it's bored.
	//
	case MENU_PLAY:
	    if (gfAudioFound)
	        aviaudioPlay(hwnd,
			 gpaviAudio,
			 AVIStreamTimeToSample(gpaviAudio, GetScrollTime(hwnd)),
			 AVIStreamEnd(gpaviAudio),
			 FALSE);
	    gfPlaying = TRUE;
	    glPlayStartTime = timeGetTime();
	    glPlayStartPos = GetScrollTime(hwnd);
	    break;

	//
	// Stop the play preview
	//
	case MENU_STOP:
	    if (gfAudioFound)
	        aviaudioStop();
	    gfPlaying = FALSE;
	    break;

#ifdef CLIPSTUFF
	case MENU_COPY:
	{
	    PAVIFILE	    pf;
	
	    AVIMakeFileFromStreams(&pf, gcpavi, gapavi);
	    AVIPutFileOnClipboard(pf);
	    AVIFileClose(pf);
	}
	    break;

	case MENU_CFILE:
	    AVIPutFileOnClipboard(gpfile);
	    break;
	
	case MENU_PASTE:
	{
	    PAVIFILE		pf  = NULL;

	    AVIGetFromClipboard(&pf);

	    if (pf) {
		DPF("Pasting file from clipboard....\n");
		FreeDrawStuff(hwnd);

		InsertAVIFile(pf, hwnd, TEXT("Clipboard"));
	    } else {
		DPF("Can't get file from clipboard....\n");
	    }
	    break;
	}
#endif
    }
    return 0L;
}

/*----------------------------------------------------------------------------*\
|   ErrMsg - Opens a Message box with a error message in it.  The user can     |
|            select the OK button to continue                                  |
\*----------------------------------------------------------------------------*/
int ErrMsg (LPTSTR sz,...)
{
    static TCHAR ach[2000];
    va_list va;

    va_start(va, sz);
    wvsprintf (ach,sz, va);
    va_end(va);
    MessageBox(NULL,ach,NULL,
#ifdef BIDI
		MB_RTL_READING |
#endif
    MB_OK|MB_ICONEXCLAMATION|MB_TASKMODAL);
    return FALSE;
}

/*----------------------------------------------------------------------------*\
|   fDialog(id,hwnd,fpfn)						       |
|									       |
|   Description:                                                               |
|	This function displays a dialog box and returns the exit code.	       |
|	the function passed will have a proc instance made for it.	       |
|									       |
|   Arguments:                                                                 |
|	id		resource id of dialog to display		       |
|	hwnd		parent window of dialog 			       |
|	fpfn		dialog message function 			       |
|                                                                              |
|   Returns:                                                                   |
|	exit code of dialog (what was passed to EndDialog)		       |
|                                                                              |
\*----------------------------------------------------------------------------*/
BOOL fDialog(int id,HWND hwnd,FARPROC fpfn)
{
    BOOL	f;
    HANDLE	hInst;

    hInst = GetWindowInstance(hwnd);
    fpfn  = MakeProcInstance(fpfn,hInst);
    f = DialogBox(hInst,MAKEINTRESOURCE(id),hwnd,fpfn);
    FreeProcInstance (fpfn);
    return f;
}

/*****************************************************************************
 *
 * dprintf() is called by the DPF macro if DEBUG is defined at compile time.
 *
 * The messages will be send to COM1: like any debug message. To
 * enable debug output, add the following to WIN.INI :
 *
 * [debug]
 * ICSAMPLE=1
 *
 ****************************************************************************/

#ifdef DEBUG

#define MODNAME "AVIVIEW"

static void FAR cdecl dprintf(LPSTR szFormat, ...)
{
    char ach[128];

    static BOOL fDebug = -1;
    va_list va;

    if (fDebug == -1)
        fDebug = GetProfileIntA("Debug", MODNAME, FALSE);

    if (!fDebug)
        return;

    lstrcpyA(ach, MODNAME ": ");
    va_start(va, szFormat);
    wvsprintfA(ach+lstrlenA(ach),szFormat, va);
    va_end(va);
//    lstrcat(ach, "\r\n");

    OutputDebugStringA(ach);
}

#endif


// !!! function that makes DIBs half as big
BOOL CrunchDIB(
    LPBITMAPINFOHEADER  lpbiSrc,    // BITMAPINFO of source
    LPVOID              lpSrc,      // input bits to crunch
    LPBITMAPINFOHEADER  lpbiDst,    // BITMAPINFO of dest
    LPVOID              lpDst);     // output bits to crunch


//
// Save a video stream into a new file, after calling CrunchDib on each frame...
//
void SaveSmall(PAVISTREAM ps, LPTSTR lpFilename)
{
    PAVIFILE		pf;
    PAVISTREAM		psSmall = NULL;
    HRESULT		hr;
    AVISTREAMINFO	strhdr;
    BITMAPINFOHEADER	bi;
    BITMAPINFOHEADER	biNew;
    LONG		l;
	
    LPVOID		lpOld = NULL;
    LPVOID		lpNew = NULL;

    AVIStreamFormatSize(ps, 0, &l);
    if (l > sizeof(bi))
	return;

    l = sizeof(bi);
    hr = AVIStreamReadFormat(ps, 0, &bi, &l);
    if (bi.biCompression != BI_RGB)
	return;

    hr = AVIStreamInfo(ps, &strhdr, sizeof(strhdr));

    hr = AVIFileOpen(&pf, lpFilename, OF_WRITE | OF_CREATE, NULL);
    if (hr != 0)
	return;

    biNew = bi;
    biNew.biWidth /= 2;
    biNew.biHeight /= 2;
    biNew.biSizeImage = ((((UINT)biNew.biBitCount * biNew.biWidth + 31)&~31) / 8) *
			biNew.biHeight;

    SetRect(&strhdr.rcFrame, 0, 0, (int) biNew.biWidth, (int) biNew.biHeight);

    hr = AVIFileCreateStream(pf, &psSmall, &strhdr);
    if (hr != 0) {
	goto exit;
    }

    hr = AVIStreamSetFormat(psSmall, 0, &biNew, sizeof(biNew));
    if (hr != 0) {
	goto exit;
    }

    lpOld = GlobalAllocPtr(GMEM_MOVEABLE, bi.biSizeImage);
    lpNew = GlobalAllocPtr(GMEM_MOVEABLE, biNew.biSizeImage);

    if (!lpOld || !lpNew) {
	goto exit;
    }

    for (l = AVIStreamStart(ps); l < AVIStreamEnd(ps); l++) {
	hr = AVIStreamRead(ps, l, 1, lpOld, bi.biSizeImage, NULL, NULL);
	// !!! error check
	
	CrunchDIB(&bi, lpOld, &biNew, lpNew);

	hr = AVIStreamWrite(psSmall, l, 1, lpNew, biNew.biSizeImage, AVIIF_KEYFRAME, NULL, NULL);
	// !!! error check
    }

exit:
    if (lpOld)
	GlobalFreePtr(lpOld);
    if (lpNew)
	GlobalFreePtr(lpNew);
    if (psSmall)
	AVIStreamClose(psSmall);
    AVIFileClose(pf);
}