Audio
Common mistakes with audio in Unreal and Blender through MifBridge: sound attenuation, reverb, sound classes, MetaSounds, recording what plays, and Blender speakers and video sound. Read it when a sound is silent, too loud or too quiet at a distance, or an audio edit reads back right and still sounds like the old version.
A recording is the only real check for most of these. The engine can store a setting that nothing reads, so judge a change by what record_audio_output captures, not by the value read back.
Sound attenuation
Full volume reaches about twice the shape extent
For a sphere, the engine subtracts the inner radius (AttenuationShapeExtents.X) from the listener distance before the curve and floors the result at 0. With the logarithmic model the gain from that expression exceeds 1 for small d and is clamped, so the full-volume plateau is about twice the shape extent, not equal to it. Worked for extent 400 cm and falloff distance 3000 cm:
| Distance | Level |
|---|---|
| out to 8.1 m | full volume |
| 15.0 m | -6 dB |
| 28.6 m | -20 dB |
| 34.0 m | silence |
Reading AttenuationShapeExtents as "the loud part" is wrong by a factor of two. The engine's own maximum-dimension function agrees: for a sphere it adds the extent to the falloff distance.
dBAttenuationAtMax is usually inert
Only the NaturalSound attenuation model reads it. On a Logarithmic, Linear, Inverse or LogReverse asset it is stored, shown in the editor and consulted by nothing. A -60 in the panel is decoration, not the floor of the curve.
Authoring audio assets
There is no endpoint per audio asset type. They are all authored with the generic calls, and the values read back changed:
| Type | How |
|---|---|
SoundAttenuation |
create_asset, then set_property on Attenuation.FalloffDistance |
SoundSubmix |
create_asset, then set_property on OutputVolume |
ReverbEffect |
create_asset, then set_property on ReflectionsDelay |
SoundClass |
create_asset, then set_property on Properties.Volume |
SoundConcurrency |
create_asset, then set_property on Concurrency.VolumeScale |
SoundCue |
create_asset, then set_property on VolumeMultiplier |
AmbientSound, AudioVolume |
spawn_actor_in_level with actorClass |
Three of these keep every field inside one struct: SoundAttenuation inside Attenuation, SoundClass inside Properties and SoundConcurrency inside Concurrency. A tool that looks only at top-level fields sees nothing to edit. Use the nested path.
A Sound Class made by create_asset must be registered
The editor's Sound Class factory registers the new class with the audio devices. A bare object creation does not, so a class made without that step is unknown to the audio devices. A wave in it played without the class's properties, "Unable to find sound class properties" was logged every tick (694 times in one test), and a sound mix on it did nothing (0.0 dB, where the same mix on a registered class is -12.05 dB). create_asset now makes the factories' registration call itself and reports it as audioDeviceInit.
A Sound Class that parents its own parent ends the editor
Making a Sound Class (or submix) the parent of its own parent is a stack overflow that takes the editor down at once, and the engine's edit hook checks for no loop. set_property on a parent field goes through the engine's setter, refuses a loop before writing and reads both ends back as parentLink.
Reverb, modulation and sound waves
- A new reverb preset is silent before UE 5.8.
FSubmixEffectReverbSettings::Gain, the reverb's master volume, defaults to 0.0 on every engine. 5.8 sets 1.0 on a new asset only. On 5.3 a fully wet reverb with a 2.5 s decay added nothing to a tone, where 5.8 gave it a 1.84 s tail. With Gain set to 1.0 both engines hear the same tail to 0.1 dB. Set Gain whenever you make a preset. - A sound wave's own modulators are ignored until its routing says Override.
ModulationSettings.VolumeRouting(and Pitch, Highpass and Lowpass) defaults to Inherit, which takes the Sound Class's modulation and ignores the wave's own modulators. A bus in the wave's modulators with a mix driving it to 0.25 gave 0.0 dB under Inherit and -12.05 dB under Override. The "Modulate" tick in the details panel is editor-only data that nothing reads at runtime. The AudioModulation plugin must be enabled for these classes to exist. - A wave's compression is heard. Its loading behavior is not. The same 1 kHz plus 10 kHz tone, measured as the residual under the tones: PCM -74.1 dB, Bink -35 dB at quality 100 and -31 dB at quality 1, ADPCM -22 dB, and Opus at quality 1 -10 dB with the 10 kHz tone 11.9 dB down (Opus and RADAudio arrive in 5.4). The numbers agree within 1 dB on 5.3.2 and 5.8.2. Every
LoadingBehaviorvalue changed nothing: they decide when data loads, not what plays.SampleRateQualityMin changed nothing either, because it applies only where the platform turns on Resampling For Device, which is off in the Windows defaults. The enum spellings are the engine's own:SampleRateQualitytakes Max, High, Medium, Low and Min, andlist_enum_valuesgives them. - An interior volume applies only to a sound whose class asks for it. Audio volumes change a sound through the interior pass, which runs only where the sound's Sound Class has
Properties.bApplyAmbientVolumesset. A box with an Interior-Exterior Attenuation at ExteriorVolume 0.25 left a tone at 0.00 dB, and took it 12.05 dB down once the class set the flag. - A mix, a concurrency or a live change is reached by
call_function. Push and pop of sound mix modifiers,PlaySound2DandSpawnSound2Dare statics on/Script/Engine.Default__GameplayStatics, with any actor of the world as the world context (in Play In Editor, the PlayerController).SpawnSound2D's return value is the component's path, andAdjustVolume,FadeOut,SetPitchMultiplierandSetFloatParameterare called on it.FadeOutis linear in amplitude: a 1 s fade is heard as 75% to 25% of full in 0.50 s. Judge a fade by its rate, since the first step also carries the time the call took. - A sound exactly on the listener's left axis pans hard right. With the listener facing +X (yaw 0), a spatialized tone 500 cm to the right is all in the right channel, and the same tone to the left is also all in the right channel. Moved 1 cm ahead or behind it is all in the left, with the right 65.6 dB down. Yaw 0 leaves an exact zero forward component where a rotation leaves a floating-point residue. Put a test source 45 degrees off the axes.
PlaySoundAtLocationalso needs itsRotationpassed, because a zeroed rotation would run and report success. - A transient object
call_functionreturns is freed by garbage collection.UQuartzSubsystem::CreateNewClockreturns a handle in the transient package that nothing references, and in Play In Editor the next garbage collection (about once a minute) frees it. Its path then stops resolving. The clock itself is fine. Ask for a fresh handle before each use. The same holds for any transient object a call returns. - A quantized play reports itself playing at once.
PlayQuantizedsets the component to FadingIn the moment it is called, while the renderer holds the sound for the boundary. Judge a quantized start from a recording, not from the play state. Quartz's "Beat" counts from the bar by default, so EighthNote and Beat mean positions in the bar unless the boundary is transport-relative.
MetaSounds
- On UE 5.3 and 5.4 a graph built from Python loses its node defaults. The editor clears every node default the first time it builds an asset's editor graph (5.5 dropped this).
SetNodeInputDefaultanswers Succeeded and the asset holds nothing, and a 440 Hz sine sounds right anyway because 440 is the class default.create_metasound_graphreads the document after building and sets each missing default again through the editor graph's pins. Test a default with a value that is not the class default. - An edit that reached the document is not an edit that plays. A MetaSound plays the graph it registered, and the registration made when a sound starts skips an asset already registered. A Sine's Frequency set to 660 read back as 660 and was still heard at 330 Hz.
edit_metasound_graphre-registers after its edits. Undo has the same shape: the document was put back and the sound still played the edited graph.undo_transactionsandredo_transactionsregister the MetaSounds their steps touched and name them asmetasoundsReregistered. - A patch's users play it as it was when they registered. A source holds the patch inside its registered graph, so a patch edited and registered again does not change the source that uses it until that source is registered again.
- On 5.5 and 5.6 an interface change can need a second registration. After a graph input was added and wired to a sine, every audition still played the graph from before the edit, on 5.5.4 and 5.6.1. 5.7.4 and 5.8.2 played the new graph.
Recording what plays
- A recording ends when its sound does. Every submix switches itself off once silent, and a switched-off submix renders nothing to record. A one-shot's recording is exactly as long as the sound, so a window after the sound is an empty slice, not a quiet one.
au.NeverDisableSubmixes 1keeps submixes on (5.8; 5.3 has no such cvar). A mono source records 9 dB under the asset (-6 dB headroom, -3 dB pan law). - A placed sound is silent in a plain editor world. The editor updates audio devices as "game ticking" only in a play session unless the real-time audio preference is on, and a placed AmbientSound is not a UI sound. Test placed sounds in Play In Editor or a game.
audition_soundworks in the editor because its preview is a UI sound. - Play In Editor turns the editor world's audio off.
record_audio_outputover the editor world during Play In Editor would record nothing and say so only in the log. It follows a running session, names the world it recorded and refuses a world that plays no audio. Play In Editor gets an audio device of its own by default, so recording that world is recording what its player hears. - A hidden or unfocused editor plays silence or lags. The app's unfocused volume applies to Play In Editor, so a recording of a sound that reports Playing is exact zeros until
au.DisableAppVolumeis 1. With the background CPU throttle on and the editor out of focus, a take began 0.3 to 0.5 s late and ended early, and a fade ran in jumps. A Blender render on all cores starved the audio renderer ("Audio Buffer Underrun (starvation)"), and one take lost everything. Turn the throttle off and keep the machine free during a take. The first play of a new wave can also be late by about 0.2 s, so play each sound once first. - Keep the speakers quiet without muting the recording.
record_audio_outputtakesmuteSpeakers(on by default). It zeroes only the main submix's output volume for the take, which the recording copies before the volume applies, and restores it afterward. The two obvious mutes, app volume and a submix's background mute, silence the recording too. - A registered submix holds the presets in its chain. A reverb preset stayed undeletable for more than 20 s after its sound stopped while its submix still listed it. Emptying
SubmixEffectChainlet it go.
Blender audio
- A speaker plays only through an NLA SOUND strip. Blender adds one playback per NLA strip of type SOUND on a speaker object.
NlaStrips.new()makes a CLIP strip, so a speaker built frombpy.dataalone never plays.add_soundmakes the object and the SOUND strip at the scene's current frame. Speaker.sound = sndfrom Python is not heard. The evaluated speaker keeps pointing at the original sound, which Blender never loads for playback, so the mixdown is silent.update_tag()alone does not change that. Rebuilding relations does:obj.data = obj.data, thenobj.data.update_tag().- On Blender 5.2
sound.mixdownanswers FINISHED before a byte exists. A worker thread writes the file about 20 ms later, and in a--backgroundBlender the finished job is never retired, so every later mixdown job in that process is suspended and never writes. Wait for the file's own header and plan one mixdown per background session. 3.6 and 4.2 also lack the operator'smixrateandchannels: they mix atscene.render.ffmpeg.audio_mixrateandscene.render.ffmpeg.audio_channels. Scene.use_audiomeans opposite things. On 3.6 it is "Audio Muted" (True silences the mixdown). From 4.2 it is "Play Audio" (False silences). Read the property's RNA name to know which way it points.- A video renders without sound unless the codec is set.
render.ffmpeg.audio_codecis NONE on every build, so a factory scene renders video with no audio stream whatever the scene plays. That one setting is the mux: set the audio codec to AAC, FLAC, PCM and so on.render_infoandset_render_settings(after.video.carriesAudio) say which case a render is in. - From Blender 5.0, setting the format to FFMPEG fails on an image setup. Blender 5.0 added a media type (IMAGE, MULTI_LAYER_IMAGE, VIDEO), a factory scene's is IMAGE, and a video format is accepted only once it is VIDEO. FFMPEG is still in the property's enum items, so reading the items says it is valid and the assignment raises TypeError.
set_render_settingssets the media type for you and reports it asmediaType. - A scene's master volume is linear. Scene volume 0.5 put a rendered AAC track 6.02 dB down, so a loudness target needs no second mixdown: a scene normalized by volume alone rendered at -23.01 LUFS in both MKV with FLAC and MP4 with H264 and AAC.
Scene.audio_volumeruns 0 to 100 on every build. - A mono sound strip is mixed into stereo at -3.01 dB per channel. A 0.05-peak tone read a true peak of -29.03 dBTP in each channel. Measure the mixdown, not the source file.
- A number past its range is clamped, not refused.
audio_bitrate: 999999stores 384 kbps (2048 on 5.2) and reports 384 as the value kept. Compare what you asked for withapplied.