# Audio

`AudioManager` plays and stops sounds, while `VolumeManager` stores and saves the six volume settings. Both managers are singletons that initialize on first access.

## Playing

`AudioManager.Play` takes an `AudioResource` and `AudioConfig`, then returns the sound's runtime `Guid`:

```cs
AudioConfig config = new AudioConfig(
    timeScale: TimeScale.GameTime,
    transition: 0f,
    channel: AudioChannel.SoundEffect,
    loop: false,
    priority: 128,
    volume: 1f,
    pitch: 1f,
    stereoPan: 0f,
    spatialBlend: 1f,
    reverbZoneMix: 1f,
    volumeRolloff: AudioRolloffMode.Logarithmic,
    minDistance: 1f,
    maxDistance: 500f,
    dopplerLevel: 0f,
    spread: 0f,
    hasTarget: true,
    target: this.gameObject,
    locationTransform: this.transform
);

Guid audioId = AudioManager.Play(this.m_Resource, config);
```

`AudioConfig` is a read-only struct configured through its constructor. Use named arguments to keep each value associated with its setting. Only the last three arguments are optional: `target`, `locationTransform`, and `locationPosition`.

These arguments control timing, channel assignment, and the sound's relationship to a game object:

| Argument | Is |
| :--- | :--- |
| `timeScale` | An `ITimeScale`. `TimeScale.GameTime` and `TimeScale.RealTime` are ready-made; a component implementing `ITimeScale` gives the sound a local clock. |
| `transition` | Fade-in seconds. `0` starts at full volume. |
| `channel` | Which [channel](../../manual/audio/channels.md) the sound belongs to, and therefore which volume and mixer group apply. |
| `hasTarget` / `target` | Marks the sound as belonging to a game object, so `Stop(GameObject, float)` and `IsPlaying(GameObject)` can find it. |
| `locationTransform` | Followed every frame. Takes precedence over `locationPosition`. |
| `locationPosition` | A fixed world point, used when `locationTransform` is `null`. |

The rest map one-to-one onto `AudioSource` and are described in [Playing a sound](../../manual/audio/playing.md).

!!! warning "Destroying the target doesn't stop its sound"
    `target` and `locationTransform` hold references without parenting the sound to the object. When you destroy the object, the sound keeps playing. A destroyed `locationTransform` falls back to `locationPosition`, which for a targeted sound is `Vector3.zero`.

    Stop the sound before you destroy its target.

## Stopping

Choose a stop method based on whether you need one sound, every instance of a resource, or a group of sounds:

| Method | Stops | Returns |
| :--- | :--- | :--- |
| `Stop(Guid)` | One sound, immediately. | `void` |
| `Stop(Guid, float)` | One sound, fading over the given seconds. | `Awaitable` |
| `Stop(AudioResource, float)` | Every sound playing that resource. | `Awaitable` |
| `Stop(GameObject, float)` | Every sound targeting that object. | `Awaitable` |
| `StopChannel(AudioChannel, float)` | Every sound on that channel. | `Awaitable` |

Await a channel fade before starting the next operation when the sequence depends on silence:

```cs
await AudioManager.StopChannel(AudioChannel.Music, 2f);
await SceneManager.LoadSceneAsync(nextScene);
```

`StopChannel` finishes the music fade before `LoadSceneAsync` starts. If you don't need to wait for the fade, you can call the method without awaiting its result.

## Queries

Use `IsPlaying` to check a specific instance, resource, or target:

| Method | Returns |
| :--- | :--- |
| `IsPlaying(Guid)` | Whether that specific sound is still active. |
| `IsPlaying(AudioResource)` | Whether any sound is playing that resource. |
| `IsPlaying(GameObject)` | Whether any sound targets that object. |

## Changing a sound while it plays

`GetActive` returns the live `IAudio` for an ID, or `null` after the sound ends:

```cs
IAudio audio = AudioManager.GetActive(audioId);
if (audio != null) audio.Volume = 0.4f;
```

Setting `Volume` changes that active instance without affecting other sounds that use the same resource. Other members expose its configuration and writable playback settings:

| Member | Access |
| :--- | :--- |
| `Resource`, `Channel`, `HasTarget`, `Target` | Read-only — fixed when the sound started. |
| `Volume`, `Pitch` | Read/write. Re-applied every frame against the channel volume and time scale. |
| `Priority`, `StereoPan`, `SpatialBlend`, `ReverbZoneMix` | Read/write. Applied directly to the `AudioSource`. |
| `VolumeRolloff`, `MinDistance`, `MaxDistance`, `DopplerLevel`, `Spread` | Read/write. Applied directly to the `AudioSource`. |

The ID distinguishes one sound from other instances playing the same resource. Play Instructions discard the returned `Guid`, so editor-authored Events cannot later address that individual instance.

!!! tip "Hold the ID only as long as the sound"
    An ID whose sound has finished is not reused, but it no longer resolves. Treat a `null` from `GetActive` as *it ended*, not as an error, and don't keep IDs in long-lived state expecting them to stay valid.

## Volumes

`VolumeManager` exposes the six volumes as `Volume` objects and provides channel-based accessors:

```cs
VolumeManager.Music.Value = 0.5f;
VolumeManager.Set(AudioChannel.Ambient, 0.2f);

float music = VolumeManager.Music.Value;
float ambient = VolumeManager.Get(AudioChannel.Ambient);
```

Every value is clamped to `0`–`1` on assignment. Assigning the current value doesn't fire an event or notify subscribers.

Final sound volume equals its own `volume` multiplied by its channel volume and `Master`.

### AudioVolume includes Master

Choose the enum based on whether the field needs to include the master volume:

| Enum | Values | Used by |
| :--- | :--- | :--- |
| `AudioChannel` | The five channels. | Everything that plays, stops or queries a sound, and every `VolumeManager` method. |
| `AudioVolume` | The five channels plus `Master`. | Serialized fields that pick *a volume to change*. |

`AudioVolume` uses the same values as `AudioChannel` for channel members. Handle `Master` separately before casting:

```cs
float value = this.m_Volume == AudioVolume.Master
    ? VolumeManager.Master.Value
    : VolumeManager.Get((AudioChannel) this.m_Volume);
```

The guard handles the only extra `AudioVolume` member. `Master` is a property rather than a channel, so channel-based `Get`, `Set`, `Subscribe`, and `Unsubscribe` reject it.

Use `VolumeManager.Master` directly, including `VolumeManager.Master.Subscribe(…)` for change notifications.

Use `AudioVolume` for a serialized field a designer picks a volume from, and `AudioChannel` everywhere else. Casting a `Master` into an `AudioChannel` and handing it to `VolumeManager` throws `ArgumentOutOfRangeException`.

## Reacting to changes

Use `Subscribe` when your callback needs a channel's `Args` context, and keep its returned ID for cleanup:

```cs
Guid subscriptionId = VolumeManager.Subscribe(AudioChannel.Music, this.OnMusicChanged, args);
VolumeManager.Unsubscribe(AudioChannel.Music, subscriptionId);
```

The subscription owns its context and disposes it during `Unsubscribe`. For a callback that recomputes all volume state, use the notification without arguments:

```cs
VolumeManager.EventChange += this.RefreshVolume;
```

`EventChange` fires when any channel or `Master` changes.

Each `Volume` also has its own `EventChange` taking the new value, if you'd rather watch one directly.

!!! warning "Unsubscribe in OnDisable"
    Both notification mechanisms are static and outlive your components. A subscription that isn't released keeps its callback alive after the object is gone. A channel subscription also retains its `Args`.

## Volumes save themselves

`VolumeManager` implements [`ISaveLoad`](../extending/save/isaveload.md) as **shared** and **persistent** data under `unity-audio-volume-info`. The manager registers itself and follows this lifecycle:

- `SaveLoadManager.SaveShared()` writes the six values.
- Loading restores the same values across every profile.
- `Unload` preserves them when starting a new game.

The manager subscribes when it initializes. Subscribing applies the values already held by the snapshot, so the stored volumes are available on first audio access.

## Apply volume to hand-placed Audio Sources

The managers govern only sources created by `AudioManager`. A hand-placed `AudioSource` ignores channel volume because Visual Script applies that volume on managed sources rather than mixer groups.

In a component with `m_AudioSource` and `m_Channel` fields, these methods apply channel and `Master` volume whenever either changes:

```cs
private void OnEnable()
{
    VolumeManager.EventChange += this.RefreshVolume;
    this.RefreshVolume();
}

private void OnDisable()
{
    VolumeManager.EventChange -= this.RefreshVolume;
}

private void RefreshVolume()
{
    this.m_AudioSource.volume = VolumeManager.Get(this.m_Channel) * VolumeManager.Master.Value;
}
```

The free **[Audio Source Volume](https://visualscript.dev/package/7nPVOtH6Apa99o9cxRGk)** package provides this behavior with a channel dropdown. Its component assigns `volume` directly, so write a custom version when another system must also contribute to that field.

## Upgrading from the Audio Unity package

Audio previously lived in a separate package under `VisualScript.Runtime.AudioUnity`. It now uses the `VisualScript.Runtime` namespace and `VisualScript.Runtime.Core` assembly.

Replace `using VisualScript.Runtime.AudioUnity;` with `using VisualScript.Runtime;`. Remove the old audio reference from assembly definitions because `VisualScript.Runtime.Core` now contains these types.

The `AudioSourceVolume` component remains in the extension package with its original namespace.

## Where to go next

- **[Audio](../../manual/audio/index.md)** — inspect the designer-facing playback model.
- **[Channels](../../manual/audio/channels.md)** — choose channel roles and mixer groups.
- **[ISaveLoad](../extending/save/isaveload.md)** — follow the interface implemented by `VolumeManager`.
