Documentation

Unity Engine


User Manual

Script Reference

Unity Engine


Generators

Learn about generators.
Read time 11 minutesLast updated 13 days ago

A generator is a custom sound source that produces audio and injects it directly into a scene, typically through an
AudioSource
. Generators are a core extension point of the scriptable audio pipeline and let you synthesize or stream real-time audio.
You can attach a generator to an
AudioSource
in the following two ways: Asset-based workflow and component-based workflow.
Both workflows rely on implementing the
IAudioGenerator
interface, which acts as a factory for creating instances of your generator.

Asset-based workflow

Create a
ScriptableObject
that implements
IAudioGenerator
.
using UnityEngine;[CreateAssetMenu(fileName = "MyAssetBasedGenerator", menuName = "Audio/Generators/My Asset-Based Generator", order = 1)]public class MyAssetBasedGenerator : ScriptableObject, IAudioGenerator{ public bool isFinite => ...; public bool isRealtime => ...; public DiscreteTime? length => ...; public GeneratorInstance CreateInstance( ControlContext context, AudioFormat? nestedConfiguration, ProcessorInstance.CreationParameters creationParameters) { return context.AllocateGenerator(new MyRealtime(...), new MyControl(...)); }}
After you've created the asset, you can assign it to an
AudioSource
by dragging and dropping to the Generator field in the Inspector.

Component-based workflow

To use the Component-based workflow, define a
MonoBehaviour
that implements
IAudioGenerator
and then add it to a GameObject in your scene. In the Inspector for your
AudioSource
, assign this component to the Generator field.
using UnityEngine;public class MyComponentBasedGenerator : MonoBehaviour, IAudioGenerator{ public bool isFinite => ...; public bool isRealtime => ...; public DiscreteTime? length => ...; public GeneratorInstance CreateInstance( ControlContext context, AudioFormat? nestedConfiguration, ProcessorInstance.CreationParameters creationParameters) { return context.AllocateGenerator(new MyRealtime(...), new MyControl(...)); }}

The
IAudioGenerator
interface

An
IAudioGenerator
includes the following:
  • Capabilities: Properties such as
    GeneratorInstance.ICapabilities
    that describe how the generator behaves in advance.
  • Factory method: The
    CreateInstance
    method, which the audio system calls to create a
    GeneratorInstance
    .
Following is a list of the capabilities:
  • Finite: When true, indicates the generator will eventually terminate. The total length does not need to be known up front.
  • Real-time: When true, indicates the generator must render in real time at the system’s buffer size and sample rate. For example, hardware-driven streams, graphs that cannot run ahead, or anything with an internal timeline that is synchronized against the system dsp clock. If unsure, set this to
    false
    .
  • Length: If the generator is finite and you know the total length, report it here. While optional, exposing this property is valuable for editor tooling and scheduling.
You must expose matching capabilities in your
GeneratorInstance.IRealtime
implementation. A mismatch between
IAudioGenerator
and
IRealtime
may cause unexpected behavior and will produce warnings in the Console.

Configure a generator

Generator configuration happens initially during construction, and additionally when the system changes configuration. This typically happens in response to a user action, such as changing the audio output device in the OS system settings or when connecting a pair of headphones. It is a two-step negotiation between the host (the owner of the generator) and the generator:
  1. The host suggests a preferred
    AudioFormat
    for the generator (sample rate, speaker layout, buffer size).
  2. The generator reports the
    Setup
    it will actually use.
Some generators (e.g. when synthesizing audio) can adapt to many formats, while others (e.g. when playing back audio files) may only support the file’s native format. If a generator selects a different format than suggested by the host, it is the host’s responsibility to convert between formats when needed.
When creating child generators via
ControlContext.AllocateGenerator
, a parent can provide an optional
AudioFormat
as a suggestion to the child. Children should follow the suggestion when possible to minimize conversions.

Process a generator

Generators are always processed within a mix cycle. For a description of mix cycles see the mix cycles subsection in the Scriptable processors concepts section.

Nested generators

Generators can be organized into hierarchical trees. In each tree, the root generator sits at the top and is responsible for invoking
Configure
,
Update
, and
Process
on its immediate child generators. Each child, in turn, forwards those calls to its own children. This pattern enables complex structures such as mixers, blend containers, and randomized sequencers.
When a parent generator creates a child using
ControlContext.AllocateGenerator
, it might pass an optional suggested
AudioFormat
. The child generator should match the suggestion if possible to minimize format conversions, but can select a different format, if required.

Use AudioClips as nested generators

AudioClip
implements
IAudioGenerator
, which means you can use imported audio clips as generators. This is the standard way to use audio clips within the scriptable audio pipeline.
AudioClip.CreateInstance
returns a
GeneratorInstance
that you can use directly with a
RealtimeContext
or nest within other generators to facilitate advanced playback behaviours such as sequencing, blending, or looping with custom logic.
To use an
AudioClip
as a nested generator, call
AudioClip.CreateInstance
from within your generator's
CreateInstance
method. You must then call
Update
,
Process
, and
Destroy
at the appropriate times to manage the child instance's lifecycle.
using UnityEngine;using UnityEngine.Audio;using Unity.IntegerTime;using static UnityEngine.Audio.ProcessorInstance;[CreateAssetMenu(fileName = "ClipPlayer", menuName = "Audio/Generators/Clip Player")]public class ClipPlayerGenerator : ScriptableObject, IAudioGenerator{ public AudioClip clip; public bool isFinite => clip != null && ((IAudioGenerator)clip).isFinite; public bool isRealtime => false; public DiscreteTime? length => clip != null ? ((IAudioGenerator)clip).length : null; public GeneratorInstance CreateInstance( ControlContext context, AudioFormat? nestedFormat, CreationParameters creationParameters) { if (clip == null) return default; // Create a child generator instance from the AudioClip var clipInstance = clip.CreateInstance(context, nestedFormat, creationParameters); return context.AllocateGenerator( new Realtime(clipInstance, clip), new Control(clipInstance), nestedFormat, creationParameters); } struct Control : GeneratorInstance.IControl<Realtime> { GeneratorInstance m_ClipInstance; public Control(GeneratorInstance clipInstance) { m_ClipInstance = clipInstance; } public void Configure( ControlContext context, ref Realtime realtime, in AudioFormat format, out GeneratorInstance.Setup setup, ref GeneratorInstance.Properties properties) { // Configure the child and use its setup context.Configure(m_ClipInstance, format); var childConfig = context.GetConfiguration(m_ClipInstance); setup = childConfig.setup; } public void Update(ControlContext context, Pipe pipe) { // Forward update to the child generator context.Update(m_ClipInstance); } public Response OnMessage(ControlContext context, Pipe pipe, Message message) { return Response.Unhandled; } public void Dispose(ControlContext context, ref Realtime realtime) { // Clean up the child generator context.Destroy(m_ClipInstance); } } struct Realtime : GeneratorInstance.IRealtime { GeneratorInstance m_ClipInstance; readonly bool m_IsFinite; readonly bool m_IsRealtime; readonly DiscreteTime? m_Length; public Realtime(GeneratorInstance clipInstance, IAudioGenerator source) { m_ClipInstance = clipInstance; m_IsFinite = source.isFinite; m_IsRealtime = source.isRealtime; m_Length = source.length; } public bool isFinite => m_IsFinite; public bool isRealtime => m_IsRealtime; public DiscreteTime? length => m_Length; public void Update(UpdatedDataContext context, Pipe pipe) { } public GeneratorInstance.Result Process( in RealtimeContext context, Pipe pipe, ChannelBuffer buffer, GeneratorInstance.Arguments args) { // Process the child generator to fill the buffer return context.Process(m_ClipInstance, buffer, args); } }}

Restrictions

You can only use persistent (imported)
AudioClip
assets as generators. The following types of clips are not supported and will throw an exception:
  • Clips created at runtime with
    AudioClip.Create
  • Clips obtained from
    DownloadHandlerAudioClip
  • Clips recorded with the
    Microphone
    API
  • Tracker module formats (
    .mod
    ,
    .it
    ,
    .s3m
    ,
    .xm
    )
Additionally, once an
AudioClip
has been used as a generator,
GetData
and
SetData
are permanently blocked for the lifetime of that clip instance:
  • AudioClip.GetData
    - Returns
    false
    and logs a warning.
  • AudioClip.SetData
    - Returns
    false
    and logs a warning.
You can call
AudioClip.UnloadAudioData
on a generator clip. If you unload a clip that has active generator instances, this process marks those generators as finished and drains any remaining buffered audio data before it completes. Therefore, to prevent synchronization costs, only unload audio data when you are sure it's no longer in use.

Seek a generator

A seek repositions a generator's playback to a different point in time, either immediately or at a scheduled position. To request a seek, send a
SeekMessage
to the generator.
A generator supports seeking only if it handles this message. An
AudioClip
, when used as a generator, handles it out of the box. A custom generator doesn't, unless you add support as described in Support seeking in a custom generator.
To send the message, use
ControlContext.SendMessage
on the generator instance:
// context is the ControlContext and instance is the generator instance to seek. // Seek immediately to second 5. var immediate = new SeekMessage(new DiscreteTime(5.0)); context.SendMessage(instance, ref immediate); // Schedule a seek: when playback reaches the 10th second exactly, jump back to the start. var scheduled = new SeekMessage(destination: DiscreteTime.FromTicks(0), when: new DiscreteTime(10.0)); context.SendMessage(instance, ref scheduled);
A
SeekMessage
carries two positions in the generator's content, both expressed as
Unity.IntegerTime.DiscreteTime
:
  • destination
    : The position to jump to.
  • when
    : The position at which the seek takes effect. The seek fires when the generator's playback position reaches
    when
    .
To seek immediately, omit the
when
argument or set it to
null
. An immediate seek applies at the start of the first processing block after the message reaches the generator. Positions can't be negative. The constructor throws
ArgumentOutOfRangeException
for a negative
destination
or
when
.
Because supporting seeking is optional, check the
ProcessorInstance.Response
that
SendMessage
returns to make sure the seek took effect.

Scheduling rules

You can send more seeks while earlier ones are still pending. An
AudioClip
generator queues them and applies them by the following rules:
  • The generator applies seeks in the order you send them. It never reorders them by
    when
    .
  • A seek that isn't due yet holds back every seek you sent after it, including immediate seeks.
  • When a seek becomes the next one due, the generator drops it with a warning if playback has already passed its
    when
    position, for example because an earlier seek jumped forward past it.
  • Each seek moves the playback position to its
    destination
    , and the generator measures the remaining seeks'
    when
    against the new position. A seek that jumps backward can therefore make a later seek's
    when
    reachable again, which lets you schedule a loop.
  • After playback reaches the end of the content, the generator drops scheduled seeks it can no longer reach. An immediate seek still applies and resumes playback.
  • If you seek an
    AudioClip
    generator while the clip is still loading, Unity keeps only the most recent seek and drops the earlier ones with a warning. The kept seek follows the rules above after the clip finishes loading.
Unity doesn't enforce these rules for custom generators: a custom generator defines its own seek handling in
OnMessage
. Follow the same rules where they apply to your generator, so code that sends seeks gets consistent behavior across generator types.

Support seeking in a custom generator

By default, a custom generator doesn't support seeking. To enable it, handle
SeekMessage
in your control part's
OnMessage
method:
  • Check if the incoming message is a
    SeekMessage
    .
  • Update your generator's playback position to the new requested time. Playback state lives in the realtime part, so typically you pass the seek to it through the pipe.
  • Return
    Response.Handled
    to confirm to Unity that the generator successfully applied the seek.
  • Return
    Response.Unhandled
    for any unrecognized messages. This tells Unity that your generator ignores those commands.
public Response OnMessage(ControlContext context, Pipe pipe, Message message) { if (message.Is<SeekMessage>()) { ref var seek = ref message.Get<SeekMessage>(); // Reposition your playback state using seek.destination and seek.when. // Playback state lives in the realtime part, so this generator hands // the seek to it through the pipe. pipe.SendData(context, seek); return Response.Handled; } return Response.Unhandled; }
If your generator wraps a child generator that already supports seeking, such as an
AudioClip
instance, forward the message to the child with
ControlContext.SendMessage
and return the child's response.
For a complete generator that applies scheduled seeks by the rules above, refer to Example: Seek a generator.

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.
  • 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.
  • Match capabilities consistently. Ensure that the values for
    isFinite
    ,
    isRealtime
    , and
    length
    in your
    IAudioGenerator
    implementation match with the values in
    GeneratorInstance.IRealtime
    .
  • Use the host format when you can. Adopting the host’s suggested
    AudioFormat
    minimizes additional resampling or channel mixing.
  • Report the length when known. Providing an accurate
    length
    property enables better waveform previews, scheduling, and progress indicators in the Editor.
  • Handle completion gracefully. When a finite generator reaches the end, return silence and signal completion so hosts can deallocate or transition.
  • Validate externally received inputs. Clamp out-of-range parameters to valid ranges and handle mismatches in channel counts to prevent errors.

Additional resources