#include "WASIO.hpp"
#include "configDlg.hpp"
#include "Writer.hpp"
#include <algorithm>
////////////////////////////////////////////////////////////////////////////////
//
// (todo) Split this module into more smaller ones.
//
////////////////////////////////////////////////////////////////////////////////
namespace WinAMP
{
namespace OutModule
{
////////////////////////////////////////////////////////////////////////////////
//
// ASIO callbacks
// --------------
//
// BufferSwitch* callbacks are optimized for speed.
//
////////////////////////////////////////////////////////////////////////////////
#pragma optimize ( "t", on )
//************************************
// Method: BufferSwitch
// FullName: WinAMP::OutModule::WAASIOOut::BufferSwitch
// Access: private static
//************************************
void WAASIOOut::BufferSwitch( long const doubleBufferIndex, ASIOBool const directProcess )
{
#if 0 // Currently testing a different design, where the TimeInfo version of
// the callback queries the sample position/time if needed (as it need not
// be in multi-device setups).
ASIOTime asioTime;
VERIFY( Device::getThreadInstance().getSamplePosition( asioTime ) == ASE_OK );
BufferSwitchTimeInfo( &asioTime, doubleBufferIndex, directProcess );
#endif // Temporarily disabled.
BufferSwitchTimeInfo( NULL, doubleBufferIndex, directProcess );
}
//************************************
// Method: BufferSwitchTimeInfo
// FullName: WinAMP::OutModule::WAASIOOut::BufferSwitchTimeInfo
// Access: private static
//************************************
ASIOTime * WAASIOOut::BufferSwitchTimeInfo( ASIOTime * const pASIOTime, long const doubleBufferIndex, ASIOBool const directProcess )
{
// According to the ASIO SDK 2.1 documentation directProcess should always
// be true on Windows.
assert( directProcess == ASIOTrue ); directProcess;
assert( doubleBufferIndex == 0 || doubleBufferIndex == 1 );
Device & myDevice( Device::getThreadInstance() );
size_t const copySizeInBytes( myDevice.outputBufferSizeInBytesPerChannel() );
MultiChannelBuffer::Reader::ChannelIterator channelIterator( myDevice.reader().getChunk( copySizeInBytes ) );
size_t const sizeAcquired ( channelIterator.size() );
size_t const underflowSize ( copySizeInBytes - sizeAcquired );
if ( underflowSize )
for ( unsigned i( 0 ); i < WAASIOOut::singleton().numberOfInChannels(); ++i )
::ZeroMemory( myDevice.outBuffers()[ i ][ doubleBufferIndex ] + sizeAcquired, underflowSize );
#if _SECURE_SCL || _HAS_ITERATOR_DEBUGGING
if ( sizeAcquired )
#endif
for ( unsigned i( 0 ); i < WAASIOOut::singleton().numberOfInChannels(); ++i )
{
std::memcpy( myDevice.outBuffers()[ i ][ doubleBufferIndex ], *channelIterator, sizeAcquired );
++channelIterator;
}
WAASIOOut::singleton().setCurrentDeviceTime( pASIOTime, underflowSize, myDevice );
// With direct processing the outputReady() call should not be needed.
assert( myDevice.outputReady() != ASE_OK );
// The ASIO SDK documentation is unclear about the return value. It seems
// that if time code is not used/not supported the return value is ignored.
return NULL;
}
#pragma optimize( "", on ) // End optimize for speed.
//************************************
// Method: SampleRateDidChange
// FullName: WinAMP::OutModule::WAASIOOut::SampleRateDidChange
// Access: private static
//************************************
void WAASIOOut::SampleRateDidChange( ASIOSampleRate )
{
assert( !"Custom sample rates/resampling and changing the sampling rate during playing unsupported." );
// (todo) Handle this more intelligently.
WAMessage( WAMessages::StopPlayback );
WAASIOOut::singleton().setShouldReinitialize();
}
//************************************
// Method: ASIOMessage
// FullName: WinAMP::OutModule::WAASIOOut::ASIOMessage
// Access: private static
//************************************
long WAASIOOut::ASIOMessage( long const selector, long const value, void * /*message*/, double * /*opt*/ )
{
switch( selector )
{
case kAsioSelectorSupported:
switch( value )
{
case kAsioEngineVersion:
case kAsioResetRequest:
case kAsioBufferSizeChange:
case kAsioResyncRequest:
case kAsioLatenciesChanged:
case kAsioSupportsTimeInfo:
case kAsioSupportsTimeCode:
case kAsioSupportsInputMonitor:
return true;
default:
return false;
}
case kAsioEngineVersion:
return 2;
case kAsioBufferSizeChange:
case kAsioResetRequest:
case kAsioLatenciesChanged:
case kAsioResyncRequest:
// (todo) Handle this more intelligently.
WAMessage( WAMessages::StopPlayback );
WAASIOOut::singleton().setShouldReinitialize();
return true;
case kAsioSupportsTimeCode:
return false;
case kAsioSupportsTimeInfo:
case kAsioSupportsInputMonitor:
return true;
default:
assert( !"Unknown ASIO message selector." );
return false;
}
}
// Functions for automatic filling of the WinAMP output module Out_Module structure.
char const * WAOutModuleDescription() { return WAASIOOut::singleton().WAOutModuleDescription(); }
intptr_t WAOutModuleID () { return WAASIOOut::singleton().WAOutModuleID (); }
////////////////////////////////////////////////////////////////////////////////
//
// WinAMP output module callbacks.
// -------------------------------
//
// Concrete implementations for declarations specified in/by WAOutModule.hpp
//
////////////////////////////////////////////////////////////////////////////////
extern "C"
{
//************************************
// Method: About
// FullName: WinAMP::OutModule::About
// Access: global
//************************************
void __cdecl About( HWND const hWnd )
{
::MessageBox( hWnd, _T( "wasiona: experimental multi-device ASIO WinAMP output plug-in\n" )
_T( "version 0.6\n" )
_T( "Compiled on " ) _T( __DATE__ ) _T( "\n" ), _T( "About" ), MB_OK );
}
//************************************
// Method: ConfigDialog
// FullName: WinAMP::OutModule::ConfigDialog
// Access: global
//************************************
void __cdecl ConfigDialog( HWND const hWnd )
{
DWORD const requiredCommonControls
(
ICC_LINK_CLASS |
ICC_STANDARD_CLASSES |
ICC_LISTVIEW_CLASSES
);
static bool const wtlInitialized( WTL::Initialize( WAOutModule::singleton().DLLInstance(), requiredCommonControls ) );
if ( wtlInitialized && InitializeCOM( asioCOMInitializationFlags ) )
{
ConfigurationDialog().DoModal( hWnd );
::CoUninitialize();
}
}
//************************************
// Method: Init
// FullName: WinAMP::OutModule::Init
// Access: global
//************************************
void __cdecl Init()
{
// To minimize WinAMP loading time all (possible) initialization is
// performed lazily.
WAOutModule::singleton().setSetVolumeCallback( &WAASIOOut::SetVolumeLazy );
}
//************************************
// Method: Quit
// FullName: WinAMP::OutModule::Quit
// Access: global
//************************************
void __cdecl Quit()
{
WAASIOOut::singleton().clear();
WTL::Uninitialize();
if ( WAASIOOut::singleton().comInitialized() )
::CoUninitialize();
}
//************************************
// Method: Open
// FullName: WinAMP::OutModule::Open
// Access: global
//************************************
int __cdecl Open( int const sampleRate, int const numChannels, int const BPS, int const bufferLen_ms, int const prebuffer_ms )
{
// (todo) Implement proper channel mapping and expansion.
if ( numChannels != 2 )
return -1;
assert( bufferLen_ms != -666 ); bufferLen_ms; // sync
assert( bufferLen_ms >= prebuffer_ms ); prebuffer_ms;
assert( BPS == 16 || BPS == 24 || BPS == 32 );
bool const mustInitialize ( !WAASIOOut::singleton().initialized() || WAASIOOut::singleton().shouldReinitialize() );
bool const ioParametersChanged( WAASIOOut::singleton().checkAndSaveNewIOParameters( sampleRate, numChannels, BPS ) || mustInitialize );
if ( mustInitialize && !WAASIOOut::singleton().initialize () ) return -1;
if ( ioParametersChanged && !WAASIOOut::singleton().updateDevicesAndWritersWithNewIOParameters() ) return -1;
int const outputLatencyInMilliseconds
(
WAASIOOut::singleton().getMaxLatency() * 1000 / WAASIOOut::singleton().inputSampleRate()
);
WAASIOOut::singleton().resetTrackVariables();
WAASIOOut::singleton().resetWriters();
WAASIOOut::singleton().startDevices();
return outputLatencyInMilliseconds;
}
//************************************
// Method: Close
// FullName: WinAMP::OutModule::Close
// Access: global
//************************************
void __cdecl Close()
{
#if 0 // Disabled until fully implemented.
// According to a WA_IPC.h note and tests IPC_ISFULLSTOP returns the wrong/
// 'inverted' value.
if ( !WACommand( IPC_ISFULLSTOP ) )
WAASIOOut::singleton().stopDevices();
else
#endif // Disabled until fully implemented.
WAASIOOut::singleton().pauseDevices();
}
//************************************
// Method: CanWrite
// FullName: WinAMP::OutModule::CanWrite
// Access: global
//************************************
int __cdecl CanWrite()
{
#if 0 // (todo) Asserts on pause. Fix this.
assert( GetWrittenTime() >= GetOutputTime() );
#endif // Disabled until fixed.
// (todo) Try to solve this WA vis. timing and Write() coupling in a smarter
// way. Remove the 'magic' number.
if ( GetWrittenTime() > ( GetOutputTime() + 50 ) )
return 0;
Writer const & writer( **WAASIOOut::singleton().activeWriters().begin() );
size_t const canWriteOutputBytesPerChannel( writer.buffer().canWrite() );
size_t const canWriteOutputBytes( canWriteOutputBytesPerChannel * writer.buffer().numberOfChannels() );
// Divide the result for output with the output-sample-size / input-sample-size
// ratio to get the final result (for input).
size_t const canWriteInputBytes( canWriteOutputBytes * WAASIOOut::singleton().inputBytesPerSample() / writer.outputBytesPerSample() );
return static_cast<int>( canWriteInputBytes );
}
//************************************
// Method: IsPlaying
// FullName: WinAMP::OutModule::IsPlaying
// Access: global
//************************************
int __cdecl IsPlaying()
{
return WAASIOOut::singleton().devices().front().state() == ASIODriver::Running &&
WAASIOOut::singleton().activeWriters().front()->buffer().hasUnreadInput();
}
//************************************
// Method: Pause
// FullName: WinAMP::OutModule::Pause
// Access: global
//************************************
int __cdecl Pause( int const newPauseState )
{
// In_FLAC (2.05) calls Pause() with an unchanged pause state when seeking
// (a bug?) so this must be checked to avoid starting/stopping already
// started/stopped devices.
return ( WAASIOOut::singleton().paused() != newPauseState )
? WAASIOOut::singleton().pause( newPauseState )
: newPauseState;
}
//************************************
// Method: Flush
// FullName: WinAMP::OutModule::Flush
// Access: global
//************************************
void __cdecl Flush( int const time_t_ms )
{
// Nothing to do here.
time_t_ms;
}
//************************************
// Method: GetOutputTime
// FullName: WinAMP::OutModule::GetOutputTime
// Access: global
//************************************
int __cdecl GetOutputTime()
{
return WAASIOOut::singleton().trackOutputTime();
}
//************************************
// Method: GetWrittenTime
// FullName: WinAMP::OutModule::GetWrittenTime
// Access: global
//************************************
int __cdecl GetWrittenTime()
{
// In_FLAC (2.05) calls GetWrittenTime() before Open() is called (a bug?)
// and could therefore happen that no active writers exist at the time of
// the call so this must be checked.
return !WAASIOOut::singleton().activeWriters().empty()
? WAASIOOut::singleton().activeWriters().front()->writtenTimeInMilliseconds()
: 0;
}
} // extern "C"
//************************************
// Method: GroupWrite
// FullName: WinAMP::OutModule::GroupWrite
// Access: global
//************************************
int __cdecl GroupWrite( char * const buf, int const len )
{
int ret( 0 );
for ( WAASIOOut::Writers::iterator pWriter( WAASIOOut::singleton().activeWriters().begin() ); pWriter != WAASIOOut::singleton().activeWriters().end(); ++pWriter )
ret |= (*pWriter)->write( buf, len );
return ret;
}
//************************************
// Method: SetVolumeASIO
// FullName: WinAMP::OutModule::WAASIOOut::SetVolumeASIO
// Access: public static
//************************************
void __cdecl WAASIOOut::SetVolumeASIO( int const volume )
{
assert( (volume >= 0 && volume <= 255) || volume == -666 );
WAASIOOut::singleton().gain() = ( volume != -666 ) ? ( ASIODriver::zero_dB / 255 * volume ) : ASIODriver::zero_dB;
for( Devices::iterator pDevice( WAASIOOut::singleton().devices().begin() ); pDevice != WAASIOOut::singleton().devices().end(); ++pDevice )
{
assert( pDevice->can( kAsioCanOutputGain ) );
for ( unsigned channel( 0 ); channel < WAASIOOut::singleton().numberOfInChannels(); ++channel )
VERIFY( pDevice->setChannelGain( channel, WAASIOOut::singleton().gain() ) );
}
}
//************************************
// Method: SetPanASIO
// FullName: WinAMP::OutModule::WAASIOOut::SetPanASIO
// Access: public static
//************************************
void __cdecl WAASIOOut::SetPanASIO( int const pan )
{
assert( pan >= -128 && pan <= 128 );
long leftGain, rightGain;
if ( pan == 0 )
leftGain = rightGain = WAASIOOut::singleton().gain();
else if ( pan < 0 )
{
leftGain = WAASIOOut::singleton().gain() / (-128) * pan;
rightGain = WAASIOOut::singleton().gain();
}
else
{
leftGain = WAASIOOut::singleton().gain();
rightGain = WAASIOOut::singleton().gain() / 128 * pan;
}
for( Devices::iterator pDevice( WAASIOOut::singleton().devices().begin() ); pDevice != WAASIOOut::singleton().devices().end(); ++pDevice )
{
assert( pDevice->can( kAsioCanOutputGain ) );
for ( unsigned channel( 0 ); channel < WAASIOOut::singleton().numberOfInChannels(); )
{
VERIFY( pDevice->setChannelGain( channel++, leftGain ) );
VERIFY( pDevice->setChannelGain( channel++, rightGain ) );
}
}
}
// End of WinAMP output module callbacks.
////////////////////////////////////////////////////////////////////////////////
//
// WAASIOOut singleton definition
//
////////////////////////////////////////////////////////////////////////////////
WAASIOOut WAASIOOut::singleton_;
// (todo) sort the following functions alphabetically.
//************************************
// Method: WAASIOOut
// FullName: WinAMP::OutModule::WAASIOOut::WAASIOOut
// Access: public
//************************************
WAASIOOut::WAASIOOut()
{
// (todo) ugh...clean this up...
::ZeroMemory
(
&numberOfInputChannels_,
( (BYTE const *)&shouldReinitialize_ - (BYTE const *)&numberOfInputChannels_ ) + sizeof( shouldReinitialize_ )
);
}
//************************************
// Method: checkAndSaveNewIOParameters
// FullName: WinAMP::OutModule::WAASIOOut::checkAndSaveNewIOParameters
// Access: public
//************************************
bool WAASIOOut::checkAndSaveNewIOParameters( int const sampleRate, int const numChannels, int const BPS )
{
bool const ioParametersChanged
(
( static_cast<unsigned>( sampleRate ) != inputSampleRate () ) ||
( static_cast<unsigned>( numChannels ) != numberOfInChannels () ) ||
( static_cast<unsigned>( ( BPS / 8 ) ) != inputBytesPerSample() )
);
if ( ioParametersChanged )
{
inputSampleRate () = sampleRate ;
numberOfInChannels () = numChannels;
inputBytesPerSample() = BPS / 8 ;
}
return ioParametersChanged;
}
//************************************
// Method: config
// FullName: WinAMP::OutModule::WAASIOOut::config
// Access: public static
//************************************
// Meyers singleton for lazy loading of the configuration (to minimize stalling
// WinAMP on startup).
WAASIOOut::Config & WAASIOOut::config()
{
static Config config;
return config;
}
//************************************
// Method: getMaxLatency
// FullName: WinAMP::OutModule::WAASIOOut::getMaxLatency
// Access: public
//************************************
long WAASIOOut::getMaxLatency()
{
assert( !devices().empty() );
long maxLatency( 0 );
for( Devices::iterator pDevice( devices().begin() ); pDevice != devices().end(); ++pDevice )
{
long inputLatency, outputLatency;
VERIFY( pDevice->getLatenciesInSamples( inputLatency, outputLatency ) == ASE_OK );
maxLatency = (std::max)( maxLatency, outputLatency );
}
assert( maxLatency );
return maxLatency;
}
//************************************
// Method: initialize
// FullName: WinAMP::OutModule::WAASIOOut::initialize
// Access: public
//************************************
bool WAASIOOut::initialize()
{
shouldReinitialize_ = false;
// Reinitialize COM and load ASIO devices.
if ( comInitialized() )
uninitializeCOM();
return initializeCOM() && loadDevices();
}
//************************************
// Method: initializeCOM
// FullName: WinAMP::OutModule::WAASIOOut::initializeCOM
// Access: public
//************************************
bool WAASIOOut::initializeCOM()
{
assert( !comInitialized_ );
return comInitialized_ = InitializeCOM( asioCOMInitializationFlags );
}
//************************************
// Method: initialized
// FullName: WinAMP::OutModule::WAASIOOut::initialized
// Access: public
//************************************
bool WAASIOOut::initialized() const
{
return !WAASIOOut::singleton().devices().empty();
}
//************************************
// Method: loadDevices
// FullName: WinAMP::OutModule::WAASIOOut::loadDevices
// Access: private
//************************************
bool WAASIOOut::loadDevices()
{
// Clean up.
devices().clear();
resetWriters();
activeWriters().clear();
//::CoFreeUnusedLibraries(); // ...first test the usefulness...
// Load driver list.
ASIODriverList const drivers;
// Initialize devices.
for
(
BYTE const * pDeviceIndex( &config().deviceIndices()[ 0 ] );
( *pDeviceIndex != Config::terminator ) && ( pDeviceIndex != ( config().deviceIndices() + sizeof( Config::DeviceIndices ) ) );
++pDeviceIndex
)
{
if ( *pDeviceIndex >= drivers.size() )
// Driver index out of range, skip it.
continue;
ASIODriverData const driverData( drivers, *pDeviceIndex );
ASIODriver driver;
if ( driver.initialize( driverData, WAOutModule::singleton().WinAMPWindow() ) == ASIODriver::Initialized )
{
devices().push_back( Device( driver, driverData.path().c_str() ) );
assert( devices().back().allChannelsAreOfSameType() && "Wasiona currently doesn't support devices with channels with different sample types." );
}
else
{
::MessageBox
(
WAOutModule::singleton().WinAMPWindow(),
driver.getInitializationErrorMessage(),
driverData.description(),
MB_ICONERROR
);
}
}
return !devices().empty();
}
//************************************
// Method: numberOfInChannels
// FullName: WinAMP::OutModule::WAASIOOut::numberOfInChannels
// Access: public
//************************************
// In the current limited state of the project, the number of channels is
// limited to 2 and channel expansion and/or remapping is not yet supported so
// this function is also used to query for the number of output channels. This
// is to be fixed and cleaned up...
//************************************
unsigned & WAASIOOut::numberOfInChannels()
{
assert( !numberOfInputChannels_ || numberOfInputChannels_ == 2 );
return numberOfInputChannels_;
}
//************************************
// Method: updateDevicesAndWritersWithNewIOParameters
// FullName: WinAMP::OutModule::WAASIOOut::updateDevicesAndWritersWithNewIOParameters
// Access: public
//************************************
bool WAASIOOut::updateDevicesAndWritersWithNewIOParameters()
{
// (todo) try to remove or better explain these magic numbers.
size_t const maxExpectedInPluginSleepTime( 15 );
size_t const minimumSamplesNeededForWaitingForInPluginSleepTime( maxExpectedInPluginSleepTime * inputSampleRate() / 1000 );
// (todo) if a fixed size for the buffers persists, change the MCB class to
// use a fixed size buffer instead of a std::vector.
size_t const bufferSizePerChannel( 8 * maxWAInputChunk );
size_t const inputBitsPerSample ( inputBytesPerSample() * 8 );
bool atLeastOneDeviceWriterPairCanPlayTheFormat( false );
for( WAASIOOut::Devices::iterator pDevice( WAASIOOut::singleton().devices().begin() ); pDevice != WAASIOOut::singleton().devices().end(); ++pDevice )
{
if ( pDevice->state() == Device::Prepared )
VERIFY( pDevice->disposeBuffers() == ASE_OK );
// (todo) Fix this ugly const_cast.
Writer & writer( const_cast<Writer &>( getReadersWriter( pDevice->reader() ) ) );
size_t const bufferSizeInSamplesPerChannel( pDevice->setOutputFormat( inputSampleRate(), numberOfInChannels() ) );
if ( bufferSizeInSamplesPerChannel )
{
pDevice->cacheOutputBufferSize( writer.outputBytesPerSample(), bufferSizeInSamplesPerChannel );
bool const writerSupportsNewIOParameters( writer.setUpForIOParameters( inputBitsPerSample, inputSampleRate() ) );
Writers::const_iterator const pActiveWriter( std::find( activeWriters().begin(), activeWriters().end(), &writer ) );
if ( ( pActiveWriter == activeWriters().end() ) && writerSupportsNewIOParameters )
{
writer.buffer().resize
(
numberOfInChannels(),
bufferSizePerChannel,
minimumSamplesNeededForWaitingForInPluginSleepTime * writer.outputBytesPerSample()
);
activeWriters().push_back( &writer );
}
else
if ( ( pActiveWriter != activeWriters().end() ) && !writerSupportsNewIOParameters )
{
assert( *pActiveWriter == &writer );
activeWriters().erase( pActiveWriter );
}
atLeastOneDeviceWriterPairCanPlayTheFormat |= writerSupportsNewIOParameters;
}
}
WAOutModule::WriteCallback writeCallback;
if ( atLeastOneDeviceWriterPairCanPlayTheFormat )
{
writeCallback = ( activeWriters().size() == 1 )
? activeWriters().front()->callback()
: GroupWrite;
}
else
{
inputSampleRate() = numberOfInChannels() = inputBytesPerSample() = 0;
writeCallback = NULL;
}
WAOutModule::singleton().setWriteCallback( writeCallback );
return writeCallback != NULL;
}
//************************************
// Method: pauseDevices
// FullName: WinAMP::OutModule::WAASIOOut::pauseDevices
// Access: public
//************************************
void WAASIOOut::pauseDevices()
{
for( Devices::iterator pDevice( devices().begin() ); pDevice != devices().end(); ++pDevice )
if ( pDevice->state() == Device::Running )
VERIFY( pDevice->stop() );
}
//************************************
// Method: startDevices
// FullName: WinAMP::OutModule::WAASIOOut::startDevices
// Access: public
//************************************
void WAASIOOut::startDevices()
{
for( Devices::iterator pDevice( devices().begin() ); pDevice != devices().end(); ++pDevice )
{
if( pDevice->state() == Device::Prepared )
{
for ( unsigned int i( 0 ); i < numberOfInChannels(); ++i )
{
// (todo) ASIO SDK says that (only) the buffer B (or 1) should
// already be filled prior to the call to IASIO::start() but
// 'leftover noise' can still be heard if both are not zeroed
// out. Investigate this.
// (todo) No zeroing should be done in case this start is only a
// resume from a pause.
::ZeroMemory( pDevice->outBuffers()[ i ][ 0 ], pDevice->outputBufferSizeInBytesPerChannel() );
::ZeroMemory( pDevice->outBuffers()[ i ][ 1 ], pDevice->outputBufferSizeInBytesPerChannel() );
}
VERIFY( pDevice->start() );
}
}
}
//************************************
// Method: stopDevices
// FullName: WinAMP::OutModule::WAASIOOut::stopDevices
// Access: public
//************************************
void WAASIOOut::stopDevices()
{
for( Devices::iterator pDevice( devices().begin() ); pDevice != devices().end(); ++pDevice )
{
VERIFY( pDevice->stop() );
VERIFY( pDevice->disposeBuffers() == ASE_OK );
}
}
//************************************
// Method: setCurrentDeviceTime
// FullName: WinAMP::OutModule::WAASIOOut::setCurrentDeviceTime
// Access: public
//************************************
void WAASIOOut::setCurrentDeviceTime( ASIOTime const * pCurrentASIOTime, size_t const underflowBytes, Device const & device )
{
// We keep track of the sample position/time for only a single device.
if ( &device != &devices().front() )
return;
ASIOTime asioTime;
if ( !pCurrentASIOTime )
{
VERIFY( device.getSamplePosition( asioTime ) == ASE_OK );
pCurrentASIOTime = &asioTime;
}
else
{
assert( pCurrentASIOTime->timeInfo.speed == 1. );
assert( pCurrentASIOTime->timeInfo.sampleRate == inputSampleRate() );
}
// Using only 32 bits of sample position resolution provides for a ~27
// hours maximum track time (@44100 kHz) and that "should be enough for
// everyone" :-D.
assert( pCurrentASIOTime->timeInfo.samplePosition.hi == 0 );
if ( pCurrentASIOTime->timeInfo.samplePosition.lo == 0 )
return;
underflowSamples_ += underflowBytes / getReadersWriter( device.reader() ).outputBytesPerSample();
assert( underflowSamples_ <= pCurrentASIOTime->timeInfo.samplePosition.lo );
size_t const newSamplePosition( pCurrentASIOTime->timeInfo.samplePosition.lo - underflowSamples_ );
// Doing the following is actually simpler than using timeInfo.systemTime
// and doing long long or float arithmetic and/or synchronizing with the
// system clock.
lastASIOOutputTime_ =
(
( ( newSamplePosition / inputSampleRate() ) * 1000 ) +
( ( ( newSamplePosition % inputSampleRate() ) * 1000 ) / inputSampleRate() )
);
}
//************************************
// Method: SetVolumeLazy
// FullName: WinAMP::OutModule::WAASIOOut::SetVolumeLazy
// Access: public static
//************************************
void __cdecl WAASIOOut::SetVolumeLazy( int const volume )
{
assert( WAOutModule::singleton().getWAOutModule().SetVolume == &SetVolumeLazy );
setVolumeAndPanningCallbacks();
assert( WAOutModule::singleton().getWAOutModule().SetVolume != &SetVolumeLazy );
WAOutModule::singleton().getWAOutModule().SetVolume( volume );
}
//************************************
// Method: setVolumeAndPanningCallbacks
// FullName: WinAMP::OutModule::WAASIOOut::setVolumeAndPanningCallbacks
// Access: public
//************************************
void WAASIOOut::setVolumeAndPanningCallbacks()
{
switch ( config().volumeControler() )
{
case Config::Void:
WAOutModule::singleton().setSetVolumeCallback( &WAOutModule::SetVolumeVoid );
WAOutModule::singleton().setSetPanCallback ( &WAOutModule::SetPanVoid );
break;
case Config::waveOUT:
WAOutModule::singleton().setSetVolumeCallback( &WAOutModule::SetVolumeWaveOut );
WAOutModule::singleton().setSetPanCallback ( &WAOutModule::SetPanWaveOut );
break;
case Config::mixerAPI:
WAOutModule::singleton().setSetVolumeCallback( &WAOutModule::SetVolumeMixer );
WAOutModule::singleton().setSetPanCallback ( &WAOutModule::SetPanMixer );
break;
case Config::ASIO:
WAOutModule::singleton().setSetVolumeCallback( &SetVolumeASIO );
WAOutModule::singleton().setSetPanCallback ( &SetPanASIO );
break;
default:
assert( false );
__assume( false );
}
}
//************************************
// Method: resetWriters
// FullName: WinAMP::OutModule::WAASIOOut::resetWriters
// Access: public
//************************************
void WAASIOOut::resetWriters()
{
for ( Writers::iterator pWriter( activeWriters().begin() ); pWriter != activeWriters().end(); ++pWriter )
(*pWriter)->buffer().reset();
}
//************************************
// Method: resetTrackVariables
// FullName: WinAMP::OutModule::WAASIOOut::resetTrackVariables
// Access: public
//************************************
void WAASIOOut::resetTrackVariables()
{
lastASIOOutputTime_ = 0;
underflowSamples_ = 0;
timeAlreadyPlayedBeforeLastUnpause_ = 0;
// WinAMP remembers this/sets it explicitly when necessary.
//gain_ = ASIODriver::zero_dB;
}
//************************************
// Method: pause
// FullName: WinAMP::OutModule::WAASIOOut::pause
// Access: public
//************************************
BOOL WAASIOOut::pause( int const newPauseState )
{
assert( ( newPauseState == FALSE ) || ( newPauseState == TRUE ) );
assert( newPauseState != paused() );
if ( newPauseState ) { underflowSamples_ = 0; timeAlreadyPlayedBeforeLastUnpause_ += lastASIOOutputTime_; pauseDevices(); }
else { startDevices(); }
int const oldPauseState( paused() );
pause_ = newPauseState;
return oldPauseState;
}
//************************************
// Method: Device
// FullName: WinAMP::OutModule::WAASIOOut::Device::Device
// Access: public
//************************************
WAASIOOut::Device::Device( ASIODriver const & driver, TCHAR const * const driverModulePath )
:
Base ( BufferSwitchTimeInfo, BufferSwitch, SampleRateDidChange, ASIOMessage, driver ),
bufferReader_ ( getOutputSampleTypeWriter( getSampleType() ).buffer().createNewReader() ),
supportsCallbackSwitching_( Base::supportsCallbackSwitching() )
{
if ( !supportsCallbackSwitching_ )
Base::hookDriverCreateThread( driverModulePath );
}
//************************************
// Method: cacheOutputBufferSize
// FullName: WinAMP::OutModule::WAASIOOut::Device::cacheOutputBufferSize
// Access: public
//************************************
void WAASIOOut::Device::cacheOutputBufferSize( size_t const outputBytesPerSample, size_t const outputBufferSizeInSamplesPerChannel )
{
outputBufferSizeInBytesPerChannel_ = outputBufferSizeInSamplesPerChannel * outputBytesPerSample;
}
//************************************
// Method: indicesEnd
// FullName: WinAMP::OutModule::WAASIOOut::Config::indicesEnd
// Access: public
//************************************
BYTE const * WAASIOOut::Config::indicesEnd() const
{
return std::find( indicesBegin(), indicesBegin() + sizeof( DeviceIndices ), terminator );
}
} // namespace OutModule
} // namespace WinAMP