/*************************************************************************************************/
/*!
   	@file		AudioDevice_ds.h
	@author 	Fanzo
*/
/*************************************************************************************************/
#pragma		once

///////////////////////////////////////////////////////////////////////////////////////////////////
//include files
#include	"iFace/iAudioDevice.h"
#include	< dsound.h >

#pragma pack( push , 8 )		//set align

namespace icubic_audio
{
namespace ds
{
using namespace icubic;

///////////////////////////////////////////////////////////////////////////////////////////////////
// preprocessor deifne

///////////////////////////////////////////////////////////////////////////////////////////////////
// type define

///////////////////////////////////////////////////////////////////////////////////////////////////
// shared functions

///////////////////////////////////////////////////////////////////////////////////////////////////
// classes define

/**************************************************************************************************
"AudioDeviceCapture" class 
**************************************************************************************************/
class AudioDeviceCapture : 
	virtual public object_base , 
	public Thread , 
	public IAudioDevice
{
	query_begin();
	iface_hook( IAudioDevice , IAudioDevice_IID )
	query_end( object_base );
	
// variable member
private:
	LPDIRECTSOUNDCAPTURE8		m_ds;
	wstring						m_name;
//	DSCCAPS						m_dscaps;
	State						m_state;
	int32						m_ctrlid;
	IAudioDeviceStream*			m_stream_ptr;

	// thread
	Array<float>				m_buffer;
	Array<float*>				m_input_ptr;
	IAudioDeviceStream::Format	m_format;
	LPDIRECTSOUNDCAPTUREBUFFER8	m_dsb;
	HANDLE						m_events[3];
	
	// const
	static	const uint32		m_bitspersample			= 16;
	static	const uint32		m_buffersamples_min		= 1;
	static	const uint32		m_buffersamples_max		= 32 * 1024;
	static	const uint32		m_buffersamples_prefer	=  4 * 1024;

// thread functions
private:	
//=================================================================================================
int ThreadProc()
{
	::SetThreadPriority( GetCurrentThread() , 15 );

	// clear stream buffer
	MemoryZero( m_buffer.GetPtr() , m_buffer.GetDatanum() * sizeof( float ) );
	DWORD		buffersize	= m_format.m_channel[0] * m_format.m_buffersamples * m_bitspersample / 8;

	cb_trace( L"ThreadProc Enter\n" );
	while( true )
	{	
		int	evoff	= WaitForMultipleObjects( _countof( m_events ) , m_events , FALSE , INFINITE ) - WAIT_OBJECT_0;
		int	pkoff	= evoff - 1;
		if( evoff == 0 )
			break;
		if( 0 <= pkoff && pkoff < 2 && m_stream_ptr != 0 )
		{
			LPVOID	p[2];
			DWORD	bytes[2];
			if( true == LockDsBuffer( buffersize * pkoff , buffersize , p , bytes ) )
			{
				cb_assert( bytes[1] == 0 , L"buffer error." );
				if( bytes[1] == 0 )
					Interleave_to_float( m_format.m_channel[0] , m_format.m_buffersamples , Int16Align16LSB , (const uint8*)p[0] , m_input_ptr.GetPtr() );
				UnlockDsBuffer( p , bytes );
			}
			m_stream_ptr->AudioDeviceStream( m_ctrlid , m_format , m_input_ptr.GetPtr() , 0 );
			cb_trace( L"buffer %d\n" , pkoff );
		}		
	}
	cb_trace( L"ThreadProc Leave\n" );
	return 0;
}
// private functions
private:
//=================================================================================================
void CreateStreamBuffer
		(
		const IAudioDevice::Format&	fmt
		)
{
	m_buffer.Resize( fmt.m_channel[0] * fmt.m_buffersamples );
	m_input_ptr.Resize( fmt.m_channel[0] );

	uint32	ch;
	uint32	pos	= 0;
	for( ch = 0 ; ch < fmt.m_channel[0] ; ch++ )
	{
		m_input_ptr[ ch ]	= &m_buffer[ pos ];
		pos += fmt.m_buffersamples;
	}
}
//=================================================================================================
LPDIRECTSOUNDCAPTUREBUFFER8 CreateDsBuffer
		(
		LPDIRECTSOUNDCAPTURE8		ds , 
		const IAudioDevice::Format&	fmt , 
		HANDLE						events[3]
		)
{
	// create
	LPDIRECTSOUNDCAPTUREBUFFER8	dsb8	= NULL;
	WAVEFORMATEX				wfx; 
	{
		MemoryZero( &wfx , sizeof( wfx ) );
		wfx.wFormatTag		= WAVE_FORMAT_PCM;
		wfx.nChannels		= (WORD)fmt.m_channel[0]; 
		wfx.nSamplesPerSec	= fmt.m_samplespersec;
		wfx.wBitsPerSample	= m_bitspersample; 
		wfx.nBlockAlign		= wfx.nChannels * wfx.wBitsPerSample / 8; 
		wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
		
		DSCBUFFERDESC dsbdesc; 
		MemoryZero( &dsbdesc , sizeof( dsbdesc ) ) ;
		dsbdesc.dwSize			= sizeof( dsbdesc ); 
		dsbdesc.dwBufferBytes	= fmt.m_buffersamples * wfx.nBlockAlign * 2;
		dsbdesc.lpwfxFormat		= &wfx; 

		LPDIRECTSOUNDCAPTUREBUFFER		dsb		= NULL;
		if( FAILED( ds->CreateCaptureBuffer( &dsbdesc , &dsb , NULL ) ) )
			return 0;
		
		if( FAILED( dsb->QueryInterface( IID_IDirectSoundCaptureBuffer8 , (LPVOID*)&dsb8 ) ) )
		{
			com_release( dsb );
			return 0;
		}
		com_release( dsb );
	}
	// notify
	{
		LPDIRECTSOUNDNOTIFY8	dsnotify;
		if( FAILED( dsb8->QueryInterface( IID_IDirectSoundNotify8 , (LPVOID*)&dsnotify ) ) )
		{
			com_release( dsb8 );
			return 0;
		}
		DSBPOSITIONNOTIFY		notify[3];
		notify[0].dwOffset		= DSBPN_OFFSETSTOP;
		notify[0].hEventNotify	= events[0];
		notify[1].dwOffset		= fmt.m_buffersamples * wfx.nBlockAlign;
		notify[1].hEventNotify	= events[1];
		notify[2].dwOffset		= 0;
		notify[2].hEventNotify	= events[2];
		if( FAILED( dsnotify->SetNotificationPositions( _countof( notify ) , notify ) ) )
		{
			com_release( dsnotify );
			com_release( dsb8 );
			return 0;
		}
		com_release( dsnotify );
	}
	return dsb8;
}
//=================================================================================================
void ClearDsBuffer()
{
	LPVOID	p[2];
	DWORD	bytes[2];
	uint32	size	= m_format.m_channel[0] * m_format.m_buffersamples * m_bitspersample  / 8;
	if( false == LockDsBuffer( 0 , size * 2 , p , bytes ) )
		return;
	MemoryZero( p[0] , bytes[0] );
	MemoryZero( p[1] , bytes[1] );

	UnlockDsBuffer( p , bytes );
}
//=================================================================================================
bool LockDsBuffer
		(
		DWORD	offset , 
		DWORD	size , 
		LPVOID	p[2] , 
		DWORD	bytes[2]
		)
{
	HRESULT		hr = m_dsb->Lock( offset , size , &p[0] , &bytes[0] , &p[1] , &bytes[1] , 0 );
	if( FAILED( hr ) )
	{
		cb_trace( L"fail to LockBuffer\n" );
		return false;
	}
	return true;
}
//=================================================================================================
void UnlockDsBuffer
		(
		LPVOID	p[2] , 
		DWORD	bytes[2]
		)
{
	m_dsb->Unlock( p[0] , bytes[0] , p[1] , bytes[1] );
}

// "IAudioDevice" interface functions
public:
//=================================================================================================
bool cb_call SetAudioDeviceStream
		(
		int32				ctrlid , 
		IAudioDeviceStream*	stream
		)
{
	if( m_state == Play_State )
		return false;
	m_ctrlid		= ctrlid;
	m_stream_ptr	= stream;
	return true;
}
//=================================================================================================
wstring cb_call GetDeviceName()
{
	return m_name;
}
//=================================================================================================
void cb_call GetChannelMaximum
		(
		uint32*		in_ch , 
		uint32*		out_ch
		)
{
	store( in_ch , (uint32)2 );//m_dscaps.dwChannels );
	store( out_ch , (uint32)0 );
}
//=================================================================================================
bool cb_call GetBufferSamplesMaximum
		(
		uint32*		min , 
		uint32*		max , 
		uint32*		prefer
		)
{
	store( min , m_buffersamples_min );
	store( max , m_buffersamples_max );
	store( prefer , m_buffersamples_prefer );
	return true;
}
//=================================================================================================
bool cb_call IsFormatAvailable
		(
		const Format&	fmt
		)
{
	if( fmt.m_channel[1] != 0
	||  fmt.m_channel[0] == 0 )
		return false;
	if( fmt.m_channel[0] > 2 )
		return false;
	if( fmt.m_buffersamples != 0 && ( fmt.m_buffersamples < m_buffersamples_min || fmt.m_buffersamples > m_buffersamples_max ) )
		return false;
	if( fmt.m_samplespersec < 1 )
		return false;
/*
	if( fmt.m_samplespersec != 11025
	&&  fmt.m_samplespersec != 22050
	&&  fmt.m_samplespersec != 44100
	&&  fmt.m_samplespersec != 96000 )
		return false;
	if( fmt.m_channel[0] == 1 && fmt.m_samplespersec == 11025 && 0 == ( m_dscaps.dwFormats & WAVE_FORMAT_1M16 ) )
		return false;
	if( fmt.m_channel[0] == 2 && fmt.m_samplespersec == 11025 && 0 == ( m_dscaps.dwFormats & WAVE_FORMAT_1S16 ) )
		return false;
	if( fmt.m_channel[0] == 1 && fmt.m_samplespersec == 22050 && 0 == ( m_dscaps.dwFormats & WAVE_FORMAT_2M16 ) )
		return false;
	if( fmt.m_channel[0] == 2 && fmt.m_samplespersec == 22050 && 0 == ( m_dscaps.dwFormats & WAVE_FORMAT_2S16 ) )
		return false;
	if( fmt.m_channel[0] == 1 && fmt.m_samplespersec == 44100 && 0 == ( m_dscaps.dwFormats & WAVE_FORMAT_4M16 ) )
		return false;
	if( fmt.m_channel[0] == 2 && fmt.m_samplespersec == 44100 && 0 == ( m_dscaps.dwFormats & WAVE_FORMAT_4S16 ) )
		return false;
	if( fmt.m_channel[0] == 1 && fmt.m_samplespersec == 96000 && 0 == ( m_dscaps.dwFormats & WAVE_FORMAT_96M16 ) )
		return false;
	if( fmt.m_channel[0] == 2 && fmt.m_samplespersec == 96000 && 0 == ( m_dscaps.dwFormats & WAVE_FORMAT_96S16 ) )
		return false;
*/
	return true;
}
//=================================================================================================
bool cb_call Create
		(
		const Format&				fmt , 
		IAudioDeviceStream::Format*	sfmt
		)
{
	Destroy();
	
	Format	t_fmt	= fmt;
	if( t_fmt.m_buffersamples == 0 )
		t_fmt.m_buffersamples	= m_buffersamples_prefer;
	if( false == IsFormatAvailable( t_fmt ) )
		return false;
	
	m_dsb	= CreateDsBuffer( m_ds , t_fmt , m_events );
	if( m_dsb == 0 )
		return false;
	CreateStreamBuffer( t_fmt );

	m_format.m_channel[0]		= t_fmt.m_channel[0];
	m_format.m_channel[1]		= t_fmt.m_channel[1];
	m_format.m_samplespersec	= t_fmt.m_samplespersec;
	m_format.m_buffersamples	= t_fmt.m_buffersamples;

	m_state	= Stop_State;
	store( sfmt , m_format );
	return true;
}
//=================================================================================================
bool cb_call GetLatency
		(
		uint32*		input_ms , 
		uint32*		output_ms
		)
{
	if( m_state == Null_State )
		return false;
	long	in	= m_format.m_channel[0] == 0 ? 0 : m_format.m_buffersamples * 1000 / m_format.m_samplespersec;
	long	out	= m_format.m_channel[1] == 0 ? 0 : m_format.m_buffersamples * 1000 / m_format.m_samplespersec;
	store( input_ms , (uint32)in );
	store( output_ms , (uint32)out );
	return true;
}
//=================================================================================================
State cb_call GetState()
{
	return m_state;
}
//=================================================================================================
bool cb_call Play()
{
	if( m_state == Play_State )
		return true;
	if( m_state != Stop_State )
		return false;

	ClearDsBuffer();
	ResetEvent( m_events[ 0 ] );
	ResetEvent( m_events[ 1 ] );
	ResetEvent( m_events[ 2 ] );
	if( false == Thread::Create() )
		return false;

	if( FAILED( m_dsb->Start( DSCBSTART_LOOPING ) ) )
	{
		SetEvent( m_events[0] );
		Thread::Destroy();
		return false;
	}

	m_state	= Play_State;
	return true;
}
//=================================================================================================
void cb_call Stop()
{
	if( m_state != Play_State )
		return;

	if( FAILED( m_dsb->Stop() ) )
		SetEvent( m_events[0] );
	Thread::Destroy();
	m_state	= Stop_State;
}
//=================================================================================================
void cb_call Destroy()
{
	if( m_state == Null_State )
		return;
	Stop();
	com_release( m_dsb );
	
	m_state	= Null_State;
}
// public functions
public:
//=================================================================================================
AudioDeviceCapture() : 
		m_ds( NULL ) , 
		m_dsb( NULL ) , 
		m_state( Null_State ) , 
		m_ctrlid( 0 ) , 
		m_stream_ptr( 0 )
{
	m_events[0]	= CreateEvent( NULL , FALSE , FALSE , NULL );
	m_events[1]	= CreateEvent( NULL , FALSE , FALSE , NULL );
	m_events[2]	= CreateEvent( NULL , FALSE , FALSE , NULL );
}
//=================================================================================================
~AudioDeviceCapture()
{
	Destroy();
	SafeCloseHandle( m_events[0] );
	SafeCloseHandle( m_events[1] );
	SafeCloseHandle( m_events[2] );
	com_release( m_dsb );
	com_release( m_ds );
}
//=================================================================================================
bool Initialize
		(
		const GUID&		clsid , 
		const wchar_t*	name
		)
{
	cb_assert( m_ds == NULL , L"Initialize called twice." );
	
	LPDIRECTSOUNDCAPTURE8	ds	= NULL;
	if( FAILED( ::DirectSoundCaptureCreate8( &clsid , &ds , NULL ) ) )
		return false;
/*
	DSCCAPS	dscaps;
	MemoryZero( &dscaps , sizeof( dscaps ) );
	dscaps.dwSize = sizeof( dscaps );
	if( FAILED( ds->GetCaps( &dscaps ) ) )
	{
		com_release( ds );
		return false;
	}
*/
	m_ds		= ds;
//	m_dscaps	= dscaps;
	m_name		= name;
	return true;
}
};
/**************************************************************************************************
"AudioDeviceRender" class 
**************************************************************************************************/
class AudioDeviceRender : 
	virtual public object_base , 
	public Thread , 
	public IAudioDevice
{
	query_begin();
	iface_hook( IAudioDevice , IAudioDevice_IID )
	query_end( object_base );
	
// variable member
private:
	LPDIRECTSOUND8				m_ds;
	wstring						m_name;
//	DSCAPS						m_dscaps;
	State						m_state;
	int32						m_ctrlid;
	IAudioDeviceStream*			m_stream_ptr;

	// thread
	Array<float>				m_buffer;
	Array<float*>				m_output_ptr;
	IAudioDeviceStream::Format	m_format;
	LPDIRECTSOUNDBUFFER8		m_dsb;
	HANDLE						m_events[3];
	
	// const
	static	const uint32		m_bitspersample			= 16;
	static	const uint32		m_buffersamples_min		= 1;
	static	const uint32		m_buffersamples_max		= 32 * 1024;
	static	const uint32		m_buffersamples_prefer	=  4 * 1024;
	
// thread functions
private:	
//=================================================================================================
int ThreadProc()
{
	::SetThreadPriority( GetCurrentThread() , 15 );

	// clear stream buffer
	MemoryZero( m_buffer.GetPtr() , m_buffer.GetDatanum() * sizeof( float ) );
	DWORD		buffersize	= m_format.m_channel[1] * m_format.m_buffersamples * m_bitspersample / 8;

	cb_trace( L"ThreadProc Enter\n" );
	while( true )
	{	
		int	evoff	= WaitForMultipleObjects( _countof( m_events ) , m_events , FALSE , INFINITE ) - WAIT_OBJECT_0;
		int	pkoff	= evoff - 1;
		if( evoff == 0 )
			break;
		if( 0 <= pkoff && pkoff < 2 && m_stream_ptr != 0 )
		{
			m_stream_ptr->AudioDeviceStream( m_ctrlid , m_format , 0 , m_output_ptr.GetPtr() );
			LPVOID	p[2];
			DWORD	bytes[2];
			if( true == LockDsBuffer( buffersize * pkoff , buffersize , p , bytes ) )
			{
				cb_assert( bytes[1] == 0 , L"buffer error." );
				if( bytes[1] == 0 )
					float_to_Interleave( m_format.m_channel[1] , m_format.m_buffersamples , m_output_ptr.GetPtr() , Int16Align16LSB , (uint8*)p[0] );

				UnlockDsBuffer( p , bytes );
			}
			cb_trace( L"buffer %d\n" , pkoff );
		}		
	}
	cb_trace( L"ThreadProc Leave\n" );
	return 0;
}
// private functions
private:
//=================================================================================================
void CreateStreamBuffer
		(
		const IAudioDevice::Format&	fmt
		)
{
	m_buffer.Resize( fmt.m_channel[1] * fmt.m_buffersamples );
	m_output_ptr.Resize( fmt.m_channel[1] );

	uint32	ch;
	uint32	pos	= 0;
	for( ch = 0 ; ch < fmt.m_channel[1] ; ch++ )
	{
		m_output_ptr[ ch ]	= &m_buffer[ pos ];
		pos += fmt.m_buffersamples;
	}
}
//=================================================================================================
LPDIRECTSOUNDBUFFER8 CreateDsBuffer
		(
		LPDIRECTSOUND8				ds , 
		const IAudioDevice::Format&	fmt , 
		HANDLE						events[3]
		)
{
	// create
	LPDIRECTSOUNDBUFFER8	dsb8	= NULL;
	WAVEFORMATEX			wfx; 
	{
		MemoryZero( &wfx , sizeof( wfx ) );
		wfx.wFormatTag		= WAVE_FORMAT_PCM;
		wfx.nChannels		= (WORD)fmt.m_channel[1]; 
		wfx.nSamplesPerSec	= fmt.m_samplespersec;
		wfx.wBitsPerSample	= m_bitspersample; 
		wfx.nBlockAlign		= wfx.nChannels * wfx.wBitsPerSample / 8; 
		wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
		
		DSBUFFERDESC dsbdesc; 
		MemoryZero( &dsbdesc , sizeof( dsbdesc ) ) ;
		dsbdesc.dwSize			= sizeof( dsbdesc ); 
		dsbdesc.dwFlags			= DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GLOBALFOCUS;;
		dsbdesc.dwBufferBytes	= fmt.m_buffersamples * wfx.nBlockAlign * 2;
		dsbdesc.lpwfxFormat		= &wfx; 

		LPDIRECTSOUNDBUFFER		dsb		= NULL;
		if( FAILED( ds->CreateSoundBuffer( &dsbdesc , &dsb , NULL ) ) )
			return 0;
		
		if( FAILED( dsb->QueryInterface( IID_IDirectSoundBuffer8 , (LPVOID*)&dsb8 ) ) )
		{
			com_release( dsb );
			return 0;
		}
		com_release( dsb );
	}
	// notify
	{
		LPDIRECTSOUNDNOTIFY8	dsnotify;
		if( FAILED( dsb8->QueryInterface( IID_IDirectSoundNotify8 , (LPVOID*)&dsnotify ) ) )
		{
			com_release( dsb8 );
			return 0;
		}
		DSBPOSITIONNOTIFY		notify[3];
		notify[0].dwOffset		= DSBPN_OFFSETSTOP;
		notify[0].hEventNotify	= events[0];
		notify[1].dwOffset		= fmt.m_buffersamples * wfx.nBlockAlign;
		notify[1].hEventNotify	= events[1];
		notify[2].dwOffset		= 0;
		notify[2].hEventNotify	= events[2];
		if( FAILED( dsnotify->SetNotificationPositions( _countof( notify ) , notify ) ) )
		{
			com_release( dsnotify );
			com_release( dsb8 );
			return 0;
		}
		com_release( dsnotify );
	}
	return dsb8;
}
//=================================================================================================
bool LockDsBuffer
		(
		DWORD	offset , 
		DWORD	size , 
		LPVOID	p[2] , 
		DWORD	bytes[2]
		)
{
	HRESULT		hr = m_dsb->Lock( offset , size , &p[0] , &bytes[0] , &p[1] , &bytes[1] , 0 );
	if( hr == DSERR_BUFFERLOST )
	{
		m_dsb->Restore();
		hr = m_dsb->Lock( offset , size , &p[0] , &bytes[0] , &p[1] , &bytes[1] , 0 );
	}
	if( FAILED( hr ) )
	{
		cb_trace( L"fail to LockBuffer\n" );
		return false;
	}
	return true;
}
//=================================================================================================
void UnlockDsBuffer
		(
		LPVOID	p[2] , 
		DWORD	bytes[2]
		)
{
	m_dsb->Unlock( p[0] , bytes[0] , p[1] , bytes[1] );
}
//=================================================================================================
void ClearDsBuffer()
{
	LPVOID	p[2];
	DWORD	bytes[2];
	uint32	size	= m_format.m_channel[1] * m_format.m_buffersamples * m_bitspersample  / 8;
	if( false == LockDsBuffer( 0 , size * 2 , p , bytes ) )
		return;
	MemoryZero( p[0] , bytes[0] );
	MemoryZero( p[1] , bytes[1] );

	UnlockDsBuffer( p , bytes );
}
// "IAudioDevice" interface functions
public:
//=================================================================================================
bool cb_call SetAudioDeviceStream
		(
		int32				ctrlid , 
		IAudioDeviceStream*	stream
		)
{
	if( m_state == Play_State )
		return false;
	m_ctrlid		= ctrlid;
	m_stream_ptr	= stream;
	return true;
}
//=================================================================================================
wstring cb_call GetDeviceName()
{
	return m_name;
}
//=================================================================================================
void cb_call GetChannelMaximum
		(
		uint32*		in_ch , 
		uint32*		out_ch
		)
{
	store( in_ch , (uint32)0 );
	store( out_ch , (uint32)2 );
}
//=================================================================================================
bool cb_call GetBufferSamplesMaximum
		(
		uint32*		min , 
		uint32*		max , 
		uint32*		prefer
		)
{
	store( min , m_buffersamples_min );
	store( max , m_buffersamples_max );
	store( prefer , m_buffersamples_prefer );
	return true;
}
//=================================================================================================
bool cb_call IsFormatAvailable
		(
		const Format&	fmt
		)
{
	if( fmt.m_channel[0] != 0
	||  fmt.m_channel[1] == 0 )
		return false;
	if( fmt.m_buffersamples != 0 && ( fmt.m_buffersamples < m_buffersamples_min || fmt.m_buffersamples > m_buffersamples_max ) )
		return false;
	if( fmt.m_samplespersec < 1 )
		return false;
//	if( fmt.m_samplespersec < m_dscaps.dwMinSecondarySampleRate
//	||  fmt.m_samplespersec > m_dscaps.dwMaxSecondarySampleRate )
//		return false;
	return true;
}
//=================================================================================================
bool cb_call Create
		(
		const Format&				fmt , 
		IAudioDeviceStream::Format*	sfmt
		)
{
	Destroy();
	
	Format	t_fmt = fmt;
	if( t_fmt.m_buffersamples == 0 )
		t_fmt.m_buffersamples	= m_buffersamples_prefer;
	if( false == IsFormatAvailable( t_fmt ) )
		return false;
	
	m_dsb	= CreateDsBuffer( m_ds , t_fmt , m_events );
	if( m_dsb == 0 )
		return false;
	CreateStreamBuffer( t_fmt );

	m_format.m_channel[0]		= t_fmt.m_channel[0];
	m_format.m_channel[1]		= t_fmt.m_channel[1];
	m_format.m_samplespersec	= t_fmt.m_samplespersec;
	m_format.m_buffersamples	= t_fmt.m_buffersamples;

	m_state	= Stop_State;
	store( sfmt , m_format );
	return true;
}
//=================================================================================================
bool cb_call GetLatency
		(
		uint32*		input_ms , 
		uint32*		output_ms
		)
{
	if( m_state == Null_State )
		return false;
	long	in	= m_format.m_channel[0] == 0 ? 0 : m_format.m_buffersamples * 1000 / m_format.m_samplespersec;
	long	out	= m_format.m_channel[1] == 0 ? 0 : m_format.m_buffersamples * 1000 / m_format.m_samplespersec;
	store( input_ms , (uint32)in );
	store( output_ms , (uint32)out );
	return true;
}
//=================================================================================================
State cb_call GetState()
{
	return m_state;
}
//=================================================================================================
bool cb_call Play()
{
	if( m_state == Play_State )
		return true;
	if( m_state != Stop_State )
		return false;

	ClearDsBuffer();
	ResetEvent( m_events[ 0 ] );
	ResetEvent( m_events[ 1 ] );
	ResetEvent( m_events[ 2 ] );
	if( false == Thread::Create() )
		return false;

	m_dsb->SetCurrentPosition( 0 );
	if( FAILED( m_dsb->Play( 0 , 0 , DSBPLAY_LOOPING ) ) )
	{
		SetEvent( m_events[0] );
		Thread::Destroy();
		return false;
	}

	m_state	= Play_State;
	return true;
}
//=================================================================================================
void cb_call Stop()
{
	if( m_state != Play_State )
		return;

	if( FAILED( m_dsb->Stop() ) )
		SetEvent( m_events[0] );
	Thread::Destroy();
	m_state	= Stop_State;
}
//=================================================================================================
void cb_call Destroy()
{
	if( m_state == Null_State )
		return;
	Stop();
	com_release( m_dsb );
	m_state	= Null_State;
}
// public functions
public:
//=================================================================================================
AudioDeviceRender() : 
		m_ds( NULL ) , 
		m_dsb( NULL ) , 
		m_state( Null_State ) , 
		m_ctrlid( 0 ) , 
		m_stream_ptr( 0 )
{
	m_events[0]	= CreateEvent( NULL , FALSE , FALSE , NULL );
	m_events[1]	= CreateEvent( NULL , FALSE , FALSE , NULL );
	m_events[2]	= CreateEvent( NULL , FALSE , FALSE , NULL );
}
//=================================================================================================
~AudioDeviceRender()
{
	Destroy();
	SafeCloseHandle( m_events[0] );
	SafeCloseHandle( m_events[1] );
	SafeCloseHandle( m_events[2] );
	com_release( m_dsb );
	com_release( m_ds );
}
//=================================================================================================
bool Initialize
		(
		const GUID&		clsid , 
		const wchar_t*	name , 
		wndptr			owner
		)
{
	cb_assert( m_ds == NULL , L"Initialize called twice." );
	
	LPDIRECTSOUND8	ds	= NULL;
	if( FAILED( ::DirectSoundCreate8( &clsid , &ds , NULL ) ) )
		return false;

	if( FAILED( ds->SetCooperativeLevel( owner , DSSCL_PRIORITY ) ) )
	{
		com_release( ds );
		return false;
	}
/*	
	DSCAPS	dscaps;
	MemoryZero( &dscaps , sizeof( dscaps ) );
	dscaps.dwSize = sizeof( dscaps );
	if( FAILED( ds->GetCaps( &dscaps ) ) )
	{
		com_release( ds );
		return false;
	}
*/
	m_ds		= ds;
//	m_dscaps	= dscaps;
	m_name		= name;
	return true;
}
};
/**************************************************************************************************
"EnumAudioDevice" class 
**************************************************************************************************/
class EnumAudioDevice : 
	virtual public object_base , 
	public IEnumAudioDevice
{
	query_begin();
	iface_hook( IEnumAudioDevice , IEnumAudioDevice_IID )
	query_end( object_base );
	
	class DeviceData
	{
	public:
		GUID		m_clsid;
		wstring		m_name;
		bool		m_capture;
	};
	class DeviceId
	{
	public:
		guid	m_clsid;
		bool	m_capture;
		bool Load
				(
				IStreamRead*	stream
				)
		{
			if( false == icubic::Load( stream , &m_clsid ) )
				return false;
			if( false == stream->Read( &m_capture , sizeof( m_capture ) , 1 , Little_EndianType ) )
				return false;
			return true;
		}
		bool Save
				(
				IStreamWrite*	stream
				)
		{
			if( false == icubic::Save( stream , m_clsid ) )
				return false;
			if( false == stream->Write( &m_capture , sizeof( m_capture ) , 1 , Little_EndianType ) )
				return false;
			return true;
		}
	};

// variable member
private:
	Straightdata<DeviceData>	m_devices;
	
// private functions
private:
//=================================================================================================
static
BOOL CALLBACK DSEnumCaptureProcStatic
		(
		LPGUID		guid , 
		LPCTSTR		desc , 
		LPCTSTR		name , 
		LPVOID		data
		)
{
	return ((EnumAudioDevice*)data)->DSEnumCaptureProc( guid , desc , name );
}
//=================================================================================================
static
BOOL CALLBACK DSEnumRenderProcStatic
		(
		LPGUID		guid , 
		LPCTSTR		desc , 
		LPCTSTR		name , 
		LPVOID		data
		)
{
	return ((EnumAudioDevice*)data)->DSEnumRenderProc( guid , desc , name );
}
//=================================================================================================
BOOL DSEnumCaptureProc
		(
		LPGUID		clsid , 
		LPCTSTR		desc , 
		LPCTSTR		name
		)
{
	cb_trace( L"name = %s\n" , name );
	cb_trace( L"desc = %s\n" , desc );
	
	uint32	off = m_devices.Add();
	m_devices[off].m_name	= desc;
	m_devices[off].m_clsid	= (clsid == 0) ? GUID_NULL : *clsid;
	m_devices[off].m_capture= true;
	return TRUE;
}
//=================================================================================================
BOOL DSEnumRenderProc
		(
		LPGUID		clsid , 
		LPCTSTR		desc , 
		LPCTSTR		name
		)
{
	cb_trace( L"name = %s\n" , name );
	cb_trace( L"desc = %s\n" , desc );
	
	uint32	off = m_devices.Add();
	m_devices[off].m_name	= desc;
	m_devices[off].m_clsid	= (clsid == 0) ? GUID_NULL : *clsid;
	m_devices[off].m_capture= false;
	return TRUE;
}
//=================================================================================================
guid GetEnumId()
{
	cb_guid_define( EnumId , 0xB6C250D0 , 0xEB524f1a , 0x811BCCA2 , 0x283F065C );
	static
	EnumId	id;
	return id;	
}
// "IEnumAudioDevice" interface functions
public:
//=================================================================================================
void cb_call Enum()
{
	Reset();
	::DirectSoundCaptureEnumerate( ( LPDSENUMCALLBACK )DSEnumCaptureProcStatic , this );
	::DirectSoundEnumerate( ( LPDSENUMCALLBACK )DSEnumRenderProcStatic , this );
}
//=================================================================================================
wstring cb_call GetDriverName()
{
	return wstring( L"Direct Sound" );
}
// "IEnumAudioDeviceCreate" interface functions
public:
//=================================================================================================
bool cb_call GetAudioDeviceId
		(
		int32			devoff , 
		IAudioDeviceId*	id
		)
{
	if( devoff < 0 || devoff >= m_devices.GetDatanum() )
		return false;
	DeviceId	devid;
	devid.m_clsid	= m_devices[devoff].m_clsid;
	devid.m_capture	= m_devices[devoff].m_capture;

	instance<MemStreamWrite>	mem;
	mem->Open();
	{
		if( false == Save( (iStreamWrite)mem , GetEnumId() ) )
			return false;
		if( false == devid.Save( (iStreamWrite)mem ) )
			return false;
	}
	id->SetData( mem->GetDataPtr() , mem->GetDataSize() );
	return true;
}
//=================================================================================================
bool cb_call SearchDevice
		(
		int32*			devoff , 
		IAudioDeviceId*	id
		)
{
	// get data
	DeviceId	devid;
	{
		instance<MemStreamRead>	mem;
		mem->Open( id->GetDataPtr() , id->GetDataSize() );
		guid	enumid;
		if( false == Load( (iStreamRead)mem , &enumid ) )
			return false;
		if( enumid != GetEnumId() ) 
			return false;
		if( false == devid.Load( (iStreamRead)mem ) )
			return false;
	}
	// search
	int32	off , num = m_devices.GetDatanum();
	for( off = 0 ; off < num ; off++ )
	{
		if( m_devices[off].m_clsid == devid.m_clsid && m_devices[off].m_capture == devid.m_capture )
		{
			store( devoff , off );
			return true;
		}
	}
	return false;
}
//=================================================================================================
int32 cb_call GetDeviceNum()
{
	return m_devices.GetDatanum();
}
//=================================================================================================
bool cb_call GetDeviceInfo
		(
		int32		devoff , 
		DeviceInfo*	info
		)
{
	if( devoff < 0 || devoff >= m_devices.GetDatanum() )
		return false;
	if( info == 0 )
		return true;
	info->m_name	= m_devices[ devoff ].m_name;
	info->m_type	= m_devices[ devoff ].m_capture == true ? Input : Output;
	return true;
}
//=================================================================================================
iAudioDevice cb_call CreateDevice
		(
		int32		devoff , 
		wndptr		owner
		)
{
	if( devoff < 0 || devoff >= m_devices.GetDatanum() )
		return iAudioDevice();
	if( m_devices[ devoff ].m_capture == true )
	{
		instance<AudioDeviceCapture>	device;
		if( false == device->Initialize( m_devices[devoff].m_clsid , m_devices[devoff].m_name.c_str() ) )
			return iAudioDevice();
		return (iAudioDevice)device;		
	}
	else
	{
		instance<AudioDeviceRender>	device;
		if( false == device->Initialize( m_devices[devoff].m_clsid , m_devices[devoff].m_name.c_str() , owner ) )
			return iAudioDevice();
		return (iAudioDevice)device;		
	}
}
// public functions
public:
//=================================================================================================
EnumAudioDevice()
{
}
//=================================================================================================
void Reset()
{
	m_devices.Resize( 0 );
}
};
///////////////////////////////////////////////////////////////////////////////////////////////////
// global variable define

///////////////////////////////////////////////////////////////////////////////////////////////////
// global functions define
};	// namespace ds
};	//namespace

//using namespace icubic;		

#pragma pack( pop )			//release align
