/* (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 #include #include #include #include #define INCLUDE_OLESTUBS #include "SoundRec.h" #include "srecids.h" #ifdef CHICAGO # if WINVER >= 0x0400 # include # else # include # 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 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 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 is NULL, do a File/Open command. Otherwise, open * . 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 // 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 is FALSE) or a File/SaveAs * operation (if 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 /* 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 . 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. * * is the name of the file that refers to. * 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 ; * 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 . <*pWaveFormat> should be * the WAVE file format and 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 { /* 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 */