You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3319 lines
109 KiB
3319 lines
109 KiB
/************************************************************************/
|
|
|
|
/*
|
|
** 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;
|
|
}
|
|
|
|
/************************************************************************/
|