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

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

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

namespace icubic_audio
{
namespace asio
{
using namespace icubic;

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

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

///////////////////////////////////////////////////////////////////////////////////////////////////
// shared function

///////////////////////////////////////////////////////////////////////////////////////////////////
// shared functions
//=================================================================================================
cb_inline
int32 ASIOSampleTypeToBufferSampleFormat
		(
		ASIOSampleType		type
		)
{
	static
	const int32	tbl[] = 
	{
		Int16Align16MSB ,		//ASIOSTInt16MSB   = 0,
		Int24Align24MSB ,		//ASIOSTInt24MSB   = 1,
		Int32Align32MSB ,		//ASIOSTInt32MSB   = 2,
		Float32Align32MSB ,		//ASIOSTFloat32MSB = 3,
		Float64Align64MSB ,		//ASIOSTFloat64MSB = 4,
		-1 , -1 , -1 , 
		Int16Align32MSB ,		//ASIOSTInt32MSB16 = 8,
		Int18Align32MSB ,		//ASIOSTInt32MSB18 = 9,
		Int20Align32MSB ,		//ASIOSTInt32MSB20 = 10,
		Int24Align32MSB ,		//ASIOSTInt32MSB24 = 11,
		-1 , -1 , -1 , -1 , 
		Int16Align16LSB ,		//ASIOSTInt16LSB   = 16,
		Int24Align24LSB ,		//ASIOSTInt24LSB   = 17,
		Int32Align32LSB ,		//ASIOSTInt32LSB   = 18,
		Float32Align32LSB ,		//ASIOSTFloat32LSB = 19,
		Float64Align64LSB ,		//ASIOSTFloat64LSB = 20,
		-1 , -1 , -1 , 
		Int16Align32LSB ,		//ASIOSTInt32LSB16 = 24,
		Int18Align32LSB ,		//ASIOSTInt32LSB18 = 25,
		Int20Align32LSB ,		//ASIOSTInt32LSB20 = 26,
		Int24Align32LSB ,		//ASIOSTInt32LSB24 = 27,
		-1 , -1 , -1 , -1 , 
		-1 ,					//ASIOSTDSDInt8LSB1 = 32,
		-1 ,					//ASIOSTDSDInt8MSB1 = 33,
		-1 , 					//ASIOSTDSDInt8NER8	= 40,
	};
	if( type < 0 || type >= _countof(tbl) )
		return -1;
	return tbl[type];	
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// classes define

/**************************************************************************************************
"AudioDevice" class 
**************************************************************************************************/
class AudioDevice : 
	virtual public object_base , 
	public IAudioDeviceAsio , 
	public IAsioCallback
{
	query_begin();
	iface_hook( IAudioDevice , IAudioDevice_IID )
	iface_hook( IAudioDeviceAsio , IAudioDeviceAsio_IID )
	query_end( object_base );
	
// variable member
private:
	instance<Asio>				m_asio;
	State						m_state;
	int32						m_ctrlid;
	IAudioDeviceStream*			m_stream_ptr;
		
	// thread
	IAudioDeviceStream::Format	m_format;
	uint32						m_input_channel;
	uint32						m_output_channel;
	bool						m_post_output;
	Array<float>				m_buffer;
	Array<float*>				m_input_ptr;
	Array<float*>				m_output_ptr;
	
// "IAsioCallback" interface functions
private:
//=================================================================================================
ASIOTime* BufferSwitchTimeInfo
		(
		uint32					input_channel , 
		uint32					output_channel , 
		const ASIOBufferInfo*	buffers , 
		const ASIOChannelInfo*	chinfo , 
		uint32					buffersamples , 
		ASIOTime*				timeinfo, 
		long					index ,
		ASIOBool				processnow
		)
{
	cb_trace( L"BufferSwitchTimeInfo\n" );
	cb_assert( buffersamples == m_format.m_buffersamples , L"asio buffersamples changed." );
	
	// convert
	uint32	ch;
	for( ch = 0 ; ch < input_channel ; ch++ )
	{
		int32	bsf	= ASIOSampleTypeToBufferSampleFormat( chinfo[ch].type );
		cb_assert( bsf != -1 , L"incapacitable ASIOSampleType" );
		if( bsf != -1 )
			BufferSampleFormat_to_float( buffersamples , (BufferSampleFormat)bsf , (const uint8*)(buffers[ch].buffers[index]) , m_input_ptr[ch] );
		else
			MemoryZero( m_input_ptr[ch] , buffersamples * sizeof(float) );
	}
	// callback
	if( m_stream_ptr != 0 )
		m_stream_ptr->AudioDeviceStream( m_ctrlid , m_format , m_input_ptr.GetConstPtr() , m_output_ptr.GetPtr() );
		
	// convert
	for( ch = 0 ; ch < output_channel ; ch++ )
	{
		int32	bsf	= ASIOSampleTypeToBufferSampleFormat( chinfo[ch+input_channel].type );
		cb_assert( bsf != -1 , L"incapacitable ASIOSampleType" );
		if( bsf != -1 )
			float_to_BufferSampleFormat( buffersamples , m_output_ptr[ch] , (BufferSampleFormat)bsf , (uint8*)(buffers[ch+input_channel].buffers[index]) );
		else
			MemoryZero( m_output_ptr[ch] , buffersamples * sizeof(float) );
	}

	if ( m_post_output == true )
		m_asio->OutputReady();
	return 0;
}
//=================================================================================================
void BufferSwitch
		(
		uint32					input_channel , 
		uint32					output_channel , 
		const ASIOBufferInfo*	buffers , 
		const ASIOChannelInfo*	chinfo , 
		uint32					buffersamples , 
		long					index, 
		ASIOBool				processnow
		)
{
	cb_trace( L"BufferSwitch\n" );

	ASIOTime  timeinfo;
	MemoryZero( &timeinfo , sizeof( timeinfo ) );
	if( true == m_asio->GetSamplePosition
			( 
			&timeinfo.timeInfo.samplePosition, 
			&timeinfo.timeInfo.systemTime 
			) )
	{
		timeinfo.timeInfo.flags = kSystemTimeValid | kSamplePositionValid;
	}
	BufferSwitchTimeInfo( input_channel , output_channel , buffers , chinfo , buffersamples , &timeinfo , index , processnow );
}
//=================================================================================================
void SampleRateChanged
		(
		ASIOSampleRate samplerate
		)
{
	cb_trace( L"SampleRateChanged\n" );
}
//=================================================================================================
long AsioMessages
		(
		long		selector , 
		long		value , 
		void*		msg , 
		double*		opt
		)
{
	cb_trace( L"AsioMessages\n" );

	long	rv = 0;
	switch( selector )
	{
	case kAsioSelectorSupported:
		if(value == kAsioResetRequest
		|| value == kAsioEngineVersion
		|| value == kAsioResyncRequest
		|| value == kAsioLatenciesChanged
		|| value == kAsioSupportsTimeInfo
		|| value == kAsioSupportsTimeCode
		|| value == kAsioSupportsInputMonitor)
			rv = 1;
		cb_trace( L"kAsioSelectorSupported\n" );
		break;

	case kAsioResetRequest:
		cb_trace( L"kAsioResetRequest\n" );
		rv = 1;
		break;
		
	case kAsioResyncRequest:
		cb_trace( L"kAsioResyncRequest\n" );
		rv = 1;
		break;

	case kAsioLatenciesChanged:
		cb_trace( L"kAsioLatenciesChanged\n" );
		rv = 1;
		break;

	case kAsioEngineVersion:
		cb_trace( L"kAsioEngineVersion\n" );
		rv = 2L;
		break;

	case kAsioSupportsTimeInfo:
		cb_trace( L"kAsioSupportsTimeInfo\n" );
		rv = 1;
		break;

	case kAsioSupportsTimeCode:
		cb_trace( L"kAsioSupportsTimeCode\n" );
		rv = 0;
		break;
	}
	return rv;
}
// private functions
private:
//=================================================================================================
void InitializeBuffer
		(
		uint32		in_ch , 
		uint32		out_ch , 
		uint32		buffersamples
		)
{
	m_buffer.Resize( ( in_ch + out_ch ) * buffersamples );
	MemoryZero( m_buffer.GetPtr() , sizeof( float ) * m_buffer.GetDatanum() );
	m_input_ptr.Resize( in_ch );
	m_output_ptr.Resize( out_ch );
	
	uint32	ct = 0;
	uint32	ch;
	for( ch = 0 ; ch < in_ch ; ch++ )
	{
		m_input_ptr[ch]		= &m_buffer[ct];
		ct += buffersamples;
	}
	for( ch = 0 ; ch < out_ch ; ch++ )
	{
		m_output_ptr[ch]	= &m_buffer[ct];
		ct += buffersamples;
	}
}
//=================================================================================================
bool cb_call Create
		(
		const IAudioDevice::Format&	fmt , 
		uint32						in_ch , 
		const uint32				in_align[] , 
		uint32						out_ch , 
		const uint32				out_align[] , 
		int32						clocksource , 
		IAudioDeviceStream::Format*	sfmt
		)
{
	uint32	buffersamples;
	if( false == m_asio->Create
			(
			in_ch , 
			in_align , 
			out_ch , 
			out_align , 
			this , 
			&buffersamples
			) )
			return false;
	m_state	= Stop_State;
	if( false == m_asio->SetSampleRate( fmt.m_samplespersec ) )
	{
		Destroy();
		return false;
	}
	if( clocksource >= 0 && false == m_asio->SetClockSource( clocksource ) )
	{
		Destroy();
		return false;
	}
	InitializeBuffer( in_ch , out_ch , buffersamples );

	// check buffer format
	uint32		off , num;
	const ASIOChannelInfo*	infos = m_asio->GetChannelInfos( &num );
	for( off = 0 ; off < num ; off++ )
	{
		if( -1 == ASIOSampleTypeToBufferSampleFormat( infos[off].type ) )
		{
			Destroy();
			return false;
		}
	}
	m_format.m_channel[0]		= in_ch;
	m_format.m_channel[1]		= out_ch;
	m_format.m_samplespersec	= fmt.m_samplespersec;
	m_format.m_buffersamples	= buffersamples;
	store( sfmt , m_format );
	return true;
}

// "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_asio->GetDriverName();
}
//=================================================================================================
void cb_call GetChannelMaximum
		(
		uint32*		in_ch , 
		uint32*		out_ch
		)
{
	uint32	in = 0 , out = 0;
	m_asio->GetChannels( &in , &out );
	store( in_ch , in );
	store( out_ch , out );
}
//=================================================================================================
bool cb_call GetBufferSamplesMaximum
		(
		uint32*		min , 
		uint32*		max , 
		uint32*		prefer
		)
{
	return false;
}
//=================================================================================================
bool cb_call IsFormatAvailable
		(
		const IAudioDevice::Format&	fmt
		)
{
	if( fmt.m_channel[0] == 0 && fmt.m_channel[1] == 0 )
		return false;
	if( fmt.m_buffersamples != 0 )
		return false;
	uint32	in = 0 , out = 0;
	m_asio->GetChannels( &in , &out );
	if( fmt.m_channel[0] > in 
	||  fmt.m_channel[1] > out )
		return false;
	if( false == m_asio->CanSampleRate( fmt.m_samplespersec ) )
		return false;
	return true;
}
//=================================================================================================
bool cb_call Create
		(
		const IAudioDevice::Format&	fmt , 
		IAudioDeviceStream::Format*	sfmt
		)
{
	Destroy();

	if( false == IsFormatAvailable( fmt ) )
		return false;

	Array<uint32>	in_align( fmt.m_channel[0] );
	Array<uint32>	out_align( fmt.m_channel[1] );
	uint32	ch;
	for( ch = 0 ; ch < fmt.m_channel[0] ; ch++ )
		in_align[ ch ] = ch;
	for( ch = 0 ; ch < fmt.m_channel[1] ; ch++ )
		out_align[ ch ] = ch;

	if( false == Create
			( 
			fmt , 
			in_align.GetDatanum() , 
			in_align.GetConstPtr() , 
			out_align.GetDatanum() , 
			out_align.GetConstPtr() , 
			-1 , 
			sfmt
			) )
			return false;
	return true;
}
//=================================================================================================
bool cb_call GetLatency
		(
		uint32*		input_ms , 
		uint32*		output_ms
		)
{
	if( m_state == Null_State )
		return false;
	long	in , out;
	if( false == m_asio->GetLatencies( &in , &out ) )
		return false;
	in	= in * 1000 / m_format.m_samplespersec;
	out	= out * 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;

	m_post_output	= m_asio->OutputReady();
	if( false == m_asio->Play() )
		return false;
	m_state	= Play_State;
	return true;
}
//=================================================================================================
void cb_call Stop()
{
	if( m_state != Play_State )
		return;
	m_asio->Stop();
	m_state	= Stop_State;
}
//=================================================================================================
void cb_call Destroy()
{
	if( m_state == Null_State )
		return;
	Stop();
	m_asio->Destroy();
	m_state	= Null_State;
}
// "IAudioDeviceAsio" interface functions
public:
//=================================================================================================
bool cb_call GetChannels
		(
		uint32*		in_channel , 
		uint32*		out_channel
		)
{
	return m_asio->GetChannels( in_channel , out_channel );
}
//=================================================================================================
bool cb_call GetChannelInfo
		(
		int32				channel , 
		bool				isinput , 
		IAsio::ChannelInfo*	info
		)
{
	return m_asio->GetChannelInfo( channel , isinput , info );
}
//=================================================================================================
bool cb_call Create
		(
		const IAudioDeviceAsio::Format&	fmt , 
		IAudioDeviceStream::Format*		sfmt
		)
{
	Destroy();

	if( false == IsFormatAvailable( fmt ) )
		return false;

	Array<uint32>	in_align;
	Array<uint32>	out_align;
	uint32	ch;
	for( ch = 0 ; ch < fmt.m_channel[0] && ch < _countof( fmt.m_channel_enable[0] ) ; ch++ )
	{
		if( true == fmt.m_channel_enable[0][ch] )
			in_align[ in_align.Add() ] = ch;
	}
	for( ch = 0 ; ch < fmt.m_channel[1] && ch < _countof( fmt.m_channel_enable[1] ) ; ch++ )
	{
		if( true == fmt.m_channel_enable[1][ch] )
			out_align[ out_align.Add() ] = ch;
	}
	if( false == Create
			( 
			fmt , 
			in_align.GetDatanum() , 
			in_align.GetConstPtr() , 
			out_align.GetDatanum() , 
			out_align.GetConstPtr() , 
			fmt.m_clocksource , 
			sfmt
			) )
			return false;
	return true;
}
//=================================================================================================
bool cb_call ControlPanel()
{
	return m_asio->ControlPanel();
}
//=================================================================================================
uint32 cb_call GetClockSourceNum()
{
	return m_asio->GetClockSourceNum();
}
//=================================================================================================
bool cb_call GetClockSource
		(
		uint32				clock_off , 
		IAsio::ClockSource*	clock
		)
{
	return m_asio->GetClockSource( clock_off , clock );
}
// public functions
public:
//=================================================================================================
AudioDevice() : 
		m_state( Null_State ) , 
		m_ctrlid( 0 ) , 
		m_stream_ptr( 0 ) , 
		m_post_output( false ) , 
		m_buffer( Expand_ArrayCashType , 0 ) , 
		m_input_ptr( Expand_ArrayCashType , 0 ) , 
		m_output_ptr( Expand_ArrayCashType , 0 )
{
}
//=================================================================================================
~AudioDevice()
{
	Destroy();
}
//=================================================================================================
bool Initialize
		(
		const guid&		clsid , 
		const wchar_t*	name , 
		wndptr			owner
		)
{
	if( false == m_asio->Initialize( clsid , owner ) )
		return false;
	uint32	in , out;
	cb_verify( true == m_asio->GetChannels( &in , &out ) );

	return true;
}
};
/**************************************************************************************************
"EnumAudioDevice" class 
**************************************************************************************************/
class EnumAudioDevice : 
	virtual public object_base , 
	public IEnumAudioDevice
{
	query_begin();
	iface_hook( IEnumAudioDevice , IEnumAudioDevice_IID )
	query_end( object_base );
	
	class DeviceId
	{
	public:
		guid	m_clsid;
		bool Load
				(
				IStreamRead*	stream
				)
		{
			if( false == icubic::Load( stream , &m_clsid ) )
				return false;
			return true;
		}
		bool Save
				(
				IStreamWrite*	stream
				)
		{
			if( false == icubic::Save( stream , m_clsid ) )
				return false;
			return true;
		}
	};
// variable member
private:
	instance<EnumAsio>		m_devices;

// private functions
private:
//=================================================================================================
guid GetEnumId()
{
	cb_guid_define( EnumId , 0x308D9251 , 0xB2834637 , 0xA7A48BA0 , 0x688015AC );
	static
	EnumId	id;
	return id;	
}
	
// "IEnumAudioDevice" interface functions
public:
//=================================================================================================
void cb_call Enum()
{
	m_devices->Enum();
}
//=================================================================================================
wstring cb_call GetDriverName()
{
	return wstring( L"Asio" );
}
//=================================================================================================
bool cb_call GetAudioDeviceId
		(
		int32			devoff , 
		IAudioDeviceId*	id
		)
{
	DeviceId	devid;
	if( false ==m_devices->GetDeviceInfo( devoff , 0 , &devid.m_clsid ) )
		return false;

	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->GetDeviceNum();
	for( off = 0 ; off < num ; off++ )
	{
		guid	t_clsid;
		if( true ==m_devices->GetDeviceInfo( off , 0 , &t_clsid ) && devid.m_clsid == t_clsid )
		{
			store( devoff , off );
			return true;
		}
	}
	return false;
}
//=================================================================================================
int32 cb_call GetDeviceNum()
{
	return m_devices->GetDeviceNum();
}
//=================================================================================================
bool cb_call GetDeviceInfo
		(
		int32		devoff , 
		DeviceInfo*	info
		)
{
	if( devoff < 0 || devoff >= m_devices->GetDeviceNum() )
		return false;
	if( info == 0 )
		return true;
	wstring		name;
	m_devices->GetDeviceInfo( devoff , &name , 0 );
	info->m_name	= name;
	info->m_type	= InputOutput;
	return true;
}
//=================================================================================================
iAudioDevice cb_call CreateDevice
		(
		int32		devoff , 
		wndptr		owner
		)
{
	guid	clsid;
	wstring	name;
	if( false ==m_devices->GetDeviceInfo( devoff , &name , &clsid ) )
		return false;

	instance<AudioDevice>	device;
	if( false == device->Initialize( clsid , name.c_str() , owner ) )
		return iAudioDevice();
	return (iAudioDevice)device;
}
// public functions
public:
//=================================================================================================
EnumAudioDevice()
{
}
};
///////////////////////////////////////////////////////////////////////////////////////////////////
// global variable define

///////////////////////////////////////////////////////////////////////////////////////////////////
// global functions define
};	// namespace asio
};	//namespace

//using namespace icubic;		

#pragma pack( pop )			//release align
