AudioDeviceManager gets tripped up by empty (but available) device Types

(I first hit this with JUCE 5.4.7, but I’ve read through the latest code in the repo and there’s nothing different in the AudioDeviceManager class that would lead me to believe things have changed in 6.0.8 or later.)

When it needs to default the current audio device, in the absence of any other configuration the AudioDeviceManager will fall back to choosing the first device for the first available device type.

That’s somewhat unfortunate if you happen to find yourself in the admittedly-unusual situation where the first audio device type contains NO devices at all, since AudioDeviceManager will nevertheless set the first type in availableDeviceTypes as current, whenever any code wants to createDeviceTypesIfNeeded():

juce_AudioDeviceManager.cpp@100:115

void AudioDeviceManager::createDeviceTypesIfNeeded()
{
    if (availableDeviceTypes.size() == 0)
    {
        OwnedArray<AudioIODeviceType> types;
        createAudioDeviceTypes (types);

        for (auto* t : types)
            addAudioDeviceType (std::unique_ptr<AudioIODeviceType> (t));

        types.clear (false);

        if (auto* first = availableDeviceTypes.getFirst())
            currentDeviceType = first->getTypeName();
    }
}

And code that sets the default device tends to query the currentDeviceType for a list of devices to choose from.

I ran into this building a command-line tool under MinGW on Win10 — because the executable isn’t compiled as a WIN32 application, it won’t detect any WASAPI devices, but the type is still created as the first one in the list — it simply contains zero devices. Whereas the DirectSound type does contain a working and usable device, but it’ll be ignored unless explicitly requested.

For that particular tool I worked around this in the supported manner: by subclassing the AudioDeviceManager, overriding createAudioDeviceTypes(), and removing the WASAPI type instantiations because I know that code will never be able to see WASAPI devices. The API is, as always, well-designed and conducive to such needs.

From reading the code it seems I could’ve also worked around it by passing "*" as the preferredDefaultDeviceName argument to AudioDeviceManager::initialise(), instead of omitting it and passing the default empty string, since having a non-empty preferredDefaultDeviceName will cause initialiseDefault() to walk the entire list of availableDeviceTypes looking for matching devices.

But it strikes me that the AudioDeviceManager should be able to notice any AudioIODeviceType* types that return an empty list for type->getDeviceNames(), and do any of the following in response:

  1. Skip over that type when defaulting the currentDeviceType, if there are more types in the availableDeviceTypes list.
  2. Sort the 0-device AudioIODeviceType*s to the end of the availableDeviceTypes list, instead of preserving the original configuration order, so that any types with detected devices are given priority.
  3. Remove the “empty” types from availableDeviceTypes entirely. (I’m not so sure about this one, could there be some types that come up with no devices initially, but are later populated in response to some sort of device activation/initialization? I assume probably yes.)

So, it turns out I’m wrong about that part. Because this device-type switching is kind of a mess.

If initialise() is called with a preferredDefaultDeviceName, it’ll be passed to initialiseDefault() which will scan all available types for a matching name. If it finds one, it’ll store that name — ONLY the name — in setup.outputDeviceName and/or setup.InputDeviceName as indicated by the channel count requested.

But then later, when setAudioDeviceSetup() is called with the resulting AudioSetup struct, it checks only the current device type for setup.{input,output}DeviceName, meaning that if the name was matched on some other type it’s going to fail and produce a "No such device: " message.

This is made slightly more annoying by the fact that AudioDeviceManager::findType(String inputDeviceName, String outputDeviceName) is PRIVATE (WHY???), so I’ll have to reimplement that in the caller if I want to discover the correct type and set it manually, as a workaround.

If you know your app will never use WASAPI devices, you can disable them completely by setting JUCE_WASAPI to 0 in your project’s preprocessor definitions.

I’ve also made some changes to the AudioDeviceManager so that it will initially attempt to find a device type with some exstant devices.

1 Like

Thanks @reuk , those changes look good to me. I think they’ll improve the defaulting process considerably.

The related issue of AudioDeviceSetup storing only the juce::String name of the input and output devices — I suppose that’d be harder to change, huh, given that it’s a public struct?

…The XML configuration does have the ability to store the deviceType, though, so I guess maybe it’s not really an issue. As long as the type-defaulting does sensible things, and XML is used for any stored configurations, the currentDeviceType should be a sensible one by the time AudioDeviceSetups are created for its selected devices.