Skip to content

Scripting guide

This page is the public runtime API a game engineer uses to drive Audity from C#. Everything lives in the DarkEffekt.Audity namespace. The exact contract of each member (parameters, ranges, remarks) is in the generated Scripting API reference; this page narrates the workflow and shows the signatures you reach for most.

AuditySource is the primary API. It plays an AudityGraph on an AudioSource with per-instance control. Add it via Add Component > Audio > Audity Source; it requires an AudioSource.

public AudityPlayable graph; // the Sound Graph to play (AudityGraph is an AudityPlayable)
public bool playOnEnable; // play automatically on enable
public AudityBatchGroup batchGroup;
public List<AuditySourceOverride> overrides;
public event System.Action Finished; // raised on the main thread when playback ends
public bool IsPlaying { get; }

overrides is the per-component form over the graph’s interface: a list of AuditySourceOverride entries, each a member name plus a value (a number for Float/Bool/Int/Enum/Time members, a vector for Vector 2/3/4 members, or an object for a Sample Set member). You edit it in the Audity Source inspector, not usually in code - it is how one placement of a graph tweaks another’s exposed values without a script. An entry exists only for a field you actually changed; everything else tracks the graph’s own defaults live. Overrides apply on every Play(), before any values you set from code, so a scripted SetFloat still wins over an inspector override.

A playing graph ends in one of two ways:

  • It finishes on its own. If the graph is finite - it has no indefinitely sustaining source (no looping Wave Player, no held ADSR sustain) - the instance ends the moment its last voice completes and nothing is pending. Finished fires on the main thread and IsPlaying goes false. A one-shot footstep does this.
  • You stop it. A graph with a looping voice or a held envelope is infinite: it renders until you call Stop() (or fire the Output node’s Stop trigger). It will not end by itself - a looping voice never completes. Stop runs the Output’s release fade, then raises Finished.

So a looping ambience keeps rendering until stopped; it does not fall silent and retire on its own.

public void Play(float fadeInSeconds = 0f);
public void PlayQuantized(AudityQuantize startQuantize, float fadeInSeconds = 0f);
public void Stop(float fadeOutSeconds = 0f);
public void Trigger(AudityQuantize quantize = AudityQuantize.Immediate);
public void Trigger(AudityParameterId id, AudityQuantize quantize = AudityQuantize.Immediate);
public void Trigger(string name, AudityQuantize quantize = AudityQuantize.Immediate);
public void SetTempo(double bpm, int beatsPerBar = 4);

Play assigns the graph and starts it. PlayQuantized holds the onset until the next musical boundary (see Musical timing). Trigger with no id fires every Trigger In; with an id or name it fires just that named one.

Parameter setters on AuditySource are sticky: the value applies to the live instance immediately (gliding over the member’s Smooth time) and is re-applied to every future Play(). Setting a parameter before Play is therefore fine.

public void SetFloat(AudityParameterId id, float value, float smoothSeconds = -1f, int elementIndex = 0);
public void SetFloat(string name, float value, float smoothSeconds = -1f, int elementIndex = 0);
public void SetBool(AudityParameterId id, bool value);
public void SetInt(AudityParameterId id, int value);
public void SetVector(AudityParameterId id, Vector3 value, float smoothSeconds = -1f);
// SetParameter(...) is the general form; Set* are typed conveniences.

Each has a string overload alongside the AudityParameterId overload. A negative smoothSeconds uses the member’s authored glide.

The members the GRAPH writes - Direction Out - go the other way. Getters are per live instance and are not sticky: a stopped source reads as nothing at all, never as the last value it had.

public bool TryGetFloat(AudityParameterId id, out float value, int element = 0);
public bool TryGetFloat(string name, out float value, int element = 0);
public float GetFloat(AudityParameterId id, float fallback = 0f, int element = 0);
public bool TryGetVector(AudityParameterId id, out Vector4 value, int componentCount = 4);
public event System.Action<AudityParameterId> TriggerFired;

TryGetFloat returns false when the source is not playing, the member is not marked Out, or the engine has not reported it yet (the first frame). GetFloat collapses all three into a fallback.

TriggerFired is raised on the main thread for an Out Trigger member, once per fire, and carries the member id. Subscribe in OnEnable and unsubscribe in OnDisable; handlers may safely stop or restart the source.

A read value is a snapshot as of the engine’s last report - roughly a frame old, because it crosses the audio thread. Every trigger fire is delivered, but its timing is approximate for the same reason. Drive gameplay, animation and UI from these; keep anything that must be sample-accurate inside the graph. See Reading a graph from code.

The string overloads hash the name on every call. On a hot path (a value you set every frame), cache an AudityParameterId once and reuse it:

static readonly AudityParameterId Cutoff = AudityParameterId.FromName("Cutoff");
// ...
source.SetFloat(Cutoff, value); // no per-call hashing

To swap the content behind a Sample Set member at runtime, bind a bank to its key. See Samples and sample sets for the concepts.

public void BindSource(string key, AudityBankHandle bank); // hot path: repoint + refcount
public void BindSource(string key, AuditySampleSet set); // convenience: loads if cold
public void ClearSource(string key); // back to the baked default
// per-call source argument (dialogue semantics): this Play uses this bank
public void Play(string sourceKey, AudityBankHandle bank, AudityQuantize quantize = AudityQuantize.Immediate);

A bind is a persistent per-source slot: it survives across Play() calls and applies to future triggers. Voices already playing ride their old bank to completion, so retrigger a looping voice to hear a rebind.

public readonly struct AudityParameterId
{
public readonly uint value;
public AudityParameterId(uint value);
public static AudityParameterId FromName(string name);
public static uint Hash(string name); // FNV-1a over the name
}

AudityBank is the residency registry for prepared Sample Sets. Warm banks up front (level load, weapon equip) so binding is free later.

public static AudityBankHandle Load(AuditySampleSet set);
public static AudityBankHandle Load(UnityEngine.Object source); // clip/sample/generator promoted
public static bool IsReady(AudityBankHandle handle);
public static void Release(AudityBankHandle handle);
public static AudityColdBindPolicy ColdBindPolicy { get; set; }
public readonly struct AudityBankHandle
{
public bool IsValid { get; }
public bool IsReady { get; } // true once every entry's PCM is resident
}

Two references keep a bank alive: the registry (one per outstanding Load handle) and playing instances. Identity is a content hash, so two identical sets share one resident bank. Release drops your handle; the bank frees only when nothing else holds it.

var bank = AudityBank.Load(weaponSet);
// ...wait until bank.IsReady, then:
source.BindSource("Weapon", bank);

AudityGlobal reaches every playing instance and owns tempo and transports. Main thread only.

public static void SetParameter(AudityParameterId id, float value);
public static void SetParameter(string name, float value); // hashes each call
public static bool TryGetParameter(AudityParameterId id, out float value);
public static void SetTransport(string name, double bpm, int beatsPerBar = 4,
int phraseBars = 4, ulong? anchorDspSample = null);
public static bool TryGetTransport(string name, out AudityTransportDesc transport);
public static void ClearTransport(string name);
public static void PublishDrive(AudityTransportSettings settings,
AudityQuantize startQuantize = AudityQuantize.Immediate);
public static ulong NowDspSample();
public static ulong NextBoundary(in AudityTransportDesc transport, ulong dspSample, AudityQuantize quantize);

Use named transports for anything musical across instances; see Musical timing.

AudityMessaging sends control messages straight to the instance on an AudioSource. It is the same path AuditySource uses internally, exposed for cases where you hold an AudioSource rather than an AuditySource, or want strictly live-instance messaging with no stickiness. Every method returns true if a live Audity instance handled it.

public static bool SetParameter(AudioSource source, AudityParameterId id, float value, float smoothSeconds = -1f, int elementIndex = 0);
public static bool Trigger(AudioSource source, AudityParameterId id, AudityQuantize quantize = AudityQuantize.Immediate);
public static bool SetTempo(AudioSource source, double bpm, int beatsPerBar = 4);
public static bool Stop(AudioSource source, float fadeSeconds);
public static bool SetSpatial(AudioSource source, Vector3 listenerPosition, Vector3 emitterPosition);
public static bool SetSpatial(AudioSource source, Vector3 listenerPosition, Vector3 emitterPosition,
Vector3 listenerForward, Vector3 listenerUp,
Vector3 listenerVelocity, Vector3 emitterVelocity);

SetSpatial is what an AuditySource calls every frame to feed Listener Info; call it yourself only if you drive a graph without an Audity Source.

AudityPlayback.Play plays a graph at a point in the world with no GameObject or component of your own, and nothing to clean up afterwards. It leases a hidden AudioSource from a pool, plays, and returns the mount to the pool when the graph finishes:

// A 3D one-shot at the impact point: full volume within 3 m, gone by 70 m.
AudityPlayback.Play(
impactGraph,
hit.point,
mixerGroup: sfxGroup,
mountSpec: AudityMountSpec.Spatial(minDistance: 3f, maxDistance: 70f)
);

Pooled mounts are hidden, so everything a scene AudioSource would carry in its inspector is passed at the call instead. mixerGroup is the routing; AudityMountSpec is the physical state: volume, spatial blend, rolloff shape and range, Doppler, and speaker spread. Omitting the spec plays 2D at full volume. Every play applies its spec in full, so a lease never inherits the previous sound’s settings. Build one spec per kind of sound and reuse it.

A graph that spatializes itself (Listener Info and Pan) should stay 2D; host 3D on top of in-graph spatial pans and attenuates twice, the same double-spatialization rule as on the component.

Play returns an AudityInstance handle. Drop it for true fire-and-forget, or keep it to drive the live instance:

var handle = AudityPlayback.Play(impactGraph, hit.point,
mountSpec: AudityMountSpec.Spatial(3f, 70f));
handle.SetFloat("Force", strength); // live value, not sticky
handle.Trigger("Ping");
handle.Stop(fadeOutSeconds: 0.2f);

The handle is generation-counted: once the instance ends, every method harmlessly no-ops and returns false. Handles do not remember values across plays; stickiness is the component’s job.

The pool holds AudityPlayback.MountCapacity mounts (default 8, shared with batch composites). A play beyond capacity stops an earlier sound and logs a warning; raise the capacity at startup if your density is intended. Two things stay with AuditySource on a GameObject by design: moving emitters, which need a transform to ride, and edit-mode audition, because AudityPlayback is play-mode only.

You do not need any Audity API at all. Assigning a graph to AudioSource.generator and calling AudioSource.Play() works, with global parameters only:

audioSource.generator = myGraph;
audioSource.Play();
AudityGlobal.SetParameter("Cutoff", 8000f);

The component exists for per-instance control (sticky parameters, binding, overrides, batching); the bare-generator path is the minimal one.

Assign the same AudityBatchGroup asset to several sources to cap how many play at once (dense bursts collapse into a composite). It is authored data, configured in the inspector, not called at runtime; see Performance and limits.

AudityTelemetry is an editor and tooling hook (it feeds the audition panel’s live badges). In a player its Sink is null unless something installs one (the diagnostics AudityPerfHud does), and with no sink instances send nothing. Game code leaves it alone.

The same surface this page describes is reachable from Unity’s animation systems: Timeline tracks and markers call Play/Stop/Trigger, and AnimationClips drive parameters through the Audity Animation Bridge component. See Timeline and animation.