Click here to Skip to main content
15,895,709 members
Articles / Desktop Programming / WTL

Multidevice ASIO output plugin for WinAMP

Rate me:
Please Sign up or sign in to vote.
4.80/5 (9 votes)
13 Feb 2009CDDL27 min read 48.3K   728   23  
A tiny WinAMP output DLL that uses a C++ replacement of the official ASIO SDK that supports multiple ASIO devices.
#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

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)


Written By
Software Developer Little Endian Ltd.
Croatia Croatia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions