Files
Client/GameTools/SoundLib/DMusic_OggLoader.cpp
LGram16 dd97ddec92 Restructure repository to include all source folders
Move git root from Client/ to src/ to track all source code:
- Client: Game client source (moved to Client/Client/)
- Server: Game server source
- GameTools: Development tools
- CryptoSource: Encryption utilities
- database: Database scripts
- Script: Game scripts
- rylCoder_16.02.2008_src: Legacy coder tools
- GMFont, Game: Additional resources

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 20:17:20 +09:00

763 lines
16 KiB
C++

#include "DMusic_OggLoader.h"
#include "OggFile.h"
#include <tchar.h>
#include <atlbase.h>
#include <string>
#include <list>
#ifndef RELEASE
#define RELEASE(x) { if(x) (x)->Release(); x = NULL; }
#endif
///////////////////////////////////////////////////////////////////////////////////////
//
struct SWaveHeader
{
char ChunkID[4];
DWORD ChunkSize;
char Format[4];
char SubChunk1ID[4];
DWORD SubChunk1Size;
WORD AudioFormat;
WORD NumChannels;
DWORD SampleRate;
DWORD ByteRate;
WORD BlockAlign;
WORD BitsPerSample;
char SubChunk2ID[4];
DWORD SubChunk2Size;
};
///////////////////////////////////////////////////////////////////////////////////////
//
COggStream::COggStream()
: m_cRef( 1 )
, m_pLoader( NULL )
, m_pOggFile( new COGGFile )
, m_pWaveHeader( new SWaveHeader )
, m_TotalWaveBytes( 0 )
, m_SeekPos( -sizeof( SWaveHeader ) )
{
}
///////////////////////////////////////////////////////////////////////////////////////
//
COggStream::~COggStream()
{
Detach();
delete m_pOggFile;
delete m_pWaveHeader;
}
///////////////////////////////////////////////////////////////////////////////////////
//
HRESULT COggStream::Attach( LPCTSTR tzFile, IDirectMusicLoader * pLoader )
{
Detach();
m_pOggFile->Create( tzFile );
m_pLoader = pLoader;
pLoader->AddRef();
//Wave 헤더를 채워넣음.
m_TotalWaveBytes = m_pOggFile->SeekWaveData( 0, ISoundFile::seek_end );
m_pOggFile->SeekWaveData( 0, ISoundFile::seek_begin );
memcpy( m_pWaveHeader->ChunkID, "RIFF", 4 ); //null 문자를 넣지 않기 위해 strcpy 대신 memcpy로 복사함.
m_pWaveHeader->ChunkSize = m_TotalWaveBytes + 36; //36 == sizeof( SWaveHeader ) - sizeof( m_pWaveHeader->m_ChunkID ) - sizeof( m_pWaveHeader->m_ChunkSize )
memcpy( m_pWaveHeader->Format, "WAVE", 4 );
memcpy( m_pWaveHeader->SubChunk1ID, "fmt ", 4 );
m_pWaveHeader->SubChunk1Size = 16;
m_pWaveHeader->AudioFormat = WAVE_FORMAT_PCM;
m_pWaveHeader->NumChannels = m_pOggFile->GetChannelCount();
m_pWaveHeader->SampleRate = m_pOggFile->GetSamplePerSec();
m_pWaveHeader->BlockAlign = m_pOggFile->GetBitsPerSample() * m_pOggFile->GetChannelCount() / 8;
m_pWaveHeader->ByteRate = m_pWaveHeader->BlockAlign * m_pWaveHeader->SampleRate;
m_pWaveHeader->BitsPerSample = m_pOggFile->GetBitsPerSample();
memcpy( m_pWaveHeader->SubChunk2ID, "data", 4 );
m_pWaveHeader->SubChunk2Size = m_TotalWaveBytes;
m_SeekPos = -sizeof( SWaveHeader );
return S_OK;
}
///////////////////////////////////////////////////////////////////////////////////////
//
void COggStream::Detach()
{
m_pOggFile->Destroy();
RELEASE( m_pLoader );
}
///////////////////////////////////////////////////////////////////////////////////////
//
MUSIC_TIME COggStream::GetMusicTime()
{
DWORD cbSamples = m_TotalWaveBytes / ( m_pWaveHeader->NumChannels * ( m_pWaveHeader->BitsPerSample / 8 ) );
// At 120 bpm (2 beats per second) and 768 music-time ticks per beat,
// we have 1536 ticks per second.
//
return (cbSamples * 2 * DMUS_PPQ) / m_pWaveHeader->SampleRate;
}
///////////////////////////////////////////////////////////////////////////////////////
//
//IUnknown methods
STDMETHODIMP COggStream::QueryInterface( REFIID iid, void ** ppv )
{
*ppv = NULL;
if (iid == IID_IUnknown || iid == IID_ISequentialStream)
{
// ISequentialStream is the base class for IStream, which
// provides only sequential file I/O.
//
*ppv = (void*)static_cast<ISequentialStream*>(this);
}
else if (iid == IID_IStream)
{
// IStream adds the concept of a file pointer to the
// sequential stream.
//
*ppv = (void*)static_cast<IStream*>(this);
}
else if (iid == IID_IDirectMusicGetLoader)
{
// This is a DirectMusic specific interface to get back
// the loader that created this IStream.
//
*ppv = (void*)static_cast<IDirectMusicGetLoader*>(this);
}
else
{
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
///////////////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP_(ULONG) COggStream::AddRef()
{
return InterlockedIncrement( &m_cRef );
}
///////////////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP_(ULONG) COggStream::Release()
{
if( InterlockedDecrement( &m_cRef ) == 0 )
{
delete this;
return 0;
}
return m_cRef;
}
///////////////////////////////////////////////////////////////////////////////////////
//
//IDirectMusicGetLoader
STDMETHODIMP COggStream::GetLoader( IDirectMusicLoader ** ppLoader )
{
m_pLoader->AddRef();
*ppLoader = m_pLoader;
return S_OK;
}
///////////////////////////////////////////////////////////////////////////////////////
//
//IStream methods
STDMETHODIMP COggStream::Read( void * pv, ULONG cb, ULONG * pcb )
{
if( m_pLoader == NULL )
return E_FAIL;
if( m_SeekPos < 0 )
{
char * pWaveHeader = (char*)m_pWaveHeader;
if( m_SeekPos + long(cb) >= 0 )
{
//WaveHeader와 Ogg 데이터 사이에 m_SeekPos가 끼어 있으므로....
//WaveHeader의 데이터를 먼저 복사...
DWORD writeSize1 = -m_SeekPos;
memcpy( pv, pWaveHeader + m_SeekPos + sizeof( SWaveHeader ), writeSize1 );
pv = (char*)pv + writeSize1;
DWORD writeSize2 = cb - writeSize1;
m_pOggFile->SeekWaveData( 0, ISoundFile::seek_begin );
size_t BytesWritten2 = m_pOggFile->Read( pv, writeSize2 );
m_SeekPos += cb;
if( pcb )
{
*pcb = writeSize1 + BytesWritten2;
}
}
else
{
memcpy( pv, pWaveHeader + m_SeekPos + sizeof( SWaveHeader ), cb );
m_SeekPos += cb;
if( pcb )
{
*pcb = cb;
}
}
}
else
{
size_t BytesWritten = m_pOggFile->Read( pv, cb );
m_SeekPos += BytesWritten;
if( pcb )
{
*pcb = BytesWritten;
}
}
return S_OK;
}
///////////////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP COggStream::Seek( LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER * plibNewPosition )
{
if( m_pLoader == NULL )
return E_FAIL;
long offset = dlibMove.LowPart;
if( dwOrigin == FILE_BEGIN )
{
m_SeekPos = offset - sizeof( SWaveHeader );
}
else if( dwOrigin == FILE_END )
{
m_SeekPos = m_TotalWaveBytes + offset - sizeof( SWaveHeader );
}
else
{
m_SeekPos += offset;
}
if( m_SeekPos >= 0 )
{
m_pOggFile->SeekWaveData( m_SeekPos, ISoundFile::seek_begin );
}
if( plibNewPosition )
{
plibNewPosition->HighPart = dlibMove.HighPart;
plibNewPosition->LowPart = m_SeekPos + sizeof( SWaveHeader );
}
return S_OK;
}
///////////////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP COggStream::Clone( IStream ** ppstm )
{
COggStream * pOther = new COggStream;
if( pOther == NULL )
{
return E_OUTOFMEMORY;
}
if( m_pOggFile )
{
HRESULT hr = pOther->Attach( m_pOggFile->GetFilename(), m_pLoader );
if( SUCCEEDED( hr ) )
{
LARGE_INTEGER liNewPosition;
liNewPosition.QuadPart = m_SeekPos + sizeof( SWaveHeader );
hr = pOther->Seek( liNewPosition, STREAM_SEEK_SET, NULL );
}
if( FAILED( hr ) )
{
RELEASE( pOther );
return hr;
}
}
*ppstm = static_cast<IStream*>( pOther );
return S_OK;
}
///////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////
//
struct SObjRef
{
wstring m_strFilename;
GUID m_guidObject;
IDirectMusicObject* m_pObject;
};
///////////////////////////////////////////////////////////////////////////////////////
//
CDMusic_OggLoader::CDMusic_OggLoader()
: m_cRef( 1 )
, m_pstrSearchPath( new std::wstring( L".\\" ) )
, m_pObjList( new LIST_OBJREF )
{
}
///////////////////////////////////////////////////////////////////////////////////////
//
CDMusic_OggLoader::~CDMusic_OggLoader()
{
delete m_pstrSearchPath;
delete m_pObjList;
}
///////////////////////////////////////////////////////////////////////////////////////
//
HRESULT CDMusic_OggLoader::Init()
{
USES_CONVERSION;
HRESULT hr = S_OK;
IDirectMusicLoader * pLoader;
hr = CoCreateInstance( CLSID_DirectMusicLoader, NULL, CLSCTX_INPROC, IID_IDirectMusicLoader, (void**)&pLoader );
if( SUCCEEDED( hr ) )
{
DMUS_OBJECTDESC ObjDesc;
IDirectMusicObject * pGMSet = NULL;
ObjDesc.guidClass = CLSID_DirectMusicCollection;
ObjDesc.guidObject = GUID_DefaultGMCollection;
ObjDesc.dwSize = sizeof( DMUS_OBJECTDESC );
ObjDesc.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_OBJECT;
hr = pLoader->GetObject( &ObjDesc, IID_IDirectMusicObject, (void**) &pGMSet );
if( SUCCEEDED( hr ) )
{
SObjRef objRef;
objRef.m_guidObject = GUID_DefaultGMCollection;
objRef.m_pObject = pGMSet;
m_pObjList->push_back( objRef );
pGMSet->AddRef();
pGMSet->Release();
}
pLoader->Release();
}
return hr;
}
///////////////////////////////////////////////////////////////////////////////////////
//
void CDMusic_OggLoader::UnInit()
{
LIST_OBJREF::iterator it, it_end;
it = m_pObjList->begin();
it_end = m_pObjList->end();
for( ; it != it_end; it++ )
{
RELEASE( it->m_pObject );
}
}
///////////////////////////////////////////////////////////////////////////////////////
//
//IUnknown methods
STDMETHODIMP CDMusic_OggLoader::QueryInterface( REFIID iid, void ** ppv )
{
*ppv = NULL;
if( iid == IID_IUnknown || iid == IID_IDirectMusicLoader )
{
*ppv = (void*)static_cast<IDirectMusicLoader*>( this );
}
else
{
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
///////////////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP_(ULONG) CDMusic_OggLoader::AddRef()
{
return InterlockedIncrement( &m_cRef );
}
///////////////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP_(ULONG) CDMusic_OggLoader::Release()
{
LONG ulCount = InterlockedDecrement( &m_cRef );
if( ulCount <= 0 )
{
delete this;
}
return (ULONG) ulCount;
}
///////////////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP CDMusic_OggLoader::SetSearchDirectory( REFGUID rguidClass, WCHAR * pwzPath, BOOL fClear )
{
*m_pstrSearchPath = pwzPath;
return S_OK;
}
///////////////////////////////////////////////////////////////////////////////////////
//
SObjRef * CDMusic_OggLoader::FindCacheObject( LPDMUS_OBJECTDESC pDesc )
{
LIST_OBJREF::iterator it, it_end;
it = m_pObjList->begin();
it_end = m_pObjList->end();
if( pDesc->dwValidData & DMUS_OBJ_OBJECT ) //같은 GUID를 가진 것이 있는지 찾는다.
{
for( ; it != it_end; it++ )
{
if( it->m_guidObject == pDesc->guidObject )
{
return &(*it);
}
}
}
else if( pDesc->dwValidData & DMUS_OBJ_FILENAME ) //같은 화일이름을 가진 것을 찾는다.
{
for( ; it != it_end; it++ )
{
if( it->m_strFilename == pDesc->wszFileName )
{
return &(*it);
}
}
}
return NULL; //못 찾으면 NULL 리턴
}
///////////////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP CDMusic_OggLoader::CreateFromFile( IDirectMusicObject * pObject, WCHAR * wszFileName, MUSIC_TIME * pMusicTime )
{
USES_CONVERSION;
HRESULT hr = S_OK;
//화일로부터 읽어들인다. 화일로부터 스트림을 만들고, 그것으로부터 읽어들인다.
WCHAR wzFileName1[MAX_PATH];
WCHAR wzExt[_MAX_EXT];
WCHAR wzDrive[_MAX_DRIVE] = L"";
WCHAR wzDir[_MAX_DIR] = L"";
_wsplitpath( wszFileName, wzDrive, wzDir, NULL, NULL );
//드라이브 문자가 있거나 Path가 \\로 시작할 대
if( wzDrive[0] != L'\0' || wzDir[0] == L'\\' )
{
wcscpy( wzFileName1, wszFileName );
}
else
{
_wmakepath( wzFileName1, NULL, m_pstrSearchPath->c_str(), wszFileName, NULL );
_wsplitpath( wzFileName1, NULL, NULL, NULL, wzExt );
}
COggStream * pOggStream = new COggStream;
if( pOggStream == NULL )
{
hr = E_OUTOFMEMORY;
}
if( SUCCEEDED( hr ) )
{
hr = pOggStream->Attach( W2CT( wzFileName1 ), this );
}
IPersistStream * pPersistStream = NULL;
if( SUCCEEDED( hr ) )
{
hr = pObject->QueryInterface( IID_IPersistStream, (void**)&pPersistStream );
}
if( SUCCEEDED( hr ) )
{
hr = pPersistStream->Load( pOggStream );
}
if( pMusicTime )
{
*pMusicTime = pOggStream->GetMusicTime();
}
RELEASE( pOggStream );
RELEASE( pPersistStream );
return hr;
}
///////////////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP CDMusic_OggLoader::CreateFromStream( IDirectMusicObject * pObject, IStream * pStream )
{
IStream * pClonedStream = NULL;
IPersistStream * pPersistStream = NULL;
HRESULT hr = pObject->QueryInterface( IID_IPersistStream, (void**)&pPersistStream );
if( SUCCEEDED( hr ) )
{
hr = pStream->Clone( &pClonedStream );
}
if( SUCCEEDED( hr ) )
{
hr = pPersistStream->Load( pClonedStream );
}
RELEASE( pPersistStream );
RELEASE( pClonedStream );
return hr;
}
///////////////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP CDMusic_OggLoader::AddToList( IDirectMusicObject * pObject, LPDMUS_OBJECTDESC pDesc )
{
DMUS_OBJECTDESC DESC;
memset( &DESC, 0, sizeof( DESC ) );
DESC.dwSize = sizeof( DMUS_OBJECTDESC );
pObject->GetDescriptor( &DESC );
if( ( DESC.dwValidData & DMUS_OBJ_OBJECT ) || ( pDesc->dwValidData & DMUS_OBJ_FILENAME ) )
{
SObjRef objRef;
objRef.m_guidObject = DESC.guidObject;
if( pDesc->dwValidData & DMUS_OBJ_FILENAME )
{
objRef.m_strFilename = pDesc->wszFileName;
}
objRef.m_pObject = pObject;
m_pObjList->push_back( objRef );
pObject->AddRef();
}
return S_OK;
}
///////////////////////////////////////////////////////////////////////////////////////
//
//IDirectMusicLoader methods
STDMETHODIMP CDMusic_OggLoader::GetObject( LPDMUS_OBJECTDESC pDesc, REFIID riid, LPVOID FAR * ppv )
{
USES_CONVERSION;
SObjRef * pFindedObj = FindCacheObject( pDesc );
//오브젝트를 찾았으면 그것이 요청한 인터페이스를 지원하는지 확인하고, 레퍼런스를 추가한다.
if( pFindedObj )
{
IDirectMusicObject * pObject = pFindedObj->m_pObject;
if( pObject )
{
HRESULT hr = pObject->QueryInterface( riid, ppv );
return hr;
}
}
else //찾지 못했으면 다시 만든다.
{
MUSIC_TIME musicTime = 0;
IDirectMusicObject * pObject = NULL;
//주어진 클래스는 IDirectMusicObject를 지원해서 생성 가능해야 한다.
HRESULT hr = CoCreateInstance( pDesc->guidClass, NULL, CLSCTX_INPROC_SERVER, IID_IDirectMusicObject, (void**)&pObject );
if( FAILED( hr ) )
{
return hr;
}
if( pDesc->dwValidData & DMUS_OBJ_FILENAME )
{
hr = CreateFromFile( pObject, pDesc->wszFileName, &musicTime );
}
else if( pDesc->dwValidData & DMUS_OBJ_STREAM )
{
hr = CreateFromStream( pObject, pDesc->pStream );
}
else
{
hr = E_FAIL;
}
if( SUCCEEDED( hr ) )
{
//로딩에 성공했으면 AddRef를 하고 나중을 위해 GUID를 저장해 놓는다.
AddToList( pObject, pDesc );
hr = pObject->QueryInterface( riid, ppv );
}
if( SUCCEEDED( hr ) && riid == IID_IDirectMusicSegment8 && musicTime )
{
//Length 설정.. 대부분의 경우는 설정을 안해줘도 문제가 안 생기지만,
//여러 종류의 MusicBuffer를 쓸 때는 문제가 생길 가능성이 있으므로
//설정을 해줌.
//현재는 템포 120 bpm을 기준으로 Music Time을 계산해서 설정하도록 했음.
IDirectMusicSegment * pSegment = (IDirectMusicSegment8*)(*ppv);
hr = pSegment->SetLength( musicTime );
}
RELEASE( pObject );
return hr;
}
return E_FAIL;
}
///////////////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP_(void) CDMusic_OggLoader::CollectGarbage()
{
}
///////////////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP CDMusic_OggLoader::ReleaseObjectByUnknown( IUnknown * pObject )
{
LIST_OBJREF::iterator it, it_end;
it = m_pObjList->begin();
it_end = m_pObjList->end();
for( ; it != it_end; it++ )
{
if( it->m_pObject == pObject )
{
if( it->m_pObject->Release() == 0 )
{
m_pObjList->erase( it );
break;
}
}
}
return S_OK;
}
///////////////////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP CDMusic_OggLoader::LoadObjectFromFile( REFGUID rguidClassID,
REFIID iidInterfaceID,
WCHAR * pwzFilePath,
void ** ppObject )
{
DMUS_OBJECTDESC dmod;
memset(&dmod, 0, sizeof(dmod));
dmod.dwSize = sizeof(dmod);
dmod.dwValidData = DMUS_OBJ_FILENAME;
dmod.guidClass = CLSID_DirectMusicSegment;
wcscpy( dmod.wszFileName, pwzFilePath );
HRESULT hr = GetObject( &dmod, IID_IDirectMusicSegment8, ppObject );
return hr;
}
///////////////////////////////////////////////////////////////////////////////////////