windows-nt/Source/XPSP1/NT/multimedia/media/mciwave/mciwave.c
2020-09-26 16:20:57 +08:00

3320 lines
106 KiB
C

/************************************************************************/
/*
** 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;
}
/************************************************************************/