Files
Client/GameTools/SoundLib/StreamBuffer.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

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)" );
}
/////////////////////////////////////////////////////////////////////////////////////////