MSTREAM.C

/*========================================================================== 
*
* Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
*
* File: mstream.c
* Content: Illustrates streaming data from a disk MIDI file to a
* MIDI stream buffer for playback.
*
***************************************************************************/
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <memory.h>
#include <mmreg.h>

#include "resource.h"
#include "debug.h"
#include "midstuff.h"
#include "mstream.h"

//////////////////////////////////////////////////////////////////////////////
// Lots of global variables

char szAppClass[] = "MStreamWndClass";
char szAppName[] = "MStream";

char szAppTitle[64];
char szAppCaption[64];
char szOpenFilter[128];
char szOpenDLGTitle[64];
char szProgress[64];
char szTempo[64];
char szVolume[64];

char szTemp[256];
char szDebug[256];
char szFileBuffer[MAX_PATH];
char szFileTitle[MAX_PATH];

HWNDhWndMain, hWndProgText, hWndProg, hWndVolText, hWndVol, hWndTempoText;
HWNDhWndTempo, hWndLoopCheck, hWndPlay, hWndPause, hWndStop;

HINSTANCEhInst;

BOOLbFileOpen = FALSE, bPlaying = FALSE, bBuffersPrepared = FALSE;
BOOLbPaused = FALSE, bLooped = FALSE;
UINTuMIDIDeviceID = MIDI_MAPPER, uCallbackStatus;
intnTextControlHeight, nCurrentBuffer, nEmptyBuffers;
DWORDdwBufferTickLength, dwTempoMultiplier, dwCurrentTempo, dwProgressBytes;
DWORDdwVolumePercent, dwVolCache[NUM_CHANNELS];

HMIDISTRM hStream;
CONVERTINFO ciStreamBuffers[NUM_STREAM_BUFFERS];

// Private to this module...
static HANDLEhBufferReturnEvent;

// From another module...
extern INFILESTATEifs;

///////////////////////////////////////////////////////////////////////////////
// Module-scope function declarations

static BOOL InitApp( HINSTANCE );
static BOOL InitInstance( HINSTANCE, int );
static void FreeBuffers( void );

/******************************************************************************
* WinMain()
*
* Entry point for all Windows programs - performs initialization, message loop
*/
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow )
{
MSG msg;

hInst = hInstance;

/* Make sure the common controls are loaded for our use */
InitCommonControls();

// Turn debugging output on or off
#ifdef DEBUG
DbgInitialize( TRUE );
#else
DbgInitialize( FALSE );
#endif

if( !hPrevInstance )
if( !InitApp( hInstance ))
{
ErrorMessageBox( IDS_ERROR_APPINIT, MB_ICONSTOP );
return( FALSE );
}

if( !InitInstance( hInstance, nCmdShow ))
{
ErrorMessageBox( IDS_ERROR_INSTANCEINIT, MB_ICONSTOP );
return( FALSE );
}

while( GetMessage((LPMSG)&msg, NULL, 0, 0 ))
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}

UnregisterClass( szAppClass, hInstance );
return( msg.wParam );
} /* End of WinMain() */


/*****************************************************************************/
/* InitApp() */
/* */
/* Inits things that only need to be created once for the this application */
/* (like creating the window class). */
/*****************************************************************************/
static BOOL InitApp( HINSTANCE hInstance )
{
WNDCLASSwc;

/* Set up and register a window class */
wc.style= CS_HREDRAW | CS_VREDRAW;
wc.lpszClassName= szAppClass;
wc.lpfnWndProc= (WNDPROC)MainWindowProc;
wc.cbClsExtra= 0;
wc.cbWndExtra= sizeof( DWORD );
wc.hInstance= hInstance;
wc.hIcon= LoadIcon( hInstance, MAKEINTRESOURCE( IDI_ICON3 ));
wc.hCursor= LoadCursor( NULL, IDC_ARROW );
wc.hbrBackground= (HBRUSH)( COLOR_WINDOW );
wc.lpszMenuName= MAKEINTRESOURCE( IDR_MAINMENU );

if( !RegisterClass( &wc ))
{
ErrorMessageBox( IDS_ERROR_REGISTERCLASS, MB_ICONSTOP );
return( FALSE );
}
return( TRUE );
} /* End of InitApp() */


/*****************************************************************************/
/* InitInstance() */
/* */
/* Performs initialization that must be done for each application instance. */
/* */
/*****************************************************************************/
static BOOL InitInstance( HINSTANCE hInstance, int nCmdShow )
{
HWNDhWnd;
RECTcrect;
UINTuCharsRead;
MMRESULTmmrRetVal;

LoadString( hInstance, IDS_APP_TITLE, szAppTitle, sizeof(szAppTitle));
LoadString( hInstance, IDS_APP_CAPTION, szAppCaption, sizeof(szAppCaption));
LoadString( hInstance, IDS_TBTITLE_VOLUME, szVolume, sizeof(szVolume));
LoadString( hInstance, IDS_TBTITLE_TEMPO, szTempo, sizeof(szTempo));
LoadString( hInstance, IDS_TBTITLE_PROGRESS,
szProgress, sizeof(szProgress));
LoadString( hInstance, IDS_OPEN_DLGTITLE,
szOpenDLGTitle, sizeof(szOpenDLGTitle));
/* This is a little trick designed to allow us to load a common dialog box
* filter string, which is really a concatentation of several NULL-terminated
* strings. Note that while is is possible to enter something else into the
* resource as placeholders for the NULL characters, this has the undesireable
* effect of forcing us to search-and-replace byte-by-byte and doesn't make it
* as easy to internationalize our strings...
*/
memset( szOpenFilter, 0, sizeof(szOpenFilter));
uCharsRead = LoadString( hInstance, IDS_OPEN_FILTER1,
szOpenFilter, sizeof(szOpenFilter)) + 1;
uCharsRead += LoadString( hInstance, IDS_OPEN_FILTER2,
&szOpenFilter[uCharsRead],
sizeof(szOpenFilter) - uCharsRead ) + 1;
uCharsRead += LoadString( hInstance, IDS_OPEN_FILTER3,
&szOpenFilter[uCharsRead],
sizeof(szOpenFilter) - uCharsRead ) + 1;
LoadString( hInstance, IDS_OPEN_FILTER4,
&szOpenFilter[uCharsRead],
sizeof(szOpenFilter) - uCharsRead );

/* Create an application window */
hWnd = CreateWindow( szAppClass,/* class name */
szAppCaption,/* caption for window */
WS_OVERLAPPEDWINDOW,/* style */
CW_USEDEFAULT,/* x position */
CW_USEDEFAULT,/* y position */
CW_USEDEFAULT,/* width */
CW_USEDEFAULT,/* height */
NULL,/* parent window */
NULL,/* menu */
hInstance,/* instance */
NULL );/* parms */

if( !hWnd )
{
ErrorMessageBox( IDS_ERROR_MAINWNDCREATE, MB_ICONSTOP );
return( FALSE );
}

hWndMain = hWnd;
GetClientRect( hWndMain, &crect );

/* Create some controls for things like volume, tempo, progress, etc. */
if( CreateChildren( crect ))
return( FALSE );

// Resize window, now that we know the height of the static text controls
SetWindowPos( hWnd, NULL, 0, 0,
2 * BORDER_SPACE_CX + TEMPO_TB_CX + 2 * CONTROL_SPACE_CX
+ VOL_TB_CX + CHECK_CX,
2 * BORDER_SPACE_CY + nTextControlHeight + TEXT_SPACE_CY
+ CONTROL_SPACE_CY + BUTTON_CY + TEMPO_TB_CY,
SWP_NOMOVE | SWP_NOZORDER );

ShowWindow( hWnd, nCmdShow );
UpdateWindow( hWnd );

if(( mmrRetVal = midiStreamOpen( &hStream,
&uMIDIDeviceID,
(DWORD)1, (DWORD)MidiProc,
(DWORD)0,
CALLBACK_FUNCTION )) != MMSYSERR_NOERROR )
{
MidiErrorMessageBox( mmrRetVal );
return( FALSE );
}

return( TRUE );
} /* End of InitInstance() */


/****************************************************************************/
/* MainWindowProc() */
/* */
/* Messages for our main window are handled here */
/* */
/****************************************************************************/
LRESULT CALLBACK MainWindowProc( HWND hWnd, unsigned uMsg,
WPARAM wParam, LPARAM lParam )
{
LPMINMAXINFO lpMinMax;
DWORDdwCDErr = 0;
BOOLbResult = FALSE;
MMRESULTmmrRetVal;

switch( uMsg )
{
case WM_CREATE:
hBufferReturnEvent = CreateEvent( NULL, FALSE,
FALSE, "Wait For Buffer Return" );
break;

case WM_MSTREAM_UPDATEVOLUME:
SetChannelVolume( wParam, dwVolumePercent );
break;

case WM_MSTREAM_PROGRESS:
/* Set the Progress text */
wsprintf( szTemp, "%s: %lu bytes", szProgress, dwProgressBytes );
Static_SetText( hWndProgText, szTemp );
break;

case WM_COMMAND:
switch( LOWORD( wParam ))
{
case IDM_FILE_OPEN:
{
OPENFILENAMEofn;
/*
* Clear out and fill in an OPENFILENAME structure in preparation
* for creating a common dialog box to open a file.
*/
memset( &ofn, 0, sizeof(OPENFILENAME));
ofn.lStructSize= sizeof(OPENFILENAME);
ofn.hwndOwner= hWnd;
ofn.hInstance= hInst;
ofn.lpstrFilter= szOpenFilter;
ofn.nFilterIndex= 1;
szFileBuffer[0]= '\0';
ofn.lpstrFile= szFileBuffer;
ofn.nMaxFile= sizeof(szFileBuffer);
ofn.lpstrFileTitle= szFileTitle;
ofn.nMaxFileTitle= sizeof(szFileTitle);
ofn.lpstrDefExt= "MID";
ofn.lpstrTitle= szOpenDLGTitle;
ofn.Flags= OFN_FILEMUSTEXIST;

bResult = GetOpenFileName( &ofn ); /* Do the dialog box */

/*
*A return of TRUE indicates that the user did not select a filename.
* The possible reasons are: Cancel was clicked, or an error occured.
* If Cancel was clicked, the CommDlgExtendedError() function will not
* return a valid code. For anything else, an error code will come back.
*/
if( bResult == FALSE )
{
dwCDErr = CommDlgExtendedError();
if( dwCDErr )
{
/* Handle a common dialog box error */
HandleCommDlgError( dwCDErr );
}
else/* Clicked Cancel, so finish msg processing */
return( 0 );
}
else
{
if( bFileOpen )
{
// Need to close the previous file before we open a new one. The best way to
// do this is by faking a menu command, so that we only have the actual code in
// one place and it can easily be changed.
SendMessage( hWnd, WM_COMMAND,
MAKEWPARAM( IDM_FILE_CLOSE, 0 ), 0L );
}

if( StreamBufferSetup())
{
// Error opening the MIDI file so abort
// The function already took care of notification
break;
}
else
{
bFileOpen = TRUE;
EnableWindow( hWndPlay, TRUE );
BuildTitleBarText();
}
}
}
break;

case IDM_FILE_CLOSE:
SendMessage( hWnd, WM_COMMAND,
MAKEWPARAM( IDC_STOP,
MSTREAM_STOPF_NOREOPEN ), 0L );
BuildTitleBarText();
break;

case IDM_HELP_ABOUT:
DialogBox( hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWndMain,
(DLGPROC)DLG_About );
break;

case IDM_ACTIONS_PAUSE:
case IDC_PAUSE:
if( bPaused )
midiStreamRestart( hStream );
else
midiStreamPause( hStream );
bPaused = !bPaused;
// If we're paused, the title bar will show (Paused)
BuildTitleBarText();
break;

case IDM_ACTIONS_LOOPED:
case IDC_LOOPCHECK:
Button_SetCheck( hWndLoopCheck, !bLooped );
bLooped = !bLooped;
break;

case IDM_ACTIONS_PLAY:
case IDC_PLAY:
// Clicking play while playback is paused will un-pause it
if( bPaused )
{
SendMessage( hWnd, WM_COMMAND,
MAKEWPARAM( IDC_PAUSE, 0 ), 0L );
break;
}
// Clicking play while playing will restart from scratch
if( bPlaying )
{
// Stop the file, allowing it to bve reset so that we
// can start it over again from the beginning
SendMessage( hWnd, WM_COMMAND,
MAKEWPARAM( IDC_STOP, 0 ), 0L );
}

if( bFileOpen )
{
// Clear the status of our callback so it will handle
// MOM_DONE callbacks once more
uCallbackStatus = 0;
if(( mmrRetVal = midiStreamRestart( hStream ))
!= MMSYSERR_NOERROR )
{
MidiErrorMessageBox( mmrRetVal );
break;
}
}
else
{
bPlaying = FALSE;
break;
}

bPlaying = TRUE;
EnableWindow( hWndPause, TRUE );
EnableWindow( hWndStop, TRUE );
break;

case IDM_ACTIONS_STOP:
case IDC_STOP:
if( bFileOpen || bPlaying
|| ( uCallbackStatus != STATUS_CALLBACKDEAD ))
{
EnableWindow( hWndStop, FALSE );
EnableWindow( hWndPause, FALSE );
bPlaying = bPaused = FALSE;
if( uCallbackStatus != STATUS_CALLBACKDEAD && uCallbackStatus != STATUS_WAITINGFOREND )
uCallbackStatus = STATUS_KILLCALLBACK;

if(( mmrRetVal = midiStreamStop( hStream ))
!= MMSYSERR_NOERROR )
{
MidiErrorMessageBox( mmrRetVal );
break;
}
if(( mmrRetVal = midiOutReset( (HMIDIOUT)hStream ))
!= MMSYSERR_NOERROR )
{
MidiErrorMessageBox( mmrRetVal );
break;
}
// Wait for the callback thread to release this thread, which it will do by
// calling SetEvent() once all buffers are returned to it
if( WaitForSingleObject( hBufferReturnEvent,
DEBUG_CALLBACK_TIMEOUT )
== WAIT_TIMEOUT )
{
// Note, this is a risky move because the callback may be genuinely busy, but
// when we're debugging, it's safer and faster than freezing the application,
// which leaves the MIDI device locked up and forces a system reset...
DebugPrint( "Timed out waiting for MIDI callback" );
uCallbackStatus = STATUS_CALLBACKDEAD;
}
}

if( uCallbackStatus == STATUS_CALLBACKDEAD )
{
uCallbackStatus = 0;
if( bFileOpen )
{
ConverterCleanup();
FreeBuffers();
if( hStream )
{
if(( mmrRetVal = midiStreamClose( hStream ))
!= MMSYSERR_NOERROR )
{
MidiErrorMessageBox( mmrRetVal );
}
hStream = NULL;
}

EnableWindow( hWndPlay, FALSE );
bFileOpen = FALSE;
}

if(!( HIWORD(wParam) & MSTREAM_STOPF_NOREOPEN ))
{
if( StreamBufferSetup())
{
// Error setting up for MIDI file
// Notification is already taken care of...
break;
}
else
{
bFileOpen = TRUE;
EnableWindow( hWndPlay, TRUE );
}
}
BuildTitleBarText();// Update the title bar
}
break;

case IDM_FILE_EXIT:
DestroyWindow( hWnd );
break;
}
break;

case WM_INITMENU:
if( bFileOpen )
{
EnableMenuItem( GetMenu( hWnd ), IDM_ACTIONS_PLAY,
MF_BYCOMMAND | MF_ENABLED );
EnableMenuItem( GetMenu( hWnd ), IDM_FILE_CLOSE,
MF_BYCOMMAND | MF_ENABLED );
}
else
{
EnableMenuItem( GetMenu( hWnd ), IDM_ACTIONS_PLAY,
MF_BYCOMMAND | MF_GRAYED );
EnableMenuItem( GetMenu( hWnd ), IDM_FILE_CLOSE,
MF_BYCOMMAND | MF_GRAYED );
}
if( bLooped )
EnableMenuItem( GetMenu( hWnd ), IDM_ACTIONS_LOOPED,
MF_BYCOMMAND | MF_ENABLED );
else
EnableMenuItem( GetMenu( hWnd ), IDM_ACTIONS_LOOPED,
MF_BYCOMMAND | MF_GRAYED );
if( bPlaying )
{
EnableMenuItem( GetMenu( hWnd ), IDM_ACTIONS_PAUSE,
MF_BYCOMMAND | MF_ENABLED );
EnableMenuItem( GetMenu( hWnd ), IDM_ACTIONS_STOP,
MF_BYCOMMAND | MF_ENABLED );
}
else
{
EnableMenuItem( GetMenu( hWnd ), IDM_ACTIONS_PAUSE,
MF_BYCOMMAND | MF_GRAYED );
EnableMenuItem( GetMenu( hWnd ), IDM_ACTIONS_STOP,
MF_BYCOMMAND | MF_GRAYED );
}
break;

case WM_GETMINMAXINFO:
/*
* We know exactly how big this window should be, and it's sort of a
* little pop-up control panel, so we can disable window sizing by
* forcing all the minimum and maximum sizes to be the calculated size.
*/
lpMinMax = (LPMINMAXINFO)lParam;

lpMinMax->ptMaxSize.x = 2*CONTROL_SPACE_CX + 2*BORDER_SPACE_CX
+ CHECK_CX + TEMPO_TB_CX + VOL_TB_CX
+ 2*GetSystemMetrics( SM_CXBORDER );
lpMinMax->ptMaxSize.y = 2*(BORDER_SPACE_CY
+ GetSystemMetrics( SM_CYBORDER ))
+ TEXT_SPACE_CY + nTextControlHeight
+ TEMPO_TB_CY + BUTTON_CY
+ CONTROL_SPACE_CY
+ GetSystemMetrics( SM_CYMENU )
+ GetSystemMetrics( SM_CYCAPTION );

lpMinMax->ptMinTrackSize.x = lpMinMax->ptMaxTrackSize.x
= lpMinMax->ptMaxSize.x;

lpMinMax->ptMinTrackSize.y = lpMinMax->ptMaxTrackSize.y
= lpMinMax->ptMaxSize.y;
break;

case WM_HSCROLL:
if(((HWND)lParam == hWndTempo) && bFileOpen )
{
HandleTempoScroll( (int)LOWORD(wParam), (int)HIWORD(wParam));
}
else if(((HWND)lParam == hWndVol) && bFileOpen )
{
HandleVolScroll( (int)LOWORD(wParam), (int)HIWORD(wParam));
}
break;

case WM_ENDSESSION:
// If the sesson is ending, we need to do our WM_DESTROY processing
if (!wParam) break;
// NOTE!!! we are falling through to the WM_CLOSE processing.
case WM_DESTROY:
// Stop anything that might be playing and send a flag which will
// tell the code not to automatically reload the file for replay.
if( hStream )
SendMessage( hWnd, WM_COMMAND,
MAKEWPARAM( IDC_STOP, MSTREAM_STOPF_NOREOPEN ), 0 );

FreeBuffers();

if( hStream )
{
if(( mmrRetVal = midiStreamClose( hStream ))
!= MMSYSERR_NOERROR )
{
MidiErrorMessageBox( mmrRetVal );
}
hStream = NULL;
}

CloseHandle( hBufferReturnEvent );

PostQuitMessage( 0 );
break;

default:
return DefWindowProc( hWnd, uMsg, wParam, lParam );
}
return 0L;
} /* MainWindowProc */


/*****************************************************************************/
/* DLG_About() */
/* */
/* Dialog procedure for the Help...About... box which simply pops up a */
/* little copyright message and brief program description. */
/* */
/*****************************************************************************/
BOOL CALLBACK DLG_About( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg )
{
case WM_INITDIALOG:
break;

case WM_COMMAND:
switch( LOWORD(wParam))
{
case IDOK:
EndDialog( hDlg, FALSE );
return( TRUE );

default:
break;
}
break;

default:
return( FALSE );
}

return( FALSE );
}


/*****************************************************************************/
/* FreeBuffers() */
/* */
/* This function unprepares and frees all our buffers -- something we must */
/* do to work around a bug in MMYSYSTEM that prevents a device from playing */
/* back properly unless it is closed and reopened after each stop. */
/*****************************************************************************/
void FreeBuffers( void )
{
DWORDidx;
MMRESULTmmrRetVal;

if( bBuffersPrepared )
{
for( idx = 0; idx < NUM_STREAM_BUFFERS; idx++ )
if(( mmrRetVal = midiOutUnprepareHeader( (HMIDIOUT)hStream,
&ciStreamBuffers[idx].mhBuffer,
sizeof(MIDIHDR)))
!= MMSYSERR_NOERROR )
{
MidiErrorMessageBox( mmrRetVal );
}
bBuffersPrepared = FALSE;
}
// Free our stream buffers...
for( idx = 0; idx < NUM_STREAM_BUFFERS; idx++ )
if( ciStreamBuffers[idx].mhBuffer.lpData )
{
GlobalFreePtr( ciStreamBuffers[idx].mhBuffer.lpData );
ciStreamBuffers[idx].mhBuffer.lpData = NULL;
}
}


/*****************************************************************************/
/* StreamBufferSetup() */
/* */
/* This function uses the filename stored in the global character array to*/
/* open a MIDI file. Then it goes about converting at least the first part of*/
/* that file into a midiStream buffer for playback. */
/*****************************************************************************/
BOOL StreamBufferSetup( void )
{
int nChkErr;
BOOL bFoundEnd = FALSE;
DWORD dwConvertFlag, idx;

MMRESULTmmrRetVal;
MIDIPROPTIMEDIVmptd;

if( !hStream )
if(( mmrRetVal = midiStreamOpen( &hStream,
&uMIDIDeviceID,
(DWORD)1, (DWORD)MidiProc,
(DWORD)0,
CALLBACK_FUNCTION )) != MMSYSERR_NOERROR )
{
MidiErrorMessageBox( mmrRetVal );
return( TRUE );
}

for( idx = 0; idx < NUM_STREAM_BUFFERS; idx++ )
{
ciStreamBuffers[idx].mhBuffer.dwBufferLength = OUT_BUFFER_SIZE;
if(( ciStreamBuffers[idx].mhBuffer.lpData
= GlobalAllocPtr( GHND, OUT_BUFFER_SIZE )) == NULL )
{
// Buffers we already allocated will be killed by WM_DESTROY
// after we fail on the create by returning with -1
return( -1 );
}
}
if( ConverterInit( szFileBuffer ))
return( TRUE );

// Initialize the volume cache array to some pre-defined value
for( idx = 0; idx < NUM_CHANNELS; idx++ )
dwVolCache[idx] = VOL_CACHE_INIT;

mptd.cbStruct = sizeof(mptd);
mptd.dwTimeDiv = ifs.dwTimeDivision;
if(( mmrRetVal = midiStreamProperty( hStream, (LPBYTE)&mptd,
MIDIPROP_SET | MIDIPROP_TIMEDIV ))
!= MMSYSERR_NOERROR )
{
MidiErrorMessageBox( mmrRetVal );
ConverterCleanup();
return( TRUE );
}

nEmptyBuffers = 0;
dwConvertFlag = CONVERTF_RESET;

for( nCurrentBuffer = 0; nCurrentBuffer < NUM_STREAM_BUFFERS;
nCurrentBuffer++ )
{
// Tell the converter to convert up to one entire buffer's length of output
// data. Also, set a flag so it knows to reset any saved state variables it
// may keep from call to call.
ciStreamBuffers[nCurrentBuffer].dwStartOffset = 0;
ciStreamBuffers[nCurrentBuffer].dwMaxLength = OUT_BUFFER_SIZE;
ciStreamBuffers[nCurrentBuffer].tkStart = 0;
ciStreamBuffers[nCurrentBuffer].bTimesUp = FALSE;

if(( nChkErr = ConvertToBuffer( dwConvertFlag,
&ciStreamBuffers[nCurrentBuffer] ))
!= CONVERTERR_NOERROR )
{
if( nChkErr == CONVERTERR_DONE )
{
bFoundEnd = TRUE;
}
else
{
DebugPrint( "Initial conversion pass failed" );
ConverterCleanup();
return( TRUE );
}
}
ciStreamBuffers[nCurrentBuffer].mhBuffer.dwBytesRecorded
= ciStreamBuffers[nCurrentBuffer].dwBytesRecorded;

if( !bBuffersPrepared )
if(( mmrRetVal = midiOutPrepareHeader( (HMIDIOUT)hStream,
&ciStreamBuffers[nCurrentBuffer].mhBuffer,
sizeof(MIDIHDR))) != MMSYSERR_NOERROR )
{
MidiErrorMessageBox( mmrRetVal );
ConverterCleanup();
return( TRUE );
}
if(( mmrRetVal = midiStreamOut( hStream,
&ciStreamBuffers[nCurrentBuffer].mhBuffer,
sizeof(MIDIHDR))) != MMSYSERR_NOERROR )
{
MidiErrorMessageBox( mmrRetVal );
break;
}
dwConvertFlag = 0;

if( bFoundEnd )
break;
}

bBuffersPrepared = TRUE;
nCurrentBuffer = 0;
UpdateFromControls();
return( FALSE );
}


/*****************************************************************************/
/* MidiProc() */
/* */
/* This is the callback handler which continually refills MIDI data buffers*/
/* as they're returned to us from the audio subsystem. */
/*****************************************************************************/
void CALLBACK MidiProc( HMIDIIN hMidi, UINT uMsg, DWORD dwInstance,
DWORD dwParam1, DWORD dwParam2 )
{
static intnWaitingBuffers = 0;
MIDIEVENT*pme;
MIDIHDR*pmh;

MMRESULTmmrRetVal;
intnChkErr;


switch( uMsg )
{
case MOM_DONE:
if( uCallbackStatus == STATUS_CALLBACKDEAD )
return;

nEmptyBuffers++;

if( uCallbackStatus == STATUS_WAITINGFOREND )
{
if( nEmptyBuffers < NUM_STREAM_BUFFERS )
{
return;
}
else
{
uCallbackStatus = STATUS_CALLBACKDEAD;
PostMessage( hWndMain, WM_COMMAND,
MAKEWPARAM( IDC_STOP, 0 ), 0L );
SetEvent( hBufferReturnEvent );
return;
}
}

// This flag is set whenever the callback is waiting for all buffers to
// come back.
if( uCallbackStatus == STATUS_KILLCALLBACK )
{
// Count NUM_STREAM_BUFFERS-1 being returned for the last time
if( nEmptyBuffers < NUM_STREAM_BUFFERS )
{
return;
}
// Then send a stop message when we get the last buffer back...
else
{
// Change the status to callback dead
uCallbackStatus = STATUS_CALLBACKDEAD;
SetEvent( hBufferReturnEvent );
return;
}
}

dwProgressBytes
+= ciStreamBuffers[nCurrentBuffer].mhBuffer.dwBytesRecorded;
PostMessage( hWndMain, WM_MSTREAM_PROGRESS, 0L, 0L );

///////////////////////////////////////////////////////////////////////////////
// Fill an available buffer with audio data again...

if( bPlaying && nEmptyBuffers )
{
ciStreamBuffers[nCurrentBuffer].dwStartOffset = 0;
ciStreamBuffers[nCurrentBuffer].dwMaxLength = OUT_BUFFER_SIZE;

ciStreamBuffers[nCurrentBuffer].tkStart = 0; 
ciStreamBuffers[nCurrentBuffer].dwBytesRecorded = 0;
ciStreamBuffers[nCurrentBuffer].bTimesUp = FALSE;

if(( nChkErr = ConvertToBuffer( 0,
&ciStreamBuffers[nCurrentBuffer] ))
!= CONVERTERR_NOERROR )
{
if( nChkErr == CONVERTERR_DONE )
{
// Don't include this one in the count
nWaitingBuffers = NUM_STREAM_BUFFERS - 1;
uCallbackStatus = STATUS_WAITINGFOREND;
return;
}
else
{
DebugPrint( "MidiProc() conversion pass failed!" );
ConverterCleanup();
return;
}
}

ciStreamBuffers[nCurrentBuffer].mhBuffer.dwBytesRecorded
= ciStreamBuffers[nCurrentBuffer].dwBytesRecorded;

if(( mmrRetVal = midiStreamOut( hStream,
&ciStreamBuffers[nCurrentBuffer].mhBuffer,
sizeof(MIDIHDR))) != MMSYSERR_NOERROR )
{
MidiErrorMessageBox( mmrRetVal );
ConverterCleanup();
return;
}
nCurrentBuffer = ( nCurrentBuffer + 1 ) % NUM_STREAM_BUFFERS;
nEmptyBuffers--;
}

break;

case MOM_POSITIONCB:
pmh = (MIDIHDR *)dwParam1;
pme = (MIDIEVENT *)(pmh->lpData + pmh->dwOffset);
if( MIDIEVENT_TYPE( pme->dwEvent ) == MIDI_CTRLCHANGE )
{
if( MIDIEVENT_DATA1( pme->dwEvent ) == MIDICTRL_VOLUME_LSB )
{
DebugPrint( "Got an LSB volume event" );
break;
}
if( MIDIEVENT_DATA1( pme->dwEvent ) != MIDICTRL_VOLUME )
break;

// Mask off the channel number and cache the volume data byte
dwVolCache[ MIDIEVENT_CHANNEL( pme->dwEvent )]
= MIDIEVENT_VOLUME( pme->dwEvent );
// Post a message so that the main program knows to counteract
// the effects of the volume event in the stream with its own
// generated event which reflects the proper trackbar position.
PostMessage( hWndMain, WM_MSTREAM_UPDATEVOLUME,
MIDIEVENT_CHANNEL( pme->dwEvent ), 0L );
}
break;

default:
break;
}

return;
}