DiamondSlideAudio.cs
using System;
using Sandbox;
namespace Diamonds;
/// <summary>Overlapping excerpts of the recorded gem scrape, without a fixed loop seam.</summary>
public sealed class DiamondSlideAudio
{
const float Crossfade = 0.07f;
readonly Random random = new();
SoundHandle current;
SoundHandle outgoing;
float currentGain;
float outgoingGain;
float currentPitch;
float outgoingPitch;
float blendTime;
float changeAt;
public void Update( float speed, float delta )
{
// Read the source duration so replacing the recording does not leave stale timings.
var source = SoundFile.Load( "sfx/slide_1.vsnd" );
if ( source is null || !source.IsValid ) { Stop(); return; }
// Load creates the resource wrapper; native audio data can still be pending
// on first use or after an asset reload. Duration requires that data.
source.Preload();
if ( !source.IsLoaded ) { Stop(); return; }
float duration = source.Duration;
if ( duration <= 0 ) { Stop(); return; }
if ( current is null || !current.IsValid || current.Time >= changeAt )
StartExcerpt( duration );
if ( current is null ) return;
blendTime = MathF.Min( Crossfade, blendTime + delta );
float blend = blendTime / Crossfade;
float volume = (0.20f + speed * 0.40f) * DiamondSettings.EffectsGain;
float pitch = 0.95f + speed * 0.10f;
current.Volume = currentGain * volume * MathF.Sin( blend * MathF.PI * 0.5f );
current.Pitch = currentPitch * pitch;
if ( outgoing is not null )
{
outgoing.Volume = outgoingGain * volume * MathF.Cos( blend * MathF.PI * 0.5f );
outgoing.Pitch = outgoingPitch * pitch;
if ( blend >= 1 ) { outgoing.Stop(); outgoing = null; }
}
}
void StartExcerpt( float duration )
{
outgoing?.Stop();
outgoing = current;
outgoingGain = currentGain;
outgoingPitch = currentPitch;
current = Sound.Play( "sfx/slide_1.sound" );
if ( current is null ) { Stop(); return; }
currentGain = current.Volume;
currentPitch = current.Pitch * (0.98f + (float)random.NextDouble() * 0.04f);
current.Volume = 0;
current.SpacialBlend = 0;
// Avoid replaying the same attack and tail. Keep enough source after the
// handoff for the outgoing voice to finish its crossfade at maximum pitch.
float margin = MathF.Min( 0.04f, duration * 0.05f );
float fadeRoom = Crossfade * 1.2f;
float length = MathF.Min( 0.22f + (float)random.NextDouble() * 0.12f, duration * 0.55f );
float startRange = MathF.Max( 0, duration - margin * 2 - length - fadeRoom );
float start = margin + (float)random.NextDouble() * startRange;
current.Time = start;
changeAt = start + length;
blendTime = 0;
}
public void Stop()
{
current?.Stop();
outgoing?.Stop();
current = null;
outgoing = null;
blendTime = 0;
}
}