Skip to content
DEV505 / Sound ManagerDOCUMENTATION

DEV505 / CODE RECIPE

Shape a running sound with parameters.

Send a new target to an existing play and let the graph smooth the change in pitch or layer volume.

Start with Your first sound if you have not set up a sound scene before. Use a separate test scene with one enabled Sound Manager, generated Sound Keys, and one Audio Listener near the sound object. Add Engine from 04-Engine to the manager's Direct events list; these are the supplied .soundgraph assets under Assets/DEV505/SoundManager/Samples.

Save the complete code below as EngineSoundControls.cs in Assets/Scripts. Wait for Unity to compile it, create an empty GameObject, and drag the script onto it. The filename must match the class name. Test one recipe component at a time so another demo controller does not compete for the same events.

No object references are needed. Keep the object within a few units of the listener. Rpm is a normalized 0–1 value, not the car HUD’s displayed revolutions per minute.

In Play Mode, wait until the Sound Manager is ready. To run a listed test action, open the component's three-dot menu in the Inspector and choose that action. These are the script's ContextMenu methods. Your own code or a Unity UI Button can call its public methods too. Inspector edits made during Play Mode are temporary.

The engine starts when Play Mode begins. Slowly move Rpm from 0 to 1, then drop it to 0. Listen as it climbs and settles. Use Sound horn to add a response without replacing the engine. Use Stop engine to hear its ending, then Start engine to repeat. Disable the component to test cleanup.

This recipe sets Load to 0.2 at start and changes only Rpm live. The full Engine demo computes both from its vehicle controls.

The graph declares Rpm from 0 to 1, with rise rate 0.8 and fall rate 1.5 units per second. An initial context override becomes the starting value. Later SetFloat calls change the target, and the graph’s current value moves toward it. Repeatedly setting the same target does not restart the play or reset the smoothing.

The graph maps that current value into playback speeds and layer volumes. To display what the audio is currently using, read the parameter state:

Inside a component with a retained engine handle · excerpt
var service = SoundServices.Current;
if (service != null &&
service.TryGetParameter(engine, SoundParameters.Engine.Rpm, out var rpmState))
{
float audibleRpm = rpmState.Current.number;
float requestedRpm = rpmState.Target.number;
}

The variables above are normalized values. Apply your own display conversion if you want an RPM dial.

Rain and Wind use the same target/current pattern to blend ambience. Rain rises at 0.35 and falls at 0.5; Wind uses 0.25 and 0.4. The StormSounds component starts them at zero, updates targets, and cleans up the entire storm by owner.

Float smoothing is part of the parameter definition. A rate of 0 means immediate change, not a zero-second fade command. Int, Bool, and Asset parameters choose values directly instead of smoothly moving through them.

EngineSoundControls.cs
using System;
using DEV505.SoundManager;
using UnityEngine;
public sealed class EngineSoundControls : MonoBehaviour
{
[SerializeField, Range(0f, 1f)] private float rpm = 0f;
private SoundHandle engine;
private IDisposable subscription;
private void OnEnable()
{
subscription = SoundBus.Notifications.Subscribe(OnSoundChanged);
}
private void Start() => StartEngine();
[ContextMenu("Start engine")]
public void StartEngine()
{
if (!Application.isPlaying || !isActiveAndEnabled || SoundBus.IsAlive(engine))
return;
var service = SoundServices.Current;
if (service == null)
{
Debug.LogWarning("Add an active Sound Manager to the scene.", this);
return;
}
var context = SoundPlayContext.Following(transform, service.OwnerOf(gameObject))
.With(SoundParameters.Engine.Rpm, rpm)
.With(SoundParameters.Engine.Load, 0.2f);
var result = SoundBus.Play(Sounds.Engine, context);
if (result.Accepted)
engine = result.Handle;
else
Debug.LogWarning($"Could not start Engine: {result.Status}", this);
}
private void Update()
{
if (SoundBus.IsAlive(engine))
SoundBus.SetFloat(engine, SoundParameters.Engine.Rpm, rpm);
}
[ContextMenu("Sound horn")]
public void SoundHorn()
{
if (SoundBus.IsAlive(engine))
SoundBus.SendSignal(engine, "Horn");
}
[ContextMenu("Pause engine")]
public void PauseEngine() => SoundBus.SetPaused(engine, true);
[ContextMenu("Resume engine")]
public void ResumeEngine() => SoundBus.SetPaused(engine, false);
[ContextMenu("Stop engine")]
public void StopEngine()
{
if (SoundBus.IsAlive(engine))
SoundBus.Stop(engine);
// Keep the handle until the ending notification so it can be identified.
}
private void OnSoundChanged(SoundNotification message)
{
if (!message.Handle.Equals(engine) || !message.IsTerminal)
return;
Debug.Log($"Engine ended: {message.Kind} ({message.Reason})", this);
engine = default;
}
private void OnDisable()
{
subscription?.Dispose();
subscription = null;
StopEngine();
engine = default;
}
}

Read the demo walkthrough for the graph, parameters, controls, and Unity screenshots. The related API reference explains the calls used here. Return to all twelve recipes to choose another interaction.

This page imports the checked C# file directly. It was compiled against the documented Unity revision; its runtime checks and source hash are recorded in the documentation repository’s validation files.