|
|
/************************************************************************/
/*
** Copyright (c) 1985-1998 Microsoft Corporation ** ** Title: mciwave.c - Multimedia Systems Media Control Interface ** waveform audio driver for RIFF wave files. ** ** Version: 1.00 ** ** Date: 18-Apr-1990 ** ** Author: ROBWI */
/************************************************************************/
/*
** Change log: ** ** DATE REV DESCRIPTION ** ----------- ----- ------------------------------------------ ** 18-APR-1990 ROBWI Original ** 19-JUN-1990 ROBWI Added wave in ** 13-Jan-1992 MikeTri Ported to NT ** @@@ To be changed ** 3-Mar-1992 SteveDav Continue port */
/************************************************************************/ #define UNICODE
#define NOGDICAPMASKS
#define NOVIRTUALKEYCODES
#define NOWINSTYLES
#define NOSYSMETRICS
#define NOMENUS
#define NOICONS
#define NOKEYSTATES
#define NOSYSCOMMANDS
#define NORASTEROPS
#define NOSHOWWINDOW
#define OEMRESOURCE
#define NOATOM
#define NOCLIPBOARD
#define NOCOLOR
#define NOCTLMGR
#define NODRAWTEXT
#define NOGDI
#define NOKERNEL
#define NONLS
#define NOMB
#define NOMEMMGR
#define NOMETAFILE
#define NOOPENFILE
#define NOSCROLL
#define NOTEXTMETRIC
#define NOWH
//#define NOWINOFFSETS Hides definition of GetDesktopWindow
#define NOCOMM
#define NOKANJI
#define NOHELP
#define NOPROFILER
#define NODEFERWINDOWPOS
#include <windows.h>
#include "mciwave.h"
#include <mmddk.h>
#include <wchar.h>
#include <gmem.h>
STATICFN LPBYTE GlobalReAllocPtr(LPVOID lp, DWORD cbNew, DWORD flags) { HANDLE h, hNew; LPBYTE lpNew = NULL;
h = GlobalHandle(lp); if (!h) { return(NULL); }
GlobalUnlock(h);
hNew = GlobalReAlloc(h , cbNew, flags); if (hNew) { lpNew = GlobalLock(hNew); if (!lpNew) { dprintf1(("FAILED to lock reallocated memory handle %8x (%8x)", hNew, lp)); // we still return the lpNew pointer, even though the memory
// is not locked down. Perhaps this should be an error?
// At this point the existing block could have been trashed!
} else { dprintf3(("Reallocated ptr %8x to %8x (Handle %8x)", lp, lpNew, h)); } } else { dprintf1(("FAILED to realloc memory handle %8x (%8x)", h, lp)); GlobalLock(h); // restore the lock
} return(lpNew); }
PRIVATE DWORD PASCAL FAR time2bytes( PWAVEDESC pwd, DWORD dTime, DWORD dFormat);
PRIVATE DWORD PASCAL FAR bytes2time( PWAVEDESC pwd, DWORD dBytes, DWORD dFormat); PRIVATE UINT PASCAL NEAR mwCheckDevice( PWAVEDESC pwd, DIRECTION Direction);
/************************************************************************/
/*
** The following constants define the default values used when creating ** a new wave file during the MCI_OPEN command. */
#define DEF_CHANNELS 1
#define DEF_AVGBYTESPERSEC 11025L
/************************************************************************/
/*
** hModuleInstance Instance handle of the wave driver module. ** cWaveOutMax Number of wave output devices available. ** cWaveInMax Number of wave output devices available. ** wAudioSeconds Contains the number of seconds of audio buffers to ** allocate for playback and recording. This is set ** during the DRV_OPEN message. ** aszPrefix Contains the prefix to use for temporary file names. */
HINSTANCE hModuleInstance; UINT cWaveOutMax; UINT cWaveInMax; UINT wAudioSeconds; PRIVATE SZCODE aszPrefix[] = L"mci";
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@func VOID | ReleaseWaveBuffers | This function releases all buffers that have been added to the wave input or output device if any device is present. This has the side affect of immediately posting signals to the task for each buffer released. That allows a task to be released if it is waiting for a buffer to be freed, and to leave the current state.
It also has the effect of resetting the byte input and output counters for the wave device, so that accurate byte counts must be retrieved before calling this function.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@rdesc Nothing. */
PRIVATE VOID PASCAL NEAR ReleaseWaveBuffers( PWAVEDESC pwd) { if (pwd->hWaveOut || pwd->hWaveIn) {
if (pwd->Direction == output) waveOutReset(pwd->hWaveOut); else waveInReset(pwd->hWaveIn); } }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api DWORD | time2bytes | Converts the specified time format to a byte equivalent. For converting milliseconds, the <f>MulDiv<d> function is used to avoid overflows on large files with high average sample rates.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@parm DWORD | dTime | Position in Bytes, Samples or Milliseconds.
@parm DWORD | dFormat | Indicates whether time is in Samples, Bytes or Milliseconds.
@rdesc Returns byte offset equivalent of the <p>lTime<d> passed. */
PRIVATE DWORD PASCAL FAR time2bytes( PWAVEDESC pwd, DWORD dTime, DWORD dFormat) { if (dFormat == MCI_FORMAT_SAMPLES) dTime = (DWORD)(MulDiv((LONG)dTime, pwd->pwavefmt->nAvgBytesPerSec, pwd->pwavefmt->nSamplesPerSec) / pwd->pwavefmt->nBlockAlign) * pwd->pwavefmt->nBlockAlign; else if (dFormat == MCI_FORMAT_MILLISECONDS) dTime = (DWORD)MulDiv((LONG)dTime, pwd->pwavefmt->nAvgBytesPerSec, 1000L);
return dTime; }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api DWORD | bytes2time | Converts a byte offset to the specified time format equivalent.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@parm DWORD | dBytes | Position in bytes.
@parm DWORD | dFormat | Indicates whether the return time is in Samples, Bytes or Milliseconds.
@rdesc Returns the specified time equivalent. */
PRIVATE DWORD PASCAL FAR bytes2time( PWAVEDESC pwd, DWORD dBytes, DWORD dFormat) { if (dFormat == MCI_FORMAT_SAMPLES) dBytes = (DWORD)MulDiv((LONG)dBytes, pwd->pwavefmt->nSamplesPerSec, pwd->pwavefmt->nAvgBytesPerSec); else if (dFormat == MCI_FORMAT_MILLISECONDS) dBytes = (DWORD)MulDiv((LONG)dBytes, 1000L, pwd->pwavefmt->nAvgBytesPerSec);
return dBytes; }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api VOID | mwCloseFile | Close the currently open file by releasing the MMIO handle and closing the temporary buffer file, if any.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@rdesc Nothing. */
PRIVATE VOID PASCAL NEAR mwCloseFile( PWAVEDESC pwd) { if (pwd->hmmio) { mmioClose(pwd->hmmio, 0); pwd->hmmio = NULL; }
if (pwd->hTempBuffers != INVALID_HANDLE_VALUE) { CloseHandle(pwd->hTempBuffers);
DeleteFile( pwd->aszTempFile );
pwd->hTempBuffers = 0; }
if (pwd->lpWaveDataNode) { GlobalFreePtr(pwd->lpWaveDataNode); pwd->lpWaveDataNode = NULL; }
if (pwd->pwavefmt) { LocalFree(pwd->pwavefmt); pwd->pwavefmt = NULL; }
}
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api VOID | SetMMIOError | Converts the specified MMIO error to an MCI error, and sets the task error <e>PWAVEDESC.wTaskError<d>.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@parm UINT | wError | Indicates the MMIO error that is to be converted to an MCI error. An unknown MMIO error will generate an MCIERR_INVALID_FILE MCI error.
@rdesc Nothing. */
PRIVATE VOID PASCAL NEAR SetMMIOError( PWAVEDESC pwd, UINT wError) { //Assumes that we already own pwd
switch (wError) { case MMIOERR_FILENOTFOUND: wError = MCIERR_FILE_NOT_FOUND; break;
case MMIOERR_OUTOFMEMORY: wError = MCIERR_OUT_OF_MEMORY; break;
case MMIOERR_CANNOTOPEN: wError = MCIERR_FILE_NOT_FOUND; break;
case MMIOERR_CANNOTREAD: wError = MCIERR_FILE_READ; break;
case MMIOERR_CANNOTWRITE: wError = MCIERR_FILE_WRITE; break;
case MMIOERR_CANNOTSEEK: wError = MCIERR_FILE_READ; break;
case MMIOERR_CANNOTEXPAND: wError = MCIERR_FILE_WRITE; break;
case MMIOERR_CHUNKNOTFOUND: default: wError = MCIERR_INVALID_FILE; break; } pwd->wTaskError = wError; }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api BOOL | ReadWaveHeader | Reads the RIFF header, and wave header chunk from the file. Allocates memory to hold that chunk, and descends into the wave data chunk, storing the offset to the beginning of the actual wave data.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@rdesc Returns TRUE if the current file is a valid RIFF format wave file, and can be read, else FALSE if a read error occurs, or invalid data is encountered. */
PRIVATE BOOL PASCAL NEAR ReadWaveHeader( PWAVEDESC pwd) { MMCKINFO mmckRIFF; MMCKINFO mmck; UINT wError;
mmckRIFF.fccType = mmioWAVE; if (0 != (wError = mmioDescend(pwd->hmmio, &mmckRIFF, NULL, MMIO_FINDRIFF))) { SetMMIOError(pwd, wError); return FALSE; }
mmck.ckid = mmioFMT; if (0 != (wError = mmioDescend(pwd->hmmio, &mmck, &mmckRIFF, MMIO_FINDCHUNK))) { SetMMIOError(pwd, wError); return FALSE; }
if (mmck.cksize < (LONG)sizeof(PCMWAVEFORMAT)) { pwd->wTaskError = MCIERR_INVALID_FILE; return FALSE; }
pwd->wFormatSize = mmck.cksize; pwd->pwavefmt = (WAVEFORMAT NEAR *)LocalAlloc(LPTR, pwd->wFormatSize); if (!pwd->pwavefmt) { pwd->wTaskError = MCIERR_OUT_OF_MEMORY; return FALSE; }
if ((DWORD)mmioRead(pwd->hmmio, (HPSTR)pwd->pwavefmt, mmck.cksize) != mmck.cksize) { pwd->wTaskError = MCIERR_FILE_READ; return FALSE; }
if (0 != (wError = mmioAscend(pwd->hmmio, &mmck, 0))) { SetMMIOError(pwd, wError); return FALSE; }
mmck.ckid = mmioDATA; if (0 != (wError = mmioDescend(pwd->hmmio, &mmck, &mmckRIFF, MMIO_FINDCHUNK))) { SetMMIOError(pwd, wError); return FALSE; }
pwd->dSize = mmck.cksize; pwd->dRiffData = mmck.dwDataOffset; pwd->dAudioBufferLen = BLOCKALIGN(pwd, pwd->pwavefmt->nAvgBytesPerSec); return TRUE; }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api DWORD | mwAllocMoreBlockNodes | This function is called in order to force more wave data nodes to be allocated. This is done in increments of DATANODEALLOCSIZE, and the index to the first new node is returned. The new nodes are initialized as free nodes.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@rdesc Returns the index to the first of the new nodes allocated, else -1 if no memory was available, in which case the task error is set. The node returned is marked as a free node, and need not be discarded if not used. */
PUBLIC DWORD PASCAL FAR mwAllocMoreBlockNodes( PWAVEDESC pwd) { LPWAVEDATANODE lpwdn; DWORD dNewBlockNode;
#ifdef DEBUG
if (pwd->thread) { dprintf(("reentering mwAllocMoreBlockNodes!!")); } #endif
//EnterCrit();
if (pwd->dWaveDataNodes) lpwdn = (LPWAVEDATANODE)GlobalReAllocPtr(pwd->lpWaveDataNode, (pwd->dWaveDataNodes + DATANODEALLOCSIZE) * sizeof(WAVEDATANODE), GMEM_MOVEABLE | GMEM_ZEROINIT); else lpwdn = (LPWAVEDATANODE)GlobalAllocPtr(GMEM_MOVEABLE | GMEM_ZEROINIT, DATANODEALLOCSIZE * sizeof(WAVEDATANODE));
if (lpwdn) { dprintf2(("Set lpWaveDataNode to %8x (it was %8x)", lpwdn, pwd->lpWaveDataNode)); pwd->lpWaveDataNode = lpwdn; for (lpwdn = LPWDN(pwd, pwd->dWaveDataNodes), dNewBlockNode = 0; dNewBlockNode < DATANODEALLOCSIZE; lpwdn++, dNewBlockNode++) RELEASEBLOCKNODE(lpwdn); dNewBlockNode = pwd->dWaveDataNodes; pwd->dWaveDataNodes += DATANODEALLOCSIZE; } else { dprintf1(("** ERROR ** Allocating more block nodes (%8x)", pwd->lpWaveDataNode)); dNewBlockNode = (DWORD)-1; pwd->wTaskError = MCIERR_OUT_OF_MEMORY; }
//LeaveCrit();
return dNewBlockNode; }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api BOOL | CreateTempFile | This function creates the temporary data file used to store newly recorded data before a Save command is issued to perminently store the data in a RIFF format file.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@rdesc Returns TRUE if the temporary data file was created, else FALSE, in which case the task error is set. */
PRIVATE BOOL PASCAL NEAR CreateTempFile( PWAVEDESC pwd) { UINT n; TCHAR tempbuf[_MAX_PATH]; /* First find out where the file should be stored */ n = GetTempPath(sizeof(tempbuf)/sizeof(TCHAR), tempbuf);
if (n && GetTempFileName(tempbuf, aszPrefix, 0, pwd->aszTempFile)) {
pwd->hTempBuffers = CreateFile( pwd->aszTempFile, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
if ( pwd->hTempBuffers != INVALID_HANDLE_VALUE) { return TRUE; } else { dprintf2(("hTempBuffers == INVALID_HANDLE_VALUE in CreateTempFile")); }
} else { dprintf2(("Error %d from GetTempFileName or GetTempPath in CreateTempFile", GetLastError())); } pwd->wTaskError = MCIERR_FILE_WRITE; return FALSE; }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api DWORD | mwFindAnyFreeDataNode | This function is used to find a free wave data node with a minimum of <p>dMinDataLength<d> temporary data space attached. To do this, all the current data nodes are traversed, looking for free ones with at least the specified amount of temporary data storage attached.
As the nodes are being traversed, if a free block is encountered that has no data attached, it is saved. Also, if a node with data attached that is too short, but is at the end of the temporary data storage file is found, that also is saved. These will then be used if an appropriate node can not be found.
If an appropriate node can not be found, but a node pointing to the last of the temporary data was found, then the data is expanded, and that node is returned. Else if an empty node was found, then it is returned with data attached, else a new empty node is created.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@parm DWORD | dMinDataLength | Indicates the minimum amount of temporary data space that must be attached to the wave data node returned. This number is rounded up to the nearest block aligned size.
@rdesc Returns a node with a least the minimum request size of temporary data attached, else -1 if not enough memory was available, or the temporary data file could not be created. In that case, the task error is set. The node returned is marked as in use, and must be discarded if not used. */
PUBLIC DWORD PASCAL FAR mwFindAnyFreeDataNode( PWAVEDESC pwd, DWORD dMinDataLength) { LPWAVEDATANODE lpwdn; DWORD dNewBlockNode; DWORD dEmptyBlockNode; DWORD dEmptyDataNode;
dEmptyBlockNode = (DWORD)-1; dEmptyDataNode = (DWORD)-1; for (lpwdn = LPWDN(pwd, 0), dNewBlockNode = 0; dNewBlockNode < pwd->dWaveDataNodes; lpwdn++, dNewBlockNode++) { if (ISFREEBLOCKNODE(lpwdn)) { if (lpwdn->dTotalLength >= dMinDataLength) { lpwdn->dDataLength = 0; return dNewBlockNode; } if (!lpwdn->dTotalLength) dEmptyBlockNode = dNewBlockNode; else if (lpwdn->dDataStart + lpwdn->dTotalLength == pwd->dWaveTempDataLength) dEmptyDataNode = dNewBlockNode; } }
dMinDataLength = ROUNDDATA(pwd, dMinDataLength); if (dEmptyDataNode != -1) { lpwdn = LPWDN(pwd, dEmptyDataNode); lpwdn->dDataLength = 0; lpwdn->dTotalLength = dMinDataLength; if (UNMASKDATASTART(lpwdn) + lpwdn->dTotalLength > pwd->dWaveTempDataLength) pwd->dWaveTempDataLength = UNMASKDATASTART(lpwdn) + lpwdn->dTotalLength; } else { if ((pwd->hTempBuffers == INVALID_HANDLE_VALUE) && !CreateTempFile(pwd)) return (DWORD)-1; if (dEmptyBlockNode != -1) { dNewBlockNode = dEmptyBlockNode; } else if ((dNewBlockNode = mwAllocMoreBlockNodes(pwd)) == -1) return (DWORD)-1; lpwdn = LPWDN(pwd, dNewBlockNode); lpwdn->dDataStart = MASKDATASTART(pwd->dWaveTempDataLength); lpwdn->dDataLength = 0; lpwdn->dTotalLength = dMinDataLength; pwd->dWaveTempDataLength += lpwdn->dTotalLength; } return dNewBlockNode; }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api VOID | InitMMIOOpen | This function initializes the MMIO open structure by zeroing out all entries, and setting the IO procedure or file type if needed.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@parm <t>LPMMIOINFO<d> | lpmmioInfo | Points to the MMIO structure to initialize.
@rdesc nothing. */
PUBLIC VOID PASCAL FAR InitMMIOOpen( PWAVEDESC pwd, LPMMIOINFO lpmmioInfo) { memset(lpmmioInfo, 0, sizeof(MMIOINFO)); lpmmioInfo->pIOProc = pwd->pIOProc; lpmmioInfo->htask = mciGetCreatorTask(pwd->wDeviceID); }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api BOOL | mwOpenFile | This function opens and verifies the file specified in the wave descriptor block. If no file is specified in the block, a new, unnamed wave format file is opened.
If <e>WAVEDESC.aszFile<d> specifies a non-zero length string, it is assumed to contain the file name to open. The function attempts to open this file name, setting the <e>WAVEDESC.hmmio<d> element, and returns any error.
If on the other hand the file name element is zero length, the function assumes that it is to open a new, unnamed wave file. It attempts to do so using the default parameters.
If the file can be opened, the format information is set. In order to be able to work with formats other than PCM, the length of the format block is not assumed, although the start of the block is assumed to be in PCM header format. The format for a new file is PCM.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@rdesc Returns TRUE if file opened and the header information read, else FALSE, in which case the <e>WAVEDESC.wTaskError<d> flag is set with the MCI error. */
PRIVATE BOOL PASCAL NEAR mwOpenFile( PWAVEDESC pwd) { LPWAVEDATANODE lpwdn;
pwd->dWaveDataStartNode = mwAllocMoreBlockNodes(pwd); if (pwd->dWaveDataStartNode == -1) return FALSE;
if (*pwd->aszFile) { MMIOINFO mmioInfo;
InitMMIOOpen(pwd, &mmioInfo); pwd->hmmio = mmioOpen(pwd->aszFile, &mmioInfo, MMIO_READ | MMIO_DENYWRITE);
if (pwd->hmmio == NULL) SetMMIOError(pwd, mmioInfo.wErrorRet); else if (ReadWaveHeader(pwd)) { lpwdn = LPWDN(pwd, pwd->dWaveDataStartNode); lpwdn->dDataLength = pwd->dSize; lpwdn->dTotalLength = pwd->dSize; lpwdn->dNextWaveDataNode = (DWORD)ENDOFNODES;
pwd->wTaskError = mwCheckDevice( pwd, pwd->Direction ); if (pwd->wTaskError) { mwCloseFile(pwd); return FALSE; } else { return TRUE; } } } else { pwd->pwavefmt = (WAVEFORMAT NEAR *)LocalAlloc(LPTR, sizeof(PCMWAVEFORMAT));
if (pwd->pwavefmt) { pwd->pwavefmt->wFormatTag = WAVE_FORMAT_PCM; pwd->pwavefmt->nChannels = DEF_CHANNELS; pwd->pwavefmt->nAvgBytesPerSec = DEF_AVGBYTESPERSEC; pwd->pwavefmt->nSamplesPerSec = DEF_AVGBYTESPERSEC / DEF_CHANNELS; pwd->pwavefmt->nBlockAlign = (WORD)(pwd->pwavefmt->nSamplesPerSec / pwd->pwavefmt->nAvgBytesPerSec); ((NPPCMWAVEFORMAT)(pwd->pwavefmt))->wBitsPerSample = (WORD)pwd->pwavefmt->nBlockAlign * (WORD)8; pwd->wFormatSize = sizeof(PCMWAVEFORMAT); pwd->dAudioBufferLen = BLOCKALIGN(pwd, DEF_AVGBYTESPERSEC);
if ((pwd->dWaveDataStartNode = mwFindAnyFreeDataNode(pwd, pwd->dAudioBufferLen)) != -1) { pwd->dWaveDataCurrentNode = pwd->dWaveDataStartNode; lpwdn = LPWDN(pwd, pwd->dWaveDataStartNode); lpwdn->dNextWaveDataNode = (DWORD)ENDOFNODES; return TRUE; } } else pwd->wTaskError = MCIERR_OUT_OF_MEMORY; }
mwCloseFile(pwd); return FALSE; }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api VOID | mwFreeDevice | This function frees the current wave device, if any.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@rdesc Nothing. */
PRIVATE VOID PASCAL NEAR mwFreeDevice( PWAVEDESC pwd) { if (pwd->hWaveOut || pwd->hWaveIn) { if (pwd->Direction == output) { waveOutClose(pwd->hWaveOut); pwd->hWaveOut = NULL; } else { waveInClose(pwd->hWaveIn); pwd->hWaveIn = NULL; }
while (TaskBlock() != WM_USER); } }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api UINT | mwCheckDevice | This function checks whether given the specified parameters, a compatible wave device is available. Depending upon the current settings in the wave descriptor block, a specific device, or all devices might be checked for the specified direction.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@parm DIRECTION | Direction | Indicates whether the parameters are being checked for input or for output.
@rdesc Returns zero on success, else an MCI error code. */
PRIVATE UINT PASCAL NEAR mwCheckDevice( PWAVEDESC pwd, DIRECTION Direction) { UINT wReturn;
if (!pwd->pwavefmt->nBlockAlign) return MCIERR_OUTOFRANGE; wReturn = 0;
if (Direction == output) { if (waveOutOpen(NULL, pwd->idOut, (NPWAVEFORMATEX)pwd->pwavefmt, 0L, 0L, (DWORD)WAVE_FORMAT_QUERY)) wReturn = (pwd->idOut == WAVE_MAPPER) ? MCIERR_WAVE_OUTPUTSUNSUITABLE : MCIERR_WAVE_SETOUTPUTUNSUITABLE;
} else if (waveInOpen(NULL, pwd->idOut, (NPWAVEFORMATEX)pwd->pwavefmt, 0L, 0L, (DWORD)WAVE_FORMAT_QUERY)) wReturn = (pwd->idOut == WAVE_MAPPER) ? MCIERR_WAVE_INPUTSUNSUITABLE : MCIERR_WAVE_SETINPUTUNSUITABLE;
return wReturn; }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api UINT | mwGetDevice | This function opens the specified input or output wave device. If the device id is -1, then the first available device which supports the format will be opened.
If the function fails to get a suitable device, it checks to see if there are any that would have worked if they were not in use. This is in order to return a more clear error to the calling function.
The function initially tries to open the device requested or the default device. Failing this, if the wave information block specifies that any device can be used, it attempts to open an appropriate device.
If all else fails, the current configuration is checked to determine if any device would have worked had it been available, and the appropriate error is returned.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@rdesc Returns 0 if wave device is successfully opened, else an MCI error. */
PRIVATE UINT PASCAL NEAR mwGetDevice( PWAVEDESC pwd) { UINT wReturn;
#if DBG
if (GetCurrentThreadId() != dwCritSecOwner) { dprintf1(("mwGetDevice called while outside the critical section")); }
#endif
wReturn = 0; if (pwd->Direction == output) { if (waveOutOpen(&(pwd->hWaveOut), pwd->idOut, (NPWAVEFORMATEX)pwd->pwavefmt, (DWORD)pwd->hTask, 0L, (DWORD)CALLBACK_TASK)) { pwd->hWaveOut = NULL; wReturn = mwCheckDevice(pwd, pwd->Direction); if (!wReturn) { if (pwd->idOut == WAVE_MAPPER) wReturn = MCIERR_WAVE_OUTPUTSINUSE; else wReturn = MCIERR_WAVE_SETOUTPUTINUSE; } } } else if (waveInOpen(&(pwd->hWaveIn), pwd->idIn, (NPWAVEFORMATEX)pwd->pwavefmt, (DWORD)pwd->hTask, 0L, (DWORD)CALLBACK_TASK)) { pwd->hWaveIn = NULL; wReturn = mwCheckDevice(pwd, pwd->Direction); if (!wReturn) { if (pwd->idIn == WAVE_MAPPER) wReturn = MCIERR_WAVE_INPUTSINUSE; else wReturn = MCIERR_WAVE_SETINPUTINUSE; } } return wReturn; }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api DWORD | mwDelayedNotify | This is a utility function that sends a notification saved with <f>mwSaveCallback<d> to mmsystem which posts a message to the application. If there is no current notification callback handle, no notification is attempted.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@parm UINT | wStatus | Speicifies the type of notification to use.
@flag MCI_NOTIFY_SUCCESSFUL | Operation completed successfully.
@flag MCI_NOTIFY_SUPERSEDED | A new command which specified notification, but did not interrupt the current operation was received.
@flag MCI_NOTIFY_ABORTED | The current command was aborted due to receipt of a new command.
@flag MCI_NOTIFY_FAILURE | The current operation failed.
@rdesc Nothing. */
PUBLIC VOID PASCAL FAR mwDelayedNotify( PWAVEDESC pwd, UINT wStatus) { if (pwd->hwndCallback) { dprintf3(("Calling driver callback")); mciDriverNotify(pwd->hwndCallback, pwd->wDeviceID, wStatus); pwd->hwndCallback = NULL; } }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api VOID | mwImmediateNotify | This is a utility function that sends a successful notification message to mmsystem.
@parm MCIDEVICEID | wDeviceID | Device ID.
@parm <t>LPMCI_GENERIC_PARMS<d> | lpParms | Far pointer to an MCI parameter block. The first field of every MCI parameter block is the callback handle.
@rdesc Nothing. */
PRIVATE VOID PASCAL NEAR mwImmediateNotify( MCIDEVICEID wDeviceID, LPMCI_GENERIC_PARMS lpParms) { mciDriverNotify((HWND)(lpParms->dwCallback), wDeviceID, MCI_NOTIFY_SUCCESSFUL); }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api VOID | mwSaveCallback | This is a utility function that saves a new callback in the instance data block.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@parm HHWND | hwndCallback | Callback handle to save.
@rdesc Nothing. */
PRIVATE VOID PASCAL NEAR mwSaveCallback( PWAVEDESC pwd, HWND hwndCallback) { pwd->hwndCallback = hwndCallback; }
/************************************************************************/
/*
@doc INTERNAL MCIWAVE
@api <t>LPWAVEHDR<d> * | NextWaveHdr | This function returns the next wave buffer based on the buffer passed. It either returns the next buffer in the list, or the first buffer in the list of the last buffer is passed.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@parm <t>LPWAVEHDR<d> * | lplpWaveHdr | Points to the array of wave buffer pointers from which a buffer pointer is returned.
@rdesc Returns the next wave buffer to use. */
PUBLIC LPWAVEHDR * PASCAL FAR NextWaveHdr( PWAVEDESC pwd, LPWAVEHDR *lplpWaveHdr) { if (lplpWaveHdr < (pwd->rglpWaveHdr + pwd->wAudioBuffers - 1)) return lplpWaveHdr + 1; else return pwd->rglpWaveHdr; }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api UINT | GetPlayRecPosition | Gets the current playback or recording position. For output, this means also determining how much data has actually passed through the wave device if a device is currently open. This must be added to the starting playback position. For input however, only the amount that has actually be written to disk is returned.
Note that the return value from the driver is verified against the actual length of the data. This is to protect against problems encountered in drivers that return bytes when samples are requested.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@parm LPDWORD | lpdTime | Points to a buffer to play the current position.
@parm DWORD | dFormatReq | Indicates whether time is in samples, bytes or milliseconds.
@rdesc Returns zero on success, else the device error on error. This can only fail if getting the current playback position. The recording position will alway succeed. */
PRIVATE UINT PASCAL NEAR GetPlayRecPosition( PWAVEDESC pwd, LPDWORD lpdTime, DWORD dFormatReq) { if (pwd->Direction == output) { MMTIME mmtime; DWORD dDelta; UINT wErrorRet;
mmtime.wType = TIME_BYTES; if (!pwd->hWaveOut) mmtime.u.cb = 0; else if (0 != (wErrorRet = waveOutGetPosition(pwd->hWaveOut, &mmtime, sizeof(MMTIME)))) return wErrorRet;
dDelta = mmtime.u.cb;
//#ifdef DEBUG
if (pwd->dFrom + dDelta > pwd->dSize) dDelta = pwd->dSize - pwd->dFrom; //#endif
*lpdTime = bytes2time(pwd, pwd->dFrom + dDelta, dFormatReq); } else *lpdTime = bytes2time(pwd, pwd->dCur, dFormatReq); return 0; }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api VOID | SetCurrentPosition | Sets the starting and current file position, that is, the the point at which playback or recording will start at.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@parm DWORD | dByteOffset | Indicates the position to set in bytes.
@rdesc Nothing. */
PRIVATE VOID PASCAL NEAR SetCurrentPosition( PWAVEDESC pwd, DWORD dByteOffset) { LPWAVEDATANODE lpwdn;
lpwdn = LPWDN(pwd, 0); if (lpwdn) { if (dByteOffset >= pwd->dVirtualWaveDataStart) lpwdn += pwd->dWaveDataCurrentNode; else { lpwdn += pwd->dWaveDataStartNode; pwd->dVirtualWaveDataStart = 0; pwd->dWaveDataCurrentNode = pwd->dWaveDataStartNode; } for (; dByteOffset > pwd->dVirtualWaveDataStart + lpwdn->dDataLength;) { pwd->dVirtualWaveDataStart += lpwdn->dDataLength; pwd->dWaveDataCurrentNode = lpwdn->dNextWaveDataNode; lpwdn = LPWDN(pwd, pwd->dWaveDataCurrentNode); } pwd->dFrom = dByteOffset; pwd->dCur = dByteOffset; } }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@func DWORD | RoundedBytePosition | This function returns the rounded byte format time position from the specified position parameter in the specified time format. It transforms the position to byte format and rounds against the current block alignment.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@parm DWORD | dTime | Specifies the time position to translate and round.
@parm DWORD | dFormat | Indicates the time format of <p>dTime<d>.
@rdesc Returns the rounded byte formate of the position passed. */
PRIVATE DWORD PASCAL NEAR RoundedBytePosition( PWAVEDESC pwd, DWORD dTime, DWORD dFormat) { DWORD dBytes;
dBytes = time2bytes(pwd, dTime, dFormat);
/*
** Get the end position right. Because lots of compressed files don't ** end with a complete sample we make sure that the end stays the ** end. */
if (dBytes >= pwd->dSize && pwd->Direction == output) return pwd->dSize;
return dBytes - (dBytes % pwd->pwavefmt->nBlockAlign); }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api VOID | mwStop | This function is called in response to an <m>MCI_STOP<d> message, and internally by several function, and is used to stop playback or recording if the task is currently not idle. The function yields until the task has actually become idle. This has the side affect of releasing any buffers currently added to the pwave input or output device, and thus signalling the task that the buffers are available.
Note that if the task is in Cleanup mode, indicating that it is blocking to remove extra signals, and ignoring any commands, the function just waits for the task to enter Idle state without signalling the task.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@rdesc Nothing. */
PRIVATE VOID PASCAL NEAR mwStop( PWAVEDESC pwd) { if (ISTASKSTATE(pwd, TASKBUSY)) { if (!ISMODE(pwd, MODE_CLEANUP)) { DWORD dPosition;
ADDMODE(pwd, COMMAND_NEW | COMMAND_STOP);
if (!GetPlayRecPosition(pwd, &dPosition, MCI_FORMAT_BYTES)) SetCurrentPosition(pwd, RoundedBytePosition(pwd, dPosition, MCI_FORMAT_BYTES));
ReleaseWaveBuffers(pwd);
//!! if (ISMODE(pwd, MODE_PAUSED | MODE_HOLDING) || (ISMODE(pwd, MODE_PLAYING) && ISMODE(pwd, MODE_CUED)))
if (ISMODE(pwd, MODE_PAUSED | MODE_HOLDING)) TaskSignal(pwd->hTask, WTM_STATECHANGE); }
while (!ISTASKSTATE(pwd, TASKIDLE)) mmYield(pwd); } }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api UINT | AllocateBuffers | Allocates and prepares an array of wave buffers used for playback or recording, up to the maximum number of seconds specified.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@rdesc Returns number of buffers successfully allocated. */
PRIVATE UINT PASCAL NEAR AllocateBuffers( PWAVEDESC pwd) { UINT wAllocatedBuffers;
for (wAllocatedBuffers = 0; wAllocatedBuffers < pwd->wSeconds; wAllocatedBuffers++) { if (!(pwd->rglpWaveHdr[wAllocatedBuffers] = (LPWAVEHDR)GlobalAllocPtr(GMEM_MOVEABLE, (DWORD)(sizeof(WAVEHDR) + pwd->dAudioBufferLen)))) break;
dprintf3(("Allocated %8X", pwd->rglpWaveHdr[wAllocatedBuffers])); pwd->rglpWaveHdr[wAllocatedBuffers]->dwFlags = WHDR_DONE; pwd->rglpWaveHdr[wAllocatedBuffers]->lpData = (LPSTR)(pwd->rglpWaveHdr[wAllocatedBuffers] + 1); pwd->rglpWaveHdr[wAllocatedBuffers]->dwBufferLength = pwd->dAudioBufferLen; if (pwd->Direction == output) { if (!waveOutPrepareHeader(pwd->hWaveOut, pwd->rglpWaveHdr[wAllocatedBuffers], sizeof(WAVEHDR))) { pwd->rglpWaveHdr[wAllocatedBuffers]->dwFlags |= WHDR_DONE; continue; } } else if (!waveInPrepareHeader(pwd->hWaveIn, pwd->rglpWaveHdr[wAllocatedBuffers], sizeof(WAVEHDR))) {
/*
** Initialize the bytes recorded or mwGetLevel can fall over */ pwd->rglpWaveHdr[wAllocatedBuffers]->dwBytesRecorded = 0; continue; } GlobalFreePtr(pwd->rglpWaveHdr[wAllocatedBuffers]); pwd->rglpWaveHdr[wAllocatedBuffers] = 0; break; }
dprintf2(("Allocated %u Buffers", wAllocatedBuffers)); return wAllocatedBuffers; }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api VOID | FreeBuffers | Frees the array of wave buffers.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@rdesc Nothing. */
PRIVATE VOID PASCAL NEAR FreeBuffers( PWAVEDESC pwd) { UINT wAllocatedBuffers;
for (wAllocatedBuffers = pwd->wAudioBuffers; wAllocatedBuffers--;) { if (!pwd->rglpWaveHdr[wAllocatedBuffers]) continue;
if (pwd->Direction == output) waveOutUnprepareHeader(pwd->hWaveOut, pwd->rglpWaveHdr[wAllocatedBuffers], sizeof(WAVEHDR)); else waveInUnprepareHeader(pwd->hWaveIn, pwd->rglpWaveHdr[wAllocatedBuffers], sizeof(WAVEHDR)); GlobalFreePtr(pwd->rglpWaveHdr[wAllocatedBuffers]); } }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api VOID | mwTask | This function represents the background task which plays or records wave audio. It is called as a result of the call to <f>mmTaskCreate<d> <f>mwOpenDevice<d>. When this function returns, the task is destroyed.
In short, the task opens the designated file, and blocks itself in a loop until it is told to do something (close, play, or record). Upon entering the function, the signal count is zero, <e>WAVEDESC.wTaskState<d> is TASKINIT. Note that <e>WAVEDESC.wMode<d> is left as is. This is because of the Wait mode feature, in which the Wait mode flag needs to be tested by the calling task to determine if this task has already performed successful notification. This means that in checking the current task mode, the task state must also be verified (except in cases of Recording and Playing modes, which are reset after leaving their functions).
If the requested file is opened, the function enters a loop to allow it to act on the current task state when the task is signalled. It then waits in a blocked state with <e>WAVEDESC.wTaskState<d> set to TASKIDLE until the state is changed. So unless the task is closing, playing, or recording, it is idle and blocked.
If the requested file cannot be opened, the task state must be reset to TASKNONE so that the task create wait loop recognizes that the create has failed.
When the task is signalled, it again checks the current state just as a precaution. This should be removed eventually.
A TASKCLOSE state simply breaks out of the outer infinite loop, allowing the wave file to be closed, and the task function exited. This in turn terminates the task.
A TASKBUSY state indicates that a wave device has been opened for either playback or recording, which is where the signal originated from. The task must first then calculate and allocate the wave buffers. The allocation function will provide up to the number of buffers requested, but may be constrained by current memory use. The number of buffers allocated must meet a minimum requirement though to allow smooth playback and recording.
If not enough buffers can be allocated, the current task error is set to an out of memory condition, and the function returns to an idle state. Note that the current command mode is reset before entering the idle loop. This ensures that all previous commands are removed before allowing the next set of commands to be set.
If enough buffers are allocated the current task error is reset, and the playback or recording function is called to act on the previously set parameters. When the recording or playback function returns, it may or may not have successfully finished. The current position is set as based on where the recording or playback actually got to. For playback, this is how much data was processed by the wave device. For recording, this is how much data was written to disk. To ensure that all buffers have been released by the wave device, the <f>ReleaseWaveBuffers<d> function is called in all cases after determining the current position.
In determining the optional notification, the <e>WAVEDESC.wTaskError<d> will contain any failure error which occurred during the playback or recording. If no error is set, then the only other error could be whether or not playback or recording was interrupted by another command.
After freeing the buffers, the Cleanup mode is set. This indicates that the task is not able to accept new commands until it reaches an idle state. The reason for this flag is that the task must retrieve any left over signals from the message queue generated by releasing the wave buffers, and by freeing the wave device. While getting the signals, it is possible for the task that opened the MCI wave device instance to try and send new commands. These commands would be ignored, so the task must wait until cleanup is done.
After entering Cleanup mode, the wave device is freed here, even though the calling task opened it. This is bad, in that it assumes that wave drivers allocate either local memory, or global memory using GMEM_DDESHARE. This of course generates another task signal from the wave device, which is ignored by the Free Device function. The task can now remove any left over signals from the queue, if any, from the releasing of the wave buffers.
Note that notification is only performed if the calling task is no longer waiting for this task, and no task error occurred (If the calling task is waiting, then notification must be either Failure or Successful, since nothing could abort this task). If notification needs to take place, the Wait mode flag is cleared, else the callback is cleared. The calling task will now know that either the background task failed, or succeeded and performed notification.
Note that when terminating the task, the <e>WAVEDESC.hTask<d> must be set to NULL to indicate to the <f>mwCloseDevice<d> function that the task has indeed terminated itself.
@parm DWORD | dInstanceData | This contains the instance data passed to the <f>mmTaskCreate<d> function. It contains a pointer to the wave audio data in the high-order word. The low-order word is not used.
@rdesc Nothing.
@xref mwOpenDevice. */
PUBLIC VOID PASCAL EXPORT mwTask( DWORD_PTR dInstanceData) { register PWAVEDESC pwd;
EnterCrit();
/*
** Make a safe "user" call so that user knows about our thread. ** This is to allow WOW setup/initialisation on this thread */ GetDesktopWindow();
pwd = (PWAVEDESC)dInstanceData;
pwd->hTask = mmGetCurrentTask(); pwd->wTaskError = 0;
dprintf2(("Bkgd Task %X", pwd->hTask));
if (mwOpenFile(pwd)) { for (; !ISTASKSTATE(pwd, TASKCLOSE);) { UINT wNotification; UINT wBuffersOutstanding;
SETTASKSTATE(pwd, TASKIDLE); while (ISTASKSTATE(pwd, TASKIDLE)) {
dprintf2(("Task is IDLE")); while (TaskBlock() != WTM_STATECHANGE) { } } pwd->wTaskError = 0;
switch (TASKSTATE(pwd)) { case TASKBUSY: #if DBG
dprintf2(("Task is BUSY")); #endif
//!! if (pwd->wTaskError = mwGetDevice(pwd)) {
//!! mwDelayedNotify(pwd, MCI_NOTIFY_FAILURE);
//!! break;
//!! }
//!! Leave(pwd);
//!! mmTaskBlock(NULL);
//!! Enter(pwd);
pwd->wAudioBuffers = AllocateBuffers(pwd); if (pwd->wAudioBuffers >= MinAudioSeconds) { DWORD dPosition;
if (pwd->Direction == output) wBuffersOutstanding = PlayFile(pwd); else wBuffersOutstanding = RecordFile(pwd);
/*
** If we've played everything don't rely on the wave ** device because for compressed files it only gives ** and approximate answer based on the uncompressed ** format */
if (pwd->Direction == output && wBuffersOutstanding == 0) { dPosition = pwd->dTo; SetCurrentPosition(pwd, RoundedBytePosition(pwd, dPosition, MCI_FORMAT_BYTES)); } else { if (!GetPlayRecPosition(pwd, &dPosition, MCI_FORMAT_BYTES)) SetCurrentPosition(pwd, RoundedBytePosition(pwd, dPosition, MCI_FORMAT_BYTES)); }
ReleaseWaveBuffers(pwd);
if (pwd->wTaskError) wNotification = MCI_NOTIFY_FAILURE; else if (pwd->dCur >= pwd->dTo) wNotification = MCI_NOTIFY_SUCCESSFUL; else wNotification = MCI_NOTIFY_ABORTED;
} else { dprintf1(("MinAudioSeconds <= wAudioBuffers MCI_NOTIFY_FAILURE")); pwd->wTaskError = MCIERR_OUT_OF_MEMORY; wNotification = MCI_NOTIFY_FAILURE; wBuffersOutstanding = 0; }
FreeBuffers(pwd); ADDMODE(pwd, MODE_CLEANUP);
if (!ISMODE(pwd, MODE_WAIT) || !pwd->wTaskError) { REMOVEMODE(pwd, MODE_WAIT); mwDelayedNotify(pwd, wNotification); } else mwSaveCallback(pwd, NULL);
mwFreeDevice(pwd);
for (; wBuffersOutstanding; wBuffersOutstanding--) { while (TaskBlock() != WM_USER) { } } break;
case TASKCLOSE: #if DBG
dprintf2(("Task is CLOSING")); #endif
break;
case TASKSAVE: dprintf2(("mwTask: saving data")); mwSaveData(pwd); break;
case TASKDELETE: dprintf2(("mwTask: deleting data")); mwDeleteData(pwd); break;
case TASKCUT: dprintf2(("mwTask: Task CUT")); break; } } dprintf2(("Closing file %ls", pwd->aszFile)); mwCloseFile(pwd);
} else { dprintf1(("Cannot open file %ls", pwd->aszFile)); SETTASKSTATE(pwd, TASKNONE); }
#if DBG
dprintf2(("Background thread %x is terminating\r\n", pwd->hTask)); #endif
pwd->hTask = 0; //NULL;
LeaveCrit(); }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api UINT | mwCloseDevice | This function is called in response to an <m>MCI_CLOSE_DRIVER<d> message, and is used to close the MCI device. Note that since the message can be sent to an MCI device that just represents the wave device itself, and has no file or <t>WAVEDESC<d>, it must check and return success in that instance.
If there is actually data attached to this MCI device, the function checks to determine if a task was successfully created for the device. This might not have happened if the <m>MCI_OPEN_DRIVER<d> message failed to create a task, or the wave device itself was being opened, and no task was created.
If there is a task, it must first stop any playback or recording that is in progress, then inform the task that it must cease and desist by setting the task state to TASKCLOSE and signalling the task. The function must then wait for the task to respond by terminating itself. Note that the last thing the task does is set <t>WAVEDESC.hTask<d> to NULL, thus allowing the wait loop to exit.
After optionally terminating the task, the wave description data is freed, and the function returns success.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@rdesc Returns zero on success, else an MCI error code. The function cannot at this time fail.
@xref mwOpenDevice. */
PRIVATE UINT PASCAL NEAR mwCloseDevice( PWAVEDESC pwd) { if (pwd) { if (pwd->hTask) { mwStop(pwd); SETTASKSTATE(pwd, TASKCLOSE); TaskSignal(pwd->hTask, WTM_STATECHANGE); TaskWaitComplete(pwd->hTaskHandle); //while (pwd->hTask)
// mmYield(pwd);
dprintf3(("Waiting for task thread to complete")); } else { } LocalFree(pwd);
} return 0; }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api UINT | mwOpenDevice | This function is called in response to an <m>MCI_OPEN_DRIVER<d> message, and is used to open the MCI device, optionally allocating a wave description block and create a background playback and recording task.
It is possible that the MCI device is being opened for information only. In this case there is no element name or ID and no wave description block need be allocated.
If an element or element ID is present, the wave descriptor block is allocated and initialized with the current device, default time formate, etc. After storing either the element, or element ID, the secondary task is created, and the task state is set to TASKINIT.
The first thing that the task must do is try and open the file specified in the descriptor block passed to the task function. The calling task must yield upon successfully creating the task until the task has opened the wave file and entered its idle loop, or has failed the open and returned and error. An error state indicates that the wave descriptor block is to be freed.
Note that driver data, which is where the pointer to the wave descriptor data is stored, is not guarenteed to be initialized to any specific value, and must be initialized even if no descriptor block is being allocated. To be to be on the safe side, the driver data is set to NULL on an error. This data parameter can then be accessed by the MCI driver through the <p>wDeviceID<d> when processing driver messages.
@parm DWORD | dFlags | Contains the open flags passed with the message (see mmsystem.h). The following flags are responded to specifically. All others are ignored.
@flag MCI_OPEN_ELEMENT | Specifies that a file name is present in the open message. This is mutually incompatible with the MCI_OPEN_ELEMENT_ID flag. If neither of these flags exist, no wave descriptor data or secondary task will be created.
@flag MCI_OPEN_ELEMENT_ID | Specifies that an alternate IO function is present in the open message. This is mutually incompatible with the MCI_OPEN_ELEMENT flag. If neither of these flags exist, no wave descriptor data or secondary task will be created.
@flag MCI_OPEN_SHAREABLE | Specifies that the more than one task can communicate with this MCI device. The wave driver does not support this.
@flag MCI_WAVE_OPEN_BUFFER | Indicates that the <e>MCI_OPEN_PARMS.dwBufferSeconds<d> parameter contains the number of seconds of audio to allow to be buffered. This number is constrained by the minimum and maximum numbers contained in mciwave.h. If this flag is not present, the default value is used, which may have been set during driver open time.
@parm <t>LPMCI_OPEN_PARMS<d> | lpOpen | Open parameters (see mmsystem.h)
@parm MCIDEVICEID | wDeviceID | The MCI Driver ID for the new device.
@rdesc Returns zero on success, else an MCI error code.
@xref mwCloseDevice. */
PRIVATE UINT PASCAL NEAR mwOpenDevice( DWORD dFlags, LPMCI_WAVE_OPEN_PARMS lpOpen, MCIDEVICEID wDeviceID) { UINT wReturn; UINT wSeconds;
wReturn = 0;
if (!(dFlags & MCI_WAVE_OPEN_BUFFER)) wSeconds = wAudioSeconds; else { wSeconds = lpOpen->dwBufferSeconds; if ((wSeconds > MaxAudioSeconds) || (wSeconds < MinAudioSeconds)) wReturn = MCIERR_OUTOFRANGE; }
if (!wReturn && (dFlags & (MCI_OPEN_ELEMENT | MCI_OPEN_ELEMENT_ID))) { PWAVEDESC pwd;
if (dFlags & MCI_OPEN_SHAREABLE) wReturn = MCIERR_UNSUPPORTED_FUNCTION;
else if ((dFlags & (MCI_OPEN_ELEMENT | MCI_OPEN_ELEMENT_ID)) == (MCI_OPEN_ELEMENT | MCI_OPEN_ELEMENT_ID)) return MCIERR_FLAGS_NOT_COMPATIBLE;
//@@@
//@@@ else if ((dFlags & MCI_OPEN_ELEMENT_ID) && !ValidateIOCallback(lpOpen))
//@@@ return MCIERR_MISSING_PARAMETER;
//@@@ See notes re. ValidateIOCallback at the top of this file
//@@@
else if (!(pwd = (PWAVEDESC)LocalAlloc(LPTR, sizeof(WAVEDESC)))) wReturn = MCIERR_OUT_OF_MEMORY;
else { pwd->wDeviceID = wDeviceID; pwd->dTimeFormat = MCI_FORMAT_MILLISECONDS; pwd->Direction = output; pwd->idOut = (DWORD)WAVE_MAPPER; pwd->idIn = (DWORD)WAVE_MAPPER; pwd->wSeconds = wSeconds; pwd->hTempBuffers = INVALID_HANDLE_VALUE;
if (dFlags & MCI_OPEN_ELEMENT_ID) pwd->pIOProc = (LPMMIOPROC)(lpOpen + 1);
if (*lpOpen->lpstrElementName) { MMIOINFO mmioInfo;
pwd->aszFile[ (sizeof(pwd->aszFile) / sizeof(WCHAR)) - 1] = '\0'; wcsncpy( pwd->aszFile, lpOpen->lpstrElementName, ( sizeof(pwd->aszFile) / sizeof(WCHAR) ) - 1 ); InitMMIOOpen(pwd, &mmioInfo); if (!mmioOpen(pwd->aszFile, &mmioInfo, MMIO_PARSE)) wReturn = MCIERR_FILENAME_REQUIRED; }
if (!wReturn) { SETTASKSTATE(pwd, TASKINIT);
switch (mmTaskCreate(mwTask, &pwd->hTaskHandle, (DWORD_PTR)pwd)) { case 0: while (ISTASKSTATE(pwd, TASKINIT)) { mmYield(pwd); }
if (ISTASKSTATE(pwd,TASKNONE)) { // Task detected an error and stopped itself
wReturn = pwd->wTaskError; TaskWaitComplete(pwd->hTaskHandle); // Wait for thread to completely terminate
} else { mciSetDriverData(wDeviceID, (DWORD_PTR)pwd); } break;
case TASKERR_OUTOFMEMORY: case TASKERR_NOTASKSUPPORT: default: wReturn = MCIERR_OUT_OF_MEMORY; break; } }
if (wReturn) { LocalFree(pwd); } else { } } } return wReturn; }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@func DWORD | VerifyRangeStart | Verifies and rounds range start value. Note that the internal byte format time is converted to the current external time format in order to compensate for rounding errors.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@parm DWORD | dStart | Value to verify.
@rdesc Returns the verified value, else -1 on range error. */
PRIVATE DWORD PASCAL NEAR VerifyRangeStart( PWAVEDESC pwd, DWORD dStart) { if (dStart <= bytes2time(pwd, pwd->dSize, pwd->dTimeFormat)) { dStart = RoundedBytePosition(pwd, dStart, pwd->dTimeFormat); if (dStart > pwd->dSize) dStart = pwd->dSize; } else dStart = (DWORD)(-1);
return dStart; }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@func DWORD | VerifyRangeEnd | Verifies and rounds range end value. Note that the internal byte format time is converted to the current external time format in order to compensate for rounding errors.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@parm DWORD | dEnd | Value to verify.
@parm BOOL | fVerifyLength | Indicates that the value specified should be verified against the current file length. This is not done in circumstances such as recording, where the length might need to be expanded beyond the current value.
@rdesc Returns the verified value, else -1 on range error. */
PRIVATE DWORD PASCAL NEAR VerifyRangeEnd( PWAVEDESC pwd, DWORD dEnd, BOOL fVerifyLength) { DWORD dTimeSize;
dTimeSize = bytes2time(pwd, pwd->dSize, pwd->dTimeFormat);
if (!fVerifyLength || (dEnd <= dTimeSize)) { if (dEnd == dTimeSize) dEnd = pwd->dSize; else { dEnd = RoundedBytePosition(pwd, dEnd, pwd->dTimeFormat); if (fVerifyLength && (dEnd > pwd->dSize)) dEnd = pwd->dSize; } } else dEnd = (DWORD)(-1);
return dEnd; }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@func UINT | SetRange | This function sets the "to" and "from" range for recording or playback after validating them. Note that the "from" parameter defaults to the current position, but the "to" parameter defaults to either the end of the file for playback, or infinity for recording.
If either the "from" parameter is specified, or the "to" position is different than the current parameter, abort notification is attempted, else supersede notification is attempted later. The "to" position could be changed by merely not specifying the MCI_TO flag if a current "to" position is in effect that does not specify the end of the data.
@parm <t>PWAVEDESC<d> | pwd | Points to the data block for this device.
@parm DWORD | dFlags | Contains the flags used to determine the parameters set in the <p>lpplay<d> structure.
@flag MCI_FROM | Indicates the <e>MCI_PLAY_PARMS.dwFrom<d> contains a starting point. If this flag is not specified, the parameter defaults to the current position. Setting this flag has the effect of resetting the wave output device so that any hold condition is signalled to continue.
This is also important in that for output, it resets the wave device's byte counter of how much data has actually been processed. This enables an accurate count to be retrieved when playback is either stopped, or finishes normally.
@flag MCI_TO | Indicates the <e>MCI_PLAY_PARMS.dwTo<d> contains an ending point. If this flag is not specified, the parameter defaults to either the end of the file for playback, or infinity for recording.
@parm <t>LPMCI_PLAY_PARMS<d> | lpplay | Optionally points to a structure containing "to" and "from" parameters.
@rdesc Returns 0 on success, or MCIERR_OUTOFRANGE if a "to" or "from" parameter is not within the current file length, or "to" is less than "from".
@xref mwSetup. */
PRIVATE UINT PASCAL NEAR SetRange( PWAVEDESC pwd, DWORD dFlags, LPMCI_PLAY_PARMS lpPlay) { DWORD dFromBytePosition; DWORD dToBytePosition;
if (dFlags & MCI_FROM) { dFromBytePosition = VerifyRangeStart(pwd, lpPlay->dwFrom); if (dFromBytePosition == -1) return MCIERR_OUTOFRANGE; } else dFromBytePosition = pwd->dFrom;
if (dFlags & MCI_TO) { dToBytePosition = VerifyRangeEnd(pwd, lpPlay->dwTo, pwd->Direction == output); if (dToBytePosition == -1) return MCIERR_OUTOFRANGE; } else if (pwd->Direction == output) dToBytePosition = pwd->dSize; else dToBytePosition = RoundedBytePosition(pwd, INFINITEFILESIZE, MCI_FORMAT_BYTES);
if ((dFlags & MCI_TO) && !(dFlags & MCI_FROM) && (pwd->dCur > dToBytePosition)) { UINT wErrorRet;
if (0 != (wErrorRet = GetPlayRecPosition(pwd, &dFromBytePosition, MCI_FORMAT_BYTES))) return wErrorRet; if (dToBytePosition < dFromBytePosition) return MCIERR_OUTOFRANGE; SetCurrentPosition(pwd, RoundedBytePosition(pwd, dFromBytePosition, MCI_FORMAT_BYTES)); ReleaseWaveBuffers(pwd); } else { if (dToBytePosition < dFromBytePosition) return MCIERR_OUTOFRANGE; if (dFlags & MCI_FROM) { SetCurrentPosition(pwd, dFromBytePosition); ReleaseWaveBuffers(pwd); } }
if ((dFlags & MCI_FROM) || (dToBytePosition != pwd->dTo)) mwDelayedNotify(pwd, MCI_NOTIFY_ABORTED); pwd->dTo = dToBytePosition;
return 0; }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api UINT | mwSetup | This function is called in response to an <m>MCI_PLAY<d>, <m>MCI_RECORD<d>, and indirectly through <m>MCI_CUE<d> and <m>MCI_STATUS<d> messages, and is used to set up for playing or recording a wave file and then signals the background task to begin.
Before trying to set up for recording or playback, the input or output direction must match the request that is being made. If it does not currently match, the current command, if any, is stopped, and the direction indicator is reset. This action might cause an abort notification to occur.
If however, the current direction matches the requested direction, the function must still wait if the task is currently in Cleanup mode and ignoring new commands. This check does not need to be performed if the stop command is sent, as <f>mwStop<d> performs the same logic. If the task is currently in Idle state, the Cleanup mode is probably set, but the loop will immediately drop out anyway.
If the start and end points are successfully parsed, the function begins either playback or recording. If the task is idle, it must set the TASKBUSY state, else it must check to see if the task needs to be possibly un-paused by starting the wave device. It does not check for a MODE_PAUSED or MODE_CUED state, as any <f>WaveOutReset<d> or <f>WaveInReset<d> will stop output or input.
If the task was idle, the act of opening a wave device sends a signal to the task, and it is ready to go as soon as this task yields. Else the task was already running, and this task just needs to yield.
In the case of playback, the task may be additionally blocked by a previous Hold command, for which the function must send an extra signal to the task.
For recording, there are two modes, Insert and Overwrite. One can change between the two modes of recording with what normally is only a very slight delay between switching. A check must be made to see if the task is currently recording, and if that method of recording is being changed (from Insert to Overwrite, or visa versa). If so, then the current position must be logged, and the wave buffers released. This is so that only data up to this point is recorded in the previous method, and all new data is recorded in the new method. Notice that in the recording function, if a new command is received, no new buffers are handed to the wave device until all the old buffers are dealt with and the new command is enacted.
If the command flags where successfully set, and all needed signalling and un-pausing was performed, then before the task can be allowed to run, the notification parameter must be set. If the previous command was not cancelled by a Stop, then a superseded notification is sent, and the current notification status is saved.
At this point, the background task is ready to be begun. If the Wait flag has been set, the calling task must now yield until the background task is finished the command (which means different things for different commands), else it can just return to the caller without waiting. As this is the driver wait loop, it must use the <f>mciDriverYield<d> function in order to execute the driver callback function (and thus process the Break key, or whatever the callback performs).
In order to make return values from a Record or Play command with the wait flag act as other commands, there is a special Wait mode flag. This tells the background task that the calling task is waiting for it to complete. If the background task encounters an error, it does not perform notification, but just returns to Idle state and allows the calling task to return the error that was encountered.
If the wait loop is broken out of, then it can check the Wait mode flag to determine if it should return the background task error. In the case of Cue and Hold, the Wait mode can be removed, and the task error would presumably be zero.
Note that the task error is set to zero before doing the first call to <f>mciDriverYield<d>, just in case an interrupt is received before the background task has a chance to run at all.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@parm DWORD | dFlags | Play flags.
@flag MCI_TO | This flag indicates that a TO parameter is present in the <p>lpPlay<d> parameter block.
@flag MCI_FROM | This flag indicates that a FROM parameter is present in the <p>lpPlay<d> parameter block.
@flag MCI_WAIT | Wait for command to complete.
@flag MCI_NOTIFY | Notify upon command completion.
@flag MCI_RECORD_OVERWRITE | This flag indicates the recording should overwrite existing data.
@flag MCI_RECORD_INSERT | This flag indicates the recording should insert in existing data. This is the default recording method.
@flag MCI_MCIWAVE_PLAY_HOLD | This flag indicates that playback should not release buffers after the TO position has been release, but instead go into a Paused state.
@flag MCI_MCIWAVE_CUE | This internal flag indicates that Recording or Playback is being cued.
@parm <t>LPMCI_PLAY_PARMS<d> | lpPlay | Play parameters.
@parm DIRECTION | Direction | Indicates the direction being set up for.
@flag output | Playback.
@flag input | Recording.
@rdesc Returns 0 if playback or recording was started, else an MCI error. */
PRIVATE UINT PASCAL NEAR mwSetup( PWAVEDESC pwd, DWORD dFlags, LPMCI_PLAY_PARMS lpPlay, DIRECTION Direction) { UINT wReturn; register UINT wMode;
wReturn = 0;
if (Direction != pwd->Direction) { mwStop(pwd); pwd->Direction = Direction; } else if (ISMODE(pwd, MODE_CLEANUP)) { while (!ISTASKSTATE(pwd, TASKIDLE)) mmYield(pwd); }
if (0 != (wReturn = SetRange(pwd, dFlags, lpPlay))) return wReturn;
wMode = COMMAND_NEW;
if (dFlags & MCI_MCIWAVE_PLAY_HOLD) wMode |= COMMAND_HOLD;
if (dFlags & MCI_MCIWAVE_CUE) wMode |= COMMAND_CUE;
if (dFlags & MCI_WAIT) wMode |= MODE_WAIT;
if (pwd->Direction == output) { wMode |= COMMAND_PLAY;
if (ISTASKSTATE(pwd, TASKIDLE)) { if (!(wReturn = mwGetDevice(pwd))) SETTASKSTATE(pwd, TASKBUSY); TaskSignal(pwd->hTask, WTM_STATECHANGE); } else { if (ISMODE(pwd, COMMAND_PLAY)) { if (0 != (wReturn = waveOutRestart(pwd->hWaveOut))) return wReturn; else wMode |= MODE_PLAYING; } if (ISMODE(pwd, MODE_HOLDING)) TaskSignal(pwd->hTask, WTM_STATECHANGE); }
} else if ((dFlags & (MCI_RECORD_OVERWRITE | MCI_RECORD_INSERT)) == (MCI_RECORD_OVERWRITE | MCI_RECORD_INSERT)) wReturn = MCIERR_FLAGS_NOT_COMPATIBLE; else { if (dFlags & MCI_RECORD_OVERWRITE) wMode |= COMMAND_OVERWRITE; else wMode |= COMMAND_INSERT;
if (ISTASKSTATE(pwd, TASKIDLE)) { if (!(wReturn = mwGetDevice(pwd))) SETTASKSTATE(pwd, TASKBUSY); TaskSignal(pwd->hTask, WTM_STATECHANGE);
} else if (ISMODE(pwd, COMMAND_INSERT | COMMAND_OVERWRITE)) {
if ((ISMODE(pwd, COMMAND_OVERWRITE) && !(dFlags & MCI_RECORD_OVERWRITE)) || (ISMODE(pwd, COMMAND_INSERT) && (dFlags & MCI_RECORD_OVERWRITE))) { DWORD dPosition;
GetPlayRecPosition(pwd, &dPosition, MCI_FORMAT_BYTES); SetCurrentPosition(pwd, RoundedBytePosition(pwd, dPosition, MCI_FORMAT_BYTES)); ReleaseWaveBuffers(pwd); }
if (!(wReturn = waveInStart(pwd->hWaveIn))) if (ISMODE(pwd, COMMAND_INSERT)) wMode |= MODE_INSERT; else wMode |= MODE_OVERWRITE; } }
if (!wReturn) { if (dFlags & MCI_NOTIFY) { mwDelayedNotify(pwd, MCI_NOTIFY_SUPERSEDED); mwSaveCallback(pwd, (HWND)(lpPlay->dwCallback)); } SETMODE(pwd, wMode); if (dFlags & MCI_WAIT) { pwd->wTaskError = 0;
//
// Wait for the device task to complete the function
//
for (;;) { LeaveCrit(); if (mciDriverYield(pwd->wDeviceID)) { EnterCrit(); break; } Sleep(10); EnterCrit();
if (ISTASKSTATE(pwd, TASKIDLE) || ISMODE(pwd, MODE_HOLDING | MODE_CUED)) { break; } }
if (ISMODE(pwd, MODE_WAIT)) { REMOVEMODE(pwd, MODE_WAIT); wReturn = pwd->wTaskError; } } } else mwStop(pwd);
return wReturn; }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api UINT | mwPause | This function is called in response to an <m>MCI_PAUSE<d> message, and is used to pause wave file output or input. By calling the <f>waveOutPause<d> or <f>waveInStop<d> function, all buffers added to the driver's queue will not be used, and thus eventually cause the background task to block itself waiting for buffers to be released.
Note that this is only done if playback or recording is currently in progress, and cueing is not also being performed. If the Cue command has been used, then the wave device is already in a paused state. Also note that pausing can only be successfully performed if playback or recording is currently being performed, and the cleanup state has not been entered.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@parm DWORD | dFlags | Contains the pause flags passed with the message
@flag MCI_WAIT | Wait for command to complete.
@flag MCI_NOTIFY | Notify upon command completion.
@rdesc Returns 0 if playback or recording was paused, else an MCI error. */
PRIVATE UINT PASCAL NEAR mwPause( PWAVEDESC pwd, DWORD dFlags) { UINT wReturn;
if (dFlags & ~(MCI_NOTIFY | MCI_WAIT)) return MCIERR_UNRECOGNIZED_KEYWORD;
if (ISTASKSTATE(pwd, TASKBUSY) && !ISMODE(pwd, COMMAND_CUE | MODE_HOLDING)) { wReturn = 0; if (!ISMODE(pwd, MODE_PAUSED)) { if (ISMODE(pwd, COMMAND_PLAY)) { if (ISMODE(pwd, MODE_CLEANUP)) wReturn = MCIERR_NONAPPLICABLE_FUNCTION; else wReturn = waveOutPause(pwd->hWaveOut); } else if (ISMODE(pwd, MODE_CLEANUP)) wReturn = MCIERR_NONAPPLICABLE_FUNCTION; else wReturn = waveInStop(pwd->hWaveIn); if (!wReturn) ADDMODE(pwd, MODE_PAUSED); } } else wReturn = MCIERR_NONAPPLICABLE_FUNCTION;
return wReturn; }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api UINT | mwResume | This function is called in response to an <m>MCI_RESUME<d> message, and is used to resume wave file output or input if it was previously paused from output or input using MCI_PAUSE.
Note that this is only done if playback or recording is currently paused, and cueing is not also being performed. If the Cue command or Play Hold command is currently in effect, then there is no Play or Record command to resume, and the command is ignored. If playback or recording is currently in effect, the command is ignored.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@parm DWORD | dFlags | Contains the resume flags passed with the message
@flag MCI_WAIT | Wait for command to complete.
@flag MCI_NOTIFY | Notify upon command completion.
@rdesc Returns 0 if playback or recording was resumed, else an MCI error. */
PRIVATE UINT PASCAL NEAR mwResume( PWAVEDESC pwd, DWORD dFlags) { UINT wReturn;
if (dFlags & ~(MCI_NOTIFY | MCI_WAIT)) return MCIERR_UNRECOGNIZED_KEYWORD;
if (ISTASKSTATE(pwd, TASKBUSY)) { wReturn = 0; if (!ISMODE(pwd, COMMAND_CUE) && ISMODE(pwd, MODE_PAUSED)) { if (ISMODE(pwd, COMMAND_PLAY)) wReturn = waveOutRestart(pwd->hWaveOut); else wReturn = waveInStart(pwd->hWaveIn); if (!wReturn) REMOVEMODE(pwd, MODE_PAUSED); } } else wReturn = MCIERR_NONAPPLICABLE_FUNCTION;
return wReturn; }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api UINT | mwCue | This function is called in response to an <m>MCI_CUE<d> message, and is used to cue wave file input or output. Cueing for playback simply causes the output device to be opened but paused, and all the buffers to fill. Cueing for record puts the device into a level checking loop, using one buffer at a time.
Note that the internal flag MCI_MCIWAVE_CUE is passed to the <f>mwSetup<d> function in order to indicate that it should use the Cue command when starting playback or recording.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@parm DWORD | dFlags | Contains the flags used to cue the MCI device.
@flag MCI_WAVE_INPUT | Indicates that the MCI device should be cued for input.
@flag MCI_WAVE_OUTPUT | Indicates that the MCI device should be cued for output. This is the default case.
@flag MCI_WAIT | Wait for command to complete.
@flag MCI_NOTIFY | Notify upon command completion.
@parm <t>LPMCI_GENERIC_PARMS<d> | lpGeneric | Far pointer to parameter block for cue.
@rdesc Returns 0 if playback or recording was cued, else an MCI error. */
PRIVATE UINT PASCAL NEAR mwCue( PWAVEDESC pwd, DWORD dFlags, LPMCI_GENERIC_PARMS lpGeneric) { MCI_PLAY_PARMS mciPlay; DWORD dWaveFlags; DIRECTION Direction;
dWaveFlags = dFlags & ~(MCI_NOTIFY | MCI_WAIT); if (dWaveFlags != (dWaveFlags & (MCI_WAVE_INPUT | MCI_WAVE_OUTPUT))) return MCIERR_UNRECOGNIZED_KEYWORD;
switch (dWaveFlags) { case MCI_WAVE_INPUT: Direction = input; break;
case MCI_WAVE_OUTPUT: case 0: Direction = output; break;
default: return MCIERR_FLAGS_NOT_COMPATIBLE; }
if (ISTASKSTATE(pwd, TASKBUSY)) { if (ISMODE(pwd, COMMAND_CUE) && (pwd->Direction == Direction)) { mwDelayedNotify(pwd, MCI_NOTIFY_SUPERSEDED); if (dFlags & MCI_NOTIFY) mwImmediateNotify(pwd->wDeviceID, lpGeneric); return 0L; } return MCIERR_NONAPPLICABLE_FUNCTION; }
if (lpGeneric && (dFlags & MCI_NOTIFY)) mciPlay.dwCallback = lpGeneric->dwCallback;
dFlags &= ~(MCI_WAVE_INPUT | MCI_WAVE_OUTPUT); return mwSetup(pwd, dFlags | MCI_MCIWAVE_CUE, &mciPlay, Direction); }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api UINT | mwSeek | This function is called in response to an <m>MCI_SEEK<d> message, and is used to seek to position in wave file.
This function has the side effect of stopping any current playback or recording. If successful, the current position is set to the position specified.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@parm DWORD | dFlags | Contains the flags for seek message.
@flag MCI_TO | This flag indicates that the parameter block contains the position to seek to.
@flag MCI_SEEK_TO_START | This flag indicates that the current position should be moved to the start of the media.
@flag MCI_SEEK_TO_END | This flag indicates that the current position should be moved to the end of the media.
@flag MCI_WAIT | Wait for command to complete.
@flag MCI_NOTIFY | Notify upon command completion.
@parm <t>LPMCI_SEEK_PARMS<d> | lpmciSeek | Contains the seek parameters.
@rdesc Returns 0 if the current position was successfully set, else an MCI error. */
PRIVATE UINT PASCAL NEAR mwSeek( PWAVEDESC pwd, DWORD dFlags, LPMCI_SEEK_PARMS lpmciSeek) { DWORD dToBytePosition;
dFlags &= ~(MCI_NOTIFY | MCI_WAIT);
if (!dFlags) return MCIERR_MISSING_PARAMETER;
if (dFlags != (dFlags & (MCI_TO | MCI_SEEK_TO_START | MCI_SEEK_TO_END))) return MCIERR_UNRECOGNIZED_KEYWORD;
switch (dFlags) { case MCI_TO: dToBytePosition = VerifyRangeEnd(pwd, lpmciSeek->dwTo, TRUE); if (dToBytePosition == -1) return MCIERR_OUTOFRANGE; break;
case MCI_SEEK_TO_START: dToBytePosition = 0; break;
case MCI_SEEK_TO_END: dToBytePosition = pwd->dSize; break;
default: return MCIERR_FLAGS_NOT_COMPATIBLE; }
mwStop(pwd); SetCurrentPosition(pwd, dToBytePosition); return 0; }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api DWORD | mwStatus | This function is called in response to an <m>MCI_STATUS<d> message, and is used to return numeric status information, including resource IDs.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@parm UINT | dFlags | Contains the status flags.
@flag MCI_STATUS_ITEM | This flag must be set, and specifies that a specific item is being queried.
@flag MCI_TRACK | This flag specifies that track information is being queried of the item. This flag is only valid with Position and Status queries.
@parm <t>LPMCI_STATUS_PARMS<d> | lpStatus | Status parameters.
@flag MCI_STATUS_POSITION | Queries the current position. If the Track flag is set, then the starting position of the track is being queried. As there is only one track, and it starts at the beginning, this returns zero. Else if the Start flag is set, the starting position of the audio is returned. Else the current position within the wave file is returned.
@flag MCI_STATUS_LENGTH | Queries the current length. If the Track flag is set, then the length of the track is being queried. As there is only one track, the track number must be one. In either case, the length of the wave file is returned.
@flag MCI_STATUS_NUMBER_OF_TRACKS | Queries the number of track. There is always one track.
@flag MCI_STATUS_CURRENT_TRACK | Queries the current of track. As there is one track, this returns one.
@flag MCI_STATUS_READY | Queries as to whether the MCI wave device can receive commands. This is always TRUE.
@flag MCI_STATUS_MODE | Queries the current mode of the MCI wave device instance. This can be one of Paused, Playing, Recording, or Stopped.
@flag MCI_STATUS_MEDIA_PRESENT | Queries as to whether there is media present. Since there must be a wave file present to enter this function, this always returns TRUE.
@flag MCI_STATUS_TIME_FORMAT | Queries the current time format. This can be one of Bytes, Samples, or Milliseconds.
@flag MCI_WAVE_STATUS_FORMATTAG | Queries the current format tag. There is only PCM now, but it will return identifiers for other tag formats.
@flag MCI_WAVE_STATUS_CHANNELS | Queries the number of channels. This is one or two.
@flag MCI_WAVE_STATUS_SAMPLESPERSEC | Queries the number of samples per second for playback and recording.
@flag MCI_WAVE_STATUS_AVGBYTESPERSEC | Queries the average number of bytes per second for playback and recording.
@flag MCI_WAVE_STATUS_BLOCKALIGN | Queries the current block alignment.
@flag MCI_WAVE_STATUS_BITSPERSAMPLE | Queries the number of bits per sample.
@flag MCI_WAVE_INPUT | Queries the current input wave device in use, if any. If no device suits the current format, an error is returned. If a device suits the current format, but the MCI wave device instance is not recording, then an error is also returned. Else the device in use is returned.
@flag MCI_WAVE_OUTPUT | Queries the current output wave device in use, if any. If no device suits the current format, an error is returned. If a device suits the current format, but the MCI wave device instance is not playing, then an error is also returned. Else the device in use is returned.
@flag MCI_WAVE_STATUS_LEVEL | Returns the current input level, if possible. Before checking the task state, the function must make sure the task is not in Cleanup mode. If it is, it must wait for the task to enter Idle state before sending new commands. If the task is currently busy, and in-use error is returned. If the task is idle, recording is Cued. The function then waits for the background task to enter the Cued state (which it might have already been in), and retrieves the level sample.
@flag MCI_WAIT | Wait for command to complete.
@flag MCI_NOTIFY | Notify upon command completion.
@rdesc Returns 0 if the request status was successfully returned, else an MCI error. */
PRIVATE DWORD PASCAL NEAR mwStatus( PWAVEDESC pwd, DWORD dFlags, LPMCI_STATUS_PARMS lpStatus) { DWORD dReturn; #ifdef _WIN64
DWORD dwPos; #endif
dReturn = 0; dFlags &= ~(MCI_NOTIFY | MCI_WAIT);
if (dFlags & MCI_STATUS_ITEM) { dFlags &= ~MCI_STATUS_ITEM;
if ((dFlags & MCI_TRACK) && !(lpStatus->dwItem == MCI_STATUS_POSITION || lpStatus->dwItem == MCI_STATUS_LENGTH)) return MCIERR_FLAGS_NOT_COMPATIBLE;
else if ((dFlags & MCI_STATUS_START) && (lpStatus->dwItem != MCI_STATUS_POSITION)) return MCIERR_FLAGS_NOT_COMPATIBLE;
switch (lpStatus->dwItem) { UINT wResource;
case MCI_STATUS_POSITION: switch (dFlags) { case 0: #ifndef _WIN64
dReturn = GetPlayRecPosition(pwd, &(lpStatus->dwReturn), pwd->dTimeFormat); #else
dwPos = (DWORD)lpStatus->dwReturn; dReturn = GetPlayRecPosition(pwd, &dwPos, pwd->dTimeFormat); lpStatus->dwReturn = dwPos; #endif
break;
case MCI_TRACK: if (lpStatus->dwTrack != 1) dReturn = MCIERR_OUTOFRANGE; else lpStatus->dwReturn = 0L; break;
case MCI_STATUS_START: lpStatus->dwReturn = 0L; break;
default: dReturn = MCIERR_UNRECOGNIZED_KEYWORD; break; } break;
case MCI_STATUS_LENGTH: switch (dFlags) { case 0: lpStatus->dwReturn = bytes2time(pwd, pwd->dSize, pwd->dTimeFormat); break;
case MCI_TRACK: if (lpStatus->dwTrack != 1) dReturn = MCIERR_OUTOFRANGE; else lpStatus->dwReturn = bytes2time(pwd, pwd->dSize, pwd->dTimeFormat); break;
default: dReturn = MCIERR_UNRECOGNIZED_KEYWORD; break; } break;
case MCI_STATUS_NUMBER_OF_TRACKS: case MCI_STATUS_CURRENT_TRACK: lpStatus->dwReturn = 1L; break;
case MCI_STATUS_READY: lpStatus->dwReturn = (DWORD)MAKEMCIRESOURCE(TRUE, MCI_TRUE); dReturn = MCI_RESOURCE_RETURNED; break;
case MCI_STATUS_MODE: if (ISTASKSTATE(pwd, TASKBUSY)) { if (ISMODE(pwd, MODE_PAUSED | COMMAND_CUE | MODE_HOLDING)) wResource = MCI_MODE_PAUSE; else if (ISMODE(pwd, COMMAND_PLAY)) wResource = MCI_MODE_PLAY; else wResource = MCI_MODE_RECORD; } else wResource = MCI_MODE_STOP; lpStatus->dwReturn = (DWORD)MAKEMCIRESOURCE(wResource, wResource); dReturn = MCI_RESOURCE_RETURNED; break;
case MCI_STATUS_MEDIA_PRESENT: lpStatus->dwReturn = (DWORD)MAKEMCIRESOURCE(TRUE, MCI_TRUE); dReturn = MCI_RESOURCE_RETURNED; break;
case MCI_STATUS_TIME_FORMAT: wResource = LOWORD(pwd->dTimeFormat); lpStatus->dwReturn = (DWORD)MAKEMCIRESOURCE(wResource, wResource + MCI_FORMAT_RETURN_BASE); dReturn = MCI_RESOURCE_RETURNED; break;
case MCI_WAVE_STATUS_FORMATTAG: if (pwd->pwavefmt->wFormatTag == WAVE_FORMAT_PCM) { lpStatus->dwReturn = (DWORD)MAKEMCIRESOURCE(WAVE_FORMAT_PCM, WAVE_FORMAT_PCM_S); dReturn = MCI_RESOURCE_RETURNED; } else lpStatus->dwReturn = MAKELONG(pwd->pwavefmt->wFormatTag, 0); break;
case MCI_WAVE_STATUS_CHANNELS: lpStatus->dwReturn = MAKELONG(pwd->pwavefmt->nChannels, 0); break;
case MCI_WAVE_STATUS_SAMPLESPERSEC: lpStatus->dwReturn = pwd->pwavefmt->nSamplesPerSec; break;
case MCI_WAVE_STATUS_AVGBYTESPERSEC: lpStatus->dwReturn = pwd->pwavefmt->nAvgBytesPerSec; break;
case MCI_WAVE_STATUS_BLOCKALIGN: lpStatus->dwReturn = MAKELONG(pwd->pwavefmt->nBlockAlign, 0); break;
case MCI_WAVE_STATUS_BITSPERSAMPLE:
if (pwd->pwavefmt->wFormatTag == WAVE_FORMAT_PCM) lpStatus->dwReturn = (((NPPCMWAVEFORMAT)(pwd->pwavefmt))->wBitsPerSample); else dReturn = MCIERR_UNSUPPORTED_FUNCTION; break;
case MCI_WAVE_INPUT:
if (pwd->idIn == WAVE_MAPPER) { lpStatus->dwReturn = (DWORD)MAKEMCIRESOURCE(WAVE_MAPPER, WAVE_MAPPER_S); dReturn = MCI_RESOURCE_RETURNED; } else lpStatus->dwReturn = pwd->idIn; break;
case MCI_WAVE_OUTPUT:
if (pwd->idOut == WAVE_MAPPER) { lpStatus->dwReturn = (DWORD)MAKEMCIRESOURCE(WAVE_MAPPER, WAVE_MAPPER_S); dReturn = MCI_RESOURCE_RETURNED; } else lpStatus->dwReturn = pwd->idOut; break;
case MCI_WAVE_STATUS_LEVEL:
if (ISMODE(pwd, MODE_CLEANUP)) { while (!ISTASKSTATE(pwd, TASKIDLE)) mmYield(pwd); }
if (ISTASKSTATE(pwd, TASKIDLE)) { pwd->Direction = input; TaskSignal(pwd->hTask, WTM_STATECHANGE);
if (0 != (dReturn = mwGetDevice(pwd))) break;
SETMODE(pwd, COMMAND_NEW | COMMAND_INSERT | COMMAND_OVERWRITE | COMMAND_CUE); SETTASKSTATE(pwd, TASKBUSY);
} else if (!ISMODE(pwd, COMMAND_INSERT | COMMAND_OVERWRITE) || !ISMODE(pwd, COMMAND_CUE)) {
dReturn = MCIERR_WAVE_INPUTSINUSE; break; }
while (!ISMODE(pwd, MODE_CUED) && !ISTASKSTATE(pwd, TASKIDLE)) mmYield(pwd);
if (pwd->wTaskError) dReturn = pwd->wTaskError; else lpStatus->dwReturn = pwd->dLevel;
break;
default: dReturn = MCIERR_UNSUPPORTED_FUNCTION; break; } } else dReturn = MCIERR_MISSING_PARAMETER;
return dReturn; }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api UINT | mwSet | This function is called in response to an <m>MCI_SET<d> message, and is used to set the specified parameters in the MCI device information block. Note that format changes can only be performed on a file with no data, as data conversion is not performed.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@parm UINT | dFlags | Contains the status flags.
@flag MCI_WAVE_INPUT | Sets the input wave device to be used to the specified device ID. This causes playback and recording to be stopped.
@flag MCI_WAVE_OUTPUT | Sets the output wave device to be used to the specified device ID. This causes playback and recording to be stopped.
@flag MCI_WAVE_SET_ANYINPUT | Enables recording to use any wave input device.
@flag MCI_WAVE_SET_ANYOUTPUT | Enables recording to use any wave input device.
@flag MCI_SET_TIME_FORMAT | Sets the time format used when interpreting or returning time-based command arguments. Note that the time format can only be set to bytes if the file format is currently PCM, it does not care if
@flag MCI_WAVE_SET_FORMATTAG | Sets the wave format tag. This causes playback and recording to be stopped, and saves a copy of the current wave format header in case the new format is not valid for either recording or playback.
@flag MCI_WAVE_SET_CHANNELS | Sets the number of channels. This causes playback and recording to be stopped, and saves a copy of the current wave format header in case the new format is not valid for either recording or playback.
@flag MCI_WAVE_SET_SAMPLESPERSEC | Sets the number of samples per second for recording and playback. This causes playback and recording to be stopped, and saves a copy of the current wave format header in case the new format is not valid for either recording or playback.
@flag MCI_WAVE_SET_AVGBYTESPERSEC | Sets the average number of bytes per second for recording and playback. This causes playback and recording to be stopped, and saves a copy of the current wave format header in case the new format is not valid for either recording or playback.
@flag MCI_WAVE_SET_BLOCKALIGN | Sets the block alignment. This causes playback and recording to be stopped, and saves a copy of the current wave format header in case the new format is not valid for either recording or playback.
@flag MCI_WAVE_SET_BITSPERSAMPLE | Sets the number of bits per sample. This causes playback and recording to be stopped, and saves a copy of the current wave format header in case the new format is not valid for either recording or playback.
@flag MCI_SET_AUDIO | This is an unsupported function.
@flag MCI_SET_DOOR_OPEN | This is an unsupported function.
@flag MCI_SET_DOOR_CLOSED | This is an unsupported function.
@flag MCI_SET_VIDEO | This is an unsupported function.
@flag MCI_SET_ON | This is an unsupported function.
@flag MCI_SET_OFF | This is an unsupported function.
@flag MCI_WAIT | Wait for command to complete.
@flag MCI_NOTIFY | Notify upon command completion.
@parm <t>LPMCI_WAVE_SET_PARMS<d> | lpSet | Set parameters.
@rdesc Returns 0 if the requested parameters were successfully set, else an MCI error if one or more error occurred. */
PRIVATE UINT PASCAL NEAR mwSet( PWAVEDESC pwd, DWORD dFlags, LPMCI_WAVE_SET_PARMS lpSet) { UINT wReturn;
dFlags &= ~(MCI_NOTIFY | MCI_WAIT); if (!dFlags) return MCIERR_MISSING_PARAMETER;
wReturn = 0; if (dFlags & (MCI_WAVE_INPUT | MCI_WAVE_OUTPUT)) { mwStop(pwd); if (dFlags & MCI_WAVE_INPUT) { if (lpSet->wInput < cWaveInMax) pwd->idIn = lpSet->wInput; else wReturn = MCIERR_OUTOFRANGE; } if (dFlags & MCI_WAVE_OUTPUT) { if (lpSet->wOutput < cWaveOutMax) pwd->idOut = lpSet->wOutput; else wReturn = MCIERR_OUTOFRANGE; } } if (dFlags & MCI_WAVE_SET_ANYINPUT) pwd->idIn = (DWORD)WAVE_MAPPER;
if (dFlags & MCI_WAVE_SET_ANYOUTPUT) pwd->idOut = (DWORD)WAVE_MAPPER;
if (dFlags & MCI_SET_TIME_FORMAT) { if ((lpSet->dwTimeFormat == MCI_FORMAT_MILLISECONDS) || (lpSet->dwTimeFormat == MCI_FORMAT_SAMPLES) || ((lpSet->dwTimeFormat == MCI_FORMAT_BYTES) && (pwd->pwavefmt->wFormatTag == WAVE_FORMAT_PCM))) pwd->dTimeFormat = lpSet->dwTimeFormat; else wReturn = MCIERR_BAD_TIME_FORMAT; }
if (dFlags & (MCI_WAVE_SET_FORMATTAG | MCI_WAVE_SET_CHANNELS | MCI_WAVE_SET_SAMPLESPERSEC | MCI_WAVE_SET_AVGBYTESPERSEC | MCI_WAVE_SET_BLOCKALIGN | MCI_WAVE_SET_BITSPERSAMPLE)) {
if (pwd->dSize) { wReturn = MCIERR_NONAPPLICABLE_FUNCTION; } else { PBYTE pbWaveFormat;
mwStop(pwd); pbWaveFormat = (PBYTE)LocalAlloc(LPTR, pwd->wFormatSize);
if (!pbWaveFormat) return MCIERR_OUT_OF_MEMORY;
memcpy(pbWaveFormat, pwd->pwavefmt, pwd->wFormatSize);
if (dFlags & MCI_WAVE_SET_FORMATTAG) pwd->pwavefmt->wFormatTag = lpSet->wFormatTag;
if (dFlags & MCI_WAVE_SET_CHANNELS) pwd->pwavefmt->nChannels = lpSet->nChannels;
if (dFlags & MCI_WAVE_SET_SAMPLESPERSEC) pwd->pwavefmt->nSamplesPerSec = lpSet->nSamplesPerSec;
if (dFlags & MCI_WAVE_SET_AVGBYTESPERSEC) pwd->pwavefmt->nAvgBytesPerSec = lpSet->nAvgBytesPerSec;
if (dFlags & MCI_WAVE_SET_BITSPERSAMPLE) if (pwd->pwavefmt->wFormatTag == WAVE_FORMAT_PCM) ((NPPCMWAVEFORMAT)(pwd->pwavefmt))->wBitsPerSample = lpSet->wBitsPerSample; else wReturn = MCIERR_UNSUPPORTED_FUNCTION;
if (dFlags & MCI_WAVE_SET_BLOCKALIGN) pwd->pwavefmt->nBlockAlign = lpSet->nBlockAlign; else if (pwd->pwavefmt->wFormatTag == WAVE_FORMAT_PCM) pwd->pwavefmt->nBlockAlign = (WORD)pwd->pwavefmt->nSamplesPerSec / (WORD)pwd->pwavefmt->nAvgBytesPerSec;
if (mwCheckDevice(pwd, output) && mwCheckDevice(pwd, input)) { wReturn = MCIERR_OUTOFRANGE; memcpy(pwd->pwavefmt, pbWaveFormat, pwd->wFormatSize); } else pwd->dAudioBufferLen = BLOCKALIGN(pwd, pwd->pwavefmt->nAvgBytesPerSec);
LocalFree(pbWaveFormat); } }
if (dFlags & (MCI_SET_DOOR_OPEN | MCI_SET_DOOR_CLOSED | MCI_SET_AUDIO | MCI_SET_VIDEO | MCI_SET_ON | MCI_SET_OFF)) wReturn = MCIERR_UNSUPPORTED_FUNCTION;
return wReturn; }
/************************************************************************/
/*
@doc INTERNAL MCIWAVE
@api UINT | mwDelete | This function is called in response to an <m>MCI_DELETE<d> message, and is used to delete a portion of the wave file.
The range checking performed on the "to" and "from" parameters is almost identical to that of playback and recording, except that the the "to" position cannot be larger than the file length.
If the parameters are equal, the function sets the current position to the "from" parameter, and returns success without actually doing anything, else the specified range is deleted from the file.
On success, the current position is set to the "from" position. This is consistent with the other commands that have "to" and "from" paramters since the "to" position becomes the same as the "from" position after a deletion.
In the future, support for Cut/Copy/Paste should be added.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@parm DWORD | dFlags | Contains the flags for delete message.
@flag MCI_FROM | Indicates a starting position is present in <p>lpDelete<d>, else the current position is used.
@flag MCI_TO | Indicates an ending position is present in <p>lpDelete<d>, else the file size is used.
@parm <t>LPMCI_WAVE_DELETE_PARMS<d> | lpDelete | Optionally contains the delete parameters.
@rdesc Returns 0 if the range was deleted, else MCIERR_OUTOFRANGE for invalid parameters or MCIERR_OUT_OF_MEMORY if the delete failed. */
PRIVATE UINT PASCAL NEAR mwDelete( PWAVEDESC pwd, DWORD dFlags, LPMCI_WAVE_DELETE_PARMS lpDelete) { DWORD dFrom; DWORD dTo;
mwStop(pwd); if (dFlags & MCI_FROM) { dFrom = VerifyRangeStart(pwd, lpDelete->dwFrom); if (dFrom == -1) return MCIERR_OUTOFRANGE; } else dFrom = pwd->dCur;
if (dFlags & MCI_TO) { dTo = VerifyRangeEnd(pwd, lpDelete->dwTo, TRUE); if (dTo == -1) return MCIERR_OUTOFRANGE; } else dTo = pwd->dSize;
if (dTo < dFrom) return MCIERR_OUTOFRANGE;
SetCurrentPosition(pwd, dFrom);
if (dTo == dFrom) return 0L;
pwd->dTo = dTo; SETTASKSTATE(pwd, TASKDELETE); TaskSignal(pwd->hTask, WTM_STATECHANGE);
while (!ISTASKSTATE(pwd, TASKIDLE)) mmYield(pwd);
return pwd->wTaskError; }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api UINT | mwSave | This function is called in response to an <m>MCI_SAVE<d> message, and is used to save the file attached to the MCI device. This has the side effect of stopping any current playback or recording.
If the file is not named, the MCI_SAVE_FILE flag must be used and a named provided, else the function will fail. If the function succeeds, and a name has been provided, the name attached to the MCI device will be changed, otherwise it will remain the same.
@parm <t>PWAVEDESC<d> | pwd | Pointer to the wave device descriptor.
@parm DWORD | dFlags | Contains the save flags.
@flag MCI_SAVE_FILE | Indicates that a file name has been provided in the <p>lpSave<d> structure.
@parm <t>LPMCI_SAVE_PARMS<d> | lpSave | Structure optionally contains a pointer to a file name to save to. The current file name is only changed if the save is successful.
@rdesc Returns 0 if the file was saved, else an MCI error. */
PRIVATE UINT PASCAL NEAR mwSave( PWAVEDESC pwd, DWORD dFlags, LPMCI_SAVE_PARMS lpSave) { if (((dFlags & MCI_SAVE_FILE) && !lpSave->lpfilename) || (!*pwd->aszFile && !(dFlags & MCI_SAVE_FILE))) return MCIERR_UNNAMED_RESOURCE;
if (dFlags & MCI_SAVE_FILE) {
MMIOINFO mmioInfo;
WCHAR aszSaveFile[_MAX_PATH];
aszSaveFile[ (sizeof(aszSaveFile) / sizeof(WCHAR)) - 1] = '\0'; wcsncpy(aszSaveFile, lpSave->lpfilename, (sizeof(aszSaveFile) / sizeof(WCHAR)) - 1);
InitMMIOOpen(pwd, &mmioInfo);
if (!mmioOpen(aszSaveFile, &mmioInfo, MMIO_PARSE)) return MCIERR_FILENAME_REQUIRED; // The fully qualified name is in aszSaveFile
if (lstrcmp(aszSaveFile, pwd->aszFile)) { pwd->szSaveFile = (LPWSTR)LocalAlloc(LPTR, sizeof(WCHAR)*lstrlen(aszSaveFile) + sizeof(WCHAR)); if (pwd->szSaveFile) lstrcpy(pwd->szSaveFile, aszSaveFile); else return MCIERR_OUT_OF_MEMORY; } }
mwStop(pwd); SETTASKSTATE(pwd, TASKSAVE); TaskSignal(pwd->hTask, WTM_STATECHANGE);
while (!ISTASKSTATE(pwd, TASKIDLE)) mmYield(pwd);
if (pwd->szSaveFile) { LocalFree(pwd->szSaveFile); pwd->szSaveFile = NULL; }
return pwd->wTaskError; }
/************************************************************************/ /*
@doc INTERNAL MCIWAVE
@api LRESULT | mciDriverEntry | Single entry point for MCI drivers.
After executing the command, if notification has been specified, any previous notification is superseded, and new notification is performed. Any command which performs delayed notification, or the special case of Cue, has already returned by this point.
@parm MCIDEVICEID | wDeviceID | Contains the MCI device ID.
@parm UINT | wMessage | The requested action to be performed.
@flag MCI_OPEN_DRIVER | Open an instance of the MCI wave device driver, possibly attaching an element to the device.
@flag MCI_CLOSE_DRIVER | Close an instance of the MCI wave device driver, closing any element attached to the device.
@flag MCI_PLAY | Play the element attached to the instance of the MCI wave device driver.
@flag MCI_RECORD | Record to the element attached to the instance of the MCI wave device driver.
@flag MCI_STOP | Stop playback or recording of the element attached to the instance of the MCI wave device driver.
@flag MCI_CUE | Cue playback or recording of the element attached to the instance of the MCI wave device driver.
@flag MCI_SEEK | Set the current position in the element attached to the instance of the MCI wave device driver.
@flag MCI_PAUSE | Pause playback or recording of the element attached to the instance of the MCI wave device driver.
@flag MCI_RESUME | Resumes playback or recording of the element attached to the instance of the MCI wave device driver.
@flag MCI_STATUS | Retrieve the specified status of the element attached to the instance of the MCI wave device driver.
@flag MCI_GETDEVCAPS | Retrieve the specified device capabilities of the instance of the MCI wave device driver.
@flag MCI_INFO | Retrieve the specified information from the element or the instance of the MCI wave device driver.
@flag MCI_SET | Set the specified parameters of the element attached to the instance of the MCI wave device driver.
@flag MCI_SAVE | Save the element attached to the instance of the MCI wave device driver.
@flag MCI_DELETE | Delete data from the element attached to the instance of the MCI wave device driver.
@flag MCI_LOAD | This is an unsupported function.
@parm DWORD | dFlags | Data for this message. Defined seperately for each message.
@parm <t>LPMCI_GENERIC_PARMS<d> | lpParms | Data for this message. Defined seperately for each message
@rdesc Defined separately for each message. */
PUBLIC LRESULT PASCAL FAR mciDriverEntry( MCIDEVICEID wDeviceID, UINT wMessage, DWORD dFlags, LPMCI_GENERIC_PARMS lpParms) { PWAVEDESC pwd; LRESULT lReturn;
if (!(pwd = (PWAVEDESC)(mciGetDriverData(wDeviceID)))) switch (wMessage) { case MCI_PLAY: case MCI_RECORD: case MCI_STOP: case MCI_CUE: case MCI_SEEK: case MCI_PAUSE: case MCI_RESUME: case MCI_STATUS: case MCI_SET: case MCI_SAVE: case MCI_DELETE: case MCI_COPY: case MCI_PASTE: return (LRESULT)MCIERR_UNSUPPORTED_FUNCTION; }
EnterCrit();
switch (wMessage) { case MCI_OPEN_DRIVER: lReturn = mwOpenDevice(dFlags, (LPMCI_WAVE_OPEN_PARMS)lpParms, wDeviceID); break;
case MCI_CLOSE_DRIVER: lReturn = mwCloseDevice(pwd); pwd = NULL; break;
case MCI_PLAY: lReturn = (LRESULT)(LONG)mwSetup(pwd, dFlags, (LPMCI_PLAY_PARMS)lpParms, output); LeaveCrit(); return lReturn;
case MCI_RECORD: lReturn = (LRESULT)(LONG)mwSetup(pwd, dFlags, (LPMCI_PLAY_PARMS)lpParms, input); LeaveCrit(); return lReturn;
case MCI_STOP: mwStop(pwd); lReturn = 0; break;
case MCI_CUE: lReturn = (LRESULT)(LONG)mwCue(pwd, dFlags, lpParms); LeaveCrit(); return lReturn;
case MCI_SEEK: lReturn = mwSeek(pwd, dFlags, (LPMCI_SEEK_PARMS)lpParms); break;
case MCI_PAUSE: lReturn = mwPause(pwd, dFlags); break;
case MCI_RESUME: lReturn = mwResume(pwd, dFlags); break;
case MCI_STATUS: lReturn = mwStatus(pwd, dFlags, (LPMCI_STATUS_PARMS)lpParms); break;
case MCI_GETDEVCAPS: lReturn = mwGetDevCaps(pwd, dFlags, (LPMCI_GETDEVCAPS_PARMS)lpParms); break;
case MCI_INFO: lReturn = mwInfo(pwd, dFlags, (LPMCI_INFO_PARMS)lpParms); break;
case MCI_SET: lReturn = mwSet(pwd, dFlags, (LPMCI_WAVE_SET_PARMS)lpParms); break;
case MCI_SAVE: lReturn = mwSave(pwd, dFlags, (LPMCI_SAVE_PARMS)lpParms); break;
case MCI_DELETE: lReturn = mwDelete(pwd, dFlags, (LPMCI_WAVE_DELETE_PARMS)lpParms); break;
case MCI_COPY: case MCI_PASTE: case MCI_LOAD: lReturn = MCIERR_UNSUPPORTED_FUNCTION; break;
default: lReturn = MCIERR_UNRECOGNIZED_COMMAND; break; } if (!LOWORD(lReturn) && (dFlags & MCI_NOTIFY)) { if (pwd) mwDelayedNotify(pwd, MCI_NOTIFY_SUPERSEDED); mwImmediateNotify(wDeviceID, lpParms); }
LeaveCrit(); return lReturn; }
/************************************************************************/
|