Context:
I am investigating a fault with our Juce based application, which exhibits when used with a particular Thunderbolt 3 external audio interface, namely the Universal Audio Arrow.
In fact, the behavior not only exhibits in our application, but also in the AudioPluginHost application whose source ships with Juce.
Using:
Universal Audio Arrow (Thunderbolt 3)
Windows 10
Visual Studio 2017
ASIO correctly enabled - ASIO SDK v2.3.3
Juce public release v5.4.7
Behavior:
When using buffer sizes of 512 of lower, the device driver issues a kAsioResyncRequest
to the host. The request does not appear to be issued as a result of a user interaction, nor does it occur after a fixed time.
The ASIO SDK manual says:
“the driver detected underruns and requires a resynchronization”.
Purpose:
The driver went out of sync, such that the timestamp is no longer valid. This is a request to re- start the engine and slave devices (sequencer).
However, Juce never successfully recovers from this scenario. It appears to attempt to restart the engine, but an error occurs when re-creating the buffers. Here is the console output:
ASIO: kAsioResyncRequest
ASIO: restart request!
ASIO: stopping
The thread 0x3138 has exited with code 0 (0x0).
ASIO: clock: Computer (cur)
ASIO: Resetting
ASIO: disposing buffers
ASIO: creating buffers: 16, 128
ASIO: error: create buffers 2 - Invalid Mode
ASIO: error: Can't create i/o buffers - Invalid Mode
The error code returned by the call to the createBuffers()
is ASE_InvalidMode
. Again from the ASIO SDK manual:
If bufferSize is not supported, or one or more of the bufferInfos elements contain invalid settings, ASE_InvalidMode will be returned.
However, Juce does not appear to be passing invalid state to the driver. The contents of the bufferInfos
struct are:
|isInput|1|long|
|channelNum|0|long|
|buffers|0x000002577ea3afa8 {0x0000000000000000, 0x0000000000000000}|void *[2]|
|[0]|0x0000000000000000|void *|
|[1]|0x0000000000000000|void *|
and currentBlockSizeSamples == 128
which is totally valid. In fact, when debugging the code from the start wherein the initial call to createBuffers()
succeeds, it uses the exact same values.
So as far as I can gather Juce is attempting to reset the device, but at the point of buffer creation, the device is in a state where it is not prepared to deal with that request. Presumably the Juce state machine is out of sync with the devices state machine?
Additional Info:
It is also worth noting that an almost identical error exhibits when the user changes the buffer size from the third-party control panel. I have found this useful when debugging because it can be used to exploit the fault on request. The only difference is that the driver issues a kAsioResetRequest
rather than the kAsioResyncRequest
. But in fairness, Juce handles both messages in the same way:
/**N.B. All that resetRequest() does is starts a timer 500 ms.
* The driver callback thread then exits and the timer callback attempts
* to handle the resetting of the state machine in the main thread */`
case kAsioResetRequest:
JUCE_ASIO_LOG ("kAsioResetRequest");
resetRequest(); return 1;
case kAsioResyncRequest:
JUCE_ASIO_LOG ("kAsioResyncRequest");
resetRequest(); return 1;
In addition, it is worth noting that this error can be recovered manually when pressing the Reset Device button in the AudioHostPlugins Audio Settings panel. I think that whatever it is that’s being done correctly in that user thread, should essentially be replicated when Juce performs the reset in response to the driver requests.
Similar Issues:
This user appeared to report a very similar issue a long while ago, but it’s not clear whether it was ever fixed.
@jules - Please can you have a look into this?
Thank you kindly
Dave