Manager for playing short sound pads. Tracks active SoundHandle entries, plays pads, supports looping replay, updates status text and exposes events for UI updates.
namespace SSoundboard;
public static class SoundBoardManager
{
public static bool LoopEnabled { get; private set; }
public static int ActiveVoiceCount => _voices.Count;
public static string StatusText { get; private set; } = "READY · PRESS ANY BUTTON";
public static string LastPlayedLabel { get; private set; } = "";
static readonly List<VoiceEntry> _voices = new();
static int _lastVoiceCount;
static RealTimeUntil _loopReplayCooldown;
public static event Action StateChanged;
public static bool IsPadPlaying( string soundId )
{
if ( string.IsNullOrWhiteSpace( soundId ) )
return false;
for ( var i = 0; i < _voices.Count; i++ )
{
if ( _voices[i].SoundId == soundId && _voices[i].Handle.IsValid() )
return true;
}
return false;
}
public static void Play( SoundPad pad )
{
if ( pad is null || string.IsNullOrWhiteSpace( pad.SoundId ) )
return;
var handle = Sound.Play( ToSoundEvent( pad.SoundId ) );
if ( !handle.IsValid() )
{
SetStatus( $"MISSING · {pad.Label}" );
StateChanged?.Invoke();
return;
}
handle.Volume = 0.9f * pad.Volume;
handle.Pitch = 0.82f + Game.Random.Float( 0f, 0.36f );
_voices.Add( new VoiceEntry
{
Handle = handle,
SoundId = pad.SoundId,
Pad = pad
} );
LastPlayedLabel = pad.Label;
SoundPlayTracker.Record( pad.SoundId );
UpdateStatus();
StateChanged?.Invoke();
}
public static void ToggleLoop()
{
LoopEnabled = !LoopEnabled;
SetStatus( LoopEnabled ? "LOOP ON · CHAOS MODE ENGAGED" : "LOOP OFF · ONE-SHOT MODE" );
StateChanged?.Invoke();
}
public static void KillAll()
{
var voices = _voices.ToArray();
_voices.Clear();
_lastVoiceCount = 0;
for ( var i = 0; i < voices.Length; i++ )
{
var handle = voices[i].Handle;
if ( !handle.IsValid() )
continue;
handle.Stop( 0f );
}
_loopReplayCooldown = 0.2f;
SetStatus( "☠ SILENCED · KILL ALL" );
StateChanged?.Invoke();
}
public static void Tick()
{
var changed = false;
for ( var i = _voices.Count - 1; i >= 0; i-- )
{
var voice = _voices[i];
if ( voice.Handle.IsValid() )
continue;
if ( LoopEnabled && voice.Pad is not null && TryReplay( voice.Pad, out var replay ) )
{
voice.Handle = replay;
changed = true;
continue;
}
_voices.RemoveAt( i );
changed = true;
}
if ( changed || _voices.Count != _lastVoiceCount )
{
_lastVoiceCount = _voices.Count;
if ( _voices.Count == 0 )
SetStatus( "READY · PRESS ANY BUTTON" );
else
UpdateStatus();
StateChanged?.Invoke();
}
}
static void UpdateStatus()
{
if ( _voices.Count >= 6 )
{
SetStatus( $"CACOPHONY · {_voices.Count} LAYERS DEEP" );
return;
}
if ( _voices.Count == 1 )
{
SetStatus( $"▶ {LastPlayedLabel}" );
return;
}
if ( _voices.Count > 1 )
{
SetStatus( $"▶ {_voices.Count} FX · {LastPlayedLabel}" );
return;
}
SetStatus( "READY · PRESS ANY BUTTON" );
}
static void SetStatus( string text ) => StatusText = text;
static string ToSoundEvent( string soundId )
{
if ( string.IsNullOrWhiteSpace( soundId ) )
return soundId;
return soundId.EndsWith( ".sound", StringComparison.OrdinalIgnoreCase )
? soundId
: $"{soundId}.sound";
}
static bool TryReplay( SoundPad pad, out SoundHandle handle )
{
if ( !_loopReplayCooldown )
{
handle = default;
return false;
}
handle = Sound.Play( ToSoundEvent( pad.SoundId ) );
if ( !handle.IsValid() )
return false;
handle.Volume = 0.9f * pad.Volume;
handle.Pitch = 0.82f + Game.Random.Float( 0f, 0.36f );
return true;
}
sealed class VoiceEntry
{
public SoundHandle Handle;
public string SoundId;
public SoundPad Pad;
}
}