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

///////////////////////////////////////////////////////////////////////////////////////////////////
//include files
#include	"iFace/iAudioDevice.h"

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

namespace icubic_audio
{
namespace mme
{
using namespace icubic;
///////////////////////////////////////////////////////////////////////////////////////////////////
// preprocessor deifne

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

///////////////////////////////////////////////////////////////////////////////////////////////////
// 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:
	HWAVEIN						m_hwi;
	uint32						m_deviceid;
	WAVEINCAPSW					m_caps;
	State						m_state;
	WAVEFORMATEX				m_wfx;
	int32						m_ctrlid;
	IAudioDeviceStream*			m_stream_ptr;
	
	// mme
	uint32						m_threadid;
	Array<uint8>				m_hwi_buffer;
	WAVEHDR						m_hwi_hdr[2];
	
	// thread
	CriticalSection				m_hwi_cs;
	HANDLE						m_event;
	Array<float>				m_buffer;
	Array<float*>				m_input_ptr;
	IAudioDeviceStream::Format	m_format;
	
	// const
	static	const uint32		m_bitspersample = 16;
	static	const uint32		m_init_msg		= ( WM_APP + 1 );
	static	const uint32		m_stop_msg		= ( WM_APP + 2 );

	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:
//=================================================================================================
void OnWIM_OPEN
		(
		HWAVEIN		hwi
		)
{
	MemoryZero( m_hwi_buffer.GetPtr() , m_hwi_buffer.GetDatanum() );
	uint32	index;
	for( index = 0 ; index < _countof( m_hwi_hdr ) ; index++ )
	{
		WAVEHDR*	phdr	= &m_hwi_hdr[index];
		phdr->dwFlags			= 0;
		phdr->dwBytesRecorded	= 0;
		cb_verify( MMSYSERR_NOERROR == waveInPrepareHeader( hwi , phdr , sizeof( WAVEHDR ) ) );
		cb_verify( MMSYSERR_NOERROR == waveInAddBuffer( hwi , phdr, sizeof( WAVEHDR ) ) );
	}
}
//=================================================================================================
void OnWIM_DATA
		(
		HWAVEIN		hwi , 
		WAVEHDR*	phdr
		)
{
	cb_verify( MMSYSERR_NOERROR == waveInUnprepareHeader( hwi , phdr , sizeof( WAVEHDR ) ) );
	cb_assert( phdr->dwBytesRecorded == phdr->dwBufferLength , L"dwBytesRecorded error" );
	uint32	samplenum = phdr->dwBytesRecorded / m_format.m_channel[0] / ( m_bitspersample / 8 );
	samplenum	= min( samplenum , m_format.m_buffersamples );
	if( m_stream_ptr != 0 )
	{
		Interleave_to_float
				(
				m_format.m_channel[0] , 
				samplenum , 
				Int16Align16LSB , 
				(uint8*)( phdr->lpData ) , 
				m_input_ptr.GetPtr()
				);
		m_stream_ptr->AudioDeviceStream( m_ctrlid , m_format , m_input_ptr.GetPtr() , 0 );
	}
	phdr->dwFlags			= 0;
	phdr->dwBytesRecorded	= 0;
	cb_verify( MMSYSERR_NOERROR == waveInPrepareHeader( hwi , phdr , sizeof( WAVEHDR ) ) );
	cb_verify( MMSYSERR_NOERROR == waveInAddBuffer( hwi , phdr, sizeof( WAVEHDR ) ) );
}
//=================================================================================================
void OnWIM_CLOSE
		(
		HWAVEIN		hwi
		)
{
	uint32	index;
	for( index = 0 ; index < _countof( m_hwi_hdr ) ; index++ )
		waveInUnprepareHeader( hwi , &m_hwi_hdr[index] , sizeof( WAVEHDR ) );
}
//=================================================================================================
int ThreadProc()
{
	::SetThreadPriority( GetCurrentThread() , 15 );

	cb_trace( L"ThreadPrc Enter\n" );

	while( true )
	{
		MSG		msg;
		::GetMessage( &msg , NULL , 0 , 0 );
		
		if( msg.message == m_init_msg )
			SetEvent( m_event );

		else if( msg.message == MM_WIM_OPEN )
		{
			Autolock	lock( m_hwi_cs );
			cb_trace( L"MM_WIM_OPEN\n" );
			OnWIM_OPEN( (HWAVEIN)msg.wParam );
		}
		else if( msg.message == MM_WIM_DATA )
		{
			Autolock	lock( m_hwi_cs );
			cb_trace( L"MM_WIM_DONE\n" );
			OnWIM_DATA( (HWAVEIN)msg.wParam , (LPWAVEHDR)msg.lParam );
		}
		else if( msg.message == m_stop_msg )
		{
			cb_trace( L"m_stop_msg\n" );
			OnWIM_CLOSE( m_hwi );
			break;
		}
	}

	cb_trace( L"ThreadPrc Exit\n" );
	return 0;
}

// private functions
private:
//=================================================================================================
void CreateMMEBuffer
		(
		const IAudioDevice::Format&	fmt
		)
{
	uint32	size = fmt.m_channel[0] * fmt.m_buffersamples * m_bitspersample / 8;
	m_hwi_buffer.Resize( size * 2 );
		
	int	off , num = _countof( m_hwi_hdr );
	for( off = 0 ; off < num ; off++ )
	{
		MemoryZero( &m_hwi_hdr[off] , sizeof( m_hwi_hdr[off] ) );
		m_hwi_hdr[off].lpData			= (LPSTR)&m_hwi_buffer[ off * size ];
		m_hwi_hdr[off].dwBufferLength	= size;
	}
}
//=================================================================================================
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;
	}
}
//=================================================================================================
WAVEFORMATEX GetWAVEFORMATEX
		(
		const IAudioDevice::Format&	fmt
		)
{
	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;
	return wfx;
}
// "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 wstring( m_caps.szPname );
}
//=================================================================================================
void cb_call GetChannelMaximum
		(
		uint32*		in_ch , 
		uint32*		out_ch
		)
{
	store( in_ch , (uint32)2 );
	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;
		
	WAVEFORMATEX	wfx	= GetWAVEFORMATEX( fmt );
	if( MMSYSERR_NOERROR != ::waveInOpen
				( 
				NULL , 
				m_deviceid , 
				&wfx ,
				0 , 
				0 , 
				WAVE_FORMAT_QUERY 
				) )
				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;
	
	CreateMMEBuffer( t_fmt );
	CreateStreamBuffer( t_fmt );

	m_wfx						= GetWAVEFORMATEX( 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;

	// thread
	ResetEvent( m_event );
	if( false == Thread::Create( &m_threadid ) )
		return false;

	// sync
	PostThreadMessage( m_threadid , m_init_msg , 0 , 0 );
	while( WAIT_OBJECT_0 != WaitForSingleObject( m_event , 500 ) )
	{
		PostThreadMessage( m_threadid , m_init_msg , 0 , 0 );
	}

	// open
	MMRESULT	hr;
	{
		Autolock	lock( m_hwi_cs );
		hr = waveInOpen
				( 
				&m_hwi , 
				m_deviceid , 
				&m_wfx , 
				( DWORD )m_threadid , 
				NULL , 
				CALLBACK_THREAD 
				);
	}
	if( hr != MMSYSERR_NOERROR )
	{
		::PostThreadMessage( m_threadid , m_stop_msg , 0 , 0 );
		Thread::Destroy();
		return false;
	}
	{
		Autolock	lock( m_hwi_cs );
		hr = ::waveInStart( m_hwi );
	}
	if( hr != MMSYSERR_NOERROR )
	{
		::PostThreadMessage( m_threadid , m_stop_msg , 0 , 0 );
		Thread::Destroy();
		if( m_hwi != NULL )
		{
			::waveInReset( m_hwi );
			::waveInClose( m_hwi );
		}
		return false;
	}
	m_state	= Play_State;
	return true;
}
//=================================================================================================
void cb_call Stop()
{
	if( m_state != Play_State )
		return;

	::PostThreadMessage( m_threadid , m_stop_msg , 0 , 0 );
	Thread::Destroy();
	if( m_hwi != NULL )
	{
		::waveInReset( m_hwi );
		::waveInClose( m_hwi );
	}
	m_hwi	= NULL;

	m_state	= Stop_State;
}
//=================================================================================================
void cb_call Destroy()
{
	if( m_state == Null_State )
		return;
	Stop();
	m_state		= Null_State;
}
// public functions
public:
//=================================================================================================
AudioDeviceCapture() : 
		m_state( Null_State ) , 
		m_ctrlid( 0 ) , 
		m_stream_ptr( 0 ) , 
		m_hwi( NULL ) , 
		m_deviceid( -1 ) , 
		m_threadid( 0 )
{
	m_event	= CreateEvent( NULL , FALSE , FALSE , NULL );
}
//=================================================================================================
~AudioDeviceCapture()
{
	Destroy();
	SafeCloseHandle( m_event );
}
//=================================================================================================
bool Initialize
		(
		uint32		id
		)
{
	cb_assert( m_deviceid == -1 , L"Initialize called twice." );
	
	WAVEINCAPSW	caps;
	if( MMSYSERR_NOERROR != ::waveInGetDevCapsW( id , &caps , sizeof( caps ) ) )
		return false;
	m_deviceid	= id;
	m_caps		= caps;
	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:
	HWAVEOUT					m_hwo;
	uint32						m_deviceid;
	WAVEOUTCAPSW				m_caps;
	State						m_state;
	WAVEFORMATEX				m_wfx;
	int32						m_ctrlid;
	IAudioDeviceStream*			m_stream_ptr;
	
	// mme
	uint32						m_threadid;
	Array<uint8>				m_hwo_buffer;
	WAVEHDR						m_hwo_hdr[2];
	
	// thread
	CriticalSection				m_hwo_cs;
	HANDLE						m_event;
	Array<float>				m_buffer;
	Array<float*>				m_output_ptr;
	IAudioDeviceStream::Format	m_format;
	
	// const
	static	const uint32		m_bitspersample = 16;
	static	const uint32		m_init_msg		= ( WM_APP + 1 );
	static	const uint32		m_stop_msg		= ( WM_APP + 2 );
	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:
//=================================================================================================
void OnWOM_OPEN
		(
		HWAVEOUT	hwo
		)
{
	MemoryZero( m_hwo_buffer.GetPtr() , m_hwo_buffer.GetDatanum() );
	uint32	index;
	for( index = 0 ; index < _countof( m_hwo_hdr ) ; index++ )
	{
		WAVEHDR*	phdr	= &m_hwo_hdr[index];
		if( m_stream_ptr != 0 )
		{
			m_stream_ptr->AudioDeviceStream( m_ctrlid , m_format , 0 , m_output_ptr.GetPtr() );
			float_to_Interleave
					(
					m_format.m_channel[1] , 
					m_format.m_buffersamples , 
					m_output_ptr.GetPtr() , 
					Int16Align16LSB , 
					(uint8*)( phdr->lpData ) 
					);
		}
		phdr->dwFlags			= 0;
		cb_verify( MMSYSERR_NOERROR == waveOutPrepareHeader( hwo , phdr , sizeof( WAVEHDR ) ) );
		cb_verify( MMSYSERR_NOERROR == waveOutWrite( hwo , phdr, sizeof( WAVEHDR ) ) );
	}
}
//=================================================================================================
void OnWOM_DONE
		(
		HWAVEOUT	hwo , 
		WAVEHDR*	phdr
		)
{
	cb_verify( MMSYSERR_NOERROR == waveOutUnprepareHeader( hwo , phdr , sizeof( WAVEHDR ) ) );
	if( m_stream_ptr != 0 )
	{
		m_stream_ptr->AudioDeviceStream( m_ctrlid , m_format , 0 , m_output_ptr.GetPtr() );
		float_to_Interleave
				(
				m_format.m_channel[1] , 
				m_format.m_buffersamples , 
				m_output_ptr.GetPtr() , 
				Int16Align16LSB , 
				(uint8*)( phdr->lpData ) 
				);
	}
	phdr->dwFlags			= 0;
	phdr->dwBytesRecorded	= 0;
	cb_verify( MMSYSERR_NOERROR == waveOutPrepareHeader( hwo , phdr , sizeof( WAVEHDR ) ) );
	cb_verify( MMSYSERR_NOERROR == waveOutWrite( hwo , phdr, sizeof( WAVEHDR ) ) );
}
//=================================================================================================
void OnWOM_CLOSE
		(
		HWAVEOUT	hwo
		)
{
	uint32	index;
	for( index = 0 ; index < _countof( m_hwo_hdr ) ; index++ )
		waveOutUnprepareHeader( hwo , &m_hwo_hdr[index] , sizeof( WAVEHDR ) );
}
//=================================================================================================
int ThreadProc()
{
	::SetThreadPriority( GetCurrentThread() , 15 );

	cb_trace( L"ThreadPrc Enter\n" );
	while( true )
	{
		MSG		msg;
		::GetMessage( &msg , NULL , 0 , 0 );
		
		if( msg.message == m_init_msg )
			SetEvent( m_event );
		else if( msg.message == MM_WOM_OPEN )
		{
			Autolock	lock( m_hwo_cs );
			cb_trace( L"MM_WOM_OPEN\n" );
			OnWOM_OPEN( (HWAVEOUT)msg.wParam );
		}
		else if( msg.message == MM_WOM_DONE )
		{
			Autolock	lock( m_hwo_cs );
			cb_trace( L"MM_WOM_DONE\n" );
			OnWOM_DONE( (HWAVEOUT)msg.wParam , (LPWAVEHDR)msg.lParam );
		}
		else if( msg.message == m_stop_msg )
		{
			cb_trace( L"m_stop_msg\n" );
			OnWOM_CLOSE( m_hwo );
			break;
		}
	}
	cb_trace( L"ThreadPrc Exit\n" );
	return 0;
}

// private functions
private:
//=================================================================================================
void CreateMMEBuffer
		(
		const IAudioDevice::Format&	fmt
		)
{
	uint32	size = fmt.m_channel[1] * fmt.m_buffersamples * m_bitspersample / 8;
	m_hwo_buffer.Resize( size * 2 );
		
	int	off , num = _countof( m_hwo_hdr );
	for( off = 0 ; off < num ; off++ )
	{
		MemoryZero( &m_hwo_hdr[off] , sizeof( m_hwo_hdr[off] ) );
		m_hwo_hdr[off].lpData			= (LPSTR)&m_hwo_buffer[ off * size ];
		m_hwo_hdr[off].dwBufferLength	= size;
	}
}
//=================================================================================================
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;
	}
}
//=================================================================================================
WAVEFORMATEX GetWAVEFORMATEX
		(
		const IAudioDevice::Format&	fmt
		)
{
	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;
	return wfx;
}
// "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 wstring( m_caps.szPname );
}
//=================================================================================================
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_channel[1] > 2 )
		return false;
	if( fmt.m_buffersamples != 0 && ( fmt.m_buffersamples < m_buffersamples_min || fmt.m_buffersamples > m_buffersamples_max ) )
		return false;
		
	WAVEFORMATEX	wfx	= GetWAVEFORMATEX( fmt );
	if( MMSYSERR_NOERROR != ::waveOutOpen
				( 
				NULL , 
				m_deviceid , 
				&wfx ,
				0 , 
				0 , 
				WAVE_FORMAT_QUERY 
				) )
				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;
	
	CreateMMEBuffer( t_fmt );
	CreateStreamBuffer( t_fmt );

	m_wfx						= GetWAVEFORMATEX( 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;

	// thread
	ResetEvent( m_event );
	if( false == Thread::Create( &m_threadid ) )
		return false;

	// sync
	PostThreadMessage( m_threadid , m_init_msg , 0 , 0 );
	while( WAIT_OBJECT_0 != WaitForSingleObject( m_event , 500 ) )
	{
		PostThreadMessage( m_threadid , m_init_msg , 0 , 0 );
	}

	// open
	MMRESULT	hr;
	{
		Autolock	lock( m_hwo_cs );
		hr = waveOutOpen
				( 
				&m_hwo , 
				m_deviceid , 
				&m_wfx , 
				( DWORD )m_threadid , 
				NULL , 
				CALLBACK_THREAD 
				);
	}		
	if( hr != MMSYSERR_NOERROR )
	{
		PostThreadMessage( m_threadid , m_stop_msg , 0 , 0 );
		Thread::Destroy();
		return false;
	}
	m_state	= Play_State;
	return true;
}
//=================================================================================================
void cb_call Stop()
{
	if( m_state != Play_State )
		return;

	::PostThreadMessage( m_threadid , m_stop_msg , 0 , 0 );
	Thread::Destroy();
	if( m_hwo != NULL )
	{
		::waveOutReset( m_hwo );
		::waveOutClose( m_hwo );
	}
	m_hwo	= NULL;

	m_state	= Stop_State;
}
//=================================================================================================
void cb_call Destroy()
{
	if( m_state == Null_State )
		return;
	Stop();
	m_state		= Null_State;
}
// public functions
public:
//=================================================================================================
AudioDeviceRender() : 
		m_state( Null_State ) , 
		m_ctrlid( 0 ) , 
		m_stream_ptr( 0 ) , 
		m_hwo( NULL ) , 
		m_deviceid( -1 ) , 
		m_threadid( 0 )
{
	m_event	= CreateEvent( NULL , FALSE , FALSE , NULL );
}
//=================================================================================================
~AudioDeviceRender()
{
	Destroy();
	SafeCloseHandle( m_event );
}
//=================================================================================================
bool Initialize
		(
		uint32		id
		)
{
	cb_assert( m_deviceid == -1 , L"Initialize called twice." );
	
	WAVEOUTCAPSW	caps;
	if( MMSYSERR_NOERROR != ::waveOutGetDevCapsW( id , &caps , sizeof( caps ) ) )
		return false;
	m_deviceid	= id;
	m_caps		= caps;
	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:
		bool		m_capture;
		wstring		m_name;
		uint32		m_id;
	};
	class DeviceId
	{
	public:
		wstring	m_name;
		bool	m_capture;
		bool Load
				(
				IStreamRead*	stream
				)
		{
			if( false == Load_utf16( stream , &m_name ) )
				return false;
			if( false == stream->Read( &m_capture , sizeof( m_capture ) , 1 , Little_EndianType ) )
				return false;
			return true;
		}
		bool Save
				(
				IStreamWrite*	stream
				)
		{
			if( false == Save_utf16( stream , m_name ) )
				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:
//=================================================================================================
guid GetEnumId()
{
	cb_guid_define( EnumId , 0xAF952CC6 , 0xA49542b3 , 0xBB8C1DEF , 0x37BCF2E0 );
	static
	EnumId	id;
	return id;	
}
// "IEnumAudioDevice" interface functions
public:
//=================================================================================================
void cb_call Enum()
{
	Reset();
	{
		uint32	devoff , devnum = ::waveInGetNumDevs();
		for( devoff = 0 ; devoff < devnum ; devoff++ )
		{
			WAVEINCAPSW	caps;
			if( MMSYSERR_NOERROR == ::waveInGetDevCapsW( devoff , &caps , sizeof( caps ) ) )
			{
				int	off = m_devices.Add();
				m_devices[ off ].m_name		= caps.szPname;
				m_devices[ off ].m_capture	= true;
				m_devices[ off ].m_id		= devoff;
			}
		}
	}
	{
		uint32	devoff , devnum = ::waveOutGetNumDevs();
		for( devoff = 0 ; devoff < devnum ; devoff++ )
		{
			WAVEOUTCAPSW	caps;
			if( MMSYSERR_NOERROR == ::waveOutGetDevCapsW( devoff , &caps , sizeof( caps ) ) )
			{
				int	off = m_devices.Add();
				m_devices[ off ].m_name		= caps.szPname;
				m_devices[ off ].m_capture	= false;
				m_devices[ off ].m_id		= devoff;
			}
		}
	}
}
//=================================================================================================
wstring cb_call GetDriverName()
{
	return wstring( L"Multi Media Extension" );
}
//=================================================================================================
bool cb_call GetAudioDeviceId
		(
		int32			devoff , 
		IAudioDeviceId*	id
		)
{
	if( devoff < 0 || devoff >= m_devices.GetDatanum() )
		return false;
	DeviceId	devid;
	devid.m_name	= m_devices[devoff].m_name;
	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_name == devid.m_name && 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 , 
		IEnumAudioDevice::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_id ) )
			return iAudioDevice();
		return (iAudioDevice)device;		
	}
	else
	{
		instance<AudioDeviceRender>	device;
		if( false == device->Initialize( m_devices[devoff].m_id ) )
			return iAudioDevice();
		return (iAudioDevice)device;		
	}
	return iAudioDevice();
}

// public functions
public:
//=================================================================================================
EnumAudioDevice()
{
}
//=================================================================================================
void Reset()
{
	m_devices.Resize( 0 );
}
};
///////////////////////////////////////////////////////////////////////////////////////////////////
// global variable define

///////////////////////////////////////////////////////////////////////////////////////////////////
// global functions define

};	//mme namespace
};	//namespace

//using namespace icubic;		

#pragma pack( pop )			//release align
