DOCUMENTATION

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 LoadingBehavior value changed nothing: they decide when data loads, not what plays. SampleRateQuality Min 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: SampleRateQuality takes Max, High, Medium, Low and Min, and list_enum_values gives 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.bApplyAmbientVolumes set. 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, PlaySound2D and SpawnSound2D are 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, and AdjustVolume, FadeOut, SetPitchMultiplier and SetFloatParameter are called on it. FadeOut is 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. PlaySoundAtLocation also needs its Rotation passed, because a zeroed rotation would run and report success.
  • A transient object call_function returns is freed by garbage collection. UQuartzSubsystem::CreateNewClock returns 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. PlayQuantized sets 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). SetNodeInputDefault answers Succeeded and the asset holds nothing, and a 440 Hz sine sounds right anyway because 440 is the class default. create_metasound_graph reads 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_graph re-registers after its edits. Undo has the same shape: the document was put back and the sound still played the edited graph. undo_transactions and redo_transactions register the MetaSounds their steps touched and name them as metasoundsReregistered.
  • 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 1 keeps 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_sound works in the editor because its preview is a UI sound.
  • Play In Editor turns the editor world's audio off. record_audio_output over 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.DisableAppVolume is 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_output takes muteSpeakers (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 SubmixEffectChain let 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 from bpy.data alone never plays. add_sound makes the object and the SOUND strip at the scene's current frame.
  • Speaker.sound = snd from 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, then obj.data.update_tag().
  • On Blender 5.2 sound.mixdown answers FINISHED before a byte exists. A worker thread writes the file about 20 ms later, and in a --background Blender 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's mixrate and channels: they mix at scene.render.ffmpeg.audio_mixrate and scene.render.ffmpeg.audio_channels.
  • Scene.use_audio means 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_codec is 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_info and set_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_settings sets the media type for you and reports it as mediaType.
  • 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_volume runs 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: 999999 stores 384 kbps (2048 on 5.2) and reports 384 as the value kept. Compare what you asked for with applied.