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

1670 lines
46 KiB
C

/* (C) Copyright Microsoft Corporation 1991-1994. All Rights Reserved */
/* file.c
*
* File I/O and related functions.
*
* Revision history:
* 4/2/91 LaurieGr (AKA LKG) Ported to WIN32 / WIN16 common code
* 5/27/92 -jyg- Added more RIFF support to BOMBAY version
* 22/Feb/94 LaurieGr merged Motown and Daytona version
*/
#include "nocrap.h"
#include <windows.h>
#include <commdlg.h>
#include <mmsystem.h>
#include <mmreg.h>
#include <windowsx.h>
#define INCLUDE_OLESTUBS
#include "SoundRec.h"
#include "srecids.h"
#ifdef CHICAGO
# if WINVER >= 0x0400
# include <shellapi.h>
# else
# include <shell2.h>
# endif
#endif
#include "file.h"
#include "convert.h"
#include "reg.h"
/* globals */
PCKNODE gpcknHead = NULL; // ??? eugh. more globals!
PCKNODE gpcknTail = NULL;
static PFACT spFact = NULL;
static long scbFact = 0;
static void FreeAllChunks(PCKNODE *ppcknHead, PCKNODE *ppcknTail);
static BOOL AddChunk(LPMMCKINFO lpCk, HPBYTE hb, PCKNODE * ppcknHead,
PCKNODE * ppcknTail);
static PCKNODE FreeHeadChunk(PCKNODE *ppcknHead);
/*
* Is the current document untitled?
*/
BOOL IsDocUntitled()
{
return (lstrcmp(gachFileName, aszUntitled) == 0);
}
/*
* Rename the current document.
*/
void RenameDoc(LPTSTR aszNewFile)
{
lstrcpy(gachFileName, aszNewFile);
lstrcpy(gachLinkFilename, gachFileName);
if (gfLinked)
AdviseRename(gachLinkFilename);
}
/* MarkWaveDirty: Mark the wave as dirty. */
void FAR PASCAL
EndWaveEdit(BOOL fDirty)
{
if (fDirty)
{
gfDirty = TRUE;
AdviseDataChange();
DoOleSave();
AdviseSaved();
}
}
void FAR PASCAL
BeginWaveEdit(void)
{
FlushOleClipboard();
}
/* fOK = PromptToSave()
*
* If the file is dirty (modified), ask the user "Save before closing?".
* Return TRUE if it's okay to continue, FALSE if the caller should cancel
* whatever it's doing.
*/
PROMPTRESULT FAR PASCAL
PromptToSave(
BOOL fMustClose,
BOOL fSetForground)
{
WORD wID;
DWORD dwMB = MB_ICONEXCLAMATION | MB_YESNOCANCEL;
if (fSetForground)
dwMB |= MB_SETFOREGROUND;
/* stop playing/recording */
StopWave();
if (gfDirty && gfStandalone && gfDirty != -1) { // changed and possible to save
wID = ErrorResBox( ghwndApp
, ghInst
, dwMB
, IDS_APPTITLE
, IDS_SAVECHANGES
, (LPTSTR) gachFileName
);
if (wID == IDCANCEL)
{
return enumCancel;
}
else if (wID == IDYES)
{
if (!FileSave(FALSE))
return enumCancel;
}
else
return enumRevert;
}
#if 0
// is this necessary?
// This is bad. It notifies the container before we actually
// DoOleClose. This will cause some containers (Excel 5.0c) to
// get confused and nuke client sites on non-dirty objects.
else if (fMustClose)
{
DebugBreak();
AdviseClosed();
}
#endif
return enumSaved;
} /* PromptToSave */
/* fOK = CheckIfFileExists(szFileName)
*
* The user specified <szFileName> as a file to write over -- check if
* this file exists. Return TRUE if it's okay to continue (i.e. the
* file doesn't exist, or the user OK'd overwriting it),
* FALSE if the caller should cancel whatever it's doing.
*/
static BOOL NEAR PASCAL
CheckIfFileExists( LPTSTR szFileName) // file name to check
{
HANDLE hFile;
hFile = CreateFile(szFileName,
GENERIC_READ|GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
return TRUE; // doesn't exist
CloseHandle(hFile);
/* prompt user for permission to overwrite the file */
return ErrorResBox(ghwndApp, ghInst, MB_ICONQUESTION | MB_OKCANCEL,
IDS_APPTITLE, IDS_FILEEXISTS, szFileName) == IDOK;
}
#define SLASH(c) ((c) == TEXT('/') || (c) == TEXT('\\'))
/* return a pointer to the filename part of the path
i.e. scan back from end to \: or start
e.g. "C:\FOO\BAR.XYZ" -> return pointer to "BAR.XYZ"
*/
LPCTSTR FAR PASCAL
FileName(LPCTSTR szPath)
{
LPCTSTR sz;
if (!szPath)
return NULL;
for (sz=szPath; *sz; sz = CharNext(sz))
;
for (; !SLASH(*sz) && *sz!=TEXT(':'); sz = CharPrev(szPath,sz))
if (sz == szPath)
return sz;
return CharNext(sz);
}
/* UpdateCaption()
*
* Set the caption of the app window.
*/
void FAR PASCAL
UpdateCaption(void)
{
TCHAR ach[_MAX_PATH + _MAX_FNAME + _MAX_EXT - 2];
static SZCODE aszTitleFormat[] = TEXT("%s - %s");
#ifdef CHICAGO
SHFILEINFO shfi;
if (!IsDocUntitled() && SHGetFileInfo(gachFileName, 0, &shfi, sizeof(shfi), SHGFI_ICON|SHGFI_DISPLAYNAME ))
{
wsprintf(ach, aszTitleFormat, shfi.szDisplayName, (LPTSTR)gachAppTitle);
SetWindowText(ghwndApp, ach);
SetClassLongPtr(ghwndApp, GCLP_HICON, (DWORD_PTR)shfi.hIcon);
return;
}
else
{
//
// reset icon to app icon
//
extern HICON ghiconApp;
SetClassLongPtr(ghwndApp, GCLP_HICON, (LONG_PTR)ghiconApp);
}
#endif
wsprintf(ach, aszTitleFormat, FileName(gachFileName), (LPTSTR)gachAppTitle);
SetWindowText(ghwndApp, ach);
} /* UpdateCaption */
//REVIEW: The functionality in FileOpen and FileNew should be more
// safe for OLE. This means, we want to open a file, but
// have no reason to revoke the server.
/* FileNew(fmt, fUpdateDisplay, fNewDlg)
*
* Make a blank document.
*
* If <fUpdateDisplay> is TRUE, then update the display after creating a new file.
*/
BOOL FAR PASCAL FileNew(
WORD fmt,
BOOL fUpdateDisplay,
BOOL fNewDlg)
{
//
// avoid reentrancy when called through OLE
//
// ??? Need to double check on this. Is this thread safe?
// ??? Does it need to be thread safe? Or are we actually
// ??? just trying to avoid recursion rather than reentrancy?
if (gfInFileNew)
return FALSE;
//
// stop playing/recording
//
StopWave();
//
// Commit all pending objects.
//
FlushOleClipboard();
//
// some client's (ie Excel 3.00 and PowerPoint 1.0) don't
// handle saved notifications, they expect to get a
// OLE_CLOSED message.
//
// if the user has chosen to update the object, but the client did
// not then send a OLE_CLOSED message.
//
if (gfEmbeddedObject && gfDirty == -1)
AdviseClosed();
//
// FileNew can be called either from FileOpen or from a menu
// or from the server, etc... We should behave as FileOpen from the
// server (i.e. the dialog can be canceled without toasting the buffer)
//
if (!NewWave(fmt,fNewDlg))
return FALSE;
//
// update state variables
//
lstrcpy(gachFileName, aszUntitled);
BuildUniqueLinkName();
gfDirty = FALSE; // file was modified and not saved?
if (fUpdateDisplay) {
UpdateCaption();
UpdateDisplay(TRUE);
}
FreeAllChunks(&gpcknHead, &gpcknTail); // free all old info
return TRUE;
} /* FileNew */
/* REVIEW: The functionality in FileOpen and FileNew should be more
* safe for OLE. This means, we want to open a file, but
* have no reason to revoke the server.
* */
BOOL FileLoad(
LPCTSTR szFileName)
{
TCHAR aszFile[_MAX_PATH];
HCURSOR hcurPrev = NULL; // cursor before hourglass
HMMIO hmmio;
BOOL fOk = TRUE;
StopWave();
// qualify
GetFullPathName(szFileName,SIZEOF(aszFile),aszFile,NULL);
hcurPrev = SetCursor(LoadCursor(NULL, IDC_WAIT));
// read the WAVE file
hmmio = mmioOpen(aszFile, NULL, MMIO_READ | MMIO_ALLOCBUF);
if (hmmio != NULL)
{
MMRESULT mmr;
LPWAVEFORMATEX pwfx;
DWORD cbwfx;
DWORD cbdata;
LPBYTE pdata;
PCKNODE pcknHead = gpcknHead;
PCKNODE pcknTail = gpcknTail;
PFACT pfct = spFact;
LONG cbfact = scbFact;
gpcknHead = NULL;
gpcknTail = NULL;
spFact = NULL;
scbFact = 0L;
mmr = ReadWaveFile(hmmio
, &pwfx
, &cbwfx
, &pdata
, &cbdata
, aszFile
, TRUE);
mmioClose(hmmio, 0);
if (mmr != MMSYSERR_NOERROR || pwfx == NULL)
{
//
// restore the cache globals
//
gpcknHead = pcknHead;
gpcknTail = pcknTail;
spFact = pfct;
scbFact = cbfact;
if (pwfx == NULL)
{
if (pdata)
GlobalFreePtr(pdata);
}
goto RETURN_ERROR;
}
DestroyWave();
gpWaveFormat = pwfx;
gcbWaveFormat = cbwfx;
gpWaveSamples = pdata;
glWaveSamples = cbdata;
//
// destroy the cache temps
//
FreeAllChunks(&pcknHead, &pcknTail);
if (pfct)
GlobalFreePtr((LPVOID)pfct);
}
else
{
ErrorResBox(ghwndApp
, ghInst
, MB_ICONEXCLAMATION | MB_OK
, IDS_APPTITLE
, IDS_ERROROPEN
, (LPTSTR) aszFile);
goto RETURN_ERROR;
}
//
// update state variables
//
RenameDoc(aszFile);
glWaveSamplesValid = glWaveSamples;
glWavePosition = 0L;
goto RETURN_SUCCESS;
RETURN_ERROR:
fOk = FALSE;
#if 0
FreeAllChunks(&gpcknHead, &gpcknTail); /* free all old info */
#endif
RETURN_SUCCESS:
if (hcurPrev != NULL)
SetCursor(hcurPrev);
/* Only mark clean on success */
if (fOk)
gfDirty = FALSE;
/* update the display */
UpdateCaption();
UpdateDisplay(TRUE);
return fOk;
}
/* FileOpen(szFileName)
*
* If <szFileName> is NULL, do a File/Open command. Otherwise, open
* <szFileName>. Return TRUE on success, FALSE otherwise.
*/
BOOL FAR PASCAL
FileOpen(
LPCTSTR szFileName) // file to open (or NULL)
{
TCHAR ach[80]; // buffer for string loading
TCHAR aszFile[_MAX_PATH];
HCURSOR hcurPrev = NULL; // cursor before hourglass
HMMIO hmmio;
BOOL fOk = TRUE;
//
// stop playing/recording
//
StopWave();
//
// Commit all pending objects.
//
FlushOleClipboard();
if (!PromptToSave(FALSE, FALSE))
goto RETURN_ERRORNONEW;
//
// get the new file name into <ofs.szPathName>
//
if (szFileName == NULL)
{
OPENFILENAME ofn;
BOOL f;
//
// prompt user for file to open
//
LoadString(ghInst, IDS_OPEN, ach, SIZEOF(ach));
aszFile[0] = 0;
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = ghwndApp;
ofn.hInstance = NULL;
ofn.lpstrFilter = aszFilter;
ofn.lpstrCustomFilter = NULL;
ofn.nMaxCustFilter = 0;
ofn.nFilterIndex = 1;
ofn.lpstrFile = aszFile;
ofn.nMaxFile = SIZEOF(aszFile);
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
ofn.lpstrTitle = ach;
ofn.Flags = OFN_FILEMUSTEXIST
| OFN_PATHMUSTEXIST
#ifdef CHICAGO
| OFN_EXPLORER
#endif
| OFN_HIDEREADONLY;
ofn.lpfnHook = NULL;
ofn.nFileOffset = 0;
ofn.nFileExtension = 0;
ofn.lpstrDefExt = gachDefFileExt;
ofn.lCustData = 0;
ofn.lpTemplateName = NULL;
f = GetOpenFileName(&ofn);
if (!f)
goto RETURN_ERRORNONEW;
}
else
{
GetFullPathName(szFileName,SIZEOF(aszFile),aszFile,NULL);
}
UpdateWindow(ghwndApp);
//
// show hourglass cursor
//
hcurPrev = SetCursor(LoadCursor(NULL, IDC_WAIT));
//
// read the WAVE file
//
hmmio = mmioOpen(aszFile, NULL, MMIO_READ | MMIO_ALLOCBUF);
if (hmmio != NULL)
{
MMRESULT mmr;
LPWAVEFORMATEX pwfx;
DWORD cbwfx;
DWORD cbdata;
LPBYTE pdata;
PCKNODE pcknHead = gpcknHead;
PCKNODE pcknTail = gpcknTail;
PFACT pfct = spFact;
LONG cbfact = scbFact;
gpcknHead = NULL;
gpcknTail = NULL;
spFact = NULL;
scbFact = 0L;
mmr = ReadWaveFile(hmmio
, &pwfx
, &cbwfx
, &pdata
, &cbdata
, aszFile
, TRUE);
mmioClose(hmmio, 0);
if (mmr != MMSYSERR_NOERROR || pwfx == NULL)
{
//
// restore the cache globals
//
gpcknHead = pcknHead;
gpcknTail = pcknTail;
spFact = pfct;
scbFact = cbfact;
if (pwfx == NULL)
{
if (pdata)
GlobalFreePtr(pdata);
}
goto RETURN_ERRORNONEW;
}
DestroyWave();
gpWaveFormat = pwfx;
gcbWaveFormat = cbwfx;
gpWaveSamples = pdata;
glWaveSamples = cbdata;
//
// destroy the cache temps
//
FreeAllChunks(&pcknHead, &pcknTail);
if (pfct)
GlobalFreePtr((LPVOID)pfct);
}
else
{
ErrorResBox(ghwndApp, ghInst, MB_ICONEXCLAMATION | MB_OK,
IDS_APPTITLE, IDS_ERROROPEN, (LPTSTR) aszFile);
goto RETURN_ERRORNONEW;
}
//
// update state variables
//
RenameDoc(aszFile);
glWaveSamplesValid = glWaveSamples;
glWavePosition = 0L;
goto RETURN_SUCCESS;
#if 0
RETURN_ERROR: // do error exit without error message
FileNew(FMT_DEFAULT, FALSE, FALSE);// revert to "(Untitled)" state
/* fall through */
#endif
RETURN_ERRORNONEW: // same as above, but don't do "new"
fOk = FALSE;
/* fall through */
RETURN_SUCCESS: // normal exit
if (hcurPrev != NULL)
SetCursor(hcurPrev);
/* Only mark clean on success */
if (fOk)
gfDirty = FALSE;
/* update the display */
UpdateCaption();
UpdateDisplay(TRUE);
return fOk;
} /* FileOpen */
/* fOK = FileSave(fSaveAs)
*
* Do a File/Save operation (if <fSaveAs> is FALSE) or a File/SaveAs
* operation (if <fSaveAs> is TRUE). Return TRUE unless the user cancelled
* or an error occurred.
*/
BOOL FAR PASCAL FileSave(
BOOL fSaveAs) // do a "Save As" instead of "Save"?
{
BOOL fOK = TRUE; // function succeeded?
TCHAR ach[80]; // buffer for string loading
TCHAR aszFile[_MAX_PATH];
BOOL fUntitled; // file is untitled?
HCURSOR hcurPrev = NULL; // cursor before hourglass
HMMIO hmmio;
// temp arguments to WriteWaveFile if a conversion is requested
PWAVEFORMATEX pwfxSaveAsFormat = NULL;
/* stop playing/recording */
StopWave();
fUntitled = IsDocUntitled();
if (fSaveAs || fUntitled)
{
OPENFILENAME ofn;
BOOL f;
// prompt user for file to save
LoadString(ghInst, IDS_SAVE, ach, SIZEOF(ach));
if (!gfEmbeddedObject && !fUntitled)
lstrcpy(aszFile, gachFileName);
else
aszFile[0] = 0;
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = ghwndApp;
ofn.hInstance = ghInst;
ofn.lpstrFilter = aszFilter;
ofn.lpstrCustomFilter = NULL;
ofn.nMaxCustFilter = 0;
ofn.nFilterIndex = 1;
ofn.lpstrFile = aszFile;
ofn.nMaxFile = SIZEOF(aszFile);
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
ofn.lpstrTitle = ach;
ofn.Flags = OFN_PATHMUSTEXIST
| OFN_HIDEREADONLY
#ifdef CHICAGO
| OFN_EXPLORER
#endif
| OFN_NOREADONLYRETURN;
ofn.nFileOffset = 0;
ofn.nFileExtension = 0;
ofn.lpstrDefExt = gachDefFileExt;
//
// We need to present a new Save As dialog template to add a convert
// button. Adding a convert button requires us to also hook and
// handle the button message ourselves.
//
if (fSaveAs)
{
// pwfxSaveAsFormat will point to a new format if the user
// requested it
ofn.lCustData = (LPARAM)(LPVOID)&pwfxSaveAsFormat;
ofn.Flags |= OFN_ENABLETEMPLATE | OFN_ENABLEHOOK;
ofn.lpTemplateName = MAKEINTRESOURCE(IDD_SAVEAS);
ofn.lpfnHook = SaveAsHookProc;
}
else
{
ofn.lpfnHook = NULL;
ofn.lpTemplateName = NULL;
}
f = GetSaveFileName(&ofn);
if (!f)
goto RETURN_CANCEL;
{
//
// Add extension if none given
//
LPTSTR lp;
for (lp = (LPTSTR)&aszFile[lstrlen(aszFile)] ; *lp != TEXT('.') ;)
{
if (SLASH(*lp) || *lp == TEXT(':') || lp == (LPTSTR)aszFile)
{
extern TCHAR FAR aszClassKey[];
lstrcat(aszFile, aszClassKey);
break;
}
lp = CharPrev(aszFile, lp);
}
}
// prompt for permission to overwrite the file
if (!CheckIfFileExists(aszFile))
return FALSE; // user cancelled
if (gfEmbeddedObject && gfDirty)
{
int id;
// see if user wants to update first
id = ErrorResBox( ghwndApp
, ghInst
, MB_ICONQUESTION | MB_YESNOCANCEL
, IDS_APPTITLE
, IDS_UPDATEBEFORESAVE);
if (id == IDCANCEL)
return FALSE;
else if (id == IDYES)
{
DoOleSave();
AdviseSaved();
gfDirty = FALSE;
}
}
}
else
{
// Copy the current name to our temporary variable
// We really should save to a different temporary file
lstrcpy(aszFile, gachFileName);
}
// show hourglass cursor
hcurPrev = SetCursor(LoadCursor(NULL, IDC_WAIT));
// write the WAVE file
// open the file -- if it already exists, truncate it to zero bytes
hmmio = mmioOpen(aszFile
, NULL
, MMIO_CREATE | MMIO_WRITE | MMIO_ALLOCBUF);
if (hmmio == NULL) {
ErrorResBox(ghwndApp
, ghInst
, MB_ICONEXCLAMATION | MB_OK
, IDS_APPTITLE
, IDS_ERROROPEN
, (LPTSTR) aszFile);
goto RETURN_ERROR;
}
if (pwfxSaveAsFormat)
{
DWORD cbNew;
DWORD cbOld;
LPBYTE pbNew;
cbOld = wfSamplesToBytes(gpWaveFormat, glWaveSamplesValid);
if (ConvertFormatDialog(ghwndApp
, gpWaveFormat
, cbOld
, gpWaveSamples
, pwfxSaveAsFormat
, &cbNew
, &pbNew
, 0
, NULL) == MMSYSERR_NOERROR )
{
GlobalFreePtr(gpWaveFormat);
GlobalFreePtr(gpWaveSamples);
gpWaveFormat = pwfxSaveAsFormat;
gcbWaveFormat = sizeof(WAVEFORMATEX);
if (pwfxSaveAsFormat->wFormatTag != WAVE_FORMAT_PCM)
gcbWaveFormat += pwfxSaveAsFormat->cbSize;
gpWaveSamples = pbNew;
glWaveSamples = wfBytesToSamples(gpWaveFormat, cbNew);
glWaveSamplesValid = wfBytesToSamples(gpWaveFormat, cbNew);
}
else
{
ErrorResBox(ghwndApp
, ghInst
, MB_ICONEXCLAMATION | MB_OK
, IDS_APPTITLE, IDS_ERR_CANTCONVERT);
goto RETURN_ERROR;
}
}
if (!WriteWaveFile(hmmio
, gpWaveFormat
, gcbWaveFormat
, gpWaveSamples
, glWaveSamplesValid))
{
mmioClose(hmmio,0);
ErrorResBox(ghwndApp
, ghInst
, MB_ICONEXCLAMATION | MB_OK
, IDS_APPTITLE, IDS_ERRORWRITE
, (LPTSTR) aszFile );
goto RETURN_ERROR;
}
mmioClose(hmmio,0);
//
// Only change file name if we succeed
//
RenameDoc(aszFile);
UpdateCaption();
if (fSaveAs || fUntitled)
{
AdviseRename(gachFileName);
}
else
{
DoOleSave();
gfDirty = FALSE;
}
goto RETURN_SUCCESS;
RETURN_ERROR: // do error exit without error message
DeleteFile(aszFile);
RETURN_CANCEL:
fOK = FALSE;
//
// Clean up conversion selection
//
if (pwfxSaveAsFormat)
GlobalFreePtr(pwfxSaveAsFormat);
RETURN_SUCCESS: // normal exit
if (hcurPrev != NULL)
SetCursor(hcurPrev);
if (fOK)
gfDirty = FALSE;
//
// update the display
//
UpdateDisplay(TRUE);
return fOK;
} /* FileSave*/
/* fOK = FileRevert()
*
* Do a File/Revert operation, i.e. let user revert to last-saved version.
*/
BOOL FAR PASCAL
FileRevert(void)
{
int id;
TCHAR achFileName[_MAX_PATH];
BOOL fOk;
BOOL fDirtyOrig;
/* "Revert..." menu is grayed unless file is dirty and file name
* is not "(Untitled)" and this is not an embedded object
*/
/* prompt user for permission to discard changes */
id = ErrorResBox(ghwndApp, ghInst, MB_ICONQUESTION | MB_YESNO,
IDS_APPTITLE, IDS_CONFIRMREVERT);
if (id == IDNO)
return FALSE;
/* discard changes and re-open file */
lstrcpy(achFileName, gachFileName); // FileNew nukes <gachFileName>
/* Make file clean temporarily, so FileOpen() won't warn user */
fDirtyOrig = gfDirty;
gfDirty = FALSE;
fOk = FileOpen(achFileName);
if (!fOk)
gfDirty = fDirtyOrig;
return fOk;
} /* FileRevert */
/* ReadWaveFile
*
* Read a WAVE file from <hmmio>. Fill in <*pWaveFormat> with
* the WAVE file format and <*plWaveSamples> with the number of samples in
* the file. Return a pointer to the samples (stored in a GlobalAlloc'd
* memory block) or NULL on error.
*
* <szFileName> is the name of the file that <hmmio> refers to.
* <szFileName> is used only for displaying error messages.
*
* On failure, an error message is displayed.
*/
MMRESULT ReadWaveFile(
HMMIO hmmio, // handle to open file
LPWAVEFORMATEX* ppWaveFormat, // fill in with the WAVE format
DWORD * pcbWaveFormat, // fill in with WAVE format size
LPBYTE * ppWaveSamples,
DWORD * plWaveSamples, // number of samples
LPTSTR szFileName, // file name (or NULL) for error msg.
BOOL fCacheRIFF) // cache RIFF?
{
MMCKINFO ckRIFF; // chunk info. for RIFF chunk
MMCKINFO ck; // info. for a chunk file
HPBYTE pWaveSamples = NULL; // waveform samples
UINT cbWaveFormat;
WAVEFORMATEX* pWaveFormat = NULL;
BOOL fHandled;
DWORD dwBlkAlignSize = 0; // initialisation only to eliminate spurious warning
MMRESULT mmr = MMSYSERR_NOERROR;
//
// added for robust RIFF checking
//
BOOL fFMT=FALSE, fDATA=FALSE, fFACT=FALSE;
DWORD dwCkEnd,dwRiffEnd;
if (ppWaveFormat == NULL
|| pcbWaveFormat == NULL
|| ppWaveSamples == NULL
|| plWaveSamples == NULL )
return MMSYSERR_ERROR;
*ppWaveFormat = NULL;
*pcbWaveFormat = 0L;
*ppWaveSamples = NULL;
*plWaveSamples = 0L;
//
// descend the file into the RIFF chunk
//
if (mmioDescend(hmmio, &ckRIFF, NULL, 0) != 0)
{
//
// Zero length files are OK.
//
if (mmioSeek(hmmio, 0L, SEEK_END) == 0L)
{
DWORD cbwfx;
LPWAVEFORMATEX pwfx;
//
// Synthesize a wave header
//
if (!SoundRec_GetDefaultFormat(&pwfx, &cbwfx))
{
cbwfx = sizeof(WAVEFORMATEX);
pwfx = (WAVEFORMATEX *)GlobalAllocPtr(GHND, sizeof(WAVEFORMATEX));
if (pwfx == NULL)
return MMSYSERR_NOMEM;
CreateWaveFormat(pwfx,FMT_DEFAULT,(UINT)WAVE_MAPPER);
}
*ppWaveFormat = pwfx;
*pcbWaveFormat = cbwfx;
*plWaveSamples = 0L;
*ppWaveSamples = NULL;
return MMSYSERR_NOERROR;
}
else
goto ERROR_NOTAWAVEFILE;
}
/* make sure the file is a WAVE file */
if ((ckRIFF.ckid != FOURCC_RIFF) ||
(ckRIFF.fccType != mmioFOURCC('W', 'A', 'V', 'E')))
goto ERROR_NOTAWAVEFILE;
/* We can preserve the order of chunks in memory
* by parsing the entire file as we read it in.
*/
/* Use AddChunk(&ck,NULL) to add a placeholder node
* for a chunk being edited.
* Else AddChunk(&ck,hpstrData)
*/
dwRiffEnd = ckRIFF.cksize;
dwRiffEnd += (dwRiffEnd % 2); /* must be even */
while ( mmioDescend( hmmio, &ck, &ckRIFF, 0) == 0)
{
fHandled = FALSE;
dwCkEnd = ck.cksize + (ck.dwDataOffset - ckRIFF.dwDataOffset);
dwCkEnd += (dwCkEnd % 2); /* must be even */
if (dwCkEnd > dwRiffEnd)
{
DPF(TEXT("Chunk End %lu> Riff End %lu\n"),dwCkEnd,dwRiffEnd);
/* CORRUPTED RIFF, when we ascend we'll be past the
* end of the RIFF
*/
if (fFMT && fDATA)
{
/* We might have enough information to deal
* with clipboard mixing/inserts, etc...
* This is for the bug with BOOKSHELF '92
* where they give us RIFF with a
* RIFF.dwSize > sum(childchunks).
* They *PROMISE* not to do this again.
*/
mmioAscend( hmmio, &ck, 0 );
goto RETURN_FINISH;
}
goto ERROR_READING;
}
switch ( ck.ckid )
{
case mmioFOURCC('f','m','t',' '):
if (fFMT)
break; /* we've been here before */
/* expect the 'fmt' chunk to be at least as
* large as <sizeof(WAVEFORMAT)>;
* if there are extra parameters at the end,
* we'll ignore them
*/
// 'fmt' chunk too small?
if (ck.cksize < sizeof(WAVEFORMAT))
goto ERROR_NOTAWAVEFILE;
/*
* always force allocation to be AT LEAST
* the size of WFX. this is required so all
* code does not have to special case the
* cbSize field. note that we alloc with zero
* init so cbSize will be zero for plain
* jane PCM
*/
cbWaveFormat = max((WORD)ck.cksize,
sizeof(WAVEFORMATEX));
pWaveFormat = (WAVEFORMATEX*)GlobalAllocPtr(GHND, cbWaveFormat);
if (pWaveFormat == NULL)
goto ERROR_FILETOOLARGE;
/*
* set the size back to the actual size
*/
cbWaveFormat = (WORD)ck.cksize;
*ppWaveFormat = pWaveFormat;
*pcbWaveFormat = cbWaveFormat;
/* read the file format into <*pWaveFormat> */
if (mmioRead(hmmio, (HPSTR)pWaveFormat, ck.cksize) != (long)ck.cksize)
goto ERROR_READING; // truncated file, probably
if (fCacheRIFF && !AddChunk(&ck,NULL,&gpcknHead,&gpcknTail))
{
goto ERROR_FILETOOLARGE;
}
//Sanity check for PCM Formats:
if (pWaveFormat->wFormatTag == WAVE_FORMAT_PCM)
{
pWaveFormat->nBlockAlign = pWaveFormat->nChannels *
((pWaveFormat->wBitsPerSample + 7)/8);
pWaveFormat->nAvgBytesPerSec = pWaveFormat->nBlockAlign *
pWaveFormat->nSamplesPerSec;
}
fFMT = TRUE;
fHandled = TRUE;
break;
case mmioFOURCC('d','a','t','a'):
/* deal with the 'data' chunk */
if (fDATA)
break; /* we've been here before */
if (!pWaveFormat)
goto ERROR_READING;
//*** is dwBlkAlignSize? Don't you want to use nBlkAlign
//*** to determine this value?
#if 0
dwBlkAlignSize = ck.cksize;
dwBlkAlignSize += (ck.cksize%pWaveFormat.nBlkAlign);
*pcbWaveSamples = ck.cksize;
#else
dwBlkAlignSize = wfBytesToBytes(pWaveFormat, ck.cksize);
#endif
if ((pWaveSamples = GlobalAllocPtr(GHND | GMEM_SHARE
, dwBlkAlignSize+4)) == NULL)
goto ERROR_FILETOOLARGE;
/* read the samples into the memory buffer */
if (mmioRead(hmmio, (HPSTR)pWaveSamples, dwBlkAlignSize) !=
(LONG)dwBlkAlignSize)
goto ERROR_READING; // truncated file, probably
if (fCacheRIFF && !AddChunk(&ck,NULL,&gpcknHead,&gpcknTail))
{
goto ERROR_FILETOOLARGE;
}
fDATA = TRUE;
fHandled = TRUE;
break;
case mmioFOURCC('f','a','c','t'):
/* deal with the 'fact' chunk */
if (fFACT)
break; /* we've been here before */
#if 0
//
// There are some wave editors that are writing 'fact' chunks
// after the data chunk, so we no longer make this assumption
//
if (fDATA)
break; /* we describe some another 'data' chunk */
#endif
if (mmioRead(hmmio,(HPSTR)plWaveSamples, sizeof(DWORD))
!= sizeof(DWORD))
goto ERROR_READING;
if (fCacheRIFF && ck.cksize > sizeof(DWORD) &&
ck.cksize < 0xffff)
{
spFact = (PFACT)GlobalAllocPtr(GHND,(UINT)(ck.cksize - sizeof(DWORD)));
if (spFact == NULL)
goto ERROR_FILETOOLARGE;
scbFact = ck.cksize - sizeof(DWORD);
if (mmioRead(hmmio,(HPSTR)spFact,scbFact) != scbFact)
goto ERROR_READING;
}
/* we don't AddChunk() the 'fact' because we
* write it out before we write our edit 'data'
*/
fFACT = TRUE;
fHandled = TRUE;
break;
#ifdef DISP
case mmioFOURCC('d','i','s','p'):
/* deal with the 'disp' chunk for clipboard transfer */
// TODO:
// DISP's are CF_DIB or CF_TEXT. Put 'em somewhere
// global and pass them through as text or a BMP when
// we copy to clipboard.
//
break;
#endif /* DISP */
case mmioFOURCC('L','I','S','T'):
if (fCacheRIFF)
{
/* seek back over the type field */
if (mmioSeek(hmmio,-4,SEEK_CUR) == -1)
goto ERROR_READING;
}
break;
default:
break;
}
/* the "default" case. */
if (fCacheRIFF && !fHandled)
{
HPBYTE hpData;
hpData = GlobalAllocPtr(GMEM_MOVEABLE, ck.cksize+4);
if (hpData == NULL)
{
goto ERROR_FILETOOLARGE;
}
/* read the data into the cache buffer */
if (mmioRead(hmmio, (HPSTR)hpData, ck.cksize) != (LONG) ck.cksize)
{
GlobalFreePtr(hpData);
goto ERROR_READING;// truncated file, probably
}
//
// Special case the copyright info. I'd rather do this than
// rewrite this whole app.
//
if (ck.ckid == mmioFOURCC('I','C','O','P'))
{
LPTSTR lpstr = GlobalAllocPtr(GHND, ck.cksize+4);
if (lpstr)
{
memcpy(lpstr, hpData, ck.cksize+4);
gpszInfo = lpstr;
}
}
if (!AddChunk(&ck,hpData,&gpcknHead, &gpcknTail))
{
goto ERROR_FILETOOLARGE;
}
}
mmioAscend( hmmio, &ck, 0 );
}
RETURN_FINISH:
if (fFMT && fDATA)
{
*plWaveSamples = wfBytesToSamples(pWaveFormat, dwBlkAlignSize);
*ppWaveSamples = pWaveSamples;
goto RETURN_SUCCESS;
}
/* goto ERROR_NOTAWAVEFILE; */
ERROR_NOTAWAVEFILE: // file is not a WAVE file
ErrorResBox(ghwndApp, ghInst, MB_ICONEXCLAMATION | MB_OK,
IDS_APPTITLE, IDS_NOTAWAVEFILE, (LPTSTR) szFileName);
goto RETURN_ERROR;
ERROR_READING: // error reading from file
ErrorResBox(ghwndApp, ghInst, MB_ICONEXCLAMATION | MB_OK,
IDS_APPTITLE, IDS_ERRORREAD, (LPTSTR) szFileName);
goto RETURN_ERROR;
ERROR_FILETOOLARGE: // out of memory
ErrorResBox(ghwndApp, ghInst, MB_ICONEXCLAMATION | MB_OK,
IDS_APPTITLE, IDS_FILETOOLARGE, (LPTSTR) szFileName);
goto RETURN_ERROR;
RETURN_ERROR:
if (pWaveSamples != NULL)
GlobalFreePtr(pWaveSamples), pWaveSamples = NULL;
if (fCacheRIFF)
FreeAllChunks(&gpcknHead, &gpcknTail);
mmr = MMSYSERR_ERROR;
RETURN_SUCCESS:
return mmr;
} /* ReadWaveFile */
/* fSuccess = AddChunk(lpCk, hpData)
*
* Adds to our linked list of chunk information.
*
* LPMMCKINFO lpCk | far pointer to the MMCKINFO describing the chunk.
* HPBYTE hpData | huge pointer to the data portion of the chunk, NULL if
* handled elsewhere.
*
* RETURNS: TRUE if added, FALSE if out of local heap.
*/
static BOOL AddChunk(
LPMMCKINFO lpCk,
HPBYTE hpData,
PCKNODE * ppcknHead,
PCKNODE * ppcknTail)
{
PCKNODE pckn;
//
// create a node
//
pckn = (PCKNODE)GlobalAllocPtr(GHND,sizeof(CKNODE));
if (pckn == NULL)
{
DPF(TEXT("No Local Heap for Cache"));
return FALSE;
}
if (*ppcknHead == NULL)
{
*ppcknHead = pckn;
}
if (*ppcknTail != NULL)
{
(*ppcknTail)->psNext = pckn;
}
*ppcknTail = pckn;
pckn->ck.ckid = lpCk->ckid;
pckn->ck.fccType = lpCk->fccType;
pckn->ck.cksize = lpCk->cksize;
pckn->ck.dwDataOffset = lpCk->dwDataOffset;
pckn->hpData = hpData;
return TRUE;
} /* AddChunk() */
/* pckn = PCKNODE FreeHeadChunk(void)
*
* Frees up the Head chunk and return a pointer to the new Head.
* Uses global gpcknHead
*
* RETURNS: PCKNODE pointer to the Head chunk. NULL if no chunks in the list.
*/
static PCKNODE FreeHeadChunk(
PCKNODE * ppcknHead)
{
PCKNODE pckn, pcknNext;
if (*ppcknHead == NULL)
{
goto SUCCESS;
}
pckn = *ppcknHead;
pcknNext = (*ppcknHead)->psNext;
if (pckn->hpData != NULL)
{
GlobalFreePtr(pckn->hpData);
}
GlobalFreePtr(pckn);
*ppcknHead = pcknNext;
SUCCESS:;
return *ppcknHead;
} /* FreeHeadChunk() */
/* void FreeAllChunks(void)
*
* Frees up the link list of chunk data.
*
* RETURNS: Nothing
*/
static void FreeAllChunks(
PCKNODE * ppcknHead,
PCKNODE * ppcknTail)
{
PCKNODE pckn = *ppcknHead;
PCKNODE pcknNext = (*ppcknHead ? (*ppcknHead)->psNext : NULL);
DPF1(TEXT("Freeing All Chunks\n"));
while (FreeHeadChunk(ppcknHead));
if (scbFact > 0)
{
GlobalFreePtr(spFact);
scbFact = 0;
}
*ppcknHead = NULL;
*ppcknTail = NULL;
} /* FreeAllChunks() */
/* fSuccess = WriteWaveFile(hmmio, pWaveFormat, lWaveSamples)
*
* Write a WAVE file into <hmmio>. <*pWaveFormat> should be
* the WAVE file format and <lWaveSamples> should be the number of samples in
* the file. Return TRUE on success, FALSE on failure.
*
*/
BOOL FAR PASCAL
WriteWaveFile(
HMMIO hmmio, // handle to open file
WAVEFORMATEX* pWaveFormat, // WAVE format
UINT cbWaveFormat, // size of WAVEFORMAT
HPBYTE pWaveSamples, // waveform samples
LONG lWaveSamples) // number of samples
{
MMCKINFO ckRIFF; // chunk info. for RIFF chunk
MMCKINFO ck; // info. for a chunk file
PCKNODE pckn = gpcknHead;
LONG cbWaveSamples;
MMRESULT mmr;
/* create the RIFF chunk of form type 'WAVE' */
ckRIFF.fccType = mmioFOURCC('W', 'A', 'V', 'E');
ckRIFF.cksize = 0L; // let MMIO figure out ck. size
mmr = mmioCreateChunk(hmmio, &ckRIFF, MMIO_CREATERIFF);
if (mmr != MMSYSERR_NOERROR)
goto wwferror;
if (pckn != NULL)
{
/* ForEach node in the linked list of chunks,
* Write out the corresponding data chunk OR
* the global edit data.
*/
do {
ck.cksize = 0L;
ck.ckid = pckn->ck.ckid;
ck.fccType = pckn->ck.fccType;
if (pckn->hpData == NULL)
{
/* This must be a data-type we have in edit
* buffers. We should preserve the original
* order.
*/
switch (pckn->ck.ckid)
{
case mmioFOURCC('f','m','t',' '):
mmr = mmioCreateChunk(hmmio, &ck, 0);
if (mmr != MMSYSERR_NOERROR)
goto wwferror;
if (mmioWrite(hmmio, (LPSTR) pWaveFormat, cbWaveFormat)
!= (long)cbWaveFormat)
goto wwfwriteerror;
mmr = mmioAscend(hmmio, &ck, 0);
if (mmr != MMSYSERR_NOERROR)
goto wwferror;
break;
case mmioFOURCC('d','a','t','a'):
/* Insert a 'fact' chunk here */
/* 'fact' should always preceed the 'data' it
* describes.
*/
ck.ckid = mmioFOURCC('f', 'a', 'c', 't');
mmr = mmioCreateChunk(hmmio, &ck, 0);
if (mmr != MMSYSERR_NOERROR)
goto wwferror;
if (mmioWrite(hmmio, (LPSTR) &lWaveSamples,
sizeof(lWaveSamples)) != sizeof(lWaveSamples))
goto wwfwriteerror;
if (scbFact > 4)
{
if ( mmioWrite(hmmio, (LPSTR)spFact, scbFact)
!= scbFact )
goto wwfwriteerror;
}
mmr = mmioAscend(hmmio, &ck, 0);
if (mmr != MMSYSERR_NOERROR)
goto wwferror;
ck.cksize = 0L;
ck.ckid = mmioFOURCC('d', 'a', 't', 'a');
mmr = mmioCreateChunk(hmmio, &ck, 0);
if (mmr != MMSYSERR_NOERROR)
goto wwferror;
cbWaveSamples = wfSamplesToBytes(pWaveFormat,
lWaveSamples);
if (cbWaveSamples)
{
/* write the waveform samples */
if (mmioWrite(hmmio, (LPSTR)pWaveSamples
, cbWaveSamples)
!= cbWaveSamples)
return FALSE;
}
mmr = mmioAscend(hmmio, &ck, 0);
if (mmr != MMSYSERR_NOERROR)
goto wwferror;
break;
#ifdef DISP
case mmioFOURCC('d','i','s','p'):
/* deal with writing the 'disp' chunk */
break;
#endif /* DISP */
case mmioFOURCC('f','a','c','t'):
/* deal with the 'fact' chunk */
/* skip it. We always write it before the 'data' */
break;
default:
/* This should never happen.*/
return FALSE;
}
}
else
{
/* generic case */
mmr = mmioCreateChunk(hmmio,&ck,0);
if (mmr != MMSYSERR_NOERROR)
goto wwferror;
if (mmioWrite(hmmio,(LPSTR)pckn->hpData,pckn->ck.cksize)
!= (long) pckn->ck.cksize)
goto wwfwriteerror;
mmr = mmioAscend(hmmio, &ck, 0);
if (mmr != MMSYSERR_NOERROR)
goto wwferror;
}
} while (pckn = pckn->psNext);
}
else
{
/* <hmmio> is now descended into the 'RIFF' chunk -- create the
* 'fmt' chunk and write <*pWaveFormat> into it
*/
ck.ckid = mmioFOURCC('f', 'm', 't', ' ');
ck.cksize = cbWaveFormat;
mmr = mmioCreateChunk(hmmio, &ck, 0);
if (mmr != MMSYSERR_NOERROR)
goto wwferror;
if (mmioWrite(hmmio, (LPSTR) pWaveFormat, cbWaveFormat) !=
(long)cbWaveFormat)
goto wwfwriteerror;
/* ascend out of the 'fmt' chunk, back into 'RIFF' chunk */
mmr = mmioAscend(hmmio, &ck, 0);
if (mmr != MMSYSERR_NOERROR)
goto wwferror;
/* write out the number of samples in the 'FACT' chunk */
ck.ckid = mmioFOURCC('f', 'a', 'c', 't');
mmr = mmioCreateChunk(hmmio, &ck, 0);
if (mmr != MMSYSERR_NOERROR)
goto wwferror;
if (mmioWrite(hmmio, (LPSTR)&lWaveSamples, sizeof(lWaveSamples))
!= sizeof(lWaveSamples))
return FALSE;
/* ascend out of the 'fact' chunk, back into 'RIFF' chunk */
mmr = mmioAscend(hmmio, &ck, 0);
if (mmr != MMSYSERR_NOERROR)
goto wwferror;
/* create the 'data' chunk that holds the waveform samples */
ck.ckid = mmioFOURCC('d', 'a', 't', 'a');
ck.cksize = 0L; // let MMIO figure out ck. size
mmr = mmioCreateChunk(hmmio, &ck, 0);
if (mmr != MMSYSERR_NOERROR)
goto wwferror;
cbWaveSamples = wfSamplesToBytes(pWaveFormat,lWaveSamples);
/* write the waveform samples */
if (cbWaveSamples)
{
if (mmioWrite(hmmio, (LPSTR)pWaveSamples, cbWaveSamples)
!= cbWaveSamples)
goto wwfwriteerror;
}
/* ascend the file out of the 'data' chunk, back into
* the 'RIFF' chunk -- this will cause the chunk size of the 'data'
* chunk to be written
*/
mmr = mmioAscend(hmmio, &ck, 0);
if (mmr != MMSYSERR_NOERROR)
goto wwferror;
}
/* ascend the file out of the 'RIFF' chunk */
mmr = mmioAscend(hmmio, &ckRIFF, 0);
if (mmr != MMSYSERR_NOERROR)
goto wwferror;
/* done */
return TRUE;
wwferror:
#if DBG
{
TCHAR sz[256];
wsprintf(sz, TEXT("WriteWaveFile: Error %lx\r\n"), mmr);
OutputDebugString(sz);
DebugBreak();
}
#endif
return FALSE;
wwfwriteerror:
#if DBG
{
TCHAR sz[256];
wsprintf(sz,TEXT("Write Error! ckid = %04x\r\n"), (DWORD)ck.ckid);
OutputDebugString(sz);
DebugBreak();
}
#endif
return FALSE;
} /* WriteWaveFile */