PackOpeningSound.cs

Plays the card pack opening sound cues. Generates short PCM tones and noise procedurally, loads two mounted ogg clips for ambient and cheer, and triggers them at age, tier and reveal thresholds.

Rough CodeFile Access
using Sandbox;
using System;
using System.Collections.Generic;

namespace Socer;

// Original synthesized foil / rising air / impact cues; no reference-game audio.
public sealed class PackOpeningSound
{
    static readonly Dictionary<int,SoundFile> files=new();
    readonly List<SoundHandle> playing=new();
    public int CuesPlayed {get;private set;}
    public void Update(float age,float previous,int tier,float reveal)
    {
        playing.RemoveAll(h=>h==null || !h.IsValid || !h.IsPlaying);
        if(GamePreferences.SoundVolume<=0){Stop();return;}
        if(previous<0 && age<.35f){Play(0,.18f);Play(5,.10f);}
        if(previous<.92f && age>=.92f && age<1.3f)Play(1,.38f);
        float rise=Math.Max(1.55f,reveal-1.65f);
        if(tier>=2 && previous<rise && age>=rise && age<rise+.3f)Play(tier>=4?4:2,.28f);
        if(previous<reveal && age>=reveal && age<reveal+.3f){Play(3,tier>=3?.48f:.28f);if(tier>=2)Play(6,.24f);}
    }
    void Play(int cue,float volume)
    {
        if(!files.TryGetValue(cue,out var file) || !file.IsValid)
        {
            if(cue>=5)
            {
                file=SoundFile.FromOgg("socer-pack-crowd-"+cue,FileSystem.Mounted.ReadAllBytes(cue==5?"sounds/stadium_ambient.ogg":"sounds/stadium_cheer.ogg"));
                files[cue]=file;
            }
            else
            {
            const int rate=22050;float seconds=cue switch{0=>.6f,1=>.36f,2=>1.45f,4=>1.65f,_=>1.1f};
            var pcm=new byte[(int)(rate*seconds)*2];uint noise=0x91a1u+(uint)cue;
            for(int i=0;i<pcm.Length/2;i++)
            {
                double t=i/(double)rate,p=t/seconds;noise=noise*1664525u+1013904223u;
                double n=(noise/(double)uint.MaxValue)*2-1;
                double v=cue switch{
                    0=>n*.13*Math.Sin(Math.PI*p)*Math.Sin(Math.PI*p),
                    1=>n*.65*Math.Pow(1-p,2.5)*(.4+.6*Math.Abs(Math.Sin(t*96))),
                    2 or 4=>(n*.10+Math.Sin(2*Math.PI*(130*t+180*t*t))*.18)*p*p*(1-Math.Pow(p,12)),
                    _=>Math.Sin(2*Math.PI*(58*t-12*t*t))*.5*Math.Exp(-t*5)+n*.18*Math.Exp(-t*15)+Math.Sin(2*Math.PI*740*t)*.06*Math.Exp(-t*4)};
                short sample=(short)(Math.Clamp(v,-.9,.9)*27000);pcm[i*2]=(byte)sample;pcm[i*2+1]=(byte)(sample>>8);
            }
            file=SoundFile.FromPcm("socer-pack-cinematic-v1-"+cue,pcm,new SoundFile.PcmOptions{Channels=1,Rate=rate,Bits=16});files[cue]=file;
            }
        }
        var handle=Sound.PlayFile(file,volume*GamePreferences.SoundVolume/100f);handle.SpacialBlend=0;handle.DistanceAttenuation=false;playing.Add(handle);CuesPlayed++;
    }
    public void Stop(){foreach(var h in playing)if(h!=null && h.IsValid)h.Stop();playing.Clear();}
    public object Diagnostics()=>new{CuesPlayed,Active=playing.Count,Volume=GamePreferences.SoundVolume};
}