Magic_Game/core/Tool/Music.cpp

687 lines
13 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "..\etools.h"
#include "..\emanagers.h"
using namespace e2d;
#if HIGHER_THAN_VS2010
#ifndef SAFE_DELETE
#define SAFE_DELETE(p) { if (p) { delete (p); (p)=nullptr; } }
#endif
#ifndef SAFE_DELETE_ARRAY
#define SAFE_DELETE_ARRAY(p) { if (p) { delete[] (p); (p)=nullptr; } }
#endif
inline bool TraceError(wchar_t* sPrompt)
{
WARN_IF(true, "Music error: %s failed!", sPrompt);
return false;
}
inline bool TraceError(wchar_t* sPrompt, HRESULT hr)
{
WARN_IF(true, "Music error: %s (%#X)", sPrompt, hr);
return false;
}
Music::Music()
: m_bOpened(false)
, m_bPlaying(false)
, m_pwfx(nullptr)
, m_hmmio(nullptr)
, m_pResourceBuffer(nullptr)
, m_pbWaveData(nullptr)
, m_dwSize(0)
, m_pSourceVoice(nullptr)
{
}
e2d::Music::Music(String strFileName)
: m_bOpened(false)
, m_bPlaying(false)
, m_pwfx(nullptr)
, m_hmmio(nullptr)
, m_pResourceBuffer(nullptr)
, m_pbWaveData(nullptr)
, m_dwSize(0)
, m_pSourceVoice(nullptr)
{
this->open(strFileName);
}
Music::~Music()
{
close();
}
bool Music::open(String strFileName)
{
if (m_bOpened)
{
WARN_IF(true, L"Music can be opened only once!");
return false;
}
if (strFileName.isEmpty())
{
WARN_IF(true, L"Music::open Invalid file name.");
return false;
}
IXAudio2 * pXAudio2 = MusicManager::getIXAudio2();
if (!pXAudio2)
{
WARN_IF(true, L"IXAudio2 nullptr pointer error!");
return false;
}
// 定位 wave 文件
wchar_t strFilePath[MAX_PATH];
if (!_findMediaFileCch(strFilePath, MAX_PATH, strFileName))
{
WARN_IF(true, L"Failed to find media file: %s", (const wchar_t*)strFileName);
return false;
}
m_hmmio = mmioOpen(strFilePath, nullptr, MMIO_ALLOCBUF | MMIO_READ);
if (nullptr == m_hmmio)
{
return TraceError(L"mmioOpen");
}
if (!_readMMIO())
{
// 读取非 wave 文件时 ReadMMIO 调用失败
mmioClose(m_hmmio, 0);
return TraceError(L"_readMMIO");
}
if (!_resetFile())
return TraceError(L"_resetFile");
// 重置文件后wave 文件的大小是 m_ck.cksize
m_dwSize = m_ck.cksize;
// 将样本数据读取到内存中
m_pbWaveData = new BYTE[m_dwSize];
if (!_read(m_pbWaveData, m_dwSize))
{
TraceError(L"Failed to read WAV data");
SAFE_DELETE_ARRAY(m_pbWaveData);
return false;
}
// 创建音源
HRESULT hr;
if (FAILED(hr = pXAudio2->CreateSourceVoice(&m_pSourceVoice, m_pwfx)))
{
TraceError(L"Error %#X creating source voice", hr);
SAFE_DELETE_ARRAY(m_pbWaveData);
return false;
}
m_bOpened = true;
m_bPlaying = false;
return true;
}
bool Music::play(int nLoopCount)
{
if (!m_bOpened)
{
WARN_IF(true, "Music::play Failed: Music must be opened first!");
return false;
}
if (m_pSourceVoice == nullptr)
{
WARN_IF(true, "Music::play Failed: IXAudio2SourceVoice Null pointer exception!");
return false;
}
if (m_bPlaying)
{
stop();
}
nLoopCount = min(nLoopCount, XAUDIO2_LOOP_INFINITE - 1);
nLoopCount = (nLoopCount < 0) ? XAUDIO2_LOOP_INFINITE : nLoopCount;
// 提交 wave 样本数据
XAUDIO2_BUFFER buffer = { 0 };
buffer.pAudioData = m_pbWaveData;
buffer.Flags = XAUDIO2_END_OF_STREAM;
buffer.AudioBytes = m_dwSize;
buffer.LoopCount = nLoopCount;
HRESULT hr;
if (FAILED(hr = m_pSourceVoice->SubmitSourceBuffer(&buffer)))
{
TraceError(L"Error %#X submitting source buffer", hr);
m_pSourceVoice->DestroyVoice();
SAFE_DELETE_ARRAY(m_pbWaveData);
return false;
}
if (SUCCEEDED(hr = m_pSourceVoice->Start(0)))
{
m_bPlaying = true;
}
return SUCCEEDED(hr);
}
void Music::pause()
{
if (m_pSourceVoice)
{
if (SUCCEEDED(m_pSourceVoice->Stop()))
{
m_bPlaying = false;
}
}
}
void Music::resume()
{
if (m_pSourceVoice)
{
if (SUCCEEDED(m_pSourceVoice->Start()))
{
m_bPlaying = true;
}
}
}
void Music::stop()
{
if (m_pSourceVoice)
{
if (SUCCEEDED(m_pSourceVoice->Stop()))
{
m_pSourceVoice->ExitLoop();
m_pSourceVoice->FlushSourceBuffers();
m_bPlaying = false;
}
}
}
void Music::close()
{
if (m_pSourceVoice)
{
m_pSourceVoice->Stop();
m_pSourceVoice->FlushSourceBuffers();
m_pSourceVoice->DestroyVoice();
m_pSourceVoice = nullptr;
}
if (m_hmmio != nullptr)
{
mmioClose(m_hmmio, 0);
m_hmmio = nullptr;
}
SAFE_DELETE_ARRAY(m_pResourceBuffer);
SAFE_DELETE_ARRAY(m_pbWaveData);
SAFE_DELETE_ARRAY(m_pwfx);
m_bOpened = false;
m_bPlaying = false;
}
bool Music::isPlaying() const
{
if (m_bOpened && m_pSourceVoice)
{
XAUDIO2_VOICE_STATE state;
m_pSourceVoice->GetState(&state);
if (state.BuffersQueued == 0)
{
m_bPlaying = false;
}
return m_bPlaying;
}
else
{
return false;
}
}
double Music::getVolume() const
{
float fVolume = 0.0f;
if (m_pSourceVoice)
{
m_pSourceVoice->GetVolume(&fVolume);
}
return static_cast<double>(fVolume);
}
bool Music::setVolume(double fVolume)
{
if (m_pSourceVoice)
{
return SUCCEEDED(m_pSourceVoice->SetVolume(min(max(static_cast<float>(fVolume), -224), 224)));
}
return false;
}
double Music::getFrequencyRatio() const
{
float fFrequencyRatio = 0.0f;
if (m_pSourceVoice)
{
m_pSourceVoice->GetFrequencyRatio(&fFrequencyRatio);
}
return static_cast<double>(fFrequencyRatio);
}
bool Music::setFrequencyRatio(double fFrequencyRatio)
{
if (m_pSourceVoice)
{
fFrequencyRatio = min(max(fFrequencyRatio, XAUDIO2_MIN_FREQ_RATIO), XAUDIO2_MAX_FREQ_RATIO);
return SUCCEEDED(m_pSourceVoice->SetFrequencyRatio(static_cast<float>(fFrequencyRatio)));
}
return false;
}
IXAudio2SourceVoice * Music::getIXAudio2SourceVoice() const
{
return m_pSourceVoice;
}
bool Music::_readMMIO()
{
MMCKINFO ckIn;
PCMWAVEFORMAT pcmWaveFormat;
memset(&ckIn, 0, sizeof(ckIn));
m_pwfx = nullptr;
if ((0 != mmioDescend(m_hmmio, &m_ckRiff, nullptr, 0)))
return TraceError(L"mmioDescend");
// 确认文件是一个合法的 wave 文件
if ((m_ckRiff.ckid != FOURCC_RIFF) ||
(m_ckRiff.fccType != mmioFOURCC('W', 'A', 'V', 'E')))
return TraceError(L"mmioFOURCC");
// 在输入文件中查找 'fmt' 块
ckIn.ckid = mmioFOURCC('f', 'm', 't', ' ');
if (0 != mmioDescend(m_hmmio, &ckIn, &m_ckRiff, MMIO_FINDCHUNK))
return TraceError(L"mmioDescend");
// 'fmt' 块至少应和 PCMWAVEFORMAT 一样大
if (ckIn.cksize < (LONG)sizeof(PCMWAVEFORMAT))
return TraceError(L"sizeof(PCMWAVEFORMAT)");
// 将 'fmt' 块读取到 pcmWaveFormat 中
if (mmioRead(m_hmmio, (HPSTR)&pcmWaveFormat,
sizeof(pcmWaveFormat)) != sizeof(pcmWaveFormat))
return TraceError(L"mmioRead");
// 分配 WAVEFORMATEX但如果它不是 PCM 格式,再读取一个 WORD 大小
// 的数据,这个数据就是额外分配的大小
if (pcmWaveFormat.wf.wFormatTag == WAVE_FORMAT_PCM)
{
m_pwfx = (WAVEFORMATEX*)new CHAR[sizeof(WAVEFORMATEX)];
// 拷贝数据
memcpy(m_pwfx, &pcmWaveFormat, sizeof(pcmWaveFormat));
m_pwfx->cbSize = 0;
}
else
{
// 读取额外数据的大小
WORD cbExtraBytes = 0L;
if (mmioRead(m_hmmio, (CHAR*)&cbExtraBytes, sizeof(WORD)) != sizeof(WORD))
return TraceError(L"mmioRead");
m_pwfx = (WAVEFORMATEX*)new CHAR[sizeof(WAVEFORMATEX) + cbExtraBytes];
// 拷贝数据
memcpy(m_pwfx, &pcmWaveFormat, sizeof(pcmWaveFormat));
m_pwfx->cbSize = cbExtraBytes;
// 读取额外数据
if (mmioRead(m_hmmio, (CHAR*)(((BYTE*)&(m_pwfx->cbSize)) + sizeof(WORD)),
cbExtraBytes) != cbExtraBytes)
{
SAFE_DELETE(m_pwfx);
return TraceError(L"mmioRead");
}
}
if (0 != mmioAscend(m_hmmio, &ckIn, 0))
{
SAFE_DELETE(m_pwfx);
return TraceError(L"mmioAscend");
}
return true;
}
bool Music::_resetFile()
{
// Seek to the data
if (-1 == mmioSeek(m_hmmio, m_ckRiff.dwDataOffset + sizeof(FOURCC),
SEEK_SET))
return TraceError(L"mmioSeek");
// Search the input file for the 'data' chunk.
m_ck.ckid = mmioFOURCC('d', 'a', 't', 'a');
if (0 != mmioDescend(m_hmmio, &m_ck, &m_ckRiff, MMIO_FINDCHUNK))
return TraceError(L"mmioDescend");
return true;
}
bool Music::_read(BYTE* pBuffer, DWORD dwSizeToRead)
{
MMIOINFO mmioinfoIn; // current status of m_hmmio
if (0 != mmioGetInfo(m_hmmio, &mmioinfoIn, 0))
return TraceError(L"mmioGetInfo");
UINT cbDataIn = dwSizeToRead;
if (cbDataIn > m_ck.cksize)
cbDataIn = m_ck.cksize;
m_ck.cksize -= cbDataIn;
for (DWORD cT = 0; cT < cbDataIn; cT++)
{
// Copy the bytes from the io to the buffer.
if (mmioinfoIn.pchNext == mmioinfoIn.pchEndRead)
{
if (0 != mmioAdvance(m_hmmio, &mmioinfoIn, MMIO_READ))
return TraceError(L"mmioAdvance");
if (mmioinfoIn.pchNext == mmioinfoIn.pchEndRead)
return TraceError(L"mmioinfoIn.pchNext");
}
// Actual copy.
*((BYTE*)pBuffer + cT) = *((BYTE*)mmioinfoIn.pchNext);
mmioinfoIn.pchNext++;
}
if (0 != mmioSetInfo(m_hmmio, &mmioinfoIn, 0))
return TraceError(L"mmioSetInfo");
return true;
}
bool Music::_findMediaFileCch(wchar_t* strDestPath, int cchDest, const wchar_t * strFilename)
{
bool bFound = false;
if (nullptr == strFilename || nullptr == strDestPath || cchDest < 10)
return false;
// Get the exe name, and exe path
wchar_t strExePath[MAX_PATH] = { 0 };
wchar_t strExeName[MAX_PATH] = { 0 };
wchar_t* strLastSlash = nullptr;
GetModuleFileName(HINST_THISCOMPONENT, strExePath, MAX_PATH);
strExePath[MAX_PATH - 1] = 0;
strLastSlash = wcsrchr(strExePath, TEXT('\\'));
if (strLastSlash)
{
wcscpy_s(strExeName, MAX_PATH, &strLastSlash[1]);
// Chop the exe name from the exe path
*strLastSlash = 0;
// Chop the .exe from the exe name
strLastSlash = wcsrchr(strExeName, TEXT('.'));
if (strLastSlash)
*strLastSlash = 0;
}
wcscpy_s(strDestPath, cchDest, strFilename);
if (GetFileAttributes(strDestPath) != 0xFFFFFFFF)
return true;
// Search all parent directories starting at .\ and using strFilename as the leaf name
wchar_t strLeafName[MAX_PATH] = { 0 };
wcscpy_s(strLeafName, MAX_PATH, strFilename);
wchar_t strFullPath[MAX_PATH] = { 0 };
wchar_t strFullFileName[MAX_PATH] = { 0 };
wchar_t strSearch[MAX_PATH] = { 0 };
wchar_t* strFilePart = nullptr;
GetFullPathName(L".", MAX_PATH, strFullPath, &strFilePart);
if (strFilePart == nullptr)
return false;
while (strFilePart != nullptr && *strFilePart != '\0')
{
swprintf_s(strFullFileName, MAX_PATH, L"%s\\%s", strFullPath, strLeafName);
if (GetFileAttributes(strFullFileName) != 0xFFFFFFFF)
{
wcscpy_s(strDestPath, cchDest, strFullFileName);
bFound = true;
break;
}
swprintf_s(strFullFileName, MAX_PATH, L"%s\\%s\\%s", strFullPath, strExeName, strLeafName);
if (GetFileAttributes(strFullFileName) != 0xFFFFFFFF)
{
wcscpy_s(strDestPath, cchDest, strFullFileName);
bFound = true;
break;
}
swprintf_s(strSearch, MAX_PATH, L"%s\\..", strFullPath);
GetFullPathName(strSearch, MAX_PATH, strFullPath, &strFilePart);
}
if (bFound)
return true;
// 失败时,将文件作为路径返回,同时也返回错误代码
wcscpy_s(strDestPath, cchDest, strFilename);
return false;
}
#else
e2d::Music::Music()
: m_wnd(NULL)
, m_dev(0L)
, m_nMusicID(0)
, m_bPlaying(false)
, m_nRepeatTimes(0)
{
m_wnd = CreateWindowEx(
WS_EX_APPWINDOW,
MUSIC_CLASS_NAME,
NULL,
WS_POPUPWINDOW,
0, 0, 0, 0,
NULL,
NULL,
MusicManager::getHInstance(),
NULL);
if (m_wnd)
{
SetWindowLongPtr(m_wnd, GWLP_USERDATA, (LONG_PTR)this);
}
}
e2d::Music::~Music()
{
close();
DestroyWindow(m_wnd);
}
bool e2d::Music::open(String pFileName)
{
if (pFileName.isEmpty())
return false;
close();
MCI_OPEN_PARMS mciOpen = { 0 };
mciOpen.lpstrDeviceType = 0;
mciOpen.lpstrElementName = pFileName;
MCIERROR mciError;
mciError = mciSendCommand(
0,
MCI_OPEN,
MCI_OPEN_ELEMENT,
reinterpret_cast<DWORD_PTR>(&mciOpen)
);
if (mciError == 0)
{
m_dev = mciOpen.wDeviceID;
m_nMusicID = pFileName.getHashCode();
m_bPlaying = false;
return true;
}
return false;
}
bool e2d::Music::play(int nLoopCount)
{
if (!m_dev)
{
return false;
}
MCI_PLAY_PARMS mciPlay = { 0 };
mciPlay.dwCallback = reinterpret_cast<DWORD_PTR>(m_wnd);
// 播放声音
MCIERROR mciError = mciSendCommand(
m_dev,
MCI_PLAY,
MCI_FROM | MCI_NOTIFY,
reinterpret_cast<DWORD_PTR>(&mciPlay)
);
if (!mciError)
{
m_bPlaying = true;
m_nRepeatTimes = nLoopCount;
return true;
}
return false;
}
void e2d::Music::close()
{
if (m_bPlaying)
{
stop();
}
if (m_dev)
{
_sendCommand(MCI_CLOSE);
}
m_dev = 0;
m_bPlaying = false;
}
void e2d::Music::pause()
{
_sendCommand(MCI_PAUSE);
m_bPlaying = false;
}
void e2d::Music::resume()
{
_sendCommand(MCI_RESUME);
m_bPlaying = true;
}
void e2d::Music::stop()
{
_sendCommand(MCI_STOP);
m_bPlaying = false;
}
bool e2d::Music::isPlaying() const
{
return m_bPlaying;
}
double Music::getVolume() const
{
return 1.0f;
}
bool Music::setVolume(double fVolume)
{
return false;
}
double Music::getFrequencyRatio() const
{
return 1.0f;
}
bool Music::setFrequencyRatio(double fFrequencyRatio)
{
return false;
}
void e2d::Music::_sendCommand(int nCommand, DWORD_PTR param1, DWORD_PTR parma2)
{
// 空设备时忽略这次操作
if (!m_dev)
{
return;
}
// 向当前设备发送操作
mciSendCommand(m_dev, nCommand, param1, parma2);
}
LRESULT WINAPI e2d::Music::MusicProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
e2d::Music * pPlayer = NULL;
if (Msg == MM_MCINOTIFY
&& wParam == MCI_NOTIFY_SUCCESSFUL
&& (pPlayer = (Music *)GetWindowLongPtr(hWnd, GWLP_USERDATA)))
{
if (pPlayer->m_nRepeatTimes > 0)
{
pPlayer->m_nRepeatTimes--;
}
if (pPlayer->m_nRepeatTimes)
{
mciSendCommand(static_cast<MCIDEVICEID>(lParam), MCI_SEEK, MCI_SEEK_TO_START, 0);
MCI_PLAY_PARMS mciPlay = { 0 };
mciPlay.dwCallback = reinterpret_cast<DWORD_PTR>(hWnd);
mciSendCommand(static_cast<MCIDEVICEID>(lParam), MCI_PLAY, MCI_NOTIFY, reinterpret_cast<DWORD_PTR>(&mciPlay));
}
else
{
pPlayer->m_bPlaying = false;
return 0;
}
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
#endif