Click here to Skip to main content
11,642,216 members (62,329 online)
Click here to Skip to main content
Add your own
alternative version

Multidevice ASIO output plugin for WinAMP

, 13 Feb 2009 CDDL 26.7K 370 21
A tiny WinAMP output DLL that uses a C++ replacement of the official ASIO SDK that supports multiple ASIO devices.
// configDlg.cpp : implementation of the ConfigurationDialog class
//
////////////////////////////////////////////////////////////////////////////////

#include "configDlg.hpp"

#include "ASIODevice.hpp"
#include "resource.h"
#include "WASIO.hpp" // (todo) try to handle this coupling better/cleaner.


//************************************
// Method:    InitializeDriverPage
// FullName:  InitializeDriverPage
// Access:    public 
//************************************

#pragma comment(lib, "shlwapi.lib")

void InitializeDriverPage( WTL::CPropertyPageWindow driverPage, HWND, ASIODriverData const * const pDriverData )
{
    assert( pDriverData );
    std::auto_ptr<ASIODriverData const> const deallocationGuard( pDriverData );

    //  Theoretically a pointer to the parent dialog could have been passed
    // (instead of a pointer to a, heap allocated, ASIODriverData) and then the
    // ASIODriverData object could have been locally created on the stack by
    // using an index into the driver list:
    // 
    //  int const driverIndex( pParentDialog->devicesSheet().HwndToIndex( driverPage ) );
    //      or
    //  int const driverIndex( pParentDialog->devicesSheet().GetActiveIndex() );
    //
    //  ASIODriverData const driverData( *pParentDialog->driverList().parentKey(), pParentDialog->driverList()[ driverIndex ]->c_str() );
    //
    // but for some reason HwndToIndex() and GetActiveIndex() do not work.
    
    ASIODriver driver( pDriverData->createInstance() );
    if ( !driver || ( driver.initializeDriverCOMObject( driverPage ) != ASIODriver::Initialized ) )
        return;

    long inputChannels, outputChannels;
    VERIFY( driver.getNumberOfChannels( inputChannels, outputChannels ) == ASE_OK );

    VERIFY( driverPage.SetDlgItemText       ( IDC_Description     , pDriverData->description()                                     ) );
    VERIFY( ::SetDlgItemTextW   ( driverPage, IDC_ClassID         , pDriverData->classIDStr()                                      ) );
          ( ::PathSetDlgItemPath( driverPage, IDC_Driver          , pDriverData->path().c_str()                                    ) );
    VERIFY( driverPage.SetDlgItemInt        ( IDC_Version         , driver.getDriverVersion()                                      ) );
    VERIFY( driverPage.SetDlgItemInt        ( IDC_InputChannels   , inputChannels                                                  ) );
    VERIFY( driverPage.SetDlgItemInt        ( IDC_OutputChannels  , outputChannels                                                 ) );
    VERIFY( driverPage.SetDlgItemText       ( IDC_InputSampleType , convertASIOSampleTypeToString( driver.getSampleType( true  ) ) ) );
    VERIFY( driverPage.SetDlgItemText       ( IDC_OutputSampleType, convertASIOSampleTypeToString( driver.getSampleType( false ) ) ) );

    VERIFY( driverPage.CheckDlgButton( IDC_Caps_InputMonitoring, driver.can( kAsioCanInputMonitor ) ) );
    VERIFY( driverPage.CheckDlgButton( IDC_Caps_TimeInfo       , driver.can( kAsioCanTimeInfo     ) ) );
    VERIFY( driverPage.CheckDlgButton( IDC_Caps_TimeCode       , driver.can( kAsioCanTimeCode     ) ) );
    VERIFY( driverPage.CheckDlgButton( IDC_Caps_Transport      , driver.can( kAsioCanTransport    ) ) );
    VERIFY( driverPage.CheckDlgButton( IDC_Caps_InputGain      , driver.can( kAsioCanInputGain    ) ) );
    VERIFY( driverPage.CheckDlgButton( IDC_Caps_InputMeter     , driver.can( kAsioCanInputMeter   ) ) );
    VERIFY( driverPage.CheckDlgButton( IDC_Caps_OutputGain     , driver.can( kAsioCanOutputGain   ) ) );
    VERIFY( driverPage.CheckDlgButton( IDC_Caps_OutputMeter    , driver.can( kAsioCanOutputMeter  ) ) );
}


////////////////////////////////////////////////////////////////////////////////
//
//  DriverPageCreator
//  -----------------
//
//  Functor for creating the driver/device property pages and.
//
////////////////////////////////////////////////////////////////////////////////

class DriverPageCreator
{
public:
    DriverPageCreator( InDialogPropertySheet & asioDevices, WTL::CListBox const & chosenDevices )
        :
        asioDevices_                ( asioDevices                                                       ),
        chosenDevices_              ( chosenDevices                                                     ),
        pChosenDeviceIndexBeginning_( WinAMP::OutModule::WAASIOOut::singleton().config().indicesBegin() ),
        pChosenDeviceIndexEnd_      ( WinAMP::OutModule::WAASIOOut::singleton().config().indicesEnd  () ),
        currentIndex_               ( 0                                                                 )
    {
    }

    void operator()( ASIODriverList::const_iterator const & driverIter )
    {
        //  ASIODriverData must be allocated on the heap to enable lazy creation
        // of property sheet pages (once the page is created it is its duty to
        // deallocate the object).
        ReadOnlyPropertyPage<ASIODriverData const *, &InitializeDriverPage> devicePage
        (
            new ASIODriverData( driverIter ),
            IDD_ASIODEVICE,
            driverIter->c_str()
        );
        asioDevices_.AddPage( devicePage );

        //  If the current device (for which we are creating a property sheet
        // page) is also specified in the configuration also add it to the
        // chosen devices list box.
        if ( std::find( pChosenDeviceIndexBeginning_, pChosenDeviceIndexEnd_, currentIndex_ ) != pChosenDeviceIndexEnd_ )
        {
            int const listEntryIndex( chosenDevices_.AddString( driverIter->c_str() ) );
            assert( listEntryIndex >= 0 );
            VERIFY( chosenDevices_.SetItemData( listEntryIndex, currentIndex_ ) >= 0 );
        }
        ++currentIndex_;
    }

private:
    InDialogPropertySheet & asioDevices_  ;
    WTL::CListBox           chosenDevices_;
    BYTE const * const      pChosenDeviceIndexBeginning_;
    BYTE const * const      pChosenDeviceIndexEnd_;
    BYTE                    currentIndex_;
};


//************************************
// Method:    ConfigurationDialog
// FullName:  ConfigurationDialog::ConfigurationDialog
// Access:    public 
//************************************

ConfigurationDialog::ConfigurationDialog()
    :
    asioDevices_       ( LPCTSTR( NULL ), 0, *this ),
    deviceListChanged_ ( false                     ),
    newVolumeControler_( 0                         )
{
}


//************************************
// Method:    OnInitDialog
// FullName:  ConfigurationDialog::OnInitDialog
// Access:    private
//************************************

bool ConfigurationDialog::OnInitDialog( HWND, LPARAM )
{
    if ( driverList().empty() )
    {
        VERIFY( ::MessageBox( *this, "No installed ASIO drivers found.", "Wasiona configuration", MB_OK | MB_ICONERROR ) );
        return false;
    }

    chosenDevices_ = GetDlgItem( IDC_LoadedDevices );

#pragma warning(push)
#pragma warning(disable: 4239)  // Nonstandard extension used : conversion from 'T' to 'T &'
    driverList().for_each( DriverPageCreator( devicesSheet(), chosenDevices() ) );
#pragma warning(pop)

    devicesSheet().Create( *this );
    WTL::CRect const devicesRect      ( GetDlgItemRect( *this, IDC_DriversBorder       ) );
    WTL::CRect const loadedDevicesRect( GetDlgItemRect( *this, IDC_LoadedDevicesBorder ) );
    devicesSheet().SetWindowPos( NULL, devicesRect.left + 6, devicesRect.top + 14, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE );

    TBBUTTON buttons[ 2 ] = { 0 };
    {
        DWORD const toolbarStyle  ( ATL_SIMPLE_TOOLBAR_STYLE | TBSTYLE_FLAT | CCS_NOPARENTALIGN | CCS_NORESIZE | CCS_VERT | CCS_NODIVIDER );
        DWORD const toolbarStyleEx( 0 );
        size_t const bitmapSize( 24 );
        size_t const buttonSize( 32 );

        int const horizontalPosition( devicesRect.right + ( loadedDevicesRect.left - devicesRect.right ) / 2 - buttonSize / 2 );
        int const verticalPosition  ( devicesRect.bottom / 2 - buttonSize );
        addRemoveToolbar_ = CreateEmptyToolbar( *this, toolbarStyle, toolbarStyleEx, horizontalPosition, verticalPosition, buttonSize, buttonSize * 2 );
        addRemoveToolbar_.SetBitmapSize( bitmapSize, bitmapSize );
        addRemoveToolbar_.LoadStdImages( IDB_HIST_LARGE_COLOR );
        
        buttons[ 0 ].idCommand = ID_AddDevice;
        buttons[ 0 ].iString = reinterpret_cast<INT_PTR>( _T("Add device...") );
        buttons[ 0 ].iBitmap = HIST_FORWARD;
        
        buttons[ 1 ].idCommand = ID_RemoveDevice;
        buttons[ 1 ].iString = reinterpret_cast<INT_PTR>( _T("Remove device...") );
        buttons[ 1 ].iBitmap = HIST_BACK;
        
        //buttons[ 0 ].fsState = buttons[ 1 ].fsState = TBSTATE_ENABLED | TBSTATE_WRAP;
        buttons[ 0 ].fsState = TBSTATE_ENABLED | TBSTATE_WRAP;
        buttons[ 1 ].fsState = TBSTATE_WRAP;

        addRemoveToolbar_.AddButtons( _countof( buttons ), buttons );
        addRemoveToolbar_.SetButtonSize( buttonSize, buttonSize );

        //addRemoveToolbar_.AutoSize();
    }

    {
        DWORD const toolbarStyle  ( ATL_SIMPLE_TOOLBAR_STYLE | TBSTYLE_LIST | TBSTYLE_FLAT | CCS_NOPARENTALIGN | CCS_NORESIZE | CCS_NODIVIDER );
        DWORD const toolbarStyleEx( TBSTYLE_EX_MIXEDBUTTONS );
        size_t const bitmapSize( 48 );
        size_t const buttonSize( 64 );

        setupToolbar_ = CreateEmptyToolbar( *this, toolbarStyle, toolbarStyleEx, loadedDevicesRect.left + loadedDevicesRect.Width() / 2 - buttonSize, loadedDevicesRect.bottom - buttonSize - 5, buttonSize * 2, buttonSize );

        setupToolbar_.SetBitmapSize( bitmapSize, bitmapSize );
        setupToolbar_.AddBitmap( 1, CreateBitmapFromIcon( 300, _T( "SndVol32.exe" ), false ).Detach() );
        setupToolbar_.AddBitmap( 1, CreateBitmapFromIcon( 274 ).Detach() );

        buttons[ 0 ].idCommand = ID_VolumeMethod;
        buttons[ 0 ].iString = reinterpret_cast<INT_PTR>( _T( "Volume setting method \n for the device..." ) );
        buttons[ 0 ].iBitmap = 0;
        buttons[ 0 ].fsStyle = BTNS_DROPDOWN | BTNS_WHOLEDROPDOWN;

        buttons[ 1 ].idCommand = ID_DeviceControlPanel;
        buttons[ 1 ].iString = reinterpret_cast<INT_PTR>( _T( "Device control panel..." ) );
        buttons[ 1 ].iBitmap = 1;

        buttons[ 0 ].fsState = buttons[ 1 ].fsState = 0; //TBSTATE_ENABLED;

        setupToolbar_.AddButtons( _countof( buttons ), buttons );
        setupToolbar_.SetButtonSize( buttonSize, buttonSize );
    }

    return true;
}


//************************************
// Method:    OnOK
// FullName:  ConfigurationDialog::OnOK
// Access:    private 
//************************************

LRESULT ConfigurationDialog::OnOK( WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/ )
{
    assert( wID == IDOK ); wID;

    if ( newVolumeControler_ )
    {
        using namespace WinAMP::OutModule;
        assert( newVolumeControler_ <= WAASIOOut::Config::ASIO );
        WAASIOOut::singleton().config().volumeControler() = static_cast<WAASIOOut::Config::VolumeControler>( newVolumeControler_ );
        WAASIOOut::singleton().setVolumeAndPanningCallbacks();
    }

    if ( deviceListChanged_ )
    {
        // (todo) make this part more readable.
        WinAMP::OutModule::WAASIOOut::Config::DeviceIndices newDeviceIndices;
        BYTE * pDeviceIndex( &newDeviceIndices[ 0 ] );
        int listItemIndex( 0 );
        do 
        {
            int const storedValue( chosenDevices().GetItemData( listItemIndex++ ) );
            *pDeviceIndex = static_cast<BYTE>( storedValue );
        } while ( *pDeviceIndex++ != static_cast<BYTE>( LB_ERR ) );

        if ( listItemIndex == 1 )
        {
            assert( newDeviceIndices[ 0 ] == static_cast<BYTE>( LB_ERR ) );
            assert( chosenDevices().GetCount() == 0 );
            ::MessageBox( *this, "You must select at least one device.", "Wasiona configuration", MB_OK | MB_ICONERROR );
            return 0;
        }
        std::memcpy( &WinAMP::OutModule::WAASIOOut::singleton().config().deviceIndices(), &newDeviceIndices, sizeof( newDeviceIndices ) );
        WinAMP::OutModule::WAASIOOut::singleton().setShouldReinitialize();
    }
        
    closeDialog( IDOK );
	return 0;
}


//************************************
// Method:    OnCancel
// FullName:  ConfigurationDialog::OnCancel
// Access:    private 
//************************************

LRESULT ConfigurationDialog::OnCancel( WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/ )
{
	closeDialog( wID );
	return 0;
}

//************************************
// Method:    closeDialog
// FullName:  ConfigurationDialog::closeDialog
// Access:    private
//************************************

void ConfigurationDialog::closeDialog( int const returnCode )
{
    VERIFY( EndDialog( returnCode ) );
}


//************************************
// Method:    OnVolumeMenu
// FullName:  ConfigurationDialog::OnVolumeMenu
// Access:    private
//************************************

LRESULT ConfigurationDialog::OnVolumeMenu( NMHDR const * const pNMHDR )
{
    RECT toolBarRect;
    VERIFY( WTL::CToolBarCtrl( pNMHDR->hwndFrom ).GetWindowRect( &toolBarRect ) );

    WTL::CMenu menu;
    VERIFY( menu.LoadMenu( IDR_VolumeControlMenu ) );
    setNewVolumeControler
    (
        menu.GetSubMenu( 0 ).TrackPopupMenuEx
        (
            TPM_NONOTIFY | TPM_RETURNCMD | TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON,
            toolBarRect.left,
            toolBarRect.bottom,
            setupToolbar_
        )
    );

    return TBDDRET_DEFAULT;
}


//************************************
// Method:    OnAddDeviceToList
// FullName:  ConfigurationDialog::OnAddDeviceToList
// Access:    private
//************************************

LRESULT ConfigurationDialog::OnAddDeviceToList( WORD const /* wNotifyCode */, WORD const /* wID */, HWND /* hWndCtl */, BOOL & /*bHandled*/ )
{
    TCHAR activeDriverPageName[ RegistryKey::maximumRegistryKeyNameLength ];
    TCITEM tcItem;
    tcItem.mask = TCIF_TEXT;
    tcItem.pszText = activeDriverPageName;
    tcItem.cchTextMax = _countof( activeDriverPageName );
    int const activeDeviceIndex( devicesSheet().GetActiveIndex() );
    assert( activeDeviceIndex >= 0 );
    WTL::CTabCtrl( devicesSheet().GetTabControl() ).GetItem( activeDeviceIndex, &tcItem );

    int const findResult( chosenDevices().FindStringExact( -1, activeDriverPageName ) );
    if ( findResult == LB_ERR )
    {
        int const listEntryIndex( chosenDevices().AddString( activeDriverPageName ) );
        assert( listEntryIndex >= 0 );
        VERIFY( chosenDevices().SetItemData( listEntryIndex, activeDeviceIndex ) >= 0 );
        setDeviceListChanged();
    }
    return 0;
}


//************************************
// Method:    OnRemoveDeviceFromList
// FullName:  ConfigurationDialog::OnRemoveDeviceFromList
// Access:    private
//************************************

LRESULT ConfigurationDialog::OnRemoveDeviceFromList( WORD const /* wNotifyCode */, WORD const /* wID */, HWND const /* hWndCtl */, BOOL & /*bHandled*/ )
{
    setDeviceListChanged();
    chosenDevices().DeleteString( chosenDevices().GetCurSel() );
    enableDeviceDependentButtons( false );
    return 0;
}


//************************************
// Method:    OnDeviceControlPanel
// FullName:  ConfigurationDialog::OnDeviceControlPanel
// Access:    private 
//************************************

LRESULT ConfigurationDialog::OnDeviceControlPanel( WORD, WORD, HWND, BOOL & bHandled )
{
    assert( bHandled == TRUE ); bHandled;
    int const currentSelection( chosenDevices().GetCurSel() );
    if ( currentSelection != LB_ERR )
    {
        TCHAR activeDriverPageName[ RegistryKey::maximumRegistryKeyNameLength ];
        int const nameLength( chosenDevices().GetText( currentSelection, activeDriverPageName ) );
        assert( nameLength != LB_ERR );
        IASIOPtr const pDriver( ASIODriverData( driverList().find( ASIODriverList::value_type( activeDriverPageName, nameLength ) ) ).createInstance() );
        if ( !pDriver || ( pDriver->controlPanel() != ASE_OK ) )
            //  This error reporting isn't actually of much use because some
            // drivers say ASE_OK but do not open the control panel.
            VERIFY( ::MessageBox( m_hWnd, "Unable to open an ASIO driver's control panel.", "Wasiona error message:", MB_OK | MB_ICONERROR ) );
    }
    return 0;
}


//************************************
// Method:    OnChosenDevicesSelectionChange
// FullName:  ConfigurationDialog::OnChosenDevicesSelectionChange
// Access:    private
//************************************

LRESULT ConfigurationDialog::OnChosenDevicesSelectionChange( WORD const wNotifyCode, WORD const wID, HWND const hListWnd, BOOL & /*bHandled*/ )
{
    assert( wNotifyCode == LBN_SELCHANGE          ); wNotifyCode;
    assert( wID         == IDC_LoadedDevices      ); wID;
    assert( hListWnd    == chosenDevices().m_hWnd ); hListWnd;

    // ListBox bug workaround. It sends the LBN_SELCHANGE message when there are
    // items in the ListBox that are all unselected and the user clicks inside
    // the ListBox but not on any of the items (so no actual selection is made).
    if ( chosenDevices().GetCurSel() != LB_ERR )
        enableDeviceDependentButtons( true );
    return 0;
}


//************************************
// Method:    OnChosenDevicesSelectionCancel
// FullName:  ConfigurationDialog::OnChosenDevicesSelectionCancel
// Access:    private
//************************************

LRESULT ConfigurationDialog::OnChosenDevicesSelectionCancel( WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/ )
{
    enableDeviceDependentButtons( false );
    return 0;
}


//************************************
// Method:    enableDeviceDependentButtons
// FullName:  ConfigurationDialog::enableDeviceDependentButtons
// Access:    private 
//************************************

void ConfigurationDialog::enableDeviceDependentButtons( bool const enable )
{
    setupToolbar_.    EnableButton( ID_VolumeMethod      , enable );
    setupToolbar_.    EnableButton( ID_DeviceControlPanel, enable );
    addRemoveToolbar_.EnableButton( ID_RemoveDevice      , enable );
}


//************************************
// Method:    OnURL
// FullName:  ConfigurationDialog::OnURL
// Access:    private
//************************************

LRESULT ConfigurationDialog::OnURL( NMHDR const * const pNMHDR )
{
    if ( ( pNMHDR->code == NM_CLICK ) || ( pNMHDR->code == NM_RETURN ) )
        VERIFY( ::ShellExecuteW( NULL, NULL, reinterpret_cast<NMLINK const &>( *pNMHDR ).item.szUrl, NULL, NULL, SW_SHOW ) > reinterpret_cast<HINSTANCE>( 32 ) );
    else
        SetMsgHandled( false );
    return 0;
}


//************************************
// Method:    enableOKButton
// FullName:  ConfigurationDialog::enableOKButton
// Access:    private 
//************************************

void ConfigurationDialog::enableOKButton()
{
    GetDlgItem( IDOK ).EnableWindow( true );
}


//************************************
// Method:    setNewVolumeControler
// FullName:  ConfigurationDialog::setNewVolumeControler
// Access:    private 
//************************************

void ConfigurationDialog::setNewVolumeControler( unsigned long const newVolumeControler )
{
    if ( newVolumeControler )
    {
        newVolumeControler_ = newVolumeControler;
        enableOKButton();
    }
}

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

You may also be interested in...

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