Code/Demos/BeatPad/BeatPadAudio.cs
using Sandbox.Audio;
namespace Sandbox;
// Thin wrapper over Sound.Play for the beat pad. Plays a .sound event, applies the
// master volume/pitch from the knobs, routes through a shared filter+reverb mixer,
// and resolves a lit-decay halflife from the sample's duration where the engine allows.
internal static class BeatPadAudio
{
// only hard-coded decay number; remove once SoundFile.Duration is confirmed and routed through ResolveHalflife.
public const float FallbackHalflife = 0.06f;
const string MixerName = "Goo.BeatPad";
const string ReverbPreset = "room.diffuse.small";
static Mixer? _mixer;
static LowPassProcessor? _lowPass;
static HighPassProcessor? _highPass;
static DspProcessor? _reverb;
// cutoffs/mix from KnobMath; FLT is bipolar (one filter open while the other engages); falls back to dry default mixer if unavailable.
public static void Play(string soundName, float volume, float pitch,
float lowPassCutoff, float highPassCutoff, float reverbMix)
{
var handle = Sound.Play(soundName);
if (!handle.IsValid()) return;
handle.Volume = volume;
handle.Pitch = pitch;
handle.ListenLocal = true; // UI sound: no 3D attenuation
var mixer = EnsureMixer();
if (mixer is null) return;
handle.TargetMixer = mixer;
if (_lowPass is not null) _lowPass.Cutoff = lowPassCutoff;
if (_highPass is not null) _highPass.Cutoff = highPassCutoff;
if (_reverb is not null) _reverb.Mix = reverbMix;
}
// Lazily build (once) a child mixer carrying low-pass + high-pass + reverb processors.
// Re-finds by name so a hot-reload reuses the bus; any failure degrades to a dry,
// unrouted sound.
static Mixer? EnsureMixer()
{
if (_mixer is not null) return _mixer;
try
{
var mixer = Mixer.FindMixerByName(MixerName);
if (mixer is null)
{
var root = Mixer.Master;
if (root is null) return null;
mixer = root.AddChild();
mixer.Name = MixerName;
}
_lowPass = mixer.GetProcessor<LowPassProcessor>();
if (_lowPass is null) { _lowPass = new LowPassProcessor(); mixer.AddProcessor(_lowPass); }
_highPass = mixer.GetProcessor<HighPassProcessor>();
if (_highPass is null) { _highPass = new HighPassProcessor(); mixer.AddProcessor(_highPass); }
_reverb = mixer.GetProcessor<DspProcessor>();
if (_reverb is null) { _reverb = new DspProcessor(ReverbPreset); mixer.AddProcessor(_reverb); }
_mixer = mixer;
return _mixer;
}
catch
{
// Mixer / AudioProcessor surface unavailable on this build; play dry.
return null;
}
}
// Returns a DecayFloat halflife driving the lit-pad fade. Tries the sample's real
// duration; falls back to a fixed snap if unavailable. A halflife of duration/5
// makes the glow visually last roughly the sample length.
public static float ResolveHalflife(string soundName)
{
try
{
var file = SoundFile.Load(soundName);
if (file != null && file.Duration > 0.01f)
return System.MathF.Max(0.02f, file.Duration / 5f);
}
catch
{
// SoundFile.Load may reject a .sound event path on this build; fall through.
}
return FallbackHalflife;
}
}