2025-11-27 16:46:48 +09:00

818 lines
17 KiB
C++

// ==========================================================================
// Class Implementation : COXSound
// ==========================================================================
//
// Version: 9.3
// This software along with its related components, documentation and files ("The Libraries")
// is © 1994-2007 The Code Project (1612916 Ontario Limited) and use of The Libraries is
// governed by a software license agreement ("Agreement"). Copies of the Agreement are
// available at The Code Project (www.codeproject.com), as part of the package you downloaded
// to obtain this file, or directly from our office. For a copy of the license governing
// this software, you may contact us at legalaffairs@codeproject.com, or by calling 416-849-8900.
//
////////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "OXSound.h"
#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif
IMPLEMENT_SERIAL(COXSound, CObject, 1)
/////////////////////////////////////////////////////////////////////////////
// Definition of static members
CMap<COXSound*, COXSound*, DWORD, DWORD> COXSound::m_allSoundObjects;
// Data members -------------------------------------------------------------
// protected:
// static CMap<COXSound*, COXSound*, DWORD, DWORD> m_allSoundObjects;
// --- List of all the COXSound objects thar currently exist
// The value (DWORD) is not used at the moment
// This data member is only used by the friend class COXSoundWnd
// HWND m_hCallbackWnd;
// --- The window to which notification will be posted
// UINT m_nRes;
// --- Resource number
// CString m_sFilename;
// --- File name of the source
// HGLOBAL m_hWave;
// LPVOID m_lpWave;
// BOOL m_bIsPlaying;
// --- Whether the sound is currently playing (TRUE) or not (FALSE)
// BOOL m_bIsLoaded;
// --- Whether the sound is currently loaded (TRUE) or not (FALSE)
// BOOL m_bLooping;
// --- Whether the sound should loop (TRUE) or not (FALSE)
// MMRESULT m_mmLastResult;
// --- The last result code
// LPVOID m_lpWaveData;
// WAVEHDR m_waveHdr;
// LPWAVEHDR m_lpWaveHdr;
// HWAVEOUT m_hWaveOut;
// WAVEFORMATEX m_waveFormatEx;
// --- Wave data
// private:
// Member functions ---------------------------------------------------------
// public:
COXSound::COXSound() :
m_hCallbackWnd(NULL),
// ... No wave loaded from resource or file...
m_nRes(0),
m_sFilename(),
m_hWave(NULL),
m_lpWave(NULL),
m_bIsPlaying(FALSE),
m_bIsLoaded(FALSE),
m_bLooping(FALSE),
// ... Set the last error to MMSYSERR_NOERROR...
m_mmLastResult(MMSYSERR_NOERROR),
m_lpWaveData(NULL),
m_lpWaveHdr(NULL),
m_hWaveOut(NULL)
{
// Clear the WAVEHDR and WAVEFORMATEX structure...
::ZeroMemory(&m_waveHdr, sizeof(m_waveHdr));
::ZeroMemory(&m_waveFormatEx, sizeof(WAVEFORMATEX));
// Register in global map
m_allSoundObjects.SetAt(this, 0);
// Make sure the helper window exists
// and is created in this thread
HWND hHelperWindow = COXSoundWnd::CreateTheSoundWindow();
ASSERT(hHelperWindow != NULL);
ASSERT(::GetCurrentThreadId() == ::GetWindowThreadProcessId(hHelperWindow, NULL));
}
void COXSound:: SetCallbackWnd(CWnd* pCallbackWnd)
{
// ... Set the callback window information
m_hCallbackWnd=pCallbackWnd->GetSafeHwnd();
}
BOOL COXSound::CanPlay()
{
// ... Return TRUE if there are any wave playback devices...
return (0 < waveOutGetNumDevs());
}
BOOL COXSound::Open(LPCTSTR pszFilename)
{
// Open a sound from a passed WAVE filename...
// .... Assume failure
BOOL bRet = FALSE;
CFile cFile;
m_nRes = 0;
m_sFilename = pszFilename;
if(cFile.Open(pszFilename, CFile::modeRead))
{
bRet=Open(&cFile);
cFile.Close();
m_SoundSourceInfo.Reset();
m_SoundSourceInfo.source=SNDSRC_FILE;
TCHAR szFullPath[_MAX_PATH];
AfxFullPath(szFullPath,pszFilename);
m_SoundSourceInfo.sFileName=szFullPath;
return bRet;
}
// .. If the file cannot be opened, return FALSE.
return bRet;
}
BOOL COXSound::Open(UINT nSoundResource, HINSTANCE hResInstance)
{
// Open a sound from a resource...
HGLOBAL hMem = NULL;
HRSRC hResInfo = NULL;
LPVOID lpRes = NULL;
// Find the resource...
hResInfo=FindResource(hResInstance,MAKEINTRESOURCE(nSoundResource),_T("WAVE"));
if(!hResInfo)
{
return FALSE;
}
// Load the resource...
hMem=LoadResource(hResInstance, hResInfo);
if(!hMem)
{
return FALSE;
}
// Save resource ID...
m_nRes=nSoundResource;
m_sFilename.Empty();
// Stop any current sound from playing...
Stop();
// Free previous memory...
FreeMem();
// Determine the size of the resource...
DWORD dwSize=SizeofResource(hResInstance, hResInfo);
lpRes=LockResource(hMem);
m_hWave=::GlobalAlloc(GHND, dwSize);
m_lpWave=::GlobalLock(m_hWave);
// Copy to the global buffer for this class...
::CopyMemory(m_lpWave,lpRes,dwSize);
m_SoundSourceInfo.Reset();
m_SoundSourceInfo.source=SNDSRC_INTRESOURCE;
m_SoundSourceInfo.hInstance=hResInstance;
m_SoundSourceInfo.nResourceID=nSoundResource;
return CanPlayLoadedData();
}
BOOL COXSound::Open(CFile* pOpenedFile)
{
ASSERT_VALID(pOpenedFile);
// Determine the length of the buffer to read...
DWORD dwFileSize = (DWORD) pOpenedFile->GetLength();
DWORD dwPos = (DWORD) pOpenedFile->GetPosition();
DWORD dwSize = dwFileSize - dwPos;
if(dwSize == 0)
{
return FALSE;
}
// Stop any current sound from playing...
Stop();
// Free previous memory...
FreeMem();
m_hWave=::GlobalAlloc(GHND, dwSize);
m_lpWave=::GlobalLock(m_hWave);
pOpenedFile->Read(m_lpWave,dwSize);
m_SoundSourceInfo.Reset();
m_SoundSourceInfo.source=SNDSRC_CFILE;
return CanPlayLoadedData();
}
BOOL COXSound::Play(BOOL bLoop, BOOL bAsync)
{
BOOL bRet = FALSE;
ASSERT(m_lpWave != NULL);
Stop();
if(bAsync)
{
// Play the sound asynchronously...
bRet=PlayWithCallback();
if(bRet)
{
m_bIsPlaying=TRUE;
m_bLooping=bLoop;
}
}
else
{
// Play synchronously and return after the call is complete...
bRet=PlaySound((LPCTSTR)m_lpWave,NULL,SND_MEMORY);
}
return bRet;
}
void COXSound::Stop()
{
// Stop the current sound from playing...
if(!m_bIsPlaying)
{
return;
}
if (m_hWaveOut)
{
// changes were made on 10th of July: commented the next line
waveOutPause(m_hWaveOut);
// and uncommented the next ones
waveOutReset(m_hWaveOut);
CloseWaveOutDevice();
}
// Stop playing any sound...
PlaySound(NULL, NULL, 0);
m_bIsPlaying = FALSE;
}
BOOL COXSound::IsWaveLoaded() const
{
// Return the status of the wave - is it loaded?
return m_bIsLoaded;
}
BOOL COXSound::IsPlaying() const
{
// Return a value of TRUE if a sound is playing...
return m_bIsPlaying;
}
BOOL COXSound::GetWaveFormat(WAVEFORMATEX* waveFormatEx)
{
ASSERT(waveFormatEx != NULL);
// Returns the current WAVEFORMATEX information...
::CopyMemory(waveFormatEx, &m_waveFormatEx, sizeof(WAVEFORMATEX));
return m_bIsLoaded;
}
MMRESULT COXSound::GetLastMMError() const
{
return m_mmLastResult;
}
void COXSound::GetErrorText(MMRESULT hResult, CString& sDesc)
{
waveOutGetErrorText(hResult,sDesc.GetBuffer(MAXERRORLENGTH),MAXERRORLENGTH);
sDesc.ReleaseBuffer();
}
BOOL COXSound::GetCurrentPosition(MMTIME* pMMTime) const
{
if(!m_hWaveOut)
{
return FALSE;
}
if(!IsPlaying())
{
return FALSE;
}
ASSERT(pMMTime != NULL);
// Return time in milliseconds
pMMTime->wType=TIME_MS;
MMRESULT hResult;
hResult=waveOutGetPosition(m_hWaveOut,pMMTime,sizeof(MMTIME));
return (hResult==MMSYSERR_NOERROR);
}
void COXSound::HandleCallback()
{
// Free up the buffer...
FreeBuffer();
// If looping, play it again...
if(m_bLooping)
{
// Notification that we are looping again...
if(m_hCallbackWnd != NULL)
{
::SendMessage(m_hCallbackWnd,WM_OX_SOUNDPLAYLOOPING,NULL,(LPARAM)this);
}
if(m_bIsPlaying)
{
PlayWithCallback();
}
}
if(m_hCallbackWnd!=NULL && (!m_bLooping || !m_bIsPlaying))
{
CloseWaveOutDevice();
m_bIsPlaying = FALSE;
// If not looping, notify the callback window we have completed playback...
::SendMessage(m_hCallbackWnd,WM_OX_SOUNDPLAYBACKCOMPLETE,NULL,(LPARAM)this);
}
}
COXSound::~COXSound()
{
Stop();
CloseWaveOutDevice();
FreeMem();
// Unregister from global map
VERIFY(m_allSoundObjects.RemoveKey(this));
}
// protected:
void COXSound::FreeMem()
// --- In:
// --- Out:
// --- Returns:
// --- Effect: Frees up global memory from a loaded WAVE
{
// Free up the global memory block associated with the wave data...
if (m_hWave)
{
::GlobalUnlock(m_hWave);
::GlobalFree(m_hWave);
m_hWave = NULL;
}
}
void COXSound::FreeBuffer()
// --- In:
// --- Out:
// --- Returns:
// --- Effect: Frees a prepared waveOut buffer
{
if(m_hWaveOut)
{
waveOutUnprepareHeader(m_hWaveOut, m_lpWaveHdr, sizeof(WAVEHDR));
}
// Reset the waveOut device...
waveOutReset(m_hWaveOut);
}
void CALLBACK COXSound::waveOutProc(HWAVEOUT /* hWaveOut */, UINT uiMsg,
DWORD_PTR /* dwInstance */, DWORD_PTR dwParam1,
DWORD_PTR /* dwParam2 */)
// --- In: hWaveOut : Handle of the WaveOut device
// uiMsg : callback message (from Windows)
// dwParam1 : Points to WAVEHDR structure
// dwParam2 : User-defined
// --- Out:
// --- Returns:
// --- Effect: Callback notification from the waveOut driver
{
// Callback function for the waveOutOpen() / waveOutWrite() calls...
#if defined (_WINDLL)
#if defined (_AFXDLL)
AFX_MANAGE_STATE(AfxGetAppModuleState());
#else
AFX_MANAGE_STATE(AfxGetStaticModuleState());
#endif
#endif
if(uiMsg==MM_WOM_DONE)
{
LPWAVEHDR lpWaveHdr = (LPWAVEHDR)dwParam1;
COXSound* pMe = (COXSound*)lpWaveHdr->dwUser;
HWND hCallbackWnd = NULL;
if (pMe == NULL)
{
return;
}
ASSERT_VALID(pMe);
hCallbackWnd = pMe->m_hCallbackWnd;
if (!pMe->m_bIsPlaying)
{
return;
}
// Post a message to the helper window
::PostMessage(COXSoundWnd::GetTheSoundWindow(),
WM_OX_INTERNAL_SOUNDCALLBACK, (DWORD_PTR)pMe, NULL);
}
}
BOOL COXSound::PrepareWaveHeader()
// --- In:
// --- Out:
// --- Returns: BOOL : TRUE if successful
// --- Effect: Prepares WAVE data and header information for playback.
{
// Internal protected call used to prepare the wave out device...
MMRESULT mmResult = MMSYSERR_NOERROR;
if (!m_lpWave)
{
return FALSE;
}
CloseWaveOutDevice();
BYTE* pbWaveData = NULL;
LPWAVEFORMATEX pWaveHeader = NULL;
DWORD dwBufferBytes = 0;
::ZeroMemory(&m_waveFormatEx, sizeof(WAVEFORMATEX));
m_waveFormatEx.cbSize = sizeof(WAVEFORMATEX);
pWaveHeader = &m_waveFormatEx;
// Parse the WAVE data...
if(!ParseWaveData(m_lpWave,&pWaveHeader,&pbWaveData,&dwBufferBytes))
{
return FALSE;
}
if (!pbWaveData)
{
return FALSE;
}
// Save the WAVEFORMATEX header information...
::CopyMemory(&m_waveFormatEx, pWaveHeader, sizeof(WAVEFORMATEX));
// Open the wave output device for playback...
mmResult = waveOutOpen(&m_hWaveOut, WAVE_MAPPER, pWaveHeader, (DWORD_PTR) &waveOutProc, (DWORD_PTR) this, CALLBACK_FUNCTION);
m_mmLastResult = mmResult;
if (mmResult != MMSYSERR_NOERROR)
{
return FALSE;
}
// Prepare the wave header to be written to the wave output device...
m_lpWaveHdr=(LPWAVEHDR) GlobalAllocPtr(GMEM_MOVEABLE|GMEM_SHARE,sizeof(WAVEHDR));
if (!m_lpWaveHdr)
{
return FALSE;
}
m_lpWaveData = GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE, dwBufferBytes + 1);
if (!m_lpWaveData)
{
return FALSE;
}
// Copy the data to the global buffer...
::CopyMemory(m_lpWaveData, pbWaveData, dwBufferBytes);
// Set up the WAVEHDR structure...
m_lpWaveHdr->lpData = (LPSTR) m_lpWaveData;
m_lpWaveHdr->dwBufferLength = dwBufferBytes;
m_lpWaveHdr->dwUser = (DWORD_PTR) this;
m_lpWaveHdr->dwFlags = 0;
m_lpWaveHdr->dwLoops = 0;
mmResult = waveOutPrepareHeader(m_hWaveOut, m_lpWaveHdr, sizeof(WAVEHDR));
m_mmLastResult = mmResult;
if (mmResult != MMSYSERR_NOERROR)
{
return FALSE;
}
mmResult = waveOutWrite(m_hWaveOut, m_lpWaveHdr, sizeof(WAVEHDR));
m_mmLastResult = mmResult;
if (mmResult != MMSYSERR_NOERROR)
{
waveOutUnprepareHeader(m_hWaveOut, m_lpWaveHdr, sizeof(WAVEHDR));
CloseWaveOutDevice();
return FALSE;
}
m_bIsPlaying = TRUE;
m_bIsLoaded = TRUE;
return TRUE;
}
BOOL COXSound::PlayWithCallback()
// --- In:
// --- Out:
// --- Returns: BOOL : TRUE if the function is successful
// --- Effect: Plays a loaded WAVE asynchronously with callback notification
{
// If playing asynchronously, there MUST be a callback
// window to handle processing when the Wave completes.
ASSERT((m_hCallbackWnd != NULL) && ::IsWindow(m_hCallbackWnd));
if(!::SendMessage(m_hCallbackWnd,WM_OX_SOUNDABOUTTOPLAY,NULL,(LPARAM)this))
{
if(m_hWaveOut)
{
waveOutPause(m_hWaveOut);
waveOutReset(m_hWaveOut);
}
CloseWaveOutDevice();
if(!PrepareWaveHeader())
{
return FALSE;
}
return TRUE;
}
return FALSE;
}
BOOL COXSound::ParseWaveData(void* pvRes, WAVEFORMATEX** ppWaveHeader,
BYTE** ppbWaveData, DWORD* pcbWaveSize)
// --- In: pvRes : Pointer to the raw WAVE data
// --- Out: ppWaveHeader : WAVEFORMATEX structure
// ppbWaveData : WAVE sample data for playback
// pcbWaveSize : Size of the WAVE sample data
// --- Returns: BOOL : TRUE if the function is successful
// --- Effect: Parses a loaded WAVE into its header and data components
{
DWORD* pdw = NULL;
DWORD* pdwEnd = NULL;
DWORD dwRiff = 0;
DWORD dwType = 0;
DWORD dwLength = 0;
ASSERT(pvRes != NULL);
if(ppWaveHeader != NULL)
{
*ppWaveHeader = NULL;
}
if(ppbWaveData != NULL)
{
*ppbWaveData = NULL;
}
if (pcbWaveSize != NULL)
{
*pcbWaveSize = 0;
}
pdw = (DWORD*)pvRes;
dwRiff = *pdw++;
dwLength = *pdw++;
dwType = *pdw++;
// Not a RIFF-encoded resource...
if (dwRiff != mmioFOURCC('R', 'I', 'F', 'F'))
{
return FALSE;
}
// This is not a WAVE...
if (dwType != mmioFOURCC('W', 'A', 'V', 'E'))
{
return FALSE;
}
pdwEnd = (DWORD*) ((BYTE*) pdw + dwLength - 4);
while (pdw < pdwEnd)
{
dwType = *pdw++;
dwLength = *pdw++;
switch(dwType)
{
case mmioFOURCC('f', 'm', 't', ' '):
if (ppWaveHeader && !*ppWaveHeader)
{
// Is this a WAVE?
if (dwLength < sizeof(WAVEFORMAT))
return FALSE;
*ppWaveHeader = (WAVEFORMATEX*) pdw;
if ((!ppbWaveData || *ppbWaveData) &&
(!pcbWaveSize || *pcbWaveSize))
{
return TRUE;
}
}
break;
case mmioFOURCC('d', 'a', 't', 'a'):
if ((ppbWaveData && !*ppbWaveData) ||
(pcbWaveSize && !*pcbWaveSize))
{
if (ppbWaveData != NULL)
*ppbWaveData = (LPBYTE) pdw;
if (pcbWaveSize != NULL)
*pcbWaveSize = dwLength;
if ((ppWaveHeader == NULL) || (*ppWaveHeader != NULL))
return TRUE;
}
break;
}
pdw = (DWORD*) ((BYTE*) pdw + ((dwLength + 1) & ~1));
}
return FALSE;
}
void COXSound::CloseWaveOutDevice()
// --- In:
// --- Out:
// --- Returns:
// --- Effect: Closes the wave output device and frees associated memory.
{
if (m_hWaveOut != NULL)
{
waveOutClose(m_hWaveOut);
m_hWaveOut = NULL;
}
if (m_lpWaveHdr != NULL)
{
GlobalFreePtr(m_lpWaveHdr);
m_lpWaveHdr = NULL;
}
if (m_lpWaveData != NULL)
{
GlobalFreePtr(m_lpWaveData);
m_lpWaveData = NULL;
}
}
LPVOID COXSound::GlobalAllocPtr(UINT uiFlags, DWORD dwBytes)
// --- In: uiFlags : Flags for GlobalAlloc()
// dwBytes : Size of requested buffer
// --- Out:
// --- Returns: LPVOID : Pointer to memory buffer
// --- Effect: Utility function which allocates and locks a block of global memory
{
HGLOBAL hMem = NULL;
LPVOID pPtr = NULL;
hMem = ::GlobalAlloc(uiFlags, dwBytes);
if (hMem == NULL)
{
return NULL;
}
pPtr = ::GlobalLock(hMem);
if (pPtr == NULL)
{
::GlobalUnlock(hMem);
return NULL;
}
return pPtr;
}
void COXSound::GlobalFreePtr(LPVOID pPtr)
// --- In: pPtr : Pointer to a global memory buffer
// --- Out:
// --- Returns:
// --- Effect: Utility function which unlocks and frees a block of global memory
{
HGLOBAL hMem = NULL;
ASSERT(pPtr != NULL);
hMem = ::GlobalHandle(pPtr);
if (hMem != NULL)
{
::GlobalUnlock(hMem);
::GlobalFree(hMem);
}
}
void COXSound::Serialize(CArchive& ar)
{
m_SoundSourceInfo.Serialize(ar);
if(!ar.IsStoring())
{
switch(m_SoundSourceInfo.source)
{
case SNDSRC_FILE:
Open(m_SoundSourceInfo.sFileName);
break;
case SNDSRC_INTRESOURCE:
Open(m_SoundSourceInfo.nResourceID,m_SoundSourceInfo.hInstance);
break;
}
}
}
BOOL COXSound::CanPlayLoadedData()
{
if(!CanPlay())
{
return FALSE;
}
if(m_lpWave==NULL)
{
return FALSE;
}
// check if loaded resource is really a WAVE data
BYTE* pbWaveData=NULL;
DWORD dwBufferBytes=0;
WAVEFORMATEX waveFormatEx={ sizeof(WAVEFORMATEX) };
LPWAVEFORMATEX pWaveHeader=&waveFormatEx;
if(!ParseWaveData(m_lpWave,&pWaveHeader,&pbWaveData,&dwBufferBytes))
{
return FALSE;
}
else
{
return TRUE;
}
}