Add a Sound

In short

There are no audio files: every sound is generated in code at startup. Build the clip in SoundBank from AudioSynth pieces (tones, noise, filters, envelopes), then play it in GameAudio from a GameEvents handler, so gameplay code never touches audio.

Steps

  1. Build the clip. In Assets/_Project/Scripts/Audio/SoundBank.cs:
    • add a property: public AudioClip Coin { get; private set; }
    • set it in the constructor: Coin = BuildCoin();
    • write the builder from AudioSynth pieces. The coin is a typical small one:
    static AudioClip BuildCoin()
    {
        var a = AudioSynth.Tone(1760f, 0.09f, 0.5f, Waveform.Square);
        AudioSynth.ExpDecay(a, 30f);
        var b = AudioSynth.Tone(2637f, 0.22f, 0.5f, Waveform.Square);
        AudioSynth.ExpDecay(b, 14f);
        var mix = AudioSynth.Silence(0.32f);
        AudioSynth.MixInto(mix, a, 0f);
        AudioSynth.MixInto(mix, b, 0.08f);
        AudioSynth.LowPass(mix, 0.5f);
        AudioSynth.Normalize(mix, 0.6f);
        return AudioSynth.ToClip("Coin", mix);
    }
    AudioSynth (in Audio/AudioSynth.cs) has Tone, Organ, Brass, Sweep, Voices (crowd), Noise, Silence, LowPass, LowPassSweep, HighPass, Envelope, ExpDecay, Gain, Wobble, Mix, MixInto, MakeLoop, Normalize, Clamp, Samples, NoteHz and ToClip. Since #40 and #42 some clips are built in GameAudio itself instead (BuildImpact, BuildFireworkBang, BuildSadTrombone, BuildCountTick, BuildRiser, kept in fields set in Awake). Either place works; SoundBank keeps the shared ones. Pass a fixed seed to Noise / Voices so the sound is the same every launch.
  2. Play it in Assets/_Project/Scripts/Audio/GameAudio.cs:
    • On a game event: in an existing handler (OnSwingResolved, OnHitLanded, OnCountChanged, OnRoundFinished, OnRunEnded, OnScoreboardHit…) call Play(_bank.MySound, volume, pitch). For a new event, add it to Core/GameEvents.cs (event, Raise... method, and a line in ClearAll), raise it from the gameplay code, and subscribe in GameAudio.OnEnable / unsubscribe in OnDisable.
    • From the UI or a cutscene: add a static one-liner like the existing ones and call it from the view or director (the cutscenes call GameAudio.Impact, Whoosh, Fanfare, FireworkBang, SadTrombone; the hit counter calls CounterTick, CounterDing, Gone, StackHit, CounterSlam, CounterStamp):
    public static void Coin() => Instance?.Play(Instance._bank.Coin, 0.7f);

Check it works

Press Play and trigger it. The SOUND slider (settings) scales every effect through SfxVolume; check it at a low value too. No self-test covers audio.

Gotchas

  • Clips are built once, in the SoundBank constructor when GameAudio wakes. A long or complex sound adds to startup time.
  • Play uses one shared one-shot source and sets its pitch per call. The hit counter’s ticks use a second source (_counter, via PlayCounter) so their rising pitch doesn’t bend the cheers playing at the same time.
  • The organ also goes quiet during the match intro and the end scenes (OnState treats Intro and Outro like an at-bat).
  • Pausing silences the field (AudioListener.pause); only the organ ignores the pause. A UI sound played while paused will not be heard.
  • The style is deliberate (see the SoundBank summary): nothing loops or hisses, and the crowd only reacts to plays.
  • The GREEN sound is generated like the rest: SoundBank.CrackGreen (picked by CrackFor when the swing is green) plus a small cheer that grows with the streak, both in GameAudio.OnSwingResolved. The README’s GreenFeedback.GreenClip no longer exists; UI/GreenFeedback only shakes the camera.