Click here to Skip to main content
15,892,298 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.2K   728   23  
A tiny WinAMP output DLL that uses a C++ replacement of the official ASIO SDK that supports multiple ASIO devices.
#pragma once

#include "WAOutModule.hpp"

#include "ASIODevice.hpp"

#include "buffers.hpp"
#include "resource.h"

#include <list>
#include <map>
#include <vector>

namespace WinAMP
{

namespace OutModule
{

class Writer;

//  The maximum number of supported channels is a compile time parameter to
// avoid the extra dereferencing related to run-time allocated arrays/containers
// /buffers and the space overhead for any practical value is negligible.
//  It is specified through a compile time constant (instead as a template
// parameter) to avoid template parameter pollution/ugliness of every
// out-of-line declaration/definition.

size_t const maxSupportedChannels = 2;


////////////////////////////////////////////////////////////////////////////////
//
//  WAASIOOut
//  ---------
//
//  A singleton class modeling the main logic of the Wasiona plugin.
//
////////////////////////////////////////////////////////////////////////////////

class WAASIOOut
{
public:
    class Config;

    ////////////////////////////////////////////////////////////////////////////
    //
    //  WAASIOOut::Device
    //  -----------------
    //
    //  Extends the ASIODevice class with the functionality needed by a WinAMP
    // output plugin.
    //
    ////////////////////////////////////////////////////////////////////////////

    class Device : public ASIODevice<0, maxSupportedChannels>
    {
    typedef ASIODevice<0, maxSupportedChannels> Base;
    public:
        Device( ASIODriver const &, TCHAR const * driverModulePath );

        static Device & getThreadInstance() { return static_cast<Device &>( Base::getThreadInstance() ); }

        size_t outputBufferSizeInBytesPerChannel() const { return outputBufferSizeInBytesPerChannel_; }
        void cacheOutputBufferSize( size_t outputBytesPerSample, size_t outputBufferSizeInSamplesPerChannel );

        bool start() { return supportsCallbackSwitching_ ? Base::startUsingCallbackSwitching() : Base::start(); }

        MultiChannelBuffer::Reader       & reader()       { return bufferReader_; }
        MultiChannelBuffer::Reader const & reader() const { return const_cast<Device &>( *this ).reader(); }

    private:
        MultiChannelBuffer::Reader bufferReader_;
        size_t outputBufferSizeInBytesPerChannel_;
        bool const supportsCallbackSwitching_;
    };

    typedef MultiChannelBuffer Buffer;
    //typedef std::vector<Writer *> Writers;    // ...trading speed for space
    typedef std::list<Writer *> Writers;        // with std::list over
    //typedef std::vector<Device> Devices;      // std::vector...
    typedef std::list<Device> Devices;

public:
    static WAASIOOut & singleton() { return singleton_; }

    Devices & devices() { return devices_; }

    Writers       & activeWriters()       { return inWriters_; }
    Writers const & activeWriters() const { return const_cast<WAASIOOut &>( *this ).inWriters_; }
    static Config & config ();

    unsigned & inputSampleRate    () { return sampleRate_; }
    unsigned & inputBytesPerSample() { return inputBytesPerSample_; }
    unsigned & numberOfInChannels ();

    long     & gain()                { return gain_; }

    bool checkAndSaveNewIOParameters( int sampleRate, int numChannels, int BPS );

    bool initialized() const;
    bool initialize();

    bool updateDevicesAndWritersWithNewIOParameters();

    long getMaxLatency();

    void pauseDevices();
    void startDevices();
    void stopDevices ();

    BOOL pause ( int newPauseState );
    BOOL paused() const { return pause_; }
    
    void          resetTrackVariables();
    void          setCurrentDeviceTime( ASIOTime const *, size_t underflowBytes, Device const & );
    unsigned long trackOutputTime() const { return timeAlreadyPlayedBeforeLastUnpause_ + lastASIOOutputTime_; }

    void resetWriters();
    void clear() { devices_.clear(); inWriters_.clear(); }

    bool comInitialized() const { return comInitialized_; }
    bool initializeCOM();
    void uninitializeCOM() { ::CoUninitialize(); comInitialized_ = false; }

    bool shouldReinitialize() const { return shouldReinitialize_; }
    void setShouldReinitialize() { shouldReinitialize_ = true; }

    static int          WAOutModuleID()          { return 76647; }
    static char const * WAOutModuleDescription() { return "wasiona: experimental multi-device ASIO output plug-in"; }
    static char const * IniSection()             { return "wasiona"; }

    static void __cdecl SetVolumeLazy( int volume );
    static void __cdecl SetVolumeASIO( int volume );
    static void __cdecl SetPanASIO   ( int pan    );

    static void setVolumeAndPanningCallbacks();

private:
    WAASIOOut();

    bool loadDevices();

private: // ASIO callbacks
    static void       __cdecl BufferSwitch        (             long doubleBufferIndex, ASIOBool directProcess );
    static ASIOTime * __cdecl BufferSwitchTimeInfo( ASIOTime *, long doubleBufferIndex, ASIOBool directProcess );
    static void       __cdecl SampleRateDidChange ( ASIOSampleRate );
    static long       __cdecl ASIOMessage         ( long selector, long value, void * message, double * opt );

private:
    Devices devices_  ;
    Writers inWriters_;

    unsigned int numberOfInputChannels_             ;
    unsigned int inputBytesPerSample_               ;
    unsigned int sampleRate_                        ;
    unsigned int lastASIOOutputTime_                ;
    unsigned int underflowSamples_                  ;
    unsigned int timeAlreadyPlayedBeforeLastUnpause_;
    BOOL         pause_                             ;
    long         gain_                              ;

    bool comInitialized_    ;
    bool shouldReinitialize_;

    static WAASIOOut singleton_;
};


////////////////////////////////////////////////////////////////////////////////
//
//  WAASIOOut::Config
//  -----------------
//
//  Wraps the configuration code and the interaction with WA's plugin.ini.
//
// (todo) To be cleaned up, documented and improved, probably the least mature
//        part of the code.
//
////////////////////////////////////////////////////////////////////////////////

class WAASIOOut::Config
{
public:
    enum VolumeControler
    {
        Void     = ID_DISABLED,
        waveOUT  = ID_WAVEOUT ,
        mixerAPI = ID_MIXERAPI,
        ASIO     = ID_ASIO
    };

    typedef BYTE DeviceIndices[ 10 ];

    static BYTE const terminator = static_cast<BYTE>( LB_ERR );

public:
    Config()
    {
        if ( !ReadConfig( *this, WAASIOOut::IniSection() ) || ( structVersion_ != currentConfigVersion ) )
            setDefault();
    }
    ~Config()
    {
        VERIFY( SaveConfig( *this, WAASIOOut::IniSection() ) );
    }

    void setDefault()
    {
        ::ZeroMemory( this, sizeof( *this ) );
        structVersion_ = currentConfigVersion;
        volumeControler_ = mixerAPI;
        deviceIndices_[ 1 ] = terminator;
    }

    VolumeControler & volumeControler() { return volumeControler_; }
    DeviceIndices   & deviceIndices  () { return deviceIndices_  ; }
    BYTE const * indicesBegin() const { return &deviceIndices_[ 0 ]; }
    BYTE const * indicesEnd  () const;

private:
    BYTE structVersion_;
    VolumeControler volumeControler_;
    DeviceIndices deviceIndices_;

    static BYTE const currentConfigVersion = 1;
#if 0  // Disabled until implemented.
    DWORD priority_;
    unsigned outputSamplingRate_;
    BYTE latency_;
    BYTE minimumOutputChannels_; // for example for mono to stereo expansion
    BYTE maximumOutputChannels_; // for downmix forcing
    bool gapless_;
    bool directInputMonitor_;
    BYTE channelMappings_[ maxSupportedChannels ];
#endif  // Disabled until implemented.
};


}   // 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