Skip to content
DEV505 / Sound ManagerDOCUMENTATION

DEV505 / CODE RECIPE

Handle busy sounds without a retry storm.

Read request results, count replaced plays through notifications, and keep event limits separate from voice capacity.

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 ArcadeZap, ArcadeCoin, and ArcadeJackpot from 07-Arcade to the manager's Direct events list; these are the supplied .soundgraph assets under Assets/DEV505/SoundManager/Samples.

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

For the same budget as the demo, assign a copy of ArcadeSoundSettings to your test manager; its voice capacity is six. Keep the original sample settings intact if experimenting with a smaller capacity.

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.

Use Zap repeatedly, then Coin repeatedly. Zap permits three overlapping plays and replaces its oldest. Coin permits two and refuses an extra request while those are alive. Choose Jackpot twice quickly; the second request should report Cooldown. Inspector menu clicking may be slower than these short clips, so use the Arcade demo’s automatic chaos control for an easy sustained test.

Your own input code can call Request(Sounds.ArcadeCoin) and inspect its returned result. Replacements and LastStatus are public properties for your own UI. The component logs refused decisions in the Console and cleans up subscriptions and owned plays on disable.

Result or notification Meaning Reasonable response
Accepted result The new request was admitted, including a queued request. Retain a handle if you need later control.
ConcurrencyLimit result This event refuses another overlapping play. Skip a nonessential sound.
Cooldown result The event was requested again too soon. Wait for another real action after the gap.
CapacityExceeded result The service cannot admit required playback within its limits. Review total capacity and competing plays.
Cancelled notification, reason ConcurrencyLimit An older play was replaced by a new one. Update UI for the old handle, not the new accepted request.
Failed notification An accepted play failed later. Record the actual reason for diagnosis.

With six slots and the supplied simple graphs, 3 Zap + 2 Coin + 1 Jackpot fits the budget. This setup is not a cross-event priority-stealing demonstration. A voice budget is global to the service; an event’s instance limit is specific to that event. More complex events can consume several voices per play, so those numbers are not interchangeable.

Request returns the immediate result. The notification callback filters by owner and counts replacement cancellations for this component’s older plays. Subscribing without that filter could count unrelated sounds elsewhere in the scene.

The example does not retry a refusal every Update. That would generate more pressure and less useful sound. Let the next player action make a new request. Use debug counters or graph inspection to investigate, then turn those editor diagnostics off when measuring normal performance.

ArcadeRequests.cs
using System;
using DEV505.SoundManager;
using UnityEngine;
public sealed class ArcadeRequests : MonoBehaviour
{
private ISoundService service;
private SoundOwnerToken owner;
private IDisposable subscription;
public SoundPlayStatus LastStatus { get; private set; }
public int Replacements { get; private set; }
private void OnEnable() => subscription = SoundBus.Notifications.Subscribe(OnSoundChanged);
public SoundPlayResult Request(SoundKey key)
{
if (!Application.isPlaying || !isActiveAndEnabled)
return new SoundPlayResult(SoundPlayStatus.NotReady);
service = SoundServices.Current;
if (service == null || !service.Ready)
return new SoundPlayResult(SoundPlayStatus.NotReady);
owner = service.OwnerOf(this);
var result = SoundBus.Play(key, SoundPlayContext.Following(transform, owner));
LastStatus = result.Status;
if (!result.Accepted)
{
// These refusals are expected under load. Do not retry every frame.
if (result.Status == SoundPlayStatus.Cooldown) Debug.Log("Wait before the next jackpot.", this);
else if (result.Status == SoundPlayStatus.ConcurrencyLimit || result.Status == SoundPlayStatus.CapacityExceeded)
Debug.Log("All permitted plays or voices are busy; skip this request.", this);
else Debug.LogWarning($"Sound could not play: {result.Status}", this);
}
return result;
}
[ContextMenu("Zap")]
public void Zap() => Request(Sounds.ArcadeZap);
[ContextMenu("Coin")]
public void Coin() => Request(Sounds.ArcadeCoin);
[ContextMenu("Jackpot")]
public void Jackpot() => Request(Sounds.ArcadeJackpot);
private void OnSoundChanged(SoundNotification message)
{
if (!owner.IsValid || !message.Owner.Equals(owner)) return;
if (message.Kind == SoundNotificationKind.Cancelled && message.Reason == SoundPlayStatus.ConcurrencyLimit)
Replacements++;
if (message.Kind == SoundNotificationKind.Failed)
Debug.LogWarning($"An accepted play later failed: {message.Reason}", this);
}
private void OnDisable()
{
subscription?.Dispose();
subscription = null;
if (ReferenceEquals(service, SoundServices.Current) && owner.IsValid)
{
service.StopOwned(owner, 0);
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.