ASIO: how do I programmatically set the buffer size FMOD should use, to match that of ASIO driver?

Hi!

Just installed and built with the FMOD Studio API 1.10.08, C++, on Windows.
The audio interface is a Behringer X-USB, set up to have an “ASIO Buffer Size” of 512 samples using its own “Control Panel”.

Following the below recommended procedure:
Studio::System::Create()
Studio::System::getLowLevelSystem()
Studio::System::getLowLevelSystem()->setOutput(FMOD_OUTPUTTYPE_ASIO));
Studio::System::initialize()

According to this page: https://www.fmod.com/docs/api/content/generated/FMOD_System_SetDSPBufferSize.html

FMOD_OUTPUTTYPE_ASIO will “change the buffer size to match their own internal optimal buffer size”.

However, using System::getDSPBufferSize, i still get the value 1024.

Only if I explicitly call: “System::setDSPBufferSize(512, 4)”, before “initialize()”, do I get FMOD to correctly initialize using this audio interface.

The problem however is that I cannot programmatically fetch this buffer size value (512), that is set in the Audio Interface’s ASIO contol panel, and which for some devices of course cannot even be 512 to begin with!

So my question is:

  • How can I get FMOD to automatically fetch and use this buffer size?
  • Alternatively, how can I myself query this value, and set it? System::getDriverInfo() does not return this value for example, and I cannot find which FMOD API method would give me this information.

Thank you!

By the way, this seems to possibly be the same issue mentioned but not resolved here:
https://www.fmod.org/questions/question/asio-device-init-dsp-buffer-size-error/

With ASIO, it is up to the user to set the buffer size, not the programmer.

If you add an extra layer of buffering to support arbitrary buffer sizes, the latency will increase, ie double in some cases, which basically defeats the point of ASIO.

As cameron said if you want low latency in windows, and arbitrary buffer sizes you might as well use the default WASAPI output.

With ASIO you have to stick to the model it provides to get the absolute lowest latency you can.

If you use ASIO4ALL for example, the program itself lets you set the buffer size.

Hi!

I know in ASIO the buffer size is chosen by the user. But, if that buffer size is not 1024, the FMOD engine will not work with ASIO.
That essentially means you do not support ASIO. Which may also be fine, except you say you do!

I now tried, following the advice of a colleague of yours, to use the “System::setDSPBufferSize(xxx, x)” call, with mixed results: it only works for 256/512/1024, but not audio interfaces whose ASIO control panels do not support such size, but have sizes such as 192, 288, etc, pre-defined in their drop-down.

I understand the argument about latency (and have experience with DSP programming). But, to our clients, who will use our software, what will I say? That we support ASIO, which the audio tech world is very strongly enamored with, for better or worse? Or that we do not support ASIO? They are now a user base which all by default use ASIO (musicians).

Same question, by extension, goes to FMOD: Does it support ASIO, or not? If not, why is it even there to begin with? Right now my understanding is you have it on the list, but there’s no way of making it work except if a user by mistake sets his buffer size to 1024 - meaning confusion.

This is not the first time I bring this up:
http://www.fmod.org/questions/question/i-get-unable-to-initialize-the-selected-device-when-selecting-asio-driver-for-device-verified-to-work-with-ableton-live/#sabai-entity-content-47476

And in the last email thread that resulted, the response from a colleague of yours (if you want I can email you the whole conversation thread), was along the lines of:

"The FMOD Studio engine sits on top of the low level engine.
To configure the DSP block size when using the Studio engine do the following:

Studio::System::Create()
Studio::System::getLowLevelSystem()
System::setDSPBufferSize()
Studio::System::initialize()"

Somehow I may have misunderstood it, but I thought with the latest FMOD release, the ability to use ASIO had been addressed. It seems as this is not the case then?

So to sum up: I am not trying to get the lowest possible latency necessarily - I’m just trying to get an answer to the question: can our end-users use ASIO with the software we release, or not? Should I remove the ability to pick ASIO in the software all-together then, or is there a way to make this work?

Thank you,
Ilias B.

Hi Ilias,
It looks like I was misinformed about the fact that studio forces the buffersize to use whatever the driver preferred. FMOD Ex certainly did that.

FMOD Studio just lets you set the buffer size to whatever you want, but it has to be within ASIO’s

  1. min buffer size (ie here it says ‘64’)
  2. max buffer size (ie here it says ‘2048’)
  3. a multiple of the granularity the driver reports (ie here it says ‘8’)

So the default of 1024 works fine here on my ASIO device.

I’m going to guess there are a variety of drivers that have all sorts of min/max/granularity settings, and the problem is we don’t expose it currently. We’ll address this in a future update.

I’ve made a workaround for you - it is a modified ‘simple_event’ example. (the top half).

I’ll paste it below instead of in a comment, for formatting

Here is a modified simple event example, to use ASIO output with the ‘preferred’ buffer size by the ASIO driver.

#define ASIO_NAME_LEN 32
typedef long ASIOBool;
typedef long ASIOError;
typedef double ASIOSampleRate;
typedef struct ASIOChannelInfo ASIOChannelInfo;
typedef struct ASIOClockSource ASIOClockSource;
typedef long long int ASIOSamples;
typedef struct ASIOBufferInfo ASIOBufferInfo;
typedef struct ASIOCallbacks ASIOCallbacks;
typedef long long int ASIOTimeStamp;
typedef long ASIOError;

interface IASIO : public IUnknown
{
    virtual ASIOBool init(void *sysHandle) = 0;
    virtual void getDriverName(char *name) = 0;	
    virtual long getDriverVersion() = 0;
    virtual void getErrorMessage(char *string) = 0;	
    virtual ASIOError start() = 0;
    virtual ASIOError stop() = 0;
    virtual ASIOError getChannels(long *numInputChannels, long *numOutputChannels) = 0;
    virtual ASIOError getLatencies(long *inputLatency, long *outputLatency) = 0;
    virtual ASIOError getBufferSize(long *minSize, long *maxSize,
	    long *preferredSize, long *granularity) = 0;
    virtual ASIOError canSampleRate(ASIOSampleRate sampleRate) = 0;
    virtual ASIOError getSampleRate(ASIOSampleRate *sampleRate) = 0;
    virtual ASIOError setSampleRate(ASIOSampleRate sampleRate) = 0;
    virtual ASIOError getClockSources(ASIOClockSource *clocks, long *numSources) = 0;
    virtual ASIOError setClockSource(long reference) = 0;
    virtual ASIOError getSamplePosition(ASIOSamples *sPos, ASIOTimeStamp *tStamp) = 0;
    virtual ASIOError getChannelInfo(ASIOChannelInfo *info) = 0;
    virtual ASIOError createBuffers(ASIOBufferInfo *bufferInfos, long numChannels,
	    long bufferSize, ASIOCallbacks *callbacks) = 0;
    virtual ASIOError disposeBuffers() = 0;
    virtual ASIOError controlPanel() = 0;
    virtual ASIOError future(long selector,void *opt) = 0;
    virtual ASIOError outputReady() = 0;
};

int getAsioBufferSize(int device)
{
    int             buffersize = -1;
    HKEY            asioKey = NULL;
    char            name[ASIO_NAME_LEN];
    unsigned long   nameLen = ASIO_NAME_LEN;
    CLSID           clsid;

    long err = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "software\\asio", 0, KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, &asioKey);
    if (err != ERROR_SUCCESS) return -1;

    /*
        Query the device name, use that to get the CLSID
    */
    err = RegEnumKeyEx(asioKey, device, name, &nameLen, NULL, NULL, NULL, NULL);
    if (err != ERROR_SUCCESS) return -1;

    HKEY asioDriverKey = NULL;
    err = RegOpenKeyEx(asioKey, name, 0, KEY_QUERY_VALUE, &asioDriverKey);
    if (err != ERROR_SUCCESS) return -1;

    unsigned char data[128] = { 0 };
    unsigned long dataSize = sizeof(data); // We know this is a GUID string so it will always fit
    err = RegQueryValueEx(asioDriverKey, "clsid", NULL, NULL, data, &dataSize);
    if (err != ERROR_SUCCESS) return -1;

    err = RegCloseKey(asioDriverKey);
    if (err != ERROR_SUCCESS) return -1;

    wchar_t wideData[128] = { 0 };
    err = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)data, -1, wideData, sizeof(wideData));
    if (err == 0) return -1;

    HRESULT hr = CLSIDFromString(wideData, &clsid);
    if (hr != NOERROR) return -1;

    err = RegCloseKey(asioKey);
    if (err != ERROR_SUCCESS) return -1;

    /*
        Query driver for capabilities
    */
    {
        IASIO *driver = NULL;
        HRESULT hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, clsid, (void **)&driver);
        if (hr == CO_E_NOTINITIALIZED)
        {
            hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
            if (FAILED(hr)) return -1;
            hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, clsid, (void **)&driver);
        }
        if (hr == S_OK)
        {
            ASIOBool success = driver->init(NULL);
            if (success)
            {
                long bufferMinSize = 0, bufferMaxSize = 0, bufferPreferredSize = 0, bufferGranularity = 0;

                ASIOError asioErr = driver->getBufferSize(&bufferMinSize, &bufferMaxSize, &bufferPreferredSize, &bufferGranularity);
                if (asioErr == 0) // 0 = success
                {
                    buffersize = bufferPreferredSize;
                }
            }

            driver->Release();
        }
    }

    return buffersize;
}


int FMOD_Main()
{
    void *extraDriverData = NULL;
    Common_Init(&extraDriverData);

    int buffersize = getAsioBufferSize(0);

    FMOD::Studio::System* system = NULL;
    ERRCHECK( FMOD::Studio::System::create(&system) );

    // The example Studio project is authored for 5.1 sound, so set up the system output mode to match
    FMOD::System* lowLevelSystem = NULL;
    ERRCHECK( system->getLowLevelSystem(&lowLevelSystem) );
    if (buffersize > 0)
    {
        ERRCHECK( lowLevelSystem->setDSPBufferSize(buffersize, 2) );
    }
    ERRCHECK( lowLevelSystem->setOutput(FMOD_OUTPUTTYPE_ASIO) );
    ERRCHECK( lowLevelSystem->setSoftwareFormat(0, FMOD_SPEAKERMODE_5POINT1, 0) );

    ERRCHECK( system->initialize(1024, FMOD_STUDIO_INIT_NORMAL, FMOD_INIT_NORMAL, extraDriverData) );

Hi!

I got a chance to try this code, and with a few caveats it works!

First, while it works for non-unicode builds, if the visual studio project is set to unicode text the above code will fail. I forget what flag it is in the Visual studio project, or if it is just changing from 32 to 64 bit… Our project is 64 bit in any case.

To make it work I altered all encoding-dependent calls, from their ***Ex to their ***ExA equivalent.
For example: RegEnumKeyEx became RegEnumKeyExA.
Otherwise, the CLSIDFromString call at the end failed.
This should be safe, I cannot imagine driver registry keys
and audio device names would use unicode characters.

Second:
With a Behringer X-32 Core the above worked fine for all buffer sizes I tried.
But for some reason yet unknown to me, with a Roland Duo Capture, which works correctly in other ASIO hosts, the “ASIOBool success = driver->init(NULL);” call returned false. All other calls that far succeeded.

I thought I should notify you since it’s been 3 weeks since you answered, even if I haven’t gotten to the bottom of these two issues yet.

Thank you.

Hi Ilias,
Thanks, the unicode thing is worth us updating. Thanks for the reminder.