Recieving Feedback from the ASIO control panel


#1

Hello hello,

I’ve been (co-)developing an application that has it’s own audio-preferences panel, similar to the AudioDeviceSelectorComponent. During my tests with different ASIO devices, I’ve noticed that when calling AudioIODevice::showControlPanel(), some devices tend to block until the panel has been closed again and some don’t.
In the time that this panel is open, all sort of settings can be changed, but there doesn’t seem to be a way to retrieve these changes. Therefore, there’s no way to display the changes in my preferences panel.

When the call to show the panel is blocking, it’s easy to acces the new settings by means of AudioDevice::getCurrentSampleRate etc. etc…
This method is unusable when the call is nonblocking though, because there’s no way of thelling when the changes have been made.

It would seem logical that any changes in the device would result in a call to AudioIODeviceCallback::audioDeviceAboutToStart(AudioIODevice *device), with device containing the new settings… but the call to ShowControlPanel also closes the device without reopening it… This has the sideeffect of me having to change something in my panel to reopen the device again…

Is there any solution to this problem? I’m using Juce Rev. 551.

thanks!


#2

Okay, maybe it’s better if I addressed a smaller piece of the problem:

It seems openening the ASIO control panel from within the juce demo shuts down the current ASIO device (you can see this happening in showControlPanel() in juce_win32_ASIO.cpp).

Well, I understand this may need to be done in order to avoid some nasty stuff, but it seems rather strange that in order to get the device to work again you have to first select some other device, then select the device that you initially chose once more.

If there’s a simple solution to this problem, I would love to learn about it!


#3

[quote]It seems openening the ASIO control panel from within the juce demo shuts down the current ASIO device (you can see this happening in showControlPanel() in juce_win32_ASIO.cpp).

Well, I understand this may need to be done in order to avoid some nasty stuff, but it seems rather strange that in order to get the device to work again you have to first select some other device, then select the device that you initially chose once more.
[/quote]

Yes, I remember it being a PITA to get that working, and the cleanest way seemed to be to just close the device, let the user mess with it, then reopen it again with its default settings to see what they changed.

If a device doesn’t block, then I’m not really sure how you’d catch the fact that they’ve altered something… I don’t have any devices like that myself to test. What soundcard is it?


#4

So far I’ve tested on a Terratec EWS 88mt, Native Instrument Audio Kontrol 1, a M-Audio Ozone and a Creamware Pulsar 2 and they all seem to behave differently :slight_smile: Yay standards!

When a call to showControlPanel is non-blocking, I immedately reopen the device and start a timer. In the timer callback I keep track of any changes to the AudioIODevice (wich are updated, thankfully).

I’m not a very experienced win32 coder, but it seems installing a hook to a window that is named differently for every card is quite tricky…

Instead of that I keep the timer running until someone switches to an other device…

The annoying thing is that a blocking call to the panel function will not give me the oppertunity to reopen the device in order for the AudioIODevice to be updated… It seems that closing the device before actually calling the controlPanel() ASIO call is a good idea stabilitywise though,.

I could just reopen the device, but the parameters I have to pass in this call make sure any changes made in the control panel will be lost…
Then I came across ASIOAudioIODevice::resetRequest(), wich seems to reopen the device async.

I tried:

((juce::ASIOAudioIODevice*)currentAsioDevice)->resetRequest();

but I get “‘ASIOAudioIODevice’ : is not a member of ‘juce’”… any suggestions?


#5

resetRequest is an internal method that should get called by the device when it needs to be updated. (I’d really expect the device to trigger that call itself when it gets changed by the control panel, rather than needing it to be called explicitly).

Hmm, not sure what’d be best to suggest…


#6

Well, It turns out that closing the device when showing the panel isn’t really necessary after all…

This led me to another finding: when the device makes a resetRequest because of updates in the device’s settings, the TimerCallback() closes and reopens the device with these new settings.
What happens is that after the device is opened, all the channels are cleared, so any audio that was playing if effectively muted.
This is because of ASIOAudioIODevice::timerCallback() calls ASIOAudioIODevice::open with the current channels as parameters. In the open() function, the current channels are first cleared and then overwritten by the parameters passed in.
But, because these pararameters are passed in through reference, they get cleared as well. Creating copies of them solved the problem for me.

so: (in ASIOAudioIODevice::timerCallback, starting from line 832)

[code]
if (isOpen_)
{
AudioIODeviceCallback* const oldCallback = currentCallback;

            close();
			BitArray out(currentChansOut), in(currentChansIn);
            open (in, out,
                  currentSampleRate, currentBlockSizeSamples);

            if (oldCallback != 0)
                start (oldCallback);
        }[/code]

and (in showControlPanel(), starting from line 783) :

[code] JUCE_TRY
{
//close();
insideControlPanelModalLoop = true;

        const uint32 started = Time::getMillisecondCounter();

        if (asioObject != 0)
        {
            
			asioObject->controlPanel();

			const int spent = (int) Time::getMillisecondCounter() - (int) started;

            log (T("spent: ") + String (spent));

            if (spent > 300)
            {
                shouldUsePreferredSize = true;
                done = true;
            }
        }
    }[/code]

This way a change from the control panel allways leads to the device staying open, meaning an AudioDeviceManager can send a changemessage. This saves me from all the timer / polling crap I did previously to keep my panel updated.


#7

ah… you must to be using an old version of the library. I fixed the reference copy stuff a while ago. Probably best to have a look at the latest version before spending any more time debugging!

As for closing the device… yes, I bet it’s not actually necessary, I’m willing to take that out and see if anything breaks!


#8

Hi Jules,
Tim/we is/are using revision 551 (not too old). We are developing a commercial application which we just released a few weeks ago, it’s still in Beta fase and we are ironing out the bugs. The ASIO stuff is one of our major problems at the moment.
We can’t just keep on updating to the tip because it’s hard to know what you’ve changed and if there are things that might cause problems. Especially now because you checked in the cocoa stuff (which is great btw, haven’t tested it though), we can’t just start using the latest versions.

Would be nice if we could follow somewhere what changes are made to the tip and which bugs are fixed.


#9

Follow up:
Could you maybe give us a revision number that (before cocoa) you think is most reliable and stable.
Maybe that would make a nice 1_47 version don’t you think?


#10

Well, I think cocoa was v600 (or was it 599?). The version immediately before I checked in the cocoa stuff would have been the most stable.


#11

I'm encountering a similar problem with a specific set of ASIO drivers : namely the ones that don't let you change bitrate or buffersize through the API (look at Behringer or Phonic firefly devices). This creates a horrible mess if your app isn't able or willing to support all options, because you can't modify the setting to something you can handle, and you may not even know that something has changed. You want to warn the user with a "this isn't going to work, please fix it" message. In a previous (pre Juce) implementation I used the "Needs a reset" callback from the ASIO SDK which worked fine.  This even works when the user changes the settings from a separate control panel. However in JUCE this callback is handled in the ASIO device handler. I'm just at the point of modifying the handler to allow the program to register an optional callback, but before I do: does anyone know another way?


#12

I'm not 100% sure I understand the problem, but if you want to suggest a code-change, I'm sure that'd make it clear to me what you're doing!


#13

OK - I'll get onto that. But just to clarify (in case I'm missing something) the ASIOAudioIODevice class has the asioMessagesCallback method which handles the  kAsioBufferSizeChange, kAsioResetRequest and kAsioResyncRequest by resetting the device after a wait of 500ms. The reset eventually goes back to open the device again which, if someone has changed the buffersize or bitrate, will set these back to their originally chosen values, which I consider a good thing. However the open method checks if these values are available and, if not, chooses some other value (either a default or the first available).  

Along comes the Behringer driver (amongst others) which allows you to change bitrate in the control panel, but when you query the bitrate via the API will tell you that only one bitrate is available - the one currently chosen. This happens with buffer sizes too, especially drivers that don't allow you to choose a buffer size but talk about "low latency" or "fastest" or something. This is actually the driver's way of telling you that it's not going to allow your program to change the value (why?!?). So the open method now accepts the current value and leaves it at that.

Because this is all asynchronous with respect to your program (it can happen in a non modal control panel, or in a seperate control panel app which, BTW, will still cause the ASIO callback to fire), we now have a situation where the driver bitrate or buffersize can change without your program's knowledge. This is IMHO Not a Good Thing.

Since I'm aiming at computer neophytes I feel I need to handle this. One way would be to ask the user to keep away from the control panel, but unfortunatey this lovely piece of driver design means that you cannot change bitrate or buffer size from the program and hence need to ask the user to open the control panel.

 


#14

Thanks - looking forward to your suggested changes!


#15

I've been looking into juce_win32_ASIO and as it turns out most of the solution is already there. The asioMessagesCallback method will be triggered when framesize or sample depth is changed, which leads to a device reset via sendResetRequest and eventually a notification to any listeners who have registered an audioDeviceListChanged callback (or are listening to a change on the AudioDeviceManager). The only thing that needs to be added is that the sampleRateChanged callback should also generate a reset request. This done, the program just needs to check if anything has changed when it deals with the callback.

There is, however, a slight curiosity here. I didn't notice it initially, but the ASIO native stuff doesn't actually trigger an audioDeviceListChanged callback when a device is plugged in or out. The reason that I didn't notice was that I also had WASAPI and DirectSound callbacks registered which triggered the same processing. The ASIO driver is just not triggering anything when a device is unplugged or plugged in (at least on the devices that I have tested - others may have seen different behaviour in which case my theory is trashed). I suspect that this is because ASIO drivers aren't real drivers in the Windows sense. The DirectSound and WASAPI versions are triggered by a message queue notification which doesn't happen in the ASIO case.  (ASIO4ALL is the exception and can sort of spot devices changing, probably because it sits on top of real WDM drivers).

For essentially the same reason, ASIO doesn't give you a list of available devices, but a list of available drivers. This implies that you can only find out if a device is really there by trying to do something to it and then seeing if it fails (a close followed by a restartLastDevice for example). What this amounts to is that the audioDeviceListChanged method will tell you when almost anything about the ASIO device has changed, except the device list. From a purist point of view this is a bit weird, but I personally would vote for just updating the sampleRateChangedCallback and leave it at that.

One last point, just in case anyone else comes across the problem. One fairly obvious way to deal with "invisible" changes made in an external control panel is to check the device status when you return from the showControlPanel method. Unfortunately this will not work, because (at least in the ASIO case) there is a 500ms asynchronous settling time before changes are registered. You can either use the callback or wait 600ms. The latter is a bad idea because you then become dependent on internals. It may be a good idea to add an optional waitForSettlingTime parameter to the showControlPanel method to deal with this.


#16

Thanks - I've updated it now to send that reset request when the sample rate changes.. Would appreciate you letting me know if it works ok!


#17

Thanks Jules - this seems to work, although I need to grab a few more devices to test it on: some implementations of ASIO trigger a resetfrequest and not a sampleratechanged when the sample rate is changed.