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>
433 lines
13 KiB
C++
433 lines
13 KiB
C++
|
|
|
|
#include "StreamBuffer.h"
|
|
#include "SoundFile.h"
|
|
|
|
#include <dsound.h>
|
|
#include <assert.h>
|
|
|
|
extern bool g_b3DSound;
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
|
|
CStreamBuffer::CStreamBuffer()
|
|
: m_dwLastPlayPos( 0 )
|
|
, m_dwPlayProgress( 0 )
|
|
, m_dwNotifySize( 0 )
|
|
, m_dwNextWriteOffset( 0 )
|
|
, m_hNotificationEvent( NULL )
|
|
, m_bFillNextNotificationWithSilence( false )
|
|
{
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
|
|
CStreamBuffer::CStreamBuffer( IDirectSound8 * pDSound, ISoundFile * pSoundFile, bool b3DSound, DWORD dwNumBuffers )
|
|
: m_dwLastPlayPos( 0 )
|
|
, m_dwPlayProgress( 0 )
|
|
, m_dwNotifySize( 0 )
|
|
, m_dwNextWriteOffset( 0 )
|
|
, m_hNotificationEvent( NULL )
|
|
, m_bFillNextNotificationWithSilence( false )
|
|
, m_bLoopPlay( false )
|
|
{
|
|
Create( pDSound, pSoundFile, b3DSound, dwNumBuffers, 0 );
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
|
|
CStreamBuffer::CStreamBuffer( IDirectSound8 * pDSound, const char * szFilename, bool b3DSound, DWORD dwNumBuffers )
|
|
: m_dwLastPlayPos( 0 )
|
|
, m_dwPlayProgress( 0 )
|
|
, m_dwNotifySize( 0 )
|
|
, m_dwNextWriteOffset( 0 )
|
|
, m_hNotificationEvent( NULL )
|
|
, m_bFillNextNotificationWithSilence( false )
|
|
, m_bLoopPlay( false )
|
|
{
|
|
Create( pDSound, szFilename, b3DSound, dwNumBuffers, 0 );
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
|
|
CStreamBuffer::~CStreamBuffer()
|
|
{
|
|
Destroy();
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
|
|
//( DWORD dwNotifyCount, DWORD dwNotifySize, HANDLE hNotifyEvent )
|
|
void CStreamBuffer::Create( IDirectSound8 * pDSound, ISoundFile * pSoundFile, bool b3DSound, DWORD dwNumBuffers, DWORD dwAddFlag )
|
|
{
|
|
Destroy();
|
|
|
|
if( g_b3DSound == false )
|
|
b3DSound = false;
|
|
|
|
m_hNotificationEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
|
|
|
|
m_pSoundFile = pSoundFile;
|
|
|
|
assert( pDSound != NULL && m_pSoundFile != NULL );
|
|
|
|
WAVEFORMATEX wfx;
|
|
ZeroMemory( &wfx, sizeof(WAVEFORMATEX) );
|
|
wfx.nChannels = m_pSoundFile->GetChannelCount();
|
|
wfx.nSamplesPerSec = m_pSoundFile->GetSamplePerSec();
|
|
wfx.wBitsPerSample = m_pSoundFile->GetBitsPerSample();
|
|
|
|
|
|
if( b3DSound )
|
|
{
|
|
if( wfx.nChannels != 1 ) //이것이 Stereo(2)로 되어 있으면 DSBCAPS_CTRL3D를 세팅할 수 없음
|
|
{
|
|
MessageBox( NULL, "3D 사운드를 사용하기 위해서는 사운드 화일이 mono로 설정이 되어 있어야 합니다. 그렇지 않으면 사운드가 이상하게 플레이 될 것입니다.", pSoundFile->GetFilename(), MB_OK );
|
|
wfx.nChannels = 1;
|
|
}
|
|
}
|
|
|
|
wfx.wFormatTag = WAVE_FORMAT_PCM;
|
|
wfx.nBlockAlign = ( wfx.wBitsPerSample * wfx.nChannels ) / 8;
|
|
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
|
|
|
|
DWORD dwNotifyCount = 32;
|
|
DWORD dwTotalSize = wfx.nSamplesPerSec * 3 * wfx.nBlockAlign; //3초 동안의 사운드를 담을 수 있도록 버퍼의 크기 설정
|
|
m_dwNotifySize = dwTotalSize / dwNotifyCount;
|
|
m_dwNotifySize -= m_dwNotifySize % wfx.nBlockAlign;
|
|
m_dwDSBufferSize = m_dwNotifySize * dwNotifyCount;
|
|
|
|
// Set up the direct sound buffer. Request the NOTIFY flag, so
|
|
// that we are notified as the sound buffer plays. Note, that using this flag
|
|
// may limit the amount of hardware acceleration that can occur.
|
|
DSBUFFERDESC dsbd;
|
|
ZeroMemory( &dsbd, sizeof(DSBUFFERDESC) );
|
|
dsbd.dwSize = sizeof(DSBUFFERDESC);
|
|
dsbd.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY |
|
|
DSBCAPS_GETCURRENTPOSITION2 |
|
|
dwAddFlag;
|
|
|
|
if( b3DSound )
|
|
dsbd.dwFlags |= DSBCAPS_CTRL3D | DSBCAPS_MUTE3DATMAXDISTANCE;
|
|
|
|
dsbd.dwBufferBytes = m_dwDSBufferSize;
|
|
dsbd.guid3DAlgorithm = GUID_NULL;
|
|
dsbd.lpwfxFormat = &wfx;
|
|
|
|
LPDIRECTSOUNDBUFFER tempBuffer = 0;
|
|
|
|
HRESULT hr = pDSound->CreateSoundBuffer( &dsbd, &tempBuffer, NULL );
|
|
if( FAILED( hr ) )
|
|
{
|
|
return;
|
|
/*
|
|
if( hr == E_INVALIDARG )
|
|
{
|
|
SNDError( "Create Streaming Buffer Failed!! ErrorCode : 0x%x, ExtraInfo : %d, %d, %d, %d, %d, %d, %d, SoundFileName : %s "
|
|
, hr, wfx.nChannels, wfx.nSamplesPerSec, wfx.wBitsPerSample, wfx.nBlockAlign
|
|
, wfx.nAvgBytesPerSec, dsbd.dwFlags, dsbd.dwBufferBytes, pSoundFile->GetFilename() );
|
|
}
|
|
else
|
|
SNDError( "Create Streaming Buffer Failed!! ErrorCode : 0x%x", hr );
|
|
*/
|
|
}
|
|
|
|
hr = tempBuffer->QueryInterface( IID_IDirectSoundBuffer8, (void**)&m_pDSBuffer8 );
|
|
|
|
ULONG refCount = tempBuffer->Release();
|
|
|
|
if( FAILED( hr ) )
|
|
{
|
|
SNDError( "QueryInterface Failed!! (at CStreamBuffer::Create)... ErrorCode : 0x%x", hr );
|
|
}
|
|
|
|
m_dwNumBuffers = dwNumBuffers;
|
|
|
|
m_apDSBuffer = new LPDIRECTSOUNDBUFFER[m_dwNumBuffers];
|
|
ZeroMemory( m_apDSBuffer, sizeof(LPDIRECTSOUNDBUFFER) * m_dwNumBuffers );
|
|
|
|
for( DWORD i = 0; i < m_dwNumBuffers; i++ )
|
|
{
|
|
if( FAILED( hr = pDSound->DuplicateSoundBuffer( m_pDSBuffer8, &m_apDSBuffer[i] ) ) )
|
|
{
|
|
delete [] m_apDSBuffer;
|
|
SNDError( "Duplicate SoundBuffer Failed!! ErrorCode : 0x%x", hr );
|
|
}
|
|
}
|
|
|
|
|
|
LPDIRECTSOUNDNOTIFY pDSNotify = NULL;
|
|
|
|
// Create the notification events, so that we know when to fill
|
|
// the buffer as the sound plays.
|
|
if( FAILED( hr = m_apDSBuffer[0]->QueryInterface( IID_IDirectSoundNotify, (VOID**)&pDSNotify ) ) )
|
|
SNDError( "Query DirectSoundNotify Object Failed!! ErrorCode : 0x%x", hr );
|
|
|
|
DSBPOSITIONNOTIFY* aPosNotify = new DSBPOSITIONNOTIFY[ dwNotifyCount ];
|
|
|
|
for( i = 0; i < dwNotifyCount; i++ )
|
|
{
|
|
aPosNotify[i].dwOffset = (m_dwNotifySize * i) + m_dwNotifySize - 1;
|
|
aPosNotify[i].hEventNotify = m_hNotificationEvent;
|
|
|
|
#ifdef DEBUG_LOG
|
|
log_file << "[NotificationPositions:" << m_pSoundFile->GetFilename() << "] Offset: "
|
|
<< aPosNotify[i].dwOffset << endl;
|
|
#endif
|
|
}
|
|
|
|
// Tell DirectSound when to notify us. The notification will come in the from
|
|
// of signaled events that are handled in WinMain()
|
|
if( FAILED( hr = pDSNotify->SetNotificationPositions( dwNotifyCount,
|
|
aPosNotify ) ) )
|
|
{
|
|
pDSNotify->Release();
|
|
delete [] aPosNotify;
|
|
SNDError( "SetNotificationPositions Failed!! ErrorCode : 0x%x", hr );
|
|
}
|
|
|
|
if( pDSNotify )
|
|
pDSNotify->Release();
|
|
delete [] aPosNotify;
|
|
|
|
if( b3DSound )
|
|
{
|
|
m_ap3DBuffer = new IDirectSound3DBuffer*[ dwNumBuffers ];
|
|
for( i = 0; i < dwNumBuffers; i++ )
|
|
{
|
|
m_apDSBuffer[i]->SetCurrentPosition(0);
|
|
m_ap3DBuffer[i] = Get3DBufferInterface( i );
|
|
}
|
|
}
|
|
|
|
FillBufferWithSound( m_apDSBuffer[0] );
|
|
|
|
m_dwLastPlayPos = 0;
|
|
m_dwPlayProgress = 0;
|
|
m_dwNextWriteOffset = 0;
|
|
m_bFillNextNotificationWithSilence = false;
|
|
|
|
#ifdef DEBUG_LOG
|
|
if( log_file.is_open() && m_pSoundFile != NULL )
|
|
log_file << "StreamBuffer Created! (" << m_pSoundFile->GetFilename() << ")\n";
|
|
#endif
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
|
|
void CStreamBuffer::Create( IDirectSound8 * pDSound, const char * szFilename, bool b3DSound, DWORD dwNumBuffers, DWORD dwAddFlag )
|
|
{
|
|
if( !IsFileExist2( szFilename ) )
|
|
{
|
|
SNDError( "%s : 화일을 열 수 없습니다.", szFilename );
|
|
}
|
|
|
|
ISoundFile * pSoundFile = ISoundFile::CreateSoundFileInstance( szFilename );
|
|
Create( pDSound, pSoundFile, b3DSound, dwNumBuffers, dwAddFlag );
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
|
|
void CStreamBuffer::Destroy()
|
|
{
|
|
CSoundBuffer::Destroy();
|
|
|
|
if( m_hNotificationEvent )
|
|
{
|
|
CloseHandle( m_hNotificationEvent );
|
|
m_hNotificationEvent = NULL;
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
|
|
int CStreamBuffer::Play( bool bLoopPlay )
|
|
{
|
|
m_bLoopPlay = bLoopPlay;
|
|
unsigned index = CSoundBuffer::Play( true );
|
|
|
|
if( index >= m_dwNumBuffers )
|
|
return index;
|
|
|
|
DWORD Pos;
|
|
m_apDSBuffer[0]->GetCurrentPosition( &Pos, NULL );
|
|
m_apDSBuffer[index]->SetCurrentPosition( Pos );
|
|
|
|
return index;
|
|
}
|
|
|
|
void CStreamBuffer::Play( DWORD dwIndex, bool bLoopPlay )
|
|
{
|
|
if( dwIndex >= m_dwNumBuffers )
|
|
return;
|
|
|
|
m_bLoopPlay = bLoopPlay;
|
|
|
|
CSoundBuffer::Play( dwIndex, true );
|
|
|
|
DWORD Pos;
|
|
m_apDSBuffer[dwIndex]->GetCurrentPosition( &Pos, NULL );
|
|
m_apDSBuffer[dwIndex]->SetCurrentPosition( Pos );
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
|
|
void CStreamBuffer::Reset( unsigned bufIndex )
|
|
{
|
|
ResetAll();
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
|
|
void CStreamBuffer::ResetAll()
|
|
{
|
|
assert( m_apDSBuffer[0] != NULL && m_pSoundFile != NULL );
|
|
|
|
m_dwLastPlayPos = 0;
|
|
m_dwPlayProgress = 0;
|
|
m_dwNextWriteOffset = 0;
|
|
m_bFillNextNotificationWithSilence = false;
|
|
|
|
if( Restore( m_apDSBuffer[0] ) )
|
|
{
|
|
FillBufferWithSound( m_apDSBuffer[0] );
|
|
}
|
|
|
|
m_pSoundFile->Reset();
|
|
|
|
HRESULT hr = m_apDSBuffer[0]->SetCurrentPosition( 0L );
|
|
if( FAILED( hr ) )
|
|
SNDError( "SetCurrentPosition(at CStreamBuffer::Reset) Failed!! ErrorCode : 0x%x", hr );
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
|
|
void CStreamBuffer::HandleNotification()
|
|
{
|
|
assert( m_apDSBuffer != NULL && m_pSoundFile != NULL );
|
|
|
|
//Play되고 있지 않은 것은 스킵한다.
|
|
DWORD dwStatus = 0;
|
|
m_apDSBuffer[0]->GetStatus( &dwStatus );
|
|
if( ( dwStatus & DSBSTATUS_PLAYING ) == 0 )
|
|
return;
|
|
|
|
if( Restore( m_apDSBuffer[0] ) )
|
|
{
|
|
FillBufferWithSound( m_apDSBuffer[0] );
|
|
return;
|
|
}
|
|
|
|
DWORD dwDSLockedBufferSize, dwDSLockedBufferSize2;
|
|
void *pDSLockedBuffer = NULL, *pDSLockedBuffer2 = NULL;
|
|
|
|
#ifdef DEBUG_LOG
|
|
DWORD dwPos;
|
|
m_apDSBuffer[0]->GetCurrentPosition( &dwPos, NULL );
|
|
char szBuf[1024];
|
|
sprintf( szBuf, "[HandleNotification:%s] NextWriteOffset : %u, Pos : %u, LastPlayPos : %u, NotifySize : %u\n",
|
|
m_pSoundFile->GetFilename(), m_dwNextWriteOffset, dwPos, m_dwLastPlayPos, m_dwNotifySize );
|
|
log_file << szBuf;
|
|
#endif
|
|
|
|
HRESULT hr = m_apDSBuffer[0]->Lock( m_dwNextWriteOffset, m_dwNotifySize,
|
|
&pDSLockedBuffer, &dwDSLockedBufferSize,
|
|
&pDSLockedBuffer2, &dwDSLockedBufferSize2, 0L );
|
|
|
|
if( FAILED( hr ) )
|
|
SNDError( "Stream SoundBuffer Lock Failed!! ErrorCode : 0x%x", hr );
|
|
|
|
if( pDSLockedBuffer2 != NULL )
|
|
SNDError( "Stream SoundBuffer Lock Failed2!! ErrorCode : 0x%x", E_UNEXPECTED );
|
|
|
|
DWORD dwBytesWrittenToBuffer = 0;
|
|
|
|
DWORD dwBitsPerSample = m_pSoundFile->GetBitsPerSample();
|
|
|
|
if( m_bFillNextNotificationWithSilence )
|
|
{
|
|
FillMemory( pDSLockedBuffer, dwDSLockedBufferSize,
|
|
(BYTE)( dwBitsPerSample == 8 ? 128 : 0 ) );
|
|
|
|
dwBytesWrittenToBuffer = dwDSLockedBufferSize;
|
|
}
|
|
else
|
|
{
|
|
dwBytesWrittenToBuffer = m_pSoundFile->Read( (BYTE*) pDSLockedBuffer, dwDSLockedBufferSize );
|
|
}
|
|
|
|
if( dwBytesWrittenToBuffer < dwDSLockedBufferSize )
|
|
{
|
|
if( m_bLoopPlay )
|
|
{
|
|
//반복을 해야 하기 때문에 나머지 버퍼를 웨이브 데이터로 계속 채운다.
|
|
DWORD dwReadSoFar = dwBytesWrittenToBuffer;
|
|
while( dwReadSoFar < dwDSLockedBufferSize )
|
|
{
|
|
//매우 짧은 화일일 경우 버퍼가 찰때까지 반복하며 읽어들인다.
|
|
m_pSoundFile->Reset();
|
|
dwBytesWrittenToBuffer = m_pSoundFile->Read( (BYTE*)pDSLockedBuffer + dwReadSoFar,
|
|
dwDSLockedBufferSize - dwReadSoFar );
|
|
dwReadSoFar += dwBytesWrittenToBuffer;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//나머지를 무음(silence)으로 채운다.
|
|
FillMemory( (BYTE*) pDSLockedBuffer + dwBytesWrittenToBuffer,
|
|
dwDSLockedBufferSize - dwBytesWrittenToBuffer,
|
|
(BYTE)( dwBitsPerSample == 8 ? 128 : 0 ) );
|
|
//이 이후에는 전부 무음으로 처리
|
|
m_bFillNextNotificationWithSilence = true;
|
|
}
|
|
}
|
|
|
|
m_apDSBuffer[0]->Unlock( pDSLockedBuffer, dwDSLockedBufferSize, NULL, 0 );
|
|
|
|
//이제까지 얼마나 플레이 되었는지 보고, 플레이 지점이 화일의 끝을 지났을 때
|
|
//m_bLoopPlay 플래그에 따라 버퍼를 무음으로 채우던지 화일의 처음부터 다시 읽어서
|
|
//복사를 하던지 한다.
|
|
DWORD dwCurrentPlayPos;
|
|
DWORD dwPlayDelta;
|
|
|
|
if( FAILED( hr = m_apDSBuffer[0]->GetCurrentPosition( &dwCurrentPlayPos, NULL ) ) )
|
|
SNDError( "GetCurrentPosition(at CStreamBuffer::HandleNotification) Failed!! ErrorCode : 0x%x", hr );
|
|
|
|
// Check to see if the position counter looped
|
|
if( dwCurrentPlayPos < m_dwLastPlayPos )
|
|
dwPlayDelta = ( m_dwDSBufferSize - m_dwLastPlayPos ) + dwCurrentPlayPos;
|
|
else
|
|
dwPlayDelta = dwCurrentPlayPos - m_dwLastPlayPos;
|
|
|
|
m_dwPlayProgress += dwPlayDelta;
|
|
m_dwLastPlayPos = dwCurrentPlayPos;
|
|
|
|
if( m_bFillNextNotificationWithSilence )
|
|
{
|
|
//끝에 도달하면 stop한다.
|
|
if( m_dwPlayProgress >= m_pSoundFile->GetSize() )
|
|
{
|
|
m_apDSBuffer[0]->Stop();
|
|
ResetAll();
|
|
}
|
|
}
|
|
|
|
m_dwNextWriteOffset += dwDSLockedBufferSize;
|
|
m_dwNextWriteOffset %= m_dwDSBufferSize; //버퍼가 순환하도록..
|
|
|
|
if( m_dwDSBufferSize - m_dwNotifySize < m_dwNextWriteOffset )
|
|
SNDError( "WriteOffset값이 잘못 계산 되었습니다.(CStreamBuffer::HandleNotification)" );
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|