Click here to Skip to main content
11,477,508 members (72,381 online)
Click here to Skip to main content
Add your own
alternative version

Multidevice ASIO output plugin for WinAMP

, 13 Feb 2009 CDDL 26.2K 361 21
A tiny WinAMP output DLL that uses a C++ replacement of the official ASIO SDK that supports multiple ASIO devices.
#include "buffers.hpp"

#include <algorithm>
#include <type_traits>


////////////////////////////////////////////////////////////////////////////////
//
//  Helpers and private implementation details.
//  -------------------------------------------
//
////////////////////////////////////////////////////////////////////////////////

namespace // anonymous
{
    //************************************
    // Method:    InterlockedAdd
    // FullName:  InterlockedAdd<T>
    // Access:    global
    //************************************

    template <typename T>
    void InterlockedAdd( T & t, long const addend )
    {
        assert( std::tr1::is_pod<T>::value );
        assert( sizeof( T ) == sizeof( long ) );
        //  Even with full optimization, intrinsics on and optimize for speed
        // the (MSVC 9.0) compiler chooses the Windows kernel implementation of
        // InterlockedExchangeAdd so here the intrinsic version is forcibly
        // called.
        _InterlockedExchangeAdd( &reinterpret_cast<long &>( t ), addend );
    }



    //************************************
    // Method:    InterlockedIteratorAdvance
    // FullName:  InterlockedIteratorAdvance<RandomAccessIterator>
    // Access:    global
    //************************************

    template <class RandomAccessIterator>
    void InterlockedIteratorAdvance( RandomAccessIterator & iterator, long const offset )
    {
    #if _SECURE_SCL || _HAS_ITERATOR_DEBUGGING
        //  Perform/force a manual check as the real modification will be done
        // "behind the checking code's back".
        RandomAccessIterator( iterator ) + offset;
        InterlockedAdd( iterator._Myptr, offset );
    #else
        assert( sizeof( firstChannelBeginning_ ) == sizeof( long ) );
        assert( std::tr1::has_trivial_destructor<RandomAccessIterator>::value );
        InterlockedAdd( iterator       , offset );
    #endif
    }
}  // namespace anonymous


//************************************
// Method:    MultiChannelBuffer
// FullName:  MultiChannelBuffer::MultiChannelBuffer
// Access:    public 
//************************************

MultiChannelBuffer::MultiChannelBuffer()
    :
    buffer_                    ( 1               ), // this actually reduces the binary by a chunk (512 bytes)...!?
    channelSize_               ( 0               ),
    bytesWritten_              ( 0               ),
    minimumSpaceThatMustRemain_( 0               ),
    writePosition_             ( buffer_.begin() )
{
}


//************************************
// Method:    allReadersHaveTheSameEndMarker
// FullName:  MultiChannelBuffer::allReadersHaveTheSameEndMarker
// Access:    private 
//************************************

bool MultiChannelBuffer::allReadersHaveTheSameEndMarker() const
{
    // (todo) This check requires atomic iterator (which is non-pod structure
    //        under secure SCL) access.
#if 0 // Disabled until fully implemented.
    Buffer::const_iterator const endMarker( writeCursor() );
    for( Readers::const_iterator pReader( ++readers_.begin() ); pReader != readers_.end(); ++pReader )
    {
        Reader & reader( **pReader );
        if ( ( reader.firstChannelBeginning_ + reader.availableDataPerChannel_  ) != endMarker )
            return false;
    }
#endif // Disabled until fully implemented.
    return true;
}


//************************************
// Method:    canWrite
// FullName:  MultiChannelBuffer::canWrite
// Access:    public 
//************************************

size_t MultiChannelBuffer::canWrite() const
{
    Buffer::const_iterator const readPosition( slowestReaderPosition()      );
    size_t                 const unreadSize  ( writeCursor() - readPosition );
    //  The unread size could be read directly from the "slowest reader"
    // ( unreadSize == slowestReader().availableDataPerChannel() ) but that
    // would require reading it atomically/locked together with its position
    // (firstChannelBeginning) because they are modified independently from
    // different threads so this is simpler to do.
    if ( unreadSize == 0 )
    {
        assert( allReadersHaveTheSameEndMarker() );

        for( Readers::const_iterator pReader( readers_.begin() ); pReader != readers_.end(); ++pReader )
        {
            //  If even the slowest reader has nothing to read then all readers
            // (must) have nothing to read.
            assert( ( **pReader ).availableDataPerChannel_ == 0            );
            assert( ( **pReader ).firstChannelBeginning_   == readPosition );
            ( **pReader ).firstChannelBeginning_ = buffer_.begin();
        }

        writePosition_ = buffer_.begin();

        assert( allReadersHaveTheSameEndMarker() );
        
        return channelSize();
    }

    //  If we are at/near the end of the buffer and the
    // minimumSpaceThatMustRemain_ threshold has been reached move the remaining
    // data to the beginning of the buffer.
    // (todo) This design and code is modeled against the WinAMP API and
    //        input-output module interaction with which a maximum size limit
    //        for write chunks is known (it is currently coupled so much that
    //        even the maxWAInputChunk/8192 deduced from the WA SDK out.h header
    //        is directly copied here instead of specified from outside). Once
    //        the 'final' design settles and the reusability potential of the
    //        MultiChannelBuffer class becomes more clear this coupling should
    //        be removed.
    size_t const spaceAtEnd( channelSize() - writeCursorOffset() );
    size_t const maxInputChunk( 8192 ); // maxWAInputChunk
    if ( ( spaceAtEnd < maxInputChunk ) && ( unreadSize < minimumSpaceThatMustRemain_ ) )
    {
        assert( allReadersHaveTheSameEndMarker() );

        size_t const spaceAtBeginning( readPosition - buffer_.begin() );

        assert( ( spaceAtBeginning > spaceAtEnd ) && "Too small buffer size chosen. minimumSpaceThatMustRemain_ should be 'much smaller' than the total buffer size" );

        assert( ( spaceAtBeginning >= unreadSize ) && "Buffer overlap. Probably a too small buffer. Requires memmove(). Thread unsafe." );
        // (todo) try to optimize this so that the source and the destinatiion
        //        are always properly aligned to for the SSE optimized routine
        //        so that VEC_memcpy() can be called directly.
        for( Buffer::iterator pChannelBuffer( buffer_.begin() ); pChannelBuffer < buffer_.end(); pChannelBuffer += channelSize() )
            std::memcpy( &*pChannelBuffer, &pChannelBuffer[ spaceAtBeginning ], unreadSize );

        for( Readers::const_iterator pReader( readers_.begin() ); pReader != readers_.end(); ++pReader )
            // "reader.firstChannelBeginning_ -= readCursorOffset;":
            InterlockedIteratorAdvance( (**pReader).firstChannelBeginning_, -static_cast<long>( spaceAtBeginning ) );

        writePosition_ = buffer_.begin() + unreadSize;

        assert( spaceAtBeginning + spaceAtEnd == channelSize() - writeCursorOffset() );

        assert( allReadersHaveTheSameEndMarker() );

        return spaceAtBeginning + spaceAtEnd;
    }

    return spaceAtEnd;
}


//************************************
// Method:    createNewReader
// FullName:  MultiChannelBuffer::createNewReader
// Access:    public 
//************************************

MultiChannelBuffer::Reader MultiChannelBuffer::createNewReader() const
{
    assert( isInResetState() );
    return Reader( buffer_.begin(), *this );
}


//************************************
// Method:    getWritePointers
// FullName:  MultiChannelBuffer::getWritePointers
// Access:    public 
//************************************

MultiChannelBuffer::WritePointers MultiChannelBuffer::getWritePointers( size_t const sz )
{
    assert( sz <= ( channelSize() - writeCursorOffset() ) );  // "Non adjusting" canWrite.
    return WritePointers( writePosition_, channelSize(), sz, *this );
}


//************************************
// Method:    hasUnreadInput
// FullName:  MultiChannelBuffer::hasUnreadInput
// Access:    public 
//************************************

bool MultiChannelBuffer::hasUnreadInput() const
{
    //return slowestReader().availableDataPerChannel() != 0;
    for( Readers::const_iterator pReader( readers_.begin() ); pReader != readers_.end(); ++pReader )
        if ( (*pReader)->availableDataPerChannel() != 0 )
            return true;
    return false;
}


//************************************
// Method:    isInResetState
// FullName:  MultiChannelBuffer::isInResetState
// Access:    private 
//************************************

bool MultiChannelBuffer::isInResetState() const
{
    for( Readers::const_iterator pReader( readers_.begin() ); pReader != readers_.end(); ++pReader )
        if ( (*pReader)->firstChannelBeginning_ != buffer_.begin() )
            return false;
    return !bytesWritten_ && ( writePosition_ == buffer_.begin() );
}


//************************************
// Method:    isReaderRegistered
// FullName:  MultiChannelBuffer::isReaderRegistered
// Access:    private 
//************************************

bool MultiChannelBuffer::isReaderRegistered( Reader const & reader ) const
{
    return std::find( readers_.begin(), readers_.end(), &reader ) != readers_.end();
}


//************************************
// Method:    registerReader
// FullName:  MultiChannelBuffer::registerReader
// Access:    private 
//************************************

void MultiChannelBuffer::registerReader( Reader & reader ) const
{
    assert( isInResetState() );
    readers_.push_back( &reader );
}


//************************************
// Method:    reset
// FullName:  MultiChannelBuffer::reset
// Access:    public 
//************************************

void MultiChannelBuffer::reset()
{
    bytesWritten_  = 0;
    writePosition_ = buffer_.begin();
    for( Readers::const_iterator pReader( readers_.begin() ); pReader != readers_.end(); ++pReader )
    {
        Reader & reader( **pReader );
        reader.firstChannelBeginning_   = buffer_.begin();
        reader.availableDataPerChannel_ = 0;
    }
    assert( isInResetState() );
}


//************************************
// Method:    resize
// FullName:  MultiChannelBuffer::resize
// Access:    public 
//************************************

void MultiChannelBuffer::resize( size_t const numberOfChannels, size_t const bytesPerChannel, size_t const maximumExpectedWriteChunk )
{
    buffer_.resize( numberOfChannels * bytesPerChannel );
    channelSize_ = bytesPerChannel;
    assert( numberOfChannels == this->numberOfChannels() );
    minimumSpaceThatMustRemain_ = maximumExpectedWriteChunk;
    reset();
}


//************************************
// Method:    slowestReader
// FullName:  MultiChannelBuffer::slowestReader
// Access:    private 
//************************************

Buffer::const_iterator MultiChannelBuffer::slowestReaderPosition() const
{
    // (todo) recheck this for thread safety.
    Buffer::const_iterator position( (*readers_.begin())->firstChannelBeginning_ );
    for ( Readers::const_iterator pCurrentReader( ++readers_.begin() ); pCurrentReader != readers_.end(); ++pCurrentReader )
    {
        if ( (*pCurrentReader)->firstChannelBeginning_ < position )
            position = (*pCurrentReader)->firstChannelBeginning_;
    }
    return position;
}


//************************************
// Method:    unregisterReader
// FullName:  MultiChannelBuffer::unregisterReader
// Access:    private 
//************************************

void MultiChannelBuffer::unregisterReader( Reader & reader ) const
{
    assert( isReaderRegistered( reader ) );
    readers_.erase( std::find( readers_.begin(), readers_.end(), &reader ) );
}


//************************************
// Method:    writeCompletedCallback
// FullName:  MultiChannelBuffer::writeCompletedCallback
// Access:    public 
//************************************

void MultiChannelBuffer::writeCompletedCallback( size_t const sizeOfWrittenChunk )
{
    writePosition_ += sizeOfWrittenChunk;
    bytesWritten_  += sizeOfWrittenChunk;

    for( Readers::const_iterator pReader( readers_.begin() ); pReader != readers_.end(); ++pReader )
        InterlockedAdd( (**pReader).availableDataPerChannel_, sizeOfWrittenChunk );
}


//************************************
// Method:    Reader
// FullName:  MultiChannelBuffer::Reader::Reader
// Access:    public 
//************************************

MultiChannelBuffer::Reader::Reader( Reader const & source )
    :
    firstChannelBeginning_  ( source.firstChannelBeginning_   ),
    availableDataPerChannel_( source.availableDataPerChannel_ ),
    parent_                 ( source.parent_                  )
{
    parent_.registerReader( *this );
}


//************************************
// Method:    operator++
// FullName:  MultiChannelBuffer::Reader::ChannelIterator::operator++
// Access:    public 
//************************************

MultiChannelBuffer::Reader::ChannelIterator & MultiChannelBuffer::Reader::ChannelIterator::operator++()
{
#if _SECURE_SCL || _HAS_ITERATOR_DEBUGGING
    Buffer const & source( reinterpret_cast<Buffer const &>( *currentBegin_._Getmycont() ) );
    if ( ( source.end() - this->end() ) < static_cast<Buffer::difference_type>( channelSize_ ) )
    {
        currentBegin_ = source.end();
        availableDataPerChannel_ = 0;
    }
    else
#endif
        currentBegin_ += channelSize_;

    return *this;
}


//************************************
// Method:    getChunk
// FullName:  MultiChannelBuffer::Reader::getChunk
// Access:    public 
//************************************

MultiChannelBuffer::Reader::ChannelIterator MultiChannelBuffer::Reader::getChunk( size_t chunkSize )
{
    assert( chunkSize < parent_.channelSize() );
    chunkSize = (std::min)( chunkSize, availableDataPerChannel() );

    ChannelIterator const result( firstChannelBeginning_, chunkSize, parent_.channelSize() );

    // "availableDataPerChannel_ -= chunkSize;":
    InterlockedAdd( availableDataPerChannel_, -static_cast<int>( chunkSize ) );

    // "firstChannelBeginning_ += chunkSize;":
    InterlockedIteratorAdvance( firstChannelBeginning_, chunkSize );

    return result;
}


//************************************
// Method:    WritePointers
// FullName:  MultiChannelBuffer::WritePointers::WritePointers
// Access:    public 
//************************************

MultiChannelBuffer::WritePointers::WritePointers( Buffer::iterator const firstChannelWritePosition, size_t const channelSize, size_t const writeChunkSize, MultiChannelBuffer & parent )
    :
    firstChannelWritePosition_( firstChannelWritePosition ),
    channelSize_              ( channelSize               ),
    writeChunkSize_           ( writeChunkSize            ),
    parent_                   ( parent                    )
{
}


#ifdef _DEBUG // Ugly workaround for the RVO not working in DEBUG builds.
//************************************
// Method:    WritePointers
// FullName:  MultiChannelBuffer::WritePointers::WritePointers
// Access:    public 
//************************************

MultiChannelBuffer::WritePointers::WritePointers( MultiChannelBuffer::WritePointers const & source )
    :
    firstChannelWritePosition_( source.firstChannelWritePosition_ ),
    channelSize_              ( source.channelSize_               ),
    writeChunkSize_           ( source.writeChunkSize_            ),
    parent_                   ( source.parent_                    )
{
    const_cast<size_t &>( source.writeChunkSize_ ) = 0;
}
#endif


//************************************
// Method:    ~WritePointers
// FullName:  MultiChannelBuffer::WritePointers::~WritePointers
// Access:    public 
//************************************

MultiChannelBuffer::WritePointers::~WritePointers()
{
#ifdef _DEBUG // Ugly workaround for the RVO not working in DEBUG builds.
    if ( writeChunkSize_ )
#endif
        parent_.writeCompletedCallback( writeChunkSize_ );
}

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)

Share

About the Author

Domagoj Šarić
Software Developer Little Endian Ltd.
Croatia Croatia
No Biography provided

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.150520.1 | Last Updated 13 Feb 2009
Article Copyright 2009 by Domagoj Šarić
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid