Scriptable audio effects
Learn how to create and apply custom scriptable audio effects to transform audio in your project's pipeline.
Read time 6 minutesLast updated 11 days ago
A scriptable audio effect is a custom audio processor that transforms audio as it flows through the audio pipeline. An effect reads audio from an input buffer, processes it, and writes the result to an output buffer. Use effects to build filters, dynamics processors, distortion, or any other transformation of an audio signal.
The most common way to use an effect is to apply it to an , where it processes the signal the audio source produces. You can also create effect instances directly in code, for example to nest an effect inside another processor.
AudioSourceTo apply an effect to an audio source, or to define a reusable effect asset, implement the interface, which acts as a factory for creating instances of your effect. Creating an instance directly in code doesn't require : you can call yourself.
IAudioEffectIAudioEffectControlContext.AllocateEffectApply an effect to an audio source
To apply an effect to an :
AudioSource- In the effect's script, define a that implements
MonoBehaviour.IAudioEffect - Add the effect component to the same GameObject as the .
AudioSource
Unity discovers the component automatically and inserts the effect into the audio source's signal chain. Unlike generators, you don't assign the component to a field in the Inspector. The component's presence on the GameObject applies the effect. This workflow is identical to built-in audio filter components, such as . For more information, refer to Use audio filters.
AudioLowPassFilterusing UnityEngine;using UnityEngine.Audio;public class MyEffect : MonoBehaviour, IAudioEffect{ public EffectInstance CreateInstance( ControlContext context, AudioFormat? nestedFormat, EffectInstance.CreationParameters creationParameters) { return context.AllocateEffect(new MyRealtime(...), new MyControl(...), nestedFormat, creationParameters); }}
Unity calls when it discovers the component. This happens when:
CreateInstance- The audio source starts playing
- The components on the GameObject change during playback.
Inside , initialize the effect from serialized fields or default values.
CreateInstanceThe AudioSource component owns the returned instance. The AudioSource destroys this instance when you remove the effect component, or you disable or destroy the audio source.
Stopping the audio source doesn't destroy the instance. Unity deactivates the effect, but keeps the instance alive and reuses it when the audio source plays again. This means that doesn't run a second time and Unity doesn't call the method of your IControl implementation. Because of this, a stateful effect keeps its state across a stop and a subsequent play. To prevent the effect's state from carrying over, you need to explicitly reset it. For example, send a message () to the effect after you call .
CreateInstanceDisposeControlContext.SendMessageAudioSource.PlayA domain reload destroys and re-creates the instance. Therefore, effect state doesn't survive a domain reload.
You can add multiple effect components to the same GameObject. Unity processes them in component order, together with any built-in filter components such as , so you can reorder effects by reordering the components in the Inspector.
AudioLowPassFilterControl an effect at runtime
The following rules control how an effect participates in the audio source's signal chain:
- Disable the effect component to bypass the effect, and make audio passes through it unchanged. Re-enable the component to resume processing.
- Enable Bypass Effects on the to deactivate all effects on the audio source.
AudioSource - Remove the component to remove the effect from the signal chain and destroy its instance.
To communicate with an effect while it plays, pass the component to and use the returned instance with the built-in control context, for example to send messages. Always guard the handle with , because the instance might not exist yet or might have been destroyed.
AudioSource.GetEffectInstanceControlContext.Existsvar instance = audioSource.GetEffectInstance(myEffectComponent);if (ControlContext.builtIn.Exists(instance)){ var message = new MyMessage(...); ControlContext.builtIn.SendMessage(instance, ref message);}
For a complete walkthrough that sends parameter changes to an effect, refer to Configure the gain in Example: Create an effect.
The IAudioEffect
interface
IAudioEffectAn implementation provides the factory method, which creates an . Unity calls it when an audio source discovers the effect component. For a nested effect, your parent processor calls it. Inside , allocate the instance with , pairing a real-time struct that implements with a control struct that implements .
IAudioEffectCreateInstanceEffectInstanceCreateInstanceControlContext.AllocateEffectEffectInstance.IRealtimeEffectInstance.IControlYou can implement on a to apply the effect to an audio source, or on a to define a reusable, asset-based effect that other processors instantiate as a nested effect.
IAudioEffectMonoBehaviourScriptableObjectTo serialize a reference to an , declare a field of type on your component. Interface references aren't directly serializable in user scripts, and this helper struct stores the reference for you.
IAudioEffectIAudioEffect.SerializableConfigure an effect
Unity calls the method of the effect's control part when it creates the effect instance, and again whenever the audio system changes configuration. A configuration change typically happens in response to a user action, such as when a user changes the audio output device in the OS system settings or connects a pair of headphones.
ConfigureConfigureAudioConfigurationConfigureEffectInstance.SetupoutdefaultNested effects
Like generators, effects can be nested inside other processors. A parent processor creates a child effect with , optionally passing a suggested , and is then responsible for the child's lifecycle:
ControlContext.AllocateEffectAudioFormat- Reconfigure the child with from the parent's
EffectInstance.Configuremethod, but only whenConfigureis true. Unity configures root processors only, so a child that caches configuration-dependent state, such as filter coefficients, keeps stale values after a sample rate or output device change unless the parent forwards the new configuration. Don't call it while you construct the child, becauseControlContext.IsSystemWideReconfiguringalready configures it, andControlContext.AllocateEffectthrows outside a system-wide reconfiguration.EffectInstance.Configure - Update the child with from the parent's control part.
ControlContext.Update - Process the child with from the parent's real-time part, within a mix cycle. The input and output buffers must have the same channel and frame counts.
EffectInstance.Process - Destroy the child with from the parent's
ControlContext.Destroymethod.Dispose
Best practices
Consider the following:
- Keep real-time safe. Avoid allocations, locks, blocking I/O, logging, and throwing exceptions on the audio thread. Use stack-allocated or pooled buffers, and struct-based states for predictable performance.
Process - Write every output sample on every code path. The output buffer starts undefined, and Unity doesn't clear it for you.
- Don't assume the input and output buffers point to different memory. When Unity's audio engine runs an effect applied to an audio source, both buffers can reference the same memory, so read each input sample before you overwrite the corresponding output sample.
- Beware of threading. Never call APIs from within
UnityEngine. Use pipes to synchronize state between the control and real-time part, and avoid any multithreaded communication that might yield indeterministic results.Process - Use Burst-compiled, struct-based code. Use simple value types and tight loops for improved performance. Leverage and organize data for optimal Burst auto-vectorization.
Unity.Mathematics - Validate externally received inputs. Clamp out-of-range parameters to valid ranges to prevent errors.