FMOD::Sound::getLength returns FMOD_ERR_NOTREADY on very short programmer sound

We are using the FMOD_STUDIO_EVENT_CALLBACK_SOUND_PLAYED when playing programmer sound ( audiotable) to retrieve subsound length.

case FMOD_STUDIO_EVENT_CALLBACK_SOUND_PLAYED:
	{
		FMOD::Sound* pSound = (FMOD::Sound*)pAudioTableInstance->pSound;
		FMOD::Sound* pSubSound = NULL;
		iResult = pSound->getSubSound( pAudioTableInstance->iSubSoundIndex, &pSubSound );
		PA_FMOD_LOG_AND_ASSERT_ON_ERROR( ERROR, iResult );

		if( pSubSound != NULL ) 
		{
			iResult = pSubSound->getLength( &pAudioTableInstance->iSoundLenghtMs, FMOD_TIMEUNIT_MS );
			PA_FMOD_LOG_AND_ASSERT_ON_ERROR( ERROR, iResult );
			pAudioTableInstance->bIsSoundLengthReady = true;
		}

		if( pAudioTableInstance->pAudioTableInstanceCallback != NULL && pAudioTableInstance->bNeedToDestroy == false )
		{
			pAudioTableInstance->pAudioTableInstanceCallback( pAudioTableInstance, true );
		}
		return FMOD_OK;
	
	}

Sometimes, on very short sound , getLength returns a FMOD_ERR_NOTREADY ( " Operation could not be performed because specified sound/DSP connection is not ready" )
Is our code correct ?
What we want is to get the subsound length as soon as possible
Thanks

I think I will need a little more context to determine what the issue here is. Can you please share your full callback so I can see how the different callback cases are working together?

The intended way to use the FMOD_STUDIO_EVENT_CALLBACK_SOUND_PLAYED callback is to cast void* parameters to a FMOD::Sound and use that sound- it looks you are using a sound stored somewhere else and that sound for whatever reason is not ready. Here is an example of the expected usage of FMOD_STUDIO_EVENT_CALLBACK_SOUND_PLAYED:

case FMOD_STUDIO_EVENT_CALLBACK_SOUND_PLAYED:
	{
		FMOD::Sound* pSound = (FMOD::Sound*)parameters;

		if( pSound != NULL ) 
		{
			iResult = pSound->getLength( &pAudioTableInstance->iSoundLenghtMs, FMOD_TIMEUNIT_MS );
			PA_FMOD_LOG_AND_ASSERT_ON_ERROR( ERROR, iResult );
			pAudioTableInstance->bIsSoundLengthReady = true;
		}

		if( pAudioTableInstance->pAudioTableInstanceCallback != NULL && pAudioTableInstance->bNeedToDestroy == false )
		{
			pAudioTableInstance->pAudioTableInstanceCallback( pAudioTableInstance, true );
		}
		return FMOD_OK;
	
	}

That would depend on whether you filled in the Sound *sound inside FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES in the FMOD_STUDIO_EVENT_CALLBACK_CREATE_PROGRAMMER_SOUND callback though, so seeing the full callback should help me reproduce the issue and figure out what might be going wrong.

Hi !
Here is a more complete code source:
We are storing inside the EventInstance’s user data a structure called AudioTableInstance that contains FMOD::Sound*

struct AudioTableInstance
{
    String						sEventDescName;
    EventDesc					oEventDesc = I_AUDIO_INVALID_EVENT_DESC;
    EventInstance				oEventInstance = I_AUDIO_INVALID_EVENT_INSTANCE;
    String						sAudioFile;
    
    void*						pSound = NULL;	// FMOD::Sound*
    int							iSubSoundIndex;
    bool						bIsSoundLengthReady = false;
    uint32						iSoundLenghtMs;
};

Programmer Sound EventInstance creation and play command:

AudioTableInstance* AudioDriverFModStudio::CreateAudioTableInstance( const char* pEventName, AudioTableInstanceCallback pCallback, void* pCallbackUserData )
{
    // Get event desc
    EventDesc oEventDesc = GetEventDesc( pEventName );
    if( oEventDesc == I_AUDIO_INVALID_EVENT_DESC )
    {
        return NULL;

    }
    FMOD::Studio::EventDescription* pFMODEventDesc = PA_EVENTDESC_TO_FMOD( oEventDesc );
    PA_ASSERT( pFMODEventDesc!= NULL );

    // Create an event instance
    FMOD::Studio::EventInstance* pFMODEventInstance = NULL;
    FMOD_RESULT iResult = pFMODEventDesc->createInstance( &pFMODEventInstance );
    if( iResult != FMOD_OK )
    {
        PA_FMOD_LOG_AND_ASSERT_ON_ERROR( ERROR, iResult );
        return NULL;
    }

    AudioTableInstance* pResult = new AudioTableInstance();
    pResult->sEventDescName = pEventName;
    pResult->oEventDesc = oEventDesc;
    pResult->oEventInstance = PA_FMOD_TO_EVENTINSTANCE( pFMODEventInstance );
    pResult->pAudioTableInstanceCallback = pCallback;
    pResult->pCallbackUserData = pCallbackUserData;

    iResult = pFMODEventInstance->setUserData( pResult );
    PA_FMOD_LOG_AND_ASSERT_ON_ERROR( ERROR, iResult );
    pFMODEventInstance->setCallback( AudioTableSoundCallback, FMOD_STUDIO_EVENT_CALLBACK_CREATE_PROGRAMMER_SOUND | FMOD_STUDIO_EVENT_CALLBACK_DESTROY_PROGRAMMER_SOUND | FMOD_STUDIO_EVENT_CALLBACK_SOUND_PLAYED | FMOD_STUDIO_EVENT_CALLBACK_SOUND_STOPPED | FMOD_STUDIO_EVENT_CALLBACK_DESTROYED );
    PA_FMOD_LOG_AND_ASSERT_ON_ERROR( ERROR, iResult );

    PA_VERIFY( pResult->oLockVar.Create() == PA_OK );
    
    return pResult;
}

void AudioDriverFModStudio::AudioTablePlaySound( AudioTableInstance* pAudioTableInstance, const String& sAudioFile )
{
    PA_ASSERT( pAudioTableInstance != NULL );
    pAudioTableInstance->sAudioFile = sAudioFile;
    pAudioTableInstance->bIsSoundLengthReady = false;
    pAudioTableInstance->iSoundLenghtMs = (uint32)-1;
    EventInstanceCommand( pAudioTableInstance->oEventInstance, 
    Audio::IAudioDriver::EVENT_PLAY );
}

And the main callback function:

// Audio Table event callback
FMOD_RESULT F_CALLBACK AudioDriverFModStudio::AudioTableSoundCallback( FMOD_STUDIO_EVENT_CALLBACK_TYPE type, FMOD_STUDIO_EVENTINSTANCE* event, void* parameters )
{
    FMOD::Studio::EventInstance* pEventInstance = (FMOD::Studio::EventInstance*)event;
    FMOD_RESULT iResult;

    AudioDriverFModStudio* pAudioDriver =  (AudioDriverFModStudio*)s_pAudioDriver;
    PA_ASSERT( pAudioDriver != NULL );
    PA_ASSERT( pAudioDriver->m_pSystem != NULL );
    PA_ASSERT( pAudioDriver->m_pEventSystem != NULL );

    AudioTableInstance* pAudioTableInstance = NULL;
    pEventInstance->getUserData( ( void** )&pAudioTableInstance );
    PA_ASSERT( pAudioTableInstance != NULL );
    switch( type )
    {
        case FMOD_STUDIO_EVENT_CALLBACK_CREATE_PROGRAMMER_SOUND:
        {
            FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES* props = (FMOD_STUDIO_PROGRAMMER_SOUND_PROPERTIES*)parameters;

            // get the user - defined context of the programmer sound
            PA_ASSERT( pAudioTableInstance != NULL );

            // Find the audio file in the audio table with the key
            FMOD_STUDIO_SOUND_INFO info;
            iResult = pAudioDriver->m_pEventSystem->getSoundInfo( pAudioTableInstance->sAudioFile.c_str(), &info );
            PA_FMOD_LOG_STRING_AND_ASSERT_ON_ERROR( ERROR, iResult, pAudioTableInstance->sAudioFile.c_str() );

            FMOD::Sound* pSound = NULL;
            iResult = pAudioDriver->m_pSystem->createSound( info.name_or_data, FMOD_LOOP_NORMAL | FMOD_CREATECOMPRESSEDSAMPLE | FMOD_NONBLOCKING | info.mode, &info.exinfo, &pSound );
            PA_FMOD_LOG_AND_ASSERT_ON_ERROR( ERROR, iResult );
            //https://qa.fmod.com/t/how-to-know-the-duration-of-a-programmer-sound-in-advance/14987/2

            pAudioTableInstance->pSound = pSound;
            pAudioTableInstance->iSubSoundIndex = info.subsoundindex;
            props->sound		 = (FMOD_SOUND*)pSound;
            props->subsoundIndex = info.subsoundindex;

            return FMOD_OK;
        }
        case FMOD_STUDIO_EVENT_CALLBACK_SOUND_PLAYED:
        {
            FMOD::Sound* pSound = (FMOD::Sound*)pAudioTableInstance->pSound;
            FMOD::Sound* pSubSound = NULL;
            iResult = pSound->getSubSound( pAudioTableInstance->iSubSoundIndex, &pSubSound );
            PA_FMOD_LOG_AND_ASSERT_ON_ERROR( ERROR, iResult );

            uint32 iSoundLength = 0;
            uint32 iSubSoundLength = 0;
            iResult = pSound->getLength( &iSoundLength, FMOD_TIMEUNIT_MS );
            iResult = pSubSound->getLength( &iSubSoundLength, FMOD_TIMEUNIT_MS );
            PA_LOG( "Sound length: %d VS %d", iSoundLength, iSubSoundLength );

            return FMOD_OK;

        }
    }
}

In my case, I’m just using one ProgrammerSound / AudioTable, that can uses different “subsound”
My goal is the get the subsound lengh as soon as possible
On some very short subsound:

returns FMOD_ERR_NOTREADY

Thank you for the additional information. I have not been able to reproduce any issues with getting the length of a subsound. The only thing I can think of is the value of iSubSoundIndex changing some time between the CREATE_PROGRAMMER_SOUND callback and the SOUND_PLAYED callback, resulting in the newly indexed sound not being ready. If this is the case, then following the recommended approach of calling getLength on the sound passed in via the parameters argument instead of retrieving it via pAudioTableInstance->getSubSound should fix the issue.

...
case FMOD_STUDIO_EVENT_CALLBACK_SOUND_PLAYED:
{
    FMOD::Sound* pSubSound = (FMOD::Sound*)parameters; //This is the ready-to-play subsound

    uint32 iSubSoundLength = 0;
    iResult = pSubSound->getLength( &iSubSoundLength, FMOD_TIMEUNIT_MS );
    PA_LOG( "Sound length: %d", iSubSoundLength );

    return FMOD_OK;

}
...

Please try implementing that and let me know if you are still getting errors.

1 Like