Skip to content
DEV505 / Sound ManagerDOCUMENTATION

DEV505 / CODE RECIPE

Pause every play in an interaction.

Pause current and outgoing music together, and distinguish that local pause from the service-wide pause used by Launch.

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 Jukebox from 08-Jukebox to the manager's Direct events list; these are the supplied .soundgraph assets under Assets/DEV505/SoundManager/Samples.

Save the complete code below as TwoTrackMusic.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.

Assign the allowed Mint Morning and Peach Sunset clips to First Track and Second Track, as in the music crossfade recipe. No other references are needed.

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.

Start the first track, wait until it fades up, then start the second. Choose Pause both tracks partway through the crossfade. Wait longer than the usual 2.5-second transition, then choose Resume both tracks. You should hear the transition continue from where it paused, with the outgoing play still present until it reaches silence.

Section titled “Local pause belongs to all related handles”
Inside TwoTrackMusic.SetPaused · excerpt
SoundBus.SetPaused(current, value);
SoundBus.SetPaused(outgoing, value);

value is true to pause and false to resume. Pausing only the current play would leave the outgoing song audible or advancing. Pausing is not stopping: it keeps the play, voices, and graph progression available for continuation. The component also avoids discarding the outgoing handle during pause.

The complete component below can be used directly. Stop music now ends both plays and clears the local pause flag; disabling the component performs the same cleanup.

Launch pauses the service as a whole so its pad, countdown, and rocket audio stay together. It also pauses its own animations and particles. Those visual effects are outside Sound Manager.

The following excerpt belongs in a controller that owns the pause interaction and restores the previous state when it leaves. Keep wasPaused and service as fields if pause and restoration happen in separate methods:

Service-wide pause and restoration · excerpt
var service = SoundServices.Current;
if (service != null)
{
bool wasPaused = service.Paused;
service.Paused = true;
// Keep it paused while your interaction needs it.
// On exit, restore the state you found:
service.Paused = wasPaused;
}

This block demonstrates the state ownership; running it as-is restores immediately. Do not blindly set Paused to false on exit if another system had already paused it. A per-play resume does not override a paused service. LaunchUI is explicitly configured to ignore game pause, allowing UI clicks to remain audible.

Read Launch’s full walkthrough for its combined pause behavior. Workshop’s signal named Pause is a third, different concept: that graph stops tools and stays alive waiting for Work.

TwoTrackMusic.cs
using DEV505.SoundManager;
using UnityEngine;
public sealed class TwoTrackMusic : MonoBehaviour
{
[SerializeField] private AudioClip firstTrack = null;
[SerializeField] private AudioClip secondTrack = null;
[SerializeField, Range(0, 1)] private float volume = 0.65f;
private ISoundService service;
private SoundOwnerToken owner;
private SoundHandle current;
private SoundHandle outgoing;
private AudioClip currentClip;
private bool paused;
[ContextMenu("Play first track")]
public void PlayFirst() => Select(firstTrack);
[ContextMenu("Play second track")]
public void PlaySecond() => Select(secondTrack);
public void Select(AudioClip clip)
{
if (!Application.isPlaying || !isActiveAndEnabled || !clip || paused) return;
var active = SoundServices.Current;
if (active == null || !active.Ready) return;
if (!ReferenceEquals(service, active))
{
current = outgoing = default;
service = active;
owner = service.OwnerOf(this);
}
if (current.IsAlive() && currentClip == clip) return;
if (outgoing.IsAlive())
{
Debug.Log("Wait for the current crossfade to finish before choosing again.", this);
return;
}
var context = SoundPlayContext.At(transform.position, owner)
.With(SoundParameters.Jukebox.Track, clip)
.With(SoundParameters.Jukebox.Gain, 0f)
.With(SoundParameters.Jukebox.Volume, volume);
var result = SoundBus.Play(Sounds.Jukebox, context);
if (!result.Accepted)
{
Debug.LogWarning($"Track could not start: {result.Status}", this);
return; // Keep the current track if the new request failed.
}
outgoing = current;
current = result.Handle;
currentClip = clip;
if (outgoing.IsAlive()) SoundBus.SetFloat(outgoing, SoundParameters.Jukebox.Gain, 0);
SoundBus.SetFloat(current, SoundParameters.Jukebox.Gain, 1);
}
private void Update()
{
if (current.IsAlive()) SoundBus.SetFloat(current, SoundParameters.Jukebox.Volume, volume);
if (outgoing.IsAlive()) SoundBus.SetFloat(outgoing, SoundParameters.Jukebox.Volume, volume);
if (paused || !ReferenceEquals(service, SoundServices.Current)) return;
if (outgoing.IsAlive() && service.TryGetParameter(outgoing, SoundParameters.Jukebox.Gain, out var gain)
&& gain.Current.number <= 0.001f)
{
SoundBus.Stop(outgoing, 0); // The graph has already faded this play to silence.
outgoing = default;
}
}
public void SetPaused(bool value)
{
paused = value;
SoundBus.SetPaused(current, value);
SoundBus.SetPaused(outgoing, value);
}
[ContextMenu("Pause both tracks")]
public void PauseMusic() => SetPaused(true);
[ContextMenu("Resume both tracks")]
public void ResumeMusic() => SetPaused(false);
[ContextMenu("Stop music now")]
public void StopMusic()
{
if (ReferenceEquals(service, SoundServices.Current) && owner.IsValid)
service.StopOwned(owner, 0);
current = outgoing = default;
currentClip = null;
paused = false;
}
private void OnDisable()
{
StopMusic();
if (ReferenceEquals(service, SoundServices.Current) && owner.IsValid)
service.ReleaseOwner(owner);
owner = default;
service = null;
}
}

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.