Get Programmer Instrument Sound Length

I’m using a programmer instrument together with an audio table to play music inside my cassette player and I want to display the duration of the played track, currently “played time” works but I can’t seem to get the total length of the track.

I’ve been reading on the forum and the closest I got was this post:
https://qa.fmod.com/t/get-length-duration-of-programmer-instrument-audio-table-loaded-sound/13911

Following the post above I get the correct subSound index based on the audio table but I can’t get the length to work consistently, it works like 1/20 times for some reason?

Here’s the full callback:

[AOT.MonoPInvokeCallback(typeof(EVENT_CALLBACK))]
static RESULT PlayMusicEventCallback(EVENT_CALLBACK_TYPE type, EventInstance eventInstance, IntPtr parameterPtr)
{
	// Retrieve the user data
	eventInstance.getUserData(out var stringPtr);

	// Get the string object
	GCHandle stringHandle = GCHandle.FromIntPtr(stringPtr);
	string key = stringHandle.Target as string;

	switch (type)
	{
		case EVENT_CALLBACK_TYPE.CREATE_PROGRAMMER_SOUND:
		{
			const MODE soundMode = MODE.LOOP_OFF | MODE.CREATESTREAM | MODE.NONBLOCKING;
			var parameter = (PROGRAMMER_SOUND_PROPERTIES) Marshal.PtrToStructure(parameterPtr, typeof(PROGRAMMER_SOUND_PROPERTIES));

			var keyResult = RuntimeManager.StudioSystem.getSoundInfo(key, out var musicSoundInfo);
			if (keyResult != RESULT.OK)
			{
				break;
			}

            // Attempt to get the track's length!
			var soundResult = RuntimeManager.CoreSystem.createSound(musicSoundInfo.name_or_data, soundMode | musicSoundInfo.mode, ref musicSoundInfo.exinfo, out Sound musicSound);
			Sound subSound;
			musicSound.getSubSound(musicSoundInfo.subsoundindex, out subSound); 
			uint length = 0; 
			subSound.getLength(out length, FMOD.TIMEUNIT.MS); 
			
			CassettePlayer.instance.SetMaxTrackDuration(length);
			Debug.Log(length);
			
			if (soundResult == RESULT.OK)
			{
				parameter.sound = musicSound.handle;
				parameter.subsoundIndex = musicSoundInfo.subsoundindex;
				Marshal.StructureToPtr(parameter, parameterPtr, false);
			}
			else
			{
				Debug.LogError("Error creating sound!");
			}
			
			break;
		}

		case EVENT_CALLBACK_TYPE.DESTROY_PROGRAMMER_SOUND:
		{
			var parameter = (PROGRAMMER_SOUND_PROPERTIES) Marshal.PtrToStructure(parameterPtr, typeof(PROGRAMMER_SOUND_PROPERTIES));
			var sound = new Sound {handle = parameter.sound};
			sound.release();
			break;
		}
		case EVENT_CALLBACK_TYPE.DESTROYED:
		{
			// Now the event has been destroyed, unpin the string memory so it can be garbage collected
			stringHandle.Free();
			break;
		}
	}

	return RESULT.OK;
}

subSound.getLength (as well as musicSound.getSubSound) will return an FMOD.RESULT that could indicate why it isn’t working.

I’m getting an ERR_NOTREADY result from the getSubSound, might be because we’re streaming the audio using MODE.CREATESTREAM?

Here’s code snipped again (runs inside CREATE_PROGRAMMER_SOUND callback)

var soundResult = RuntimeManager.CoreSystem.createSound(musicSoundInfo.name_or_data, soundMode | musicSoundInfo.mode, ref musicSoundInfo.exinfo, out Sound musicSound);
Sound subSound;
RESULT result = musicSound.getSubSound(musicSoundInfo.subsoundindex, out subSound);
if (result != RESULT.OK)
{
	Debug.LogError(result.ToString());
}
else
{
	uint length = 0;
	result = subSound.getLength(out length, FMOD.TIMEUNIT.MS);
	if (result != RESULT.OK)
	{
		Debug.LogError(result.ToString());
	}
	else
	{
		CassettePlayer.instance.SetMaxTrackDuration(length);
	}
}

This is because you are using NONBLOCKING when you create the sound:

System::createSound

Use FMOD_NONBLOCKING to have the sound open or load in the background. You can use Sound::getOpenState to determine if it has finished loading / opening or not. While it is loading (not ready), sound functions are not accessible for that sound.

Using LateUpdate to check if Sound::getOpenState == OK fixed my issue, Thanks Cameron!

Here’s the code:

// Inside LateUpdate switch case logic
var result = musicSound.getOpenState(out var openState, out _, out _, out _);
if (result == RESULT.OK)
{
	if (openState != OPENSTATE.ERROR)
	{
		musicSound.getSubSound(subSoundIndex, out var subSound);
		// Wait until the length have been fetched!
		result = subSound.getLength(out var length, FMOD.TIMEUNIT.MS);
		if (result == RESULT.OK)
		{
			CassettePlayer.instance.SetMaxTrackDuration((int) length);
			gotMaxLength = true;
		}
	}
}
2 Likes