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.
1724 lines
49 KiB
1724 lines
49 KiB
/* (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"
|
|
|
|
#define STRSAFE_LIB
|
|
#include <strsafe.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)
|
|
{
|
|
HRESULT hr;
|
|
|
|
hr = StringCchCopy(gachFileName, SIZEOF(gachFileName), aszNewFile);
|
|
Assert( hr == S_OK );
|
|
hr = StringCchCopy(gachLinkFilename, SIZEOF(gachLinkFilename), gachFileName);
|
|
Assert( hr == S_OK );
|
|
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)
|
|
{
|
|
HRESULT hr;
|
|
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 ))
|
|
{
|
|
hr = StringCchPrintf(ach, SIZEOF(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
|
|
hr = StringCchPrintf(ach, SIZEOF(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)
|
|
{
|
|
HRESULT hr;
|
|
//
|
|
// 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
|
|
//
|
|
hr = StringCchCopy(gachFileName, SIZEOF(gachFileName), aszUntitled);
|
|
Assert( hr == S_OK );
|
|
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]; // SIZEOF(aszFile) must be <= SIZEOF(gachFileName)
|
|
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]; // SIZEOF(aszFile) must be <= SIZEOF(gachFileName)
|
|
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]; // SIZEOF(aszFile) must be <= SIZEOF(gachFileName)
|
|
BOOL fUntitled; // file is untitled?
|
|
HCURSOR hcurPrev = NULL; // cursor before hourglass
|
|
HMMIO hmmio;
|
|
HRESULT hr;
|
|
|
|
// 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)
|
|
{
|
|
hr = StringCchCopy(aszFile, SIZEOF(aszFile), gachFileName);
|
|
Assert( hr == S_OK );
|
|
if( hr != S_OK )
|
|
{
|
|
aszFile[0] = 0;
|
|
}
|
|
}
|
|
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[];
|
|
hr = StringCchCat(aszFile, SIZEOF(aszFile), aszClassKey);
|
|
if( hr != S_OK )
|
|
{
|
|
ErrorResBox(ghwndApp
|
|
, ghInst
|
|
, MB_ICONEXCLAMATION | MB_OK
|
|
, IDS_APPTITLE
|
|
, IDS_ERRORFILENAME
|
|
, (LPTSTR) aszFile);
|
|
goto RETURN_CANCEL;
|
|
}
|
|
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
|
|
hr = StringCchCopy(aszFile, SIZEOF(aszFile), gachFileName);
|
|
Assert( hr == S_OK );
|
|
if( hr != S_OK )
|
|
{
|
|
ErrorResBox(ghwndApp
|
|
, ghInst
|
|
, MB_ICONEXCLAMATION | MB_OK
|
|
, IDS_APPTITLE
|
|
, IDS_ERRORFILENAME
|
|
, (LPTSTR) aszFile);
|
|
goto RETURN_CANCEL;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
HRESULT hr;
|
|
|
|
/* "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 */
|
|
hr = StringCchCopy(achFileName, SIZEOF(achFileName), gachFileName); // FileNew nukes <gachFileName>
|
|
Assert( hr == S_OK );
|
|
if( hr == S_OK )
|
|
{
|
|
/* Make file clean temporarily, so FileOpen() won't warn user */
|
|
fDirtyOrig = gfDirty;
|
|
gfDirty = FALSE;
|
|
|
|
fOk = FileOpen(achFileName);
|
|
if (!fOk)
|
|
gfDirty = fDirtyOrig;
|
|
}
|
|
else
|
|
{
|
|
ErrorResBox(ghwndApp
|
|
, ghInst
|
|
, MB_ICONEXCLAMATION | MB_OK
|
|
, IDS_APPTITLE
|
|
, IDS_ERRORFILENAME
|
|
, (LPTSTR) achFileName);
|
|
fOk = FALSE;
|
|
}
|
|
|
|
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];
|
|
HRESULT hr = StringCchPrintf(sz, SIZEOF(sz), TEXT("WriteWaveFile: Error %lx\r\n"), mmr);
|
|
Assert( hr == S_OK );
|
|
OutputDebugString(sz);
|
|
DebugBreak();
|
|
}
|
|
#endif
|
|
return FALSE;
|
|
|
|
wwfwriteerror:
|
|
#if DBG
|
|
{
|
|
TCHAR sz[256];
|
|
HRESULT hr = StringCchPrintf(sz, SIZEOF(sz), TEXT("Write Error! ckid = %04x\r\n"), (DWORD)ck.ckid);
|
|
Assert( hr == S_OK );
|
|
OutputDebugString(sz);
|
|
DebugBreak();
|
|
}
|
|
#endif
|
|
return FALSE;
|
|
} /* WriteWaveFile */
|