using System;
using System.Collections.Generic;
using DatReaderWriter.DBObjs;
using DRWSound = DatReaderWriter.Enums.Sound;
namespace AcDream.Core.Audio;
///
/// Probabilistic entry picker over a retail .
///
///
/// Each key in
/// SoundTable.Sounds maps to a list of
/// items each carrying a
/// probability weight. Retail picks one entry per trigger by rolling the
/// cumulative distribution โ that's how footsteps sound slightly
/// different each step, how weapon swings have 3 swoosh variants, etc.
///
///
///
/// r05 ยง4: the picker samples a uniform random in [0,1) and walks the
/// entries accumulating probabilities; the first entry whose running total
/// exceeds the sample wins. If all probabilities sum to < 1, the
/// remaining mass means "silence" โ the call returns null. If probabilities
/// sum to > 1 the picker still works correctly (it clamps on the last
/// entry).
///
///
public static class SoundCookbook
{
///
/// Pick one entry from a sound's variant list, weighted by probability.
/// Returns null when the rolled sample falls into the "silence"
/// remainder of the distribution (probability sum < 1).
///
public static DatReaderWriter.Types.SoundEntry? Roll(
IReadOnlyList entries,
Random rng)
{
ArgumentNullException.ThrowIfNull(entries);
ArgumentNullException.ThrowIfNull(rng);
if (entries.Count == 0) return null;
if (entries.Count == 1) return entries[0];
float sample = (float)rng.NextDouble();
float cum = 0f;
for (int i = 0; i < entries.Count; i++)
{
cum += Math.Max(0f, entries[i].Probability);
if (sample < cum) return entries[i];
}
// Fell past the last entry โ either probabilities sum to >1 (return
// last) or < 1 and we rolled into the "silence" tail (return null).
float total = 0f;
for (int i = 0; i < entries.Count; i++)
total += Math.Max(0f, entries[i].Probability);
return total > 0.999f ? entries[entries.Count - 1] : null;
}
///
/// Convenience lookup: given a SoundTable + a retail
/// key (e.g. Swoosh1, Footstep1), roll the
/// entry list and return the winning entry. Returns null if:
///
/// - has no mapping for
/// .
/// - The mapping's entry list is empty.
/// - The probability roll hits the silence
/// tail.
///
///
public static DatReaderWriter.Types.SoundEntry? Roll(
SoundTable table,
DRWSound sound,
Random rng)
{
ArgumentNullException.ThrowIfNull(table);
if (!table.Sounds.TryGetValue(sound, out var soundData)) return null;
return Roll(soundData.Entries, rng);
}
}