#pragma once
#include "COMSmartPtrWrapper.hpp"
#include "Registry.hpp"
#include "tlsObject.hpp"
#include "ASIO SDK/common/iasiodrv.h"
#include "Dbghelp.h"
#include <cassert>
#include <string>
////////////////////////////////////////////////////////////////////////////////
//
// ImportsHooker
// -------------
//
// Wraps the functionality for hooking DLL (ex/im)ports. Based on code from
// Jeffery Richter's "Programming Applications for Microsoft Windows (4th
// edition)".
//
// Use of Create/factory methods is a quick-fix for lack of delegating
// constructors.
//
// (todo) currently only a 'helper class', move to a separate/appropriate
// module.
// (todo) refactor to lazy load the dbghelp.dll or remove the dbghelp.dll
// dependency completely and use __ImageBase directly.
//
////////////////////////////////////////////////////////////////////////////////
class ImportsHooker
{
public:
static ImportsHooker Create( TCHAR const * moduleName );
static ImportsHooker Create( HMODULE moduleHandle );
bool switchFunctionThunk( PCSTR originalFunctionImplementingModuleName, LPCSTR pOriginalFunctionName, void * pNewFunction ) const;
bool switchFunctionThunk( PCSTR originalFunctionImplementingModuleName, void * pOriginalFunction , void * pNewFunction ) const;
private:
ImportsHooker( PIMAGE_IMPORT_DESCRIPTOR const pImportDesc, BYTE * const pModuleOffset ) : pImportDesc_( pImportDesc ), pModuleOffset_( pModuleOffset ) {}
PIMAGE_IMPORT_DESCRIPTOR findImportDescriptorForModule( PCSTR module ) const;
void operator=( ImportsHooker const & );
private:
PIMAGE_IMPORT_DESCRIPTOR const pImportDesc_;
BYTE * const pModuleOffset_;
};
////////////////////////////////////////////////////////////////////////////////
//
// asioCOMInitializationFlags
// --------------------------
//
// Public definition to enable consistent (re)use.
//
////////////////////////////////////////////////////////////////////////////////
DWORD const asioCOMInitializationFlags
(
COINIT_APARTMENTTHREADED |
COINIT_DISABLE_OLE1DDE |
COINIT_SPEED_OVER_MEMORY
);
////////////////////////////////////////////////////////////////////////////////
//
// IASIOPtr
// --------
//
// IASIOPtr typedef used by the rest of the code. With the use of the
// COMAutoPtrWrapper, provides a single point for switching/testing different
// COM smart pointer implementations.
//
////////////////////////////////////////////////////////////////////////////////
typedef _com_ptr_t<_com_IIID<IASIO, &IID_IUnknown>> NativeIASIOPtr;
typedef ATL::CComPtr<IASIO> ATLIASIOPtr ;
typedef IASIO * RawIASIOPtr ;
typedef COMAutoPtrWrapper<RawIASIOPtr, IASIO> IASIOPtr;
////////////////////////////////////////////////////////////////////////////////
//
// ASIODriverList
// --------------
//
// A thin extension to the SubKeys class which automatically opens/selects the
// ASIO registry key and provides the getDriverData() member function which
// searches for a driver and returns the first one if the requested one isn't
// found.
//
////////////////////////////////////////////////////////////////////////////////
class ASIODriverData;
class ASIODriverList : public SubKeys
{
public:
ASIODriverList();
ASIODriverData getDriverData( TCHAR const * driverName ) const;
};
////////////////////////////////////////////////////////////////////////////////
//
// ASIODriverData
// --------------
//
// Stores and wraps access to data available in a driver's registry
// (sub)key(s).
//
////////////////////////////////////////////////////////////////////////////////
class ASIODriverData
{
public:
ASIODriverData( ASIODriverList::const_iterator const & );
ASIODriverData( ASIODriverList const &, ASIODriverList::size_type driverIndex );
ASIODriverData( HKEY const driverKey ) { readDriverData( driverKey ); }
ASIODriverData( HKEY hASIORootKey, TCHAR const * driverKeyName );
CLSID const & classID () const { return classID_; }
OLECHAR const * classIDStr () const { return classIDStr_; }
std::wstring classIDStr2() const { assert( classIDStr()[ clsidStrLen ] == '\0' ); return std::wstring( classIDStr(), clsidStrLen ); }
TCHAR const * description() const { return description_; }
WinString path () const;
tstring path2() const;
IASIOPtr createInstance() const;
public:
static size_t const clsidStrLen = 38;
static size_t const maxDrvNameLen = 128;
private:
void readDriverData( HKEY driverKey );
private:
CLSID classID_;
OLECHAR classIDStr_[ clsidStrLen + 1 ];
TCHAR description_[ maxDrvNameLen ];
};
////////////////////////////////////////////////////////////////////////////////
//
// ASIODriver
// ----------
//
// First wrapper layer around the IASIO interface. Besides the various
// overloads that try to fix some of the ugliness, it keeps track of the state
// of the driver/device (as documented/drawn in the ASIO SDK) and offers a few
// named constants/enums for some 'magic numbers' found in the SDK.
//
////////////////////////////////////////////////////////////////////////////////
class ASIODriver
{
public:
enum State
{
Unloaded = false,
Loaded = true,
Initialized,
Prepared,
Running
};
enum Gain
{
minusInf_dB = 0,
zero_dB = 0x20000000,
twelve_dB = 0x7fffffff
};
enum Pan
{
left = 0,
right = 0x7fffffff,
centre = ( right - left ) / 2
};
////////////////////////////////////////////////////////////////////////////
//
// ASIOCallbacks
// -------------
//
// Redefinition of the ASIOCallbacks struct from asio.h to add the __cdecl
// specifier needed to compile with a default calling convention different
// from cdecl.
//
////////////////////////////////////////////////////////////////////////////
typedef void (__cdecl * BufferSwitchCallback )( long doubleBufferIndex, ASIOBool directProcess );
typedef void (__cdecl * SampleRateDidChangeCallback )( ASIOSampleRate );
typedef long (__cdecl * AsioMessageCallback )( long selector, long value, void * message, double * opt );
typedef ASIOTime * (__cdecl * BufferSwitchTimeInfoCallback)( ASIOTime *, long doubleBufferIndex, ASIOBool directProcess );
#pragma pack(push,4)
struct ASIOCallbacks
{
BufferSwitchCallback bufferSwitch ;
SampleRateDidChangeCallback sampleRateDidChange ;
AsioMessageCallback asioMessage ;
BufferSwitchTimeInfoCallback bufferSwitchTimeInfo;
};
#pragma pack(pop)
public:
ASIODriver() : state_( Unloaded ) {}
ASIODriver( IASIOPtr const & pDriver ) : pDriver_( pDriver ), state_( static_cast<State>( static_cast<bool>( pDriver ) ) ) {}
// Initialization
State loadDriverCOMObject( ASIODriverData const & driverData );
State initializeDriverCOMObject( HWND parentWindow );
void unloadDriverCOMObject();
// Initialization helper wrapper
State initialize( ASIODriverData const & driverData, HWND parentWindow );
char const * getInitializationErrorMessage();
char const * getDriverName () const;
long getDriverVersion() const { return driver().getDriverVersion(); }
char const * getErrorMessage () const;
bool can( long kAsioCanXEnum ) const;
bool canSampleRate( ASIOSampleRate const & sampleRate ) const { return driver().canSampleRate( sampleRate ) == ASE_OK; }
ASIOError getSampleRate( ASIOSampleRate & sampleRate ) const { return driver().getSampleRate( &sampleRate ); }
double getSampleRate() const { ASIOSampleRate sampleRate; VERIFY( getSampleRate( sampleRate ) == ASE_OK ); return sampleRate; }
ASIOError setSampleRate( ASIOSampleRate const & sampleRate ) { return driver().setSampleRate( sampleRate ); }
ASIOError getBufferSize( long & minSize, long & maxSize, long & preferredSize, long & granularity ) const { return driver().getBufferSize( &minSize, &maxSize, &preferredSize, &granularity ); }
long fixBufferSize( long bufferSize = 0 ) const;
ASIOError createBuffers( ASIOBufferInfo bufferInfos[], long numChannels, long bufferSizeInSamples, ASIOCallbacks & callbacks );
ASIOError disposeBuffers();
ASIOError getClockSources( ASIOClockSource clockSources[], long & numSources ) const { return driver().getClockSources( clockSources, &numSources ); }
ASIOError setClockSource ( long const clockSourceIndex ) const { return driver().setClockSource ( clockSourceIndex ); }
ASIOError getLatenciesInSamples( long & inputLatency, long & outputLatency ) const { return driver().getLatencies( &inputLatency, &outputLatency ); }
ASIOError getNumberOfChannels ( long & maxInputChannels, long & maxOutputChannels ) const { return driver().getChannels( &maxInputChannels, &maxOutputChannels ); }
ASIOError getSamplePosition( unsigned long long & samplesPlayed, unsigned long long & nsTimeStamp ) const;
ASIOError getSamplePosition( ASIOTime & asioTime ) const;
ASIOError getChannelInfo( ASIOChannelInfo & info ) const { return driver().getChannelInfo( &info ); }
ASIOSampleType getSampleType ( ASIOBool const forInputChannel = ASIOFalse ) const;
bool allChannelsAreOfSameType() const;
bool start();
bool stop ();
bool timeCodeRead ( bool const enable ) { return futureCall( enable ? kAsioEnableTimeCodeRead : kAsioDisableTimeCodeRead, NULL ) == ASE_SUCCESS; }
bool setInputMonitor( long inChannel, long outChannel, long gain = zero_dB, bool turnOn = true, long pan = centre );
bool setChannelGain ( long channel, long gain = zero_dB, bool isInput = false );
ASIOError futureCall( long const selector, void * const opt ) { return driver().future( selector, opt ); }
ASIOError openControlPanel() const { return driver().controlPanel(); }
ASIOError outputReady() const { return driver().outputReady(); }
State state() const { assert( static_cast<bool>( pDriver_ ) == ( state_ >= Loaded ) ); return state_; }
typedef bool (ASIODriver::*unspecified_bool_type)();
operator unspecified_bool_type() const { return pDriver_ ? &ASIODriver::start : 0; }
private:
// The copy constructor is not disabled because it is required by
// STL containers although it should be because this class is not
// designed to be copyable (because, then, one instance could change the
// state of the underlying device/driver behind the back of a 'copy'
// working with the same driver).
// This will be fixed with boost::intrusive_containers (or "someday"
// with C++(0/1)x move semantics). Until then make sure that the copy
// constructor is used only by STL containers in insert-like operations.
//ASIODriver( ASIODriver const & );
IASIO & driver() const { assert( pDriver_ ); return *pDriver_; }
private:
IASIOPtr pDriver_;
State state_ ;
};
////////////////////////////////////////////////////////////////////////////////
//
// ASIODevice
// ----------
//
// The second wrapper layer around the IASIO interface. Extends the ASIODriver
// class with functionality related to ASIO buffers and callbacks. A template
// class to eliminate runtime allocation of the buffer pointers array to avoid
// the extra pointer dereferencing in buffer switch callbacks.
//
// (todo) Try to further split the class to minimize template related in-header/
// in-place implementation details ugliness.
//
////////////////////////////////////////////////////////////////////////////////
template <unsigned maxInChannels, unsigned maxOutChannels>
class ASIODevice : public ASIODriver
{
public:
ASIODevice() { setCallbacks( NULL, NULL, NULL, NULL ); }
ASIODevice
(
BufferSwitchTimeInfoCallback ,
BufferSwitchCallback ,
SampleRateDidChangeCallback ,
AsioMessageCallback ,
IASIOPtr pDriver = IASIOPtr(),
HWND parentWindow = NULL
);
ASIODevice
(
BufferSwitchTimeInfoCallback ,
BufferSwitchCallback ,
SampleRateDidChangeCallback ,
AsioMessageCallback ,
ASIODriver const &
);
~ASIODevice() {}
ASIOError createBuffers( size_t numInChannels, size_t numOutChannels, size_t bufferSize );
size_t setOutputFormat( ASIOSampleRate, size_t numOutChannels = 2, size_t numInChannels = 0, size_t requestedBufferSize = 0 );
void setCallbacks( BufferSwitchTimeInfoCallback, BufferSwitchCallback, SampleRateDidChangeCallback, AsioMessageCallback );
void setCallbacks( ASIOCallbacks const & callbacks ) { callbacks_ = callbacks; }
bool supportsCallbackSwitching();
bool startUsingCallbackSwitching();
bool start();
static void hookDriverCreateThread( TCHAR const * driverModulePath );
typedef BYTE const * InputBuffers [ maxInChannels ? maxInChannels : 1 ][ 2 ];
typedef BYTE * OutputBuffers[ maxOutChannels ? maxOutChannels : 1 ][ 2 ];
InputBuffers const & inBuffers () const { return inBuffers_ ; }
OutputBuffers const & outBuffers() { return outBuffers_; }
static ASIODevice & getThreadInstance() { return TLSObject<ASIODevice>::getThreadLocal(); }
private: // (todo) clean this up, document and remove from the header.
////////////////////////////////////////////////////////////////////////////
//
// TLSObject-setting-through-CreateThread-hooking functionality.
//
////////////////////////////////////////////////////////////////////////////
struct OriginalThreadParameters
{
OriginalThreadParameters( LPTHREAD_START_ROUTINE const lpStartAddress, LPVOID const lpParameter ) : lpStartAddress( lpStartAddress ), lpParameter( lpParameter ) {}
void operator=( OriginalThreadParameters const & );
LPTHREAD_START_ROUTINE const lpStartAddress;
LPVOID const lpParameter ;
};
static DWORD WINAPI CreateThreadHook( LPVOID const pOriginalParameters )
{
OriginalThreadParameters const originalParameters( *static_cast<OriginalThreadParameters const *>( pOriginalParameters ) );
delete pOriginalParameters;
TLSObject<ASIODevice>::setThreadLocal();
return originalParameters.lpStartAddress( originalParameters.lpParameter );
}
static HANDLE WINAPI CreateThreadPassThrough
(
LPSECURITY_ATTRIBUTES const lpThreadAttributes,
SIZE_T const dwStackSize,
LPTHREAD_START_ROUTINE const lpStartAddress,
LPVOID const lpParameter,
DWORD const dwCreationFlags,
LPDWORD const lpThreadId
)
{
OriginalThreadParameters * const pOriginalThreadParameters( new OriginalThreadParameters( lpStartAddress, lpParameter ) );
return ::CreateThread( lpThreadAttributes, dwStackSize, CreateThreadHook, pOriginalThreadParameters, dwCreationFlags, lpThreadId );
}
////////////////////////////////////////////////////////////////////////////
//
// "Buffer switch" helper callbacks used for testing callback switching
// capability.
//
////////////////////////////////////////////////////////////////////////////
static void __cdecl callbackSwitchCapabilityHelper1( long, ASIOBool )
{
assert( TLSObject<ASIODevice>::isGlobalSet() );
if ( TLSObject<ASIODevice>::getGlobal().callbacks_.bufferSwitch == callbackSwitchCapabilityHelper2 )
// It is not the first call (callbacks already switched but the old
// one got called): test failed.
{
assert( TLSObject<ASIODevice>::getGlobal().callbacks_.bufferSwitchTimeInfo == callbackSwitchCapabilityHelperTI2 );
//assert( !TLSObject<ASIODevice>::isThreadLocalSet() );
lastCallbackSwitchCapabilityTestPassed_ = false;
WinHandle const testEvent( ::OpenEvent( EVENT_MODIFY_STATE , false, tlsSynchronizationEventName ) );
TLSObject<ASIODevice>::setThreadLocal();
VERIFY( ::SetEvent( *testEvent ) );
}
else
{
TLSObject<ASIODevice>::getGlobal().callbacks_.bufferSwitch = callbackSwitchCapabilityHelper2;
TLSObject<ASIODevice>::getGlobal().callbacks_.bufferSwitchTimeInfo = callbackSwitchCapabilityHelperTI2;
}
}
static ASIOTime * __cdecl callbackSwitchCapabilityHelperTI1( ASIOTime *, long, ASIOBool ) { callbackSwitchCapabilityHelper1( 0, 0 ); return NULL; }
static void __cdecl callbackSwitchCapabilityHelper2( long, ASIOBool )
{
// Switched callback called: Test passed.
lastCallbackSwitchCapabilityTestPassed_ = true;
WinHandle const testEvent( ::OpenEvent( EVENT_MODIFY_STATE , false, tlsSynchronizationEventName ) );
TLSObject<ASIODevice>::setThreadLocal();
VERIFY( ::SetEvent( *testEvent ) );
}
static ASIOTime * __cdecl callbackSwitchCapabilityHelperTI2( ASIOTime *, long, ASIOBool ) { callbackSwitchCapabilityHelper2( 0, 0 ); return NULL; }
////////////////////////////////////////////////////////////////////////////
//
// "Buffer switch" helper callbacks used for starting ASIO devices and
// automatically setting the TLSObject through callback switching.
//
////////////////////////////////////////////////////////////////////////////
static void __cdecl SetTLSDeviceHelper( long const doubleBufferIndex, ASIOBool const directProcess )
{
assert( doubleBufferIndex == 0 );
ASIODevice & device( TLSObject<ASIODevice>::setThreadLocal() );
assert( device.originalBufferSwitchTimeInfo_ == NULL );
device.callbacks_.bufferSwitch = device.originalBufferSwitch_;
device.callbacks_.bufferSwitchTimeInfo = device.originalBufferSwitchTimeInfo_;
device.originalBufferSwitch_( doubleBufferIndex, directProcess );
}
static ASIOTime * __cdecl SetTLSDeviceHelperTI( ASIOTime * const params, long const doubleBufferIndex, ASIOBool const directProcess )
{
assert( doubleBufferIndex == 0 );
ASIODevice & device( TLSObject<ASIODevice>::setThreadLocal() );
//assert( device.originalBufferSwitch_ == NULL );
device.callbacks_.bufferSwitch = device.originalBufferSwitch_;
device.callbacks_.bufferSwitchTimeInfo = device.originalBufferSwitchTimeInfo_;
return device.originalBufferSwitchTimeInfo_( params, doubleBufferIndex, directProcess );
}
private:
OutputBuffers outBuffers_;
InputBuffers inBuffers_ ;
ASIOCallbacks callbacks_;
BufferSwitchCallback originalBufferSwitch_;
BufferSwitchTimeInfoCallback originalBufferSwitchTimeInfo_;
static bool lastCallbackSwitchCapabilityTestPassed_;
static TLSObject<ASIODevice> tlsDevice_;
// (todo) A named synchronization object should not be needed. Clean this up.
static TCHAR const * const tlsSynchronizationEventName;
};
TCHAR const * convertASIOSampleTypeToString( ASIOSampleType );
#include "ASIODevice.inl"