How to retrieve a sound total time/length/duration?

Hi there!
I’m trying to figure out how to retrieve a sound total time. I’ve tested the following:
soundState = FMODUnity.RuntimeManager.CreateInstance(soundKey);
FMODUnity.RuntimeManager.AttachInstanceToGameObject(soundState, transform);
soundState.start();
FMOD.Studio.EventDescription evt;
soundState.getDescription(out evt);
int len = 0;
evt.getLength(out len);

Unfortunately, len value ends with the value of 0 - zero. Am I using the wrong method for the task?

Thanks,

EventDescription::getLength only works on events with a timeline parameter, does the event in question have a timeline parameter?

No, it does not. It’s good to know that the issue is there. The sound guy wants to keep it this way, so there may be sounds for Japanese VA, English VA.

What do you think? It would be better to try another way to retrieve a sound length, or another way for separating audio for localized versions?

If you want the length of a sound when it is played you can implement a callback on FMOD_STUDIO_EVENT_CALLBACK_SOUND_PLAYED, which will give you the underlying Sound object, on which you can call Sound::getLength. We have an example of how to implement an event callback here: Timeline Callbacks
You would need to add the FMOD.Studio.EVENT_CALLBACK_TYPE.SOUND_PLAYED flag when calling EventInstance::setCallback and then the callback would be something like this:

[AOT.MonoPInvokeCallback(typeof(FMOD.Studio.EVENT_CALLBACK))]
static FMOD.RESULT SoundCallback(FMOD.Studio.EVENT_CALLBACK_TYPE type, System.IntPtr instancePtr, System.IntPtr parameterPtr)
{
	FMOD.RESULT result;
	FMOD.Studio.EventInstance instance = new FMOD.Studio.EventInstance(instancePtr);
		
	switch (type)
	{
		case FMOD.Studio.EVENT_CALLBACK_TYPE.SOUND_PLAYED:
			{
				FMOD.Sound sound = new FMOD.Sound(parameterPtr);
				result = sound.getName(out string name, 256);
				if (result != FMOD.RESULT.OK)
					return result;

				result = sound.getLength(out uint length, FMOD.TIMEUNIT.MS);
				if(result == FMOD.RESULT.OK)
				    return result;

				Debug.Log("Sound Played. Name: " + name + " Length: " + length); 
				break; 
			}

	}
	return FMOD.RESULT.OK; 
}
1 Like

Hi! Could you elaborate a bit on the example code given here?

Which lines are must-have in order to retrive the audio file length?
I’m slightly confused with the switch case and the FMOD.RESULT return type here.

Would this also work if it’s a programmer instrument that calls a different wav file each time?

The minimum amount of code would be:

[AOT.MonoPInvokeCallback(typeof(FMOD.Studio.EVENT_CALLBACK))]
static FMOD.RESULT SoundCallback(FMOD.Studio.EVENT_CALLBACK_TYPE type, System.IntPtr instancePtr, System.IntPtr parameterPtr)
{
	FMOD.Studio.EventInstance instance = new FMOD.Studio.EventInstance(instancePtr);
	FMOD.Sound sound = new FMOD.Sound(parameterPtr);
	return sound.getLength(out uint length, FMOD.TIMEUNIT.MS);
}

This is inside a generic callback, which might not necessarily be SOUND_PLAYED every time. If you look at our Programmer Sounds example you will see the relevance of the switch.
As for the FMOD.RESULT return type, every function in the FMOD API returns an FMOD.RESULT, telling you whether the operation was successful. This can be used for fault finding and recovering from errors. A requirement of Event Callbacks is that they return an FMOD.RESULT, which is why it has return FMOD.RESULT.OK at the bottom of the callback, to notify the internal system that the callback completed successfully.

Yes. The FMOD.Sound object will be wrapping a single audio file in the callback. If your event has two audio files in it, then this callback will be triggered twice, once for each audio file when it begins playing.

Thanks a lot for your explanation!

I’m still not quite sure where to place the sound.getLength() command.

After checking the Programmer Sounds example, which we are using, I ended up placing the sound.getLength() calls in both if-else cases under the CREATE_PROGRAMMER_SOUND case, with the “dialogueSound” Sound var.

In order to use the returned length, I initiated an uint variable outside the function and passed it in as the “out”. However, when trying to print this return value, it is still always 0.
Even after trying with a temp var as shown in example, the printed result is still 0.
There is always a dialogue file playing, so it shouldn’t be 0, I assume.

What am I doing or understanding wrong here?
(Hopefully my description makes sense.)

It sounds like you are using global data, which is not the correct way to pass data in and out of a static callback- you should be using Studio.EventInstance.setUserData / Studio.EventInstance.getUserData to pass data in and out of the callback.
Can you please share a code snippet of your callback?

For using that global data, since it’s attached to an instance of a singleton, it should be working I assume?

Our code snippet is identical to the example here in Programmer Sounds. There is no line number here, but it’s just in the last DialogueEventCallback() function, under the first CREATE_PROGRAMMER_SOUND case, in both “if” and “else” cases, after the if (soundResult == FMOD.RESULT.OK) braces.

The correct way to use external data in the callback API is to pass it as user data into Studio.EventInstance.setUserData and retrieve it with Studio.EventInstance.getUserData. It might be fine in your case to do otherwise, but you can hit easily run into unexpected behavior and obscure crashes due to memory management issues, so it is best to get/set the user data as recommended.
As for whether that is actually the cause of your problem I am not sure- I have added these lines to our example and it is working fine:

[AOT.MonoPInvokeCallback(typeof(FMOD.Studio.EVENT_CALLBACK))]
static FMOD.RESULT DialogueEventCallback(FMOD.Studio.EVENT_CALLBACK_TYPE type, IntPtr instancePtr, IntPtr parameterPtr)
{
...
    FMOD.Sound dialogueSound;
    var soundResult = FMODUnity.RuntimeManager.CoreSystem.createSound(Application.streamingAssetsPath + "/" + userData.Key, soundMode, out dialogueSound);
    if (soundResult == FMOD.RESULT.OK)
    {
        parameter.sound = dialogueSound.handle;
        parameter.subsoundIndex = -1;
        Marshal.StructureToPtr(parameter, parameterPtr, false);
    }

    var result = dialogueSound.getName(out string name, 256);
    if (result != FMOD.RESULT.OK)
        return result;

    result = dialogueSound.getLength(out uint length, FMOD.TIMEUNIT.MS);
    if (result != FMOD.RESULT.OK)
        return result;

    Debug.Log("Sound Played. Name: " + name + " Length: " + length);
...
}

What version of FMOD are you using?

We are using FMOD v 2.02.07.

In the example, we have very similar code to what you have up there. The code didn’t print anything to the console, so I moved the Debug.Log line before checking the result. This time the line is printed, indicating that the result just now is not OK.

Then I tried printing the result - ERR_NOTREADY.

We also tried placing the following code outside the switch case:

FMOD.Sound dialogueSound1 = new FMOD.Sound(parameterPtr); 
dialogueSound1.getLength(out uint length, FMOD.TIMEUNIT.MS);
Debug.Log(length);

Placed before or after the switch case, this would give us a number, but it’s always the length of the previous file played. Therefore we still cannot really apply this value.

Any leads on how to solve this issue with the “not ready” error?

This indicates your Sound has not loaded yet. Can you please query the Sound’s open state with Sound::getOpenState and tell me which FMOD_OPENSTATE it is reporting?