Bug in juce_win32_ASIO.cpp

In order to handle an input-only ASIO stream, the code


    AudioIODevice* createDevice (const String& outputDeviceName,
                                 const String& inputDeviceName)
    {
        // ASIO can't open two different devices for input and output - they must be the same one.
        jassert (inputDeviceName == outputDeviceName || outputDeviceName.isEmpty() || inputDeviceName.isEmpty());
        jassert (hasScanned); // need to call scanForDevices() before doing this
        const int index = deviceNames.indexOf (outputDeviceName.isNotEmpty() ? outputDeviceName
                                                                             : inputDeviceName);
        if (index >= 0)
        {
            const int freeSlot = findFreeSlot();
            if (freeSlot >= 0)
                return new ASIOAudioIODevice (this, outputDeviceName,
                                              classIds.getReference (index), freeSlot);
        }
        return nullptr;
    }

should be changed to


   AudioIODevice* createDevice (const String& outputDeviceName,
                                 const String& inputDeviceName)
    {
        // ASIO can't open two different devices for input and output - they must be the same one.
        jassert (inputDeviceName == outputDeviceName || outputDeviceName.isEmpty() || inputDeviceName.isEmpty());
        jassert (hasScanned); // need to call scanForDevices() before doing this
        const String deviceName = outputDeviceName.isNotEmpty() ? outputDeviceName
                                                                : inputDeviceName;
        const int index = deviceNames.indexOf (deviceName);
        if (index >= 0)
        {
            const int freeSlot = findFreeSlot();
            if (freeSlot >= 0)
                return new ASIOAudioIODevice (this, deviceName,
                                              classIds.getReference (index), freeSlot);
        }
        return nullptr;
    }

so that the device gets a name.

Ah, good one, thanks!

You're welcome :) 

While we're on the subject, the size of the currentASIODev array is 3. Any reason it is this small ? I'm enumerating devices and store instances of them and the number of ASIO devices might well surpass 3 (even though the devices won't be opened/streaming at the same time). Is there any reason to limit the number of ASIO devices ? After all, they're just COM objects...

 

(I think I bumped it up to 4 the other day..)

There's no particular reason, other than it needs to be a static array, so I didn't want to allocate a lot of space, and didn't really expect people to open more than a couple at once. I guess it could be a higher number, but I would recommend not opening more than you need simultaneously anyway, since you never know how drivers will interact with each other - if you're enumerating then better to open + close them one at a time.

But if ill behaved drivers do interact, they could do that with only two drivers open (with enough bad luck), thereby rendering the limit moot. That would be an argument against a limit, wouldn't you agree?

 

Sure. There does have to be a limit, because it's a preallocated array, but yes, it could indeed be a bit higher without any problem. TBH even a number as low as 8 should be more than enough - I can't imagine anyone would have more than 8 difference ASIO devices connected at one time!

The RME Madi drivers support up to three Madi interfaces (if I recall correctly) - that's a whopping 3 x 64 ins and 3 x 64 outs... 

Would it still report as a single ASIO device? Just curious ;-)

Yes - normally an ASIO device appears as a single object that you open - inside it, it can have as many input + output channels as it wants.

ASIO was only ever designed to allow an application to have a single device open at once - in fact the only reason JUCE lets you open more than one is via a bit of a hack. Since the ASIO callbacks provide no per-device information, the only way to run more than one device is by giving each device a different set of functions to call, each of which is hard-coded to know which instance of a device it's attached to. Have a look at the horrible things I had to do with templates in setCallbackFunctions() to see what a pain this is! For each extra simultaneous device that can be opened, there needs to be another set of static functions declared. (And actually, I just noticed that I shouldn't have changed the maximum number to 4 without updating the templates.. I'll fix that now..)

Yes, the designers of ASIO weren't the brightest. Just to leave out the possibility to pass a void* pointer via the callback mechanism (which is programming 101 in C callbacks)...

Ah well, anyways, if you're interested, I've managed to make a hack via "thunking", so that I can provide a per-ASIO-device specific C++ callback interface (which ASIOAudioIODevice inherits), supporting an arbitrary number of devices :)

That way, there's no need for the device index template hack.

 

Yes, would be interested in how you got a thunk to work - does it involve nasty assembly-language jumps and things?

Yes, by necessity. But it's not that nasty, and not a lot of code really, and by making sure that the calling convention is cdecl, it only involved making the stack look correct upon calling the C++ functions, and before returning.

I got x86 working, now porting it to x64.

I can send you the code when I'm done with that.

Edit: Wow! x64 is really a different beast! This'll take time, not even sure if I'll manage :(

 

Ok, you should've gotten the thunk code, managed to get x64 working aswell :)

 

it is possible to use template meta programming to ease up coding, these changes in the code:

 

[...snip...]
    template<int INDEX>
    struct SetCallbacks
    {
    public:
        inline static void setCallbacks(ASIOCallbacks& callbacks, ASIOAudioIODevice* pThis)
        {
            if (currentASIODev[INDEX] == pThis)
            {
                ASIOCallbackFunctions<INDEX>::setCallbacks(callbacks);
            }
            else
                SetCallbacks<INDEX+1>::setCallbacks(callbacks, pThis);
                
        }
    };
    template<>
    struct  SetCallbacks<sizeof(currentASIODev)/sizeof(currentASIODev[0])>
    {
    public:
        inline static void setCallbacks(ASIOCallbacks&, ASIOAudioIODevice*)
        {
            // Terminates recursive template
        }
    };
    void setCallbackFunctions()
    {
        SetCallbacks<0>::setCallbacks(callbacks, this);
    }


makes it possible to increase the size of the currentASIODev array, without having to modify anything else :)

So now its OK to bump it up to say... 16 ?

Ta, will take a look when I get a sec.