Documentation

Unity Engine


User Manual

Script Reference

Unity Engine


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
AudioSource
, 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.
To apply an effect to an audio source, or to define a reusable effect asset, implement the
IAudioEffect
interface, which acts as a factory for creating instances of your effect. Creating an instance directly in code doesn't require
IAudioEffect
: you can call
ControlContext.AllocateEffect
yourself.

Apply an effect to an audio source

To apply an effect to an
AudioSource
:
  1. In the effect's script, define a
    MonoBehaviour
    that implements
    IAudioEffect
    .
  2. 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
AudioLowPassFilter
. For more information, refer to Use audio filters.
using 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
CreateInstance
when it discovers the component. This happens when:
  • The audio source starts playing
  • The components on the GameObject change during playback.
Inside
CreateInstance
, initialize the effect from serialized fields or default values.
The 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
CreateInstance
doesn't run a second time and Unity doesn't call the
Dispose
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 (
ControlContext.SendMessage
) to the effect after you call
AudioSource.Play
.
A 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
AudioLowPassFilter
, so you can reorder effects by reordering the components in the Inspector.

Control 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
    AudioSource
    to deactivate all effects on the audio source.
  • 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
AudioSource.GetEffectInstance
and use the returned instance with the built-in control context, for example to send messages. Always guard the handle with
ControlContext.Exists
, because the instance might not exist yet or might have been destroyed.
var 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

An
IAudioEffect
implementation provides the
CreateInstance
factory method, which creates an
EffectInstance
. Unity calls it when an audio source discovers the effect component. For a nested effect, your parent processor calls it. Inside
CreateInstance
, allocate the instance with
ControlContext.AllocateEffect
, pairing a real-time struct that implements
EffectInstance.IRealtime
with a control struct that implements
EffectInstance.IControl
.
You can implement
IAudioEffect
on a
MonoBehaviour
to apply the effect to an audio source, or on a
ScriptableObject
to define a reusable, asset-based effect that other processors instantiate as a nested effect.
To serialize a reference to an
IAudioEffect
, declare a field of type
IAudioEffect.Serializable
on your component. Interface references aren't directly serializable in user scripts, and this helper struct stores the reference for you.

Configure an effect

Unity calls the
Configure
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.
Configure
receives the
AudioConfiguration
the effect runs in, such as the sample rate and speaker mode. Use it to set any real-time fields that depend on the configuration, for example filter coefficients. During reconfiguration, the real-time part is temporarily suspended from processing, so you can safely modify its fields.
Configure
also returns an
EffectInstance.Setup
through an
out
parameter. This type is reserved for future configure-time output and currently carries no data, so assign
default
.

Nested effects

Like generators, effects can be nested inside other processors. A parent processor creates a child effect with
ControlContext.AllocateEffect
, optionally passing a suggested
AudioFormat
, and is then responsible for the child's lifecycle:
  • Reconfigure the child with
    EffectInstance.Configure
    from the parent's
    Configure
    method, but only when
    ControlContext.IsSystemWideReconfiguring
    is 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, because
    ControlContext.AllocateEffect
    already configures it, and
    EffectInstance.Configure
    throws outside a system-wide reconfiguration.
  • Update the child with
    ControlContext.Update
    from the parent's control part.
  • Process the child with
    EffectInstance.Process
    from the parent's real-time part, within a mix cycle. The input and output buffers must have the same channel and frame counts.
  • Destroy the child with
    ControlContext.Destroy
    from the parent's
    Dispose
    method.

Best practices

Consider the following:
  • Keep
    Process
    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.
  • 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
    UnityEngine
    APIs from within
    Process
    . Use pipes to synchronize state between the control and real-time part, and avoid any multithreaded communication that might yield indeterministic results.
  • Use Burst-compiled, struct-based code. Use simple value types and tight loops for improved performance. Leverage
    Unity.Mathematics
    and organize data for optimal Burst auto-vectorization.
  • Validate externally received inputs. Clamp out-of-range parameters to valid ranges to prevent errors.

Additional resources