Possible bug in ASIOAudioIODevice::open?

Hi,

First, the context:
For a particular application I am trying to restrict the user in setting sensible settings for his soundcard (buffersize between 1024 and 4096, samplerate 44100). To do that, I am showing a component that has the standard AudioDeviceSelectorComponent, launch a dialog with that component, and upon returning, I check the selected settings. If they are not OK, I find out which settings are available that do meet my criteria, and try to set the device to these settings using AudioDeviceManager::setAudioDeviceSetup (followed by a warning dialog box informing the user).
I know:

  • it could be better to restrict the options in the audio setup dialog itself, by creating my own AudioDeviceSelectorComponent but no time for that…
  • my code should work properly with all samplerates and buffer sizes, and that’s going to happen in the end, but not now…

Anyway:
When I call AudioDeviceManager::setAudioDeviceSetup, it looks like the underlying code is not handling the requested buffersize correctly. [this is on Windows7 64-bit, latest Juce pulled from git just some minutes ago]

If the user had set the device to a buffersize of 64, I correct that by getting the current audio device setup, copying it and changing the bufferSize value to 1024, or in code:

AudioDeviceManager::AudioDeviceSetup oldDeviceSetup, newDeviceSetup; deviceManager.getAudioDeviceSetup(oldDeviceSetup); newDeviceSetup = oldDeviceSetup; // ... some checking and condition handling ... newDeviceSetup.bufferSize = 1024; // note: this is the only change made String error = deviceManager.setAudioDeviceSetup(newDeviceSetup,true); // ... check error string ...

What I am seeing, is that this does not always work (on the same computer with the same sound card). The buffer size is not changed to 1024, although the error string is empty, and the audio card can be set to 1024 at that sample rate.

When stepping into the code, AudioDeviceManager::setAudioDeviceSetup somewhere calls ASIOAudioIODevice::open (still with the correct new bufferSize 1024) and we reach the following lines of code:

[code]long newPreferredSize = 0;
if (asioObject->getBufferSize (&minSize, &maxSize, &newPreferredSize, &granularity) == ASE_OK)
{
if (preferredSize != 0 && newPreferredSize != 0 && newPreferredSize != preferredSize)
shouldUsePreferredSize = true;

if (bufferSizeSamples < minSize || bufferSizeSamples > maxSize)
    shouldUsePreferredSize = true;

preferredSize = newPreferredSize;

}[/code]

What I see happen, is that the first if within the “if (asioObject->getBufferSize…” block evaluates to true, and makes shouldUsePreferredSize end up true. Further down, we then end up in:

[code]if (shouldUsePreferredSize)
{
JUCE_ASIO_LOG (“Using preferred size for buffer…”);

if ((err = asioObject->getBufferSize (&minSize, &maxSize, &preferredSize, &granularity)) == ASE_OK)
{
    bufferSizeSamples = preferredSize;
}
else
{
    bufferSizeSamples = 1024;
    JUCE_ASIO_LOG_ERROR ("getBufferSize1", err);
}

shouldUsePreferredSize = false;

}[/code]

and this means my supplied bufferSizeSamples gets totally ignored, and replaced by the preferred buffer size of the sound card.

–> While I of course do understand the range checking with minSize and maxSize (the second if clause in the big if clause), it seems like that first if clause above that can lead to a situation where the supplied buffer size is totally ignored.
Why should the buffer size be set to the preferred size if that changed from the last preferred size? Shouldn’t the supplied buffer size be used if it is in the valid range for the sound card?

I just ran the Juce Demo application (after enabling ASIO support).
Latest version of Juce, pulled from git this morning.

I see this phenomenon: each time when I switch the buffer size, I actually have to select it twice, before it really sticks.
Reproduction:

  • start Juce demo
  • go to Demo -> Audio
  • in the Audio Device Setup tab, do this:
    . select ASIO for Audio device type
    . select a device (in my case M-Audio ProFire ASIO)
    . change the buffer size to 64 samples (coming from 512)
    –> the change doesn’t stick (still set at 512)
    . change the buffer size a second time to 64
    –> now the change does stick (set to 64)
    . change the buffer size to 256
    –> the change doesn’t stick (still set at 64)
    . change the buffer size a second time to 256
    –> now the change does stick
    etc…

This only happens when changing the buffer size, not when changing the sample rate.

It looks like setting the audio device setup where a new buffer size is used, doesn’t work properly.

And I guess (not sure) that’s also what I am seeing in my application when I use setAudioDeviceSetup.
Actually, when I call the setAudioDeviceSetup twice after each other in my code (similar to what I need to do in the demo application using the user interface), it seems to be working as expected.

The strange thing is that if I choose the ASIO4All drivers (using the same sound card), this doesn’t seem to happen, and changed buffer sizes are right away when selecting them (no need to set them twice).

By the way, I also saw that the AudioDeviceSettingsPanel::comboBoxChanged method keeps getting called while the AudioDeviceSelectorComponent is open on the audio demo tab page in the Juce Demo.
I put this:
DBG(“combo box changed”);
at line 317 in juce_AudioDeviceSelectorComponent.cpp, and this keeps getting called, even if I do nothing at all.

This happens when I set the driver to ASIO4ALL v2 or M-Audio ProFire ASIO, but not when I set it to ASIO DirectX Full Duplex Driver…

Alright, I debugged the Demo application to see what happens when I switch from 256 samples buffer size to 64 samples, in particular in this peice of code in ASIOAudioIODevice::open:

        if (asioObject->getBufferSize (&minSize, &maxSize, &newPreferredSize, &granularity) == ASE_OK)
        {
            if (preferredSize != 0 && newPreferredSize != 0 && newPreferredSize != preferredSize)
                shouldUsePreferredSize = true;

            if (bufferSizeSamples < minSize || bufferSizeSamples > maxSize)
                shouldUsePreferredSize = true;

            preferredSize = newPreferredSize;
        }

When the getBufferSize call returns, I get:

preferredSize = 512 minSize = 64 maxSize = 4096 newPreferredSize = 256 granularity = -1

So, as in my application, the next if check:

evaluates to true, and shouldUsePreferredSize ends up being true, and preferredSize ends up being 256.

Then further down, we get:

[code] if (shouldUsePreferredSize)
{
JUCE_ASIO_LOG (“Using preferred size for buffer…”);

        if ((err = asioObject->getBufferSize (&minSize, &maxSize, &preferredSize, &granularity)) == ASE_OK)
        {
            bufferSizeSamples = preferredSize;
        }
        else
        {
            bufferSizeSamples = 1024;
            JUCE_ASIO_LOG_ERROR ("getBufferSize1", err);
        }

        shouldUsePreferredSize = false;
    }

[/code]

and bufferSizeSamples is being changed to preferredSize (256), instead of the original 64 samples as requested…
So, the buffer size stays at 256, instead of being changed to 64…

Then, the second time we change the buffer size to 64, the condition if (preferredSize != 0 && newPreferredSize != 0 && newPreferredSize != preferredSize) evaluates to false (preferredSize is 256 then) and currentBlockSizeSamples gets to be set to our bufferSizeSamples value (64) and things go right this time.

That explains what I’m seeing, but no idea what the solution should be…

I think the reason that logic exists is: When the ASIO device’s preferred size changes, we have to assume that it has been changed by the device itself - usually by the user fiddling with the device’s own control panel. So when that happens, the sensible thing to do is for the class to switch to using that new preferred size. Otherwise, users get confused about the fact that they’re changing the size in their device’s control panel, but the app isn’t picking up that change.

But it seems that your soundcard is changing its preferred size, presumably to different values depending on the actual buffer size that’s in use… I’m a bit baffled by that. AFAICT the code that sets the preferredSize variable is all correct, but perhaps it’d help if we added some code to check the preferred size yet again, after the device has been opened, e.g. by adding this at line 662?

asioObject->getBufferSize (&minSize, &maxSize, &preferredSize, &granularity); deviceIsOpen = true;

Sorry for taking my time to respond.

I tried adding that extra line (662) and indeed, that fixes the problem for me!
Thanks a lot!

Are you going to commit this to the repository?
Then I can remove the workaround I had put in place (calling setAudioDeviceSetup twice with the same settings).

Thanks! Yes - will commit it now…