These fixes are mainly to address performance issues related to MetaSounds on the first frame when told to play. For more information on the problem and profiling done to identify it, you can scroll down to after the fix. Note: MetaSounds is in Early Access and there is no doubt in my mind that the power and performance of MetaSounds will be exponentially better by the time UE5 is released officially. These fixes are for folks like me who are impatient and want to try out ideas that were just not possible to do easily in UE4.

The Fixes

1. Re-Enable Asynchronous Sound Building.

According to Aaron Mcleran, this used to be on but they ran into technical issues working on Valley of Ancients and had to turn it off temporarily to fix other issues. Now those issues are fixed and we can re-enable it.

//ENGINE OVERRIDE - Enabling Async Task - Updating Graph seems to cause issues when doing the builder async
//BuilderTask->StartSynchronousTask();
//BuilderTask = nullptr;
//UpdateGraphIfPending();
		
BuilderTask->StartBackgroundTask(GBackgroundPriorityThreadPool);
// ENGINE OVERRIDE END
MetasoundGenerator.cpp diff view

2. Set BlockRate to 50 to ease CPU cost of playing a MetaSound

au.MetaSound.BlockRate 50

3. A bit hacky – Cache the Metadata into a Map to reduce the overhead of searching through for the same type of Metadata.

//ENGINE OVERRIDE - Caching pins to reduce cost by half for processing.
TMap<FName, FMetasoundFrontendClassMetadata> CachedMap;
  
// Inject receive nodes for each transmittable input
for (const FSendInfoAndVertexName& InfoAndVertexName : SendInfoAndVertexes)
{	
	bool bSuccess = false;
	FMetasoundFrontendClassMetadata ReceiveNodeMetadata = FMetasoundFrontendClassMetadata();
	//UE_LOG(LogTemp, Warning, TEXT("TypeName %s"), *InfoAndVertexName.SendInfo.TypeName.ToString());
	if(CachedMap.Contains(InfoAndVertexName.SendInfo.TypeName))
	{
		bSuccess = true;
		ReceiveNodeMetadata = *CachedMap.Find(InfoAndVertexName.SendInfo.TypeName);
	}
	else
	{
		ReceiveNodeMetadata = FMetasoundFrontendClassMetadata();
		bSuccess = GetReceiveNodeMetadataForDataType(InfoAndVertexName.SendInfo.TypeName, ReceiveNodeMetadata);
		CachedMap.Add(InfoAndVertexName.SendInfo.TypeName, ReceiveNodeMetadata);
	}

	//ENGINE OVERRIDE END
MetasoundSource.cpp Diff view

4. Make sure to enable stream caching and force streaming on your audio files.

In project settings, enabling stream caching will ensure that audio files can be processed as fast as possible during runtime.
On your wav files you can force streaming and make them seekable to take full advantage of wave samplers in MetaSounds

The Problems (More Details) – Synchronous sound building and Slow Metadata Searching On Input

I have recently jumped into UE5 to test out MetaSounds for some cool ideas for Project MIX. First thing I noticed however, is that runtime performance is severely impacted by large hitches during play (Remember that UE5’s MetaSounds are super super hot off the press, nothing is set in stone and everything is likely to improve and change).

Here is the MetaSound I am using for example. Nothing too complex, but I am playing many of these at a time so its understandable that there would be some performance concerns.

When looking at the results from using stat startfile and stat stopfile. We can clearly see the trouble areas.
For more information on profiling performance you can go here.

When playing multiple sounds on the same frame (around 5-10), we see these massive spikes.
Processing Sources – 37.044 ms
FAsyncMetaSoundBuilder – 10.418 ms.

Image

The first thing was getting some great insider knowledge from Aaron Mcleran saying that FAsyncSoundBuilder was actually built to run asynchronously. (Fix is above for that)

When going through and switching it back to an Asynchronous task we now get this

What we see now is the FAsyncMetaSoundBuilder issue is now gone without any real delay issues even with using the metasounds with Quartz.

This now leaves Processing sources as our next big hitter. After a few hours of digging I was able to narrow it down to this function in MetasoundSource.cpp

Every time FindClassesWithClassName is run, it can hit the cpu anywhere from 0.5ms to 2ms depending on complexity of your input graph pins. Essentially if your Metasound has 100’s of inputs, you will see a cost of about 1ms per pin when the sound plays, which is something that is likely to be improved. FOR NOW up above, I have a bit of a hacky fix where we make sure to only run this function once per pin type. So if you have 10 float inputs, it will cost 1ms to gather the metadata instead of 10 ms.

The reason is it seems to have to re-execute a query in order to get the proper meta-data. This to me feels like it is something that will be improved upon since it doesn’t quite make sense why it needs to do that on-play.

After the hack fix in MetasoundSource.cpp listed above, we see this as a result.

You can see the largest spike now happens much more rarily and is MAX about 12.8ms. Still reallllly high but comparatively now at least the game is playable. Hopefully this helps other folks like myself who want to dive into some awesome new tech!