Meta sounds not receiving parameters updates with Pooling system enabled

Hello,

We’re experiencing sounds losing MetaSound parameter updates after returning from virtualization. Affected sounds perform spatialization, attenuation (per-layer) and occlusion (custom plugin) inside the

MetaSound and are looping (OneShot interface disabled). The audible result is “stuck” vehicle sounds playing at MetaSound parameter defaults — RPM, gear state, etc. — until the sound re-enters and exits the

virtualization radius again. A separate attenuation asset handling the overall range continues to work correctly throughout.

We have a custom audio component pooling system. The stuck audio is confirmed to occur only on pooled components. Disabling audio component pooling eliminates the issue entirely.

Acquire flow

When no free component exists, we call NewObject<UAudioComponent>(this) (owner is a UGameInstanceSubsystem). Whether the component comes from the pool or is newly created, it goes through an identical init

path:

-bAutoActivate = false; bStopWhenOwnerDestroyed = false;

-SetWorldLocation(from SourceObject);

-AudioDeviceID = world audio device;

-RegisterComponentWithWorld(ContextWorld); // required — omitting breaks spatialization even without pooling

-InitializeComponent();

-BeginPlay();

Components are only returned to the pool in two cases: the sound finishes/is stopped, or the owning context is destroyed. A virtualizing sound is never returned to the pool — the component stays active

throughout virtualization.

Release/reset flow

Audio subsystem pre-release:

- Broadcast OnAudioComponentPreDestroyed

- Unbind OnAudioFinishedDelegateHandle

- Stop() / DetachFromComponent(KeepWorld)

Pooling subsystem (ReleaseComponent):

- Rename outer to subsystem

- EndPlay(RemovedFromWorld)

- UninitializeComponent()

- DetachFromComponent (shared scene component path)

- UnregisterComponent()

- SetComponentTickEnabled(false)

Reset specialization (AudioComponentPoolingSpecialization::ResetObject):

- If ActiveCount > 0: binds OnAudioFinishedNative async callback, calls Stop() if still active, returns RequiresAsync — component stays in pending reset until the audio thread clears

- Once clear: SetSound(nullptr), clears attenuation, concurrency, source effects, low-pass filter, parameters, volume, component tags, and all native delegates (OnAudioFinished, play state, virtualization,

playback percent, envelope)

The fix at runtime is setting the master bus volume to 0 and back — which flushes the audio system and unsticks the sounds. The problem occurs most reliably in larger playtests (50+ players) but reproduces

occasionally at smaller scale. Reproduction is inconsistent given the number of concurrent actions.

1. Is there anything missing from the reset flow above that could cause a recycled UAudioComponent to lose its MetaSound parameter connection after RegisterComponentWithWorld / InitializeComponent /

BeginPlay are re-called on re-acquisition?

2. Could UnregisterComponent + RegisterComponentWithWorld on a recycled component leave the component in a state where the audio thread no longer routes position/parameter updates to its active sound,

specifically after that sound returns from virtualization?

3. Is there a known issue with pooled UAudioComponent instances and MetaSound parameter updates post-virtualization?

Any guidance on what to verify or instrument would be appreciated.

We made a [related [Content removed] a while back, but had temporarily disabled this work due to the ongoing issue.

Cheers,

Mathias

[Attachment Removed]

Hi Mathias,

Thank you for reaching out and for your detailed description.

It sounds like there may be an issue with the AudioComponentID which is used to coordinate communication between the AudioComponent and a MetaSound.

Inside MetaSoundSource.cpp there is an FRouter which attempts to coordinate DataChannels for communication. I think the first step for investigation would be to see if those data channels are correct for the FMetaSoundGenerator (The thing that renders the audio) and the FMetasoundParameterTransmitter (the thing that transmits data from the audio component to the metasound).

My guess is that the AudioComponent reset is triggering the AudioComponentID to be incremented which then loses its link with the rendering MetasoundGenerator. We have done our own parameter pooling before without an issue, but the method is a little different to the one you describe.

[Attachment Removed]

pooling + parameter loss is a lifecycle race: when a pooled sound is grabbed from the pool, its previous state (stale input values, playback position mid-stream) comes with it, and your new set parameter calls can land before the pooled instance is re-primed, or get overwritten by the reset path. the standard fixes:

  1. set parameters after activation, not before: with pooled players, get the instance, activate/play, then set parameter values. setting before play on some pooling implementations hits the pre-reset state.
  2. reset inputs on pool return and on pool grab: your pool should explicitly reset every settable input (floats to defaults, triggers cleared) when an instance returns to the pool, and again after grab. if the metasound exposes a reset trigger input, hit it as part of the lifecycle.
  3. stop versus pause: if the pool uses stop and the metasound release phase (envelope tails) runs while the instance sits in the pool, subsequent parameter sets race the tail. use the on-release behavior deliberately, or force stop with fade length 0 on return.
  4. the priming caveat: prime sound on load and pooling interact badly when instances are grabbed before their first full playback; play once silently at pool warmup so all inputs initialize.
  5. verify the write path: to confirm parameters are actually reaching the instance (versus a pooling identity mixup where you set instance A and play instance B), print the instance IDs or route a debug float through a metasound meter output. identity mixups are the most common real bug in hand-rolled pools.
    worth knowing: stock spawn sound at location / play sound 2d already implement virtualization and voice management; hand-rolled pools mostly make sense for strictly controlled counts (ui clicks, pooled music stems). if the pooling exists to work around voice limits, sound class concurrency settings may remove the need for the pool entirely.