ASIO driver loads twice

Hello brain trust,

I have a simple Windows program where I’m trying to play an audio file using the Universal Audio Thunderbolt ASIO device. After I init the device manager, the ASIO driver gets loaded twice, where I would expect it to happen only once. My sound initially plays but then gets cut off as the second ASIO instance seems to stomp on the first one, after which I can’t get any sound to render.

The first instance gets loaded directly as a result from calling AudioDeviceManager::initialise():

>	UADASIO.dll!UadAsio::CreateInstance(IUnknown * pUnk, HRESULT * phr) Line 66	C++
 	[Inline Frame] UADASIO.dll!CFactoryTemplate::CreateInstance(IUnknown *) Line 262	C++
 	UADASIO.dll!CClassFactory::CreateInstance(IUnknown * pUnkOuter, const _GUID & riid, void * * pv) Line 126	C++
 	[External Code]	
 	AudioPlayerTest.exe!juce::ASIOAudioIODevice::tryCreatingDriver(bool & crashed) Line 1126	C++
 	AudioPlayerTest.exe!juce::ASIOAudioIODevice::loadDriver() Line 1112	C++
 	AudioPlayerTest.exe!juce::ASIOAudioIODevice::openDevice() Line 1194	C++
 	AudioPlayerTest.exe!juce::ASIOAudioIODevice::ASIOAudioIODevice(juce::ASIOAudioIODeviceType * ownerType, const juce::String & devName, _GUID clsID, int slotNumber) Line 330	C++
 	AudioPlayerTest.exe!juce::ASIOAudioIODeviceType::createDevice(const juce::String & outputDeviceName, const juce::String & inputDeviceName) Line 1524	C++
 	AudioPlayerTest.exe!juce::AudioDeviceManager::setAudioDeviceSetup(const juce::AudioDeviceManager::AudioDeviceSetup & newSetup, bool treatAsChosenDevice) Line 566	C++
 	AudioPlayerTest.exe!juce::AudioDeviceManager::initialiseDefault(const juce::String & preferredDefaultDeviceName, const juce::AudioDeviceManager::AudioDeviceSetup * preferredSetupOptions) Line 276	C++
 	AudioPlayerTest.exe!juce::AudioDeviceManager::initialise(int numInputChannelsNeeded, int numOutputChannelsNeeded, const juce::XmlElement * xml, bool selectDefaultDeviceOnFailure, const juce::String & preferredDefaultDeviceName, const juce::AudioDeviceManager::AudioDeviceSetup * preferredSetupOptions) Line 239	C++
 	AudioPlayerTest.exe!MainComponent::InitDeviceManager() Line 109	C++
 	AudioPlayerTest.exe!MainComponent::MainComponent() Line 41	C++
 	AudioPlayerTest.exe!AudioPlayerTestApplication::MainWindow::MainWindow(juce::String name) Line 69	C++
 	AudioPlayerTest.exe!AudioPlayerTestApplication::initialise(const juce::String & commandLine) Line 30	C++
 	AudioPlayerTest.exe!juce::JUCEApplicationBase::initialiseApp() Line 297	C++
 	AudioPlayerTest.exe!juce::JUCEApplication::initialiseApp() Line 93	C++
 	AudioPlayerTest.exe!juce::JUCEApplicationBase::main() Line 256	C++
 	AudioPlayerTest.exe!WinMain(HINSTANCE__ * __formal, HINSTANCE__ * __formal, char * __formal, int __formal) Line 106	C++

The second one gets loaded by an internal timer in JUCE:

>	UADASIO.dll!UadAsio::CreateInstance(IUnknown * pUnk, HRESULT * phr) Line 66	C++
 	[Inline Frame] UADASIO.dll!CFactoryTemplate::CreateInstance(IUnknown *) Line 262	C++
 	UADASIO.dll!CClassFactory::CreateInstance(IUnknown * pUnkOuter, const _GUID & riid, void * * pv) Line 126	C++
 	[External Code]	
 	AudioPlayerTest.exe!juce::ASIOAudioIODevice::tryCreatingDriver(bool & crashed) Line 1126	C++
 	AudioPlayerTest.exe!juce::ASIOAudioIODevice::loadDriver() Line 1112	C++
 	AudioPlayerTest.exe!juce::ASIOAudioIODevice::open(const juce::BigInteger & inputChannels, const juce::BigInteger & outputChannels, double sr, int bufferSizeSamples) Line 463	C++
 	AudioPlayerTest.exe!juce::ASIOAudioIODevice::timerCallback() Line 720	C++
 	AudioPlayerTest.exe!juce::Timer::TimerThread::callTimers() Line 119	C++
 	AudioPlayerTest.exe!juce::Timer::TimerThread::CallTimersMessage::messageCallback() Line 181	C++
 	AudioPlayerTest.exe!juce::InternalMessageQueue::dispatchMessage(juce::MessageManager::MessageBase * message) Line 202	C++
 	AudioPlayerTest.exe!juce::InternalMessageQueue::dispatchMessages() Line 240	C++
 	AudioPlayerTest.exe!juce::InternalMessageQueue::dispatchNextMessage(bool returnIfNoPendingMessages) Line 125	C++
 	AudioPlayerTest.exe!juce::MessageManager::dispatchNextMessageOnSystemQueue(bool returnIfNoPendingMessages) Line 266	C++
 	AudioPlayerTest.exe!juce::MessageManager::runDispatchLoop() Line 130	C++
 	AudioPlayerTest.exe!juce::JUCEApplicationBase::main() Line 266	C++
 	AudioPlayerTest.exe!WinMain(HINSTANCE__ * __formal, HINSTANCE__ * __formal, char * __formal, int __formal) Line 106	C++

Why would JUCE make attempt to load the driver again if it’s (presumably) already loaded?

Is there something wrong in my initialization? In order to set the Universal Audio Thunderbolt device, I first have to call AudioDeviceManager::setCurrentAudioDeviceType() to set it to ASIO. Only after that can I call initialise() with the name of the preferred device.

Thanks in advance for any help.

Here is what my code looks like. Header:

#pragma once
#include <JuceHeader.h>

class MainComponent : public Component, public Button::Listener
{
public:
    friend class TransportSource;

    MainComponent();
    ~MainComponent();

    bool SetAudioFile(const File& audioFile);
    void Play();
    void Stop();

private:

    bool InitDeviceManager();
    void paint(Graphics& g) override;
    void resized() override;
    void buttonClicked(Button* inButton) override;

    // subclass AudioTransportSource so we can keep an eye on getNextAudioBlock()
    class TransportSource : public AudioTransportSource
    {
    public:
        void getNextAudioBlock(const AudioSourceChannelInfo& bufferToFill) override;
        void setParent(MainComponent* parent) { mParent = parent; } // just to access the logger. hey, it's a demo.
    private:
        MainComponent* mParent;
    };

    std::unique_ptr<FileLogger> mFileLogger;

    AudioDeviceManager    mAudioDeviceManager;
    AudioFormatManager    mAudioFormatManager;
    AudioSourcePlayer     mAudioSourcePlayer;
    AudioFormatReader*    mAudioFormatReader;
    TransportSource       mTransportSource;
    File                  mCurrentAudioFile;

    std::unique_ptr<AudioFormatReaderSource> mCurrentAudioFileSource;

    TextButton            mPlayButton;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};

CPP:

#include "MainComponent.h"

namespace
{
    String getListOfActiveBits(const BigInteger& b)
    {
        StringArray bits;

        for (int i = 0; i <= b.getHighestBit(); ++i)
            if (b[i])
                bits.add(String(i));

        return bits.joinIntoString(", ");
    }

    const int kComponentWidth = 600;
    const int kComponentHeight = 400;
    const int kButtonWidth = 100;
    const int kButtonHeight = 40;

    const String kLogFilePath(File::getSpecialLocation(File::currentExecutableFile).getParentDirectory().getFullPathName() + File::getSeparatorString() + "AudioPlayerTest.log");
    const String kLogFileWelcomeMessage("**********************************************************************");

    const File   kTestFile(File::getSpecialLocation(File::currentExecutableFile).getParentDirectory().getChildFile("test.wav"));
}

#define LOG_MESSAGE(x) mFileLogger->logMessage(x)

//==============================================================================
MainComponent::MainComponent() : mAudioFormatReader(nullptr)
{
    File logFile(kLogFilePath);
    mFileLogger.reset(new FileLogger(logFile, kLogFileWelcomeMessage));

    addAndMakeVisible(&mPlayButton);
    mPlayButton.setButtonText("PLAY");
    mPlayButton.addListener(this);

    setSize(kComponentWidth, kComponentHeight);

    if (InitDeviceManager())
    {
        mTransportSource.setParent(this);
        mAudioFormatManager.registerBasicFormats();

        LOG_MESSAGE("MainComponent ctor: adding audio callback");
        mAudioDeviceManager.addAudioCallback(&mAudioSourcePlayer);
        mAudioSourcePlayer.setSource(&mTransportSource);

        if (kTestFile.existsAsFile() && SetAudioFile(kTestFile))
        {
            Play();
        }
    }
}

MainComponent::~MainComponent()
{
    mTransportSource.setSource(nullptr);
    mAudioSourcePlayer.setSource(nullptr);

    mAudioDeviceManager.removeAudioCallback(&mAudioSourcePlayer);

    if (mAudioFormatReader)
        delete mAudioFormatReader;
}

bool MainComponent::InitDeviceManager()
{
    const String kPreferredDeviceName("Universal Audio Thunderbolt");
    String preferredDeviceName;
    String typeName;
    const OwnedArray< AudioIODeviceType >& devices = mAudioDeviceManager.getAvailableDeviceTypes();
    for (int i = 0; i < devices.size() && preferredDeviceName.isEmpty(); i++)
    {
        typeName = devices[i]->getTypeName();
        LOG_MESSAGE("\nDevice Type:  " + typeName);
        LOG_MESSAGE("Devices:");
        StringArray deviceNames = devices[i]->getDeviceNames();
        for (int j = 0; j < deviceNames.size() && preferredDeviceName.isEmpty(); j++)
        {
            String str = deviceNames[j];
            LOG_MESSAGE("  " + deviceNames[j]);
            if (deviceNames[j] == kPreferredDeviceName)
            {
                preferredDeviceName = deviceNames[j];
                LOG_MESSAGE(" -> found " + preferredDeviceName + "!\n");
            }
        }
    }

    String err;
    if (preferredDeviceName.isEmpty())
        err << kPreferredDeviceName << " device not found";

    if (err.isEmpty())
    {
        mAudioDeviceManager.setCurrentAudioDeviceType(typeName, true);

        const int kOutputsRequested = 16;
        const int kMinOutputChannelsNeeded = 16;

        AudioDeviceManager::AudioDeviceSetup setup;
        setup.inputDeviceName = setup.outputDeviceName = preferredDeviceName;
        setup.sampleRate = 44100;
        setup.bufferSize = 512;
        setup.outputChannels = kOutputsRequested;

        err = mAudioDeviceManager.initialise(0,                 // inputs requested
                                             kOutputsRequested, // outputs requested
                                             nullptr,           // saved XML state
                                             false,             // don't select default device if device in XML fails to open (don't care since we passed in nullptr)
                                             String(),          // preferred default device name
                                             &setup);

        if (err.isEmpty())
        {
            setup = mAudioDeviceManager.getAudioDeviceSetup(); // verify setup
            if (setup.outputChannels.toInteger() < kMinOutputChannelsNeeded)
                err << setup.outputChannels.toInteger() << " output channels created (minimum is " << kMinOutputChannelsNeeded << ")";
            else if (setup.outputDeviceName != kPreferredDeviceName)
                err << "output device is not a " << kPreferredDeviceName << " device!('" << setup.outputDeviceName << "')'";
            else
            {
                int outputsCreated = setup.outputChannels.toInteger();
                LOG_MESSAGE("MainComponent::InitDeviceManager() : " + String(kOutputsRequested) + " output channels requested, " + String(outputsCreated) + " created\n");
            }
        }
    }

    if (err.isEmpty())
    {
#ifdef _DEBUG
        AudioIODevice* device = mAudioDeviceManager.getCurrentAudioDevice();
        if (device)
        {
            BigInteger activeOutputChannels = device->getActiveOutputChannels();
            StringArray outputChannelNames = device->getOutputChannelNames();

            auto setup = mAudioDeviceManager.getAudioDeviceSetup();
            setup.useDefaultOutputChannels = false;

            LOG_MESSAGE("active channels: " + String(activeOutputChannels.toInteger()));
            LOG_MESSAGE("channel names:");
            for (int i = 0; i < outputChannelNames.size(); i++)
                LOG_MESSAGE("   " + outputChannelNames[i]);

            LOG_MESSAGE("Active output channels: " + getListOfActiveBits(device->getActiveOutputChannels()));
        }
        else
            err = "could not get current audio device!";
#endif
    }
    else
    {
        LOG_MESSAGE("MainComponent::InitDeviceManager() :  ERROR initializing AudioDeviceManager:  " + err.quoted());
    }

    return err.isEmpty();
}

void MainComponent::buttonClicked(Button* inButton)
{
    if (inButton == &mPlayButton)
    {
        if (mPlayButton.getButtonText() == "PLAY")
        {
            mPlayButton.setButtonText("STOP");
            mPlayButton.setToggleState(true, dontSendNotification);
            Play();
        }
        else
        {
            mPlayButton.setButtonText("PLAY");
            mPlayButton.setToggleState(false, dontSendNotification);
            Stop();
        }
    }
}

void MainComponent::paint (Graphics& g)
{
    // (Our component is opaque, so we must completely fill the background with a solid colour)
    g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
}

void MainComponent::resized()
{
   mPlayButton.centreWithSize(kButtonWidth, kButtonHeight);
}

void MainComponent::Play()
{
    LOG_MESSAGE("MainComponent::Play()");
    Stop();
    mTransportSource.setPosition(0);
    mTransportSource.start();
}

void MainComponent::Stop()
{
    LOG_MESSAGE("MainComponent::Stop()");
    if (mTransportSource.isPlaying())
        mTransportSource.stop();
}

bool MainComponent::SetAudioFile(const File& audioFile)
{
    // unload the previous file source and delete it..
    Stop();
    mTransportSource.setSource(nullptr);
    mCurrentAudioFileSource.reset();

    if (mAudioFormatReader)
        deleteAndZero(mAudioFormatReader);

    mCurrentAudioFile = audioFile;
    mAudioFormatReader = mAudioFormatManager.createReaderFor(mCurrentAudioFile);

    if (mAudioFormatReader != nullptr)
    {
        mCurrentAudioFileSource.reset(new AudioFormatReaderSource(mAudioFormatReader, false));
        mCurrentAudioFileSource->setLooping(true);

        // ..and plug it into our transport source
        mTransportSource.setSource(mCurrentAudioFileSource.get(), 0, nullptr, mAudioFormatReader->sampleRate);
        return true;
    }
    return false;
}

void MainComponent::TransportSource::getNextAudioBlock(const AudioSourceChannelInfo& bufferToFill)
{
    // print a log message every kInterval so we can kinda see when the callback gets killed
    const int kInterval = 50;
    static int count = 0;
    count++;
    if (!(count % kInterval))
    {
        mParent->LOG_MESSAGE("TransportSource::getNextAudioBlock(): call " + String(count));
    }
    AudioTransportSource::getNextAudioBlock(bufferToFill);
}


As a quick test to determine if you are doing something incorrectly, you could try one of the example apps from JUCE, to see if it has the same behavior.