Crash When Duplicating MetaSound Source Assets in Packaged Builds

I am attempting to duplicate a MetaSound Source asset at runtime so I can play multiple sounds independently. Although it’s possible to play multiple sounds through a single MetaSound Source that’s assigned to multiple audio components they share the same MSS settings (submix, volume, etc.) which I want to customise for each sound.

I’ve found that calling `DuplicateObject` on a MetaSound Source seems to work perfectly well in PIE, and even editor standalone, but crashes in a packaged build. From my investigation it seems to stem from `UMetaSoundSource::Serialize(…)` calling `UMetaSoundSource::SerializeCookedQualitySettings(…)`.

When a duplication occurs the new object is created and the source object then has `Serialize(…)` called on it and given an archive to store the data in. That archive is then passed to the new object’s `Serialize(…)` to populate the properties in the same way. However, the quality settings portion of the MSS’s serialization is guarded by checks which prevent it from being run unless it’s either cooking or loading cooked data. This means the archive that’s being generated by the duplication function is incomplete: since it’s missing the `FMetaSoundQualitySettings` object that’s pushed onto the end.

I’ve found a potential quick fix for this is to include the condition `|| InArchive.IsSaving()` wrapped in an `#if !WITH_EDITORONLY_DATA` guard when doing the check before calling `UMetaSoundSource::SerializeCookedQualitySettings(…)` in `UMetaSoundSource::Serialize(…)`.

I also have an alternative fix which changes a bit more which resolves-out the aliasing of `FMetaSoundQualitySettings` to `FCookedQualitySettings` and instead stores the quality settings as a UPROPERTY (updated on save and cook) so they are automatically serialised instead of having to be manually pushed into the archive.

Steps to Reproduce
Tested in 5.8.1:

  • Create a new project (I used a C++ Third Person template)
  • Create a MetaSound Source asset.
  • Create a UDeveloperSettings class with a `TSoftObjectPtr<UMetaSoundSource>` property.
  • At runtime call `DuplicateObject<UMetaSoundSource>(MSS.Get(), MSS.Get()->GetPackage(), TEXT(“DupedMetaSoundSource”));` where MSS is the loaded asset pointed at by the aforementioned property.

In the attached project the dupe is performed inside a console cmd so it can be manually triggered at runtime.

Hello!

If I’m understanding correctly, you shouldn’t need to duplicate the asset at runtime when what you’re trying to achieve is changing settings on an instance of the sound. The options you mentioned are settable on the audio component itself in BP/code, ex. SetSubmixSend or SetVolumeMultiplier. If you want MetaSound graph level control on an instance at runtime, you can take a look at UMetaSoundBuilderSubsystem.

Thanks for the extra context and apologies for the delay in getting back to you.

You are right that SetSubmixSend adds a parallel override send instead of replacing the sound’s existing send, resulting in the behavior you were seeing. Sorry for any confusion this may have caused.

Given that, if I understand correctly, you’re looking to do mixing based on groups of sounds, I would recommend using Modulation instead which is intended for mixing. It also gives you flexibility for dynamically controlling/modulating volume levels based on gameplay data or levels of other groups of sounds.

You should be able to use Set/AddModulationRouting for Volume with a Control Bus on the audio component, and mix your different categories with a Control Bus Mix. Lastly, Modulation is a more modern solution, but you could also consider using Sound Classes.

Here are the documentation pages for reference:

https://dev.epicgames.com/documentation/unreal\-engine/audio\-modulation\-overview\-in\-unreal\-engine?lang\=en\-US

https://dev.epicgames.com/documentation/unreal\-engine/audio\-modulation\-quick\-start\-guide

https://dev.epicgames.com/documentation/unreal\-engine/sound\-classes\-in\-unreal\-engine

Hi. Thanks for the reply.

I had a quick look at SetSubmixSend(…) and SetVolumeMultiplier(…) and the latter certainly seems like something I can use for volume control of the specific sound. However SetSubmixSend(…) seems to be doing some weird stuff and I’m not sure if it’s just my misunderstanding of it.

To give a bit more context:

I have multiple submixes: Music and SFX which are children of Master. When I enter PIE I create an actor which creates an audio component per sound that’s requested: music, gunshot, UI click, etc. and sets the MetaSound Source on that component. It then passes various data in as parameters:

Comp->SetWaveParameter("Wave Asset", SoundAsset);
Comp->SetParameter(FAudioParameter("IsLooping", bIsLooping));
Comp->SetParameter(FAudioParameter("World", Cast<UObject>(WorldContext));
Comp->Sound->SoundSubmixObject = Submix;
Comp->Play();

Using the above (some parameters are just examples of the type of stuff I’m planning to pass in) I can have it play different sounds. However they all use the last set submix, hence why I’ve tried to duplicate the asset so they don’t all switch to using the new submix. When I duplicate the asset I can control the submix volumes at runtime (using CVars that get the submixes from developer settings and sets the volume) and I’m able to mute them in this way.

When swapping out Comp->Sound->SoundSubmixObject = Submix to SetSubmixSend(…) it seems they do keep their own submix but I’m now unable to mute it? I tried passing in 0.0f and 1.0f as the SendLevel (based on what I can find elsewhere in the codebase) but neither seemed to have much impact. Is the SendLevel linear or logarithmic? And does using SetSubmixLevel(…) mean I can’t directly control the submixes anymore?