# Example: Seek a generator

> Build a custom generator that supports seeking and send it immediate and scheduled seeks.

This example builds a custom generator that supports seeking, and a script that seeks it. The generator plays a 440 Hz tone that fades out over its first 10 seconds of playback, so seeks are audible.

The generator follows the same [scheduling rules](/engine/6000.7/manual/audio/scriptable-processors/generators.md#scheduling-rules) as an `AudioClip` generator.

## Required includes

Some of the code in this section requires the following includes:

```cs
using Unity.Burst;
using Unity.Collections;
using Unity.IntegerTime;
using UnityEngine;
using UnityEngine.Audio;
using static UnityEngine.Audio.ProcessorInstance;
```

Make sure to add these includes at the start of your code for them to compile.

## Send seeks from a script

The seeks come from a `MonoBehaviour` on the same GameObject as the `AudioSource` that plays the generator:

```cs
private AudioSource m_AudioSource;

        private void Awake()
        {
            // Expects an AudioSource on the same GameObject.
            m_AudioSource = GetComponent<AudioSource>();
        }
```

Get the built-in control context and the generator instance from the audio source. Always guard the handle: the instance may be missing or have been destroyed if the audio source was stopped elsewhere.

```cs
// The built-in context controls generator instances created by an AudioSource.
            var context = ControlContext.builtIn;
            var instance = m_AudioSource.generatorInstance;

            // Guard the handle: the instance may be missing or have been destroyed.
            if (!context.Exists(instance))
                return;
```

Then send the seeks:

```cs
// 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);
```

## Forward the message in the control part

The control part is a struct that implements `GeneratorInstance.IControl<Realtime>`. Its `Dispose` and `Update` methods stay empty in this example:

```cs
struct Control : GeneratorInstance.IControl<Realtime>
    {
        public void Dispose(ControlContext context, ref Realtime realtime) { }

        public void Update(ControlContext context, Pipe pipe) { }
```

A custom generator ignores `SeekMessage` until `OnMessage` handles it. Playback state lives in the realtime part, so `OnMessage` forwards the seek through the pipe and returns `Response.Handled`:

```cs
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;
        }
```

In `Configure`, capture the sample rate. The realtime part needs it to convert seek positions to frames:

```cs
public void Configure(
            ControlContext context,
            ref Realtime realtime,
            in AudioFormat format,
            out GeneratorInstance.Setup setup,
            ref GeneratorInstance.Properties properties)
        {
            realtime.sampleRate = format.sampleRate;

            // Prefer matching the host's format when possible to avoid conversion.
            setup = new GeneratorInstance.Setup(format.speakerMode, format.sampleRate);
        }
```

## Apply seeks in the realtime part

Store pending seeks in frames, so they compare directly against the playback position:

```cs
// A SeekMessage converted to frames, queued until playback reaches its position.
    // A negative whenFrames marks an immediate seek.
    struct SeekCommand
    {
        public long destinationFrames;
        public long whenFrames;
    }
```

The realtime part queues incoming seeks in `Update` and applies them in `Process`. A fixed-capacity list keeps the audio thread allocation-free. `Process` splits the block at a scheduled seek's position, so the seek fires exactly when playback reaches it:

```cs
// A custom generator that supports seeking: a 440 Hz tone that fades out over its
    // first 10 seconds of playback, so repositioning the playback is audible. Seeks
    // follow the same scheduling rules as AudioClip generators: strict send order,
    // applied when playback reaches `when`, dropped when playback has already passed it.
    [BurstCompile(CompileSynchronously = true)]
    struct Realtime : GeneratorInstance.IRealtime
    {
        private const float k_TwoPi = 2.0f * Mathf.PI;
        private const float k_Frequency = 440.0f; // A4
        private const float k_FadeSeconds = 10.0f;

        private float phase;                                  // [0, 1)
        private long positionFrames;                          // Playback position; seeks reposition it.
        private FixedList128Bytes<SeekCommand> pendingSeeks;  // Seeks wait here in send order.
        internal float sampleRate;                            // Hz, set from Configure

        // Capabilities must match those reported from IAudioGenerator and IRealtime.
        public bool isFinite => false;
        public bool isRealtime => false;
        public DiscreteTime? length => null;

        public void Update(UpdatedDataContext context, Pipe pipe)
        {
            // Queue the seeks the control part passed down through the pipe, in send order.
            foreach (var element in pipe.GetAvailableData(context))
            {
                if (element.TryGetData(out SeekMessage seek))
                {
                    if (pendingSeeks.Length == pendingSeeks.Capacity)
                        continue; // The queue is full; drop the seek.

                    // Convert the content positions to frames. A null `when` seeks immediately.
                    pendingSeeks.Add(new SeekCommand
                    {
                        destinationFrames = (long)((double)seek.destination * sampleRate),
                        whenFrames = seek.when.HasValue ? (long)((double)seek.when.Value * sampleRate) : -1,
                    });
                }

                // Ignore other message types gracefully.
            }
        }

        public GeneratorInstance.Result Process(
            in RealtimeContext context,
            Pipe pipe,
            ChannelBuffer buffer,
            GeneratorInstance.Arguments args)
        {
            float phaseIncrement = k_Frequency / sampleRate;
            int frame = 0;

            while (frame < buffer.frameCount)
            {
                int segmentEnd = buffer.frameCount;

                // Only look at the seek at the front of the queue: a seek that isn't due
                // yet holds back every seek sent after it, including immediate seeks.
                if (!pendingSeeks.IsEmpty)
                {
                    var seek = pendingSeeks[0];

                    if (seek.whenFrames <= positionFrames) // Due now; immediate seeks are negative.
                    {
                        pendingSeeks.RemoveAt(0);

                        // Apply the seek, unless it's a scheduled seek whose position playback
                        // has already passed - drop that one, it can no longer be reached.
                        bool alreadyPassed = seek.whenFrames >= 0 && seek.whenFrames < positionFrames;
                        if (!alreadyPassed)
                            positionFrames = seek.destinationFrames;

                        continue;
                    }

                    // Not due yet: if `when` lands inside this block, process up to it, so
                    // the seek applies exactly when playback reaches its position.
                    long offset = seek.whenFrames - positionFrames;
                    if (offset < segmentEnd - frame)
                        segmentEnd = frame + (int)offset;
                }

                for (; frame < segmentEnd; frame++)
                {
                    // The fade depends on the playback position, so a seek is audible.
                    float seconds = positionFrames / sampleRate;
                    float amplitude = Mathf.Clamp01(1.0f - seconds / k_FadeSeconds);
                    float s = amplitude * Mathf.Sin(phase * k_TwoPi);

                    for (int ch = 0; ch < buffer.channelCount; ch++)
                        buffer[ch, frame] = s;

                    phase += phaseIncrement;
                    if (phase >= 1.0f) phase -= 1.0f;
                    positionFrames++;
                }
            }

            return buffer.frameCount;
        }
    }
```

`Process` implements the scheduling rules:

* It only inspects the seek at the front of the queue, so a seek that isn't due yet holds back every seek sent after it.
* It jumps the playback position to a due seek's destination, and drops a scheduled seek that playback has already passed.
* It measures `when` against the playback position, which jumps on every applied seek, so an earlier seek that jumps backward can make a later seek's `when` reachable again.

## Create the generator

Tie everything together in a `MonoBehaviour` that implements `IAudioGenerator`:

```cs
public class Driver : MonoBehaviour, IAudioGenerator
    {
        public bool isFinite => false;
        public bool isRealtime => false;
        public DiscreteTime? length => null;

        public GeneratorInstance CreateInstance(
            ControlContext context,
            AudioFormat? nestedConfiguration,
            CreationParameters creationParameters)
        {
            // Allocate a new generator instance pairing the realtime and control structs.
            return context.AllocateGenerator(new Realtime(), new Control(), nestedConfiguration, creationParameters);
        }
    }
```

To try it out:

1. Add the `Driver` component to a GameObject with an `AudioSource`, and assign it to the **Generator** field in the Inspector.
2. Add the `SeekDriver` component to the same GameObject.
3. Enter Play Mode and call `SendSeeks`, for example from a UI button. The tone jumps to second 5 and fades from there. When playback reaches second 10, it jumps back to the start and the fade restarts.

## Additional resources

* [Seek a generator](/engine/6000.7/manual/audio/scriptable-processors/generators.md#seek-a-generator)
* [Example: Creating a simple generator](/engine/6000.7/manual/audio/scriptable-processors/example-creating-a-generator.md)
* [SeekMessage API reference](/engine/6000.7/script-reference/unityengine/audio/seekmessage.md)
