using System; using System.Collections.Generic; namespace AcDream.Core.Audio; // ───────────────────────────────────────────────────────────────────── // Scaffold for R5 — audio data model + engine interface. // Full research: docs/research/deepdives/r05-audio-sound.md // Runtime backend (Silk.NET.OpenAL) lives in AcDream.App. // ───────────────────────────────────────────────────────────────────── /// /// Enumerated retail sound IDs. See r05 §5 for the full 204-entry list. /// Only the commonly-fired ones are given names here. /// public enum SoundId : uint { None = 0, FootstepDefault = 0x02, FootstepGrass = 0x03, FootstepWater = 0x04, FootstepDirt = 0x05, FootstepStone = 0x06, FootstepWood = 0x07, SwingSword = 0x10, SwingAxe = 0x11, HitMetalOnMetal = 0x20, HitMetalOnLeather = 0x21, HitFlesh = 0x22, ArrowWhoosh = 0x30, ArrowThud = 0x31, SpellCastWar = 0x40, SpellCastLife = 0x41, SpellCastItem = 0x42, SpellCastCreature = 0x43, PortalWhoosh = 0x50, LifestoneTie = 0x51, Death = 0x60, PotionDrink = 0x70, BuffApplied = 0x80, // More constants populated during R5 audio port. } /// /// Per-SoundId entry from the SoundTable dat (0x20000000..0x2000FFFF). /// One Sound can have multiple entries with probabilities — that's /// retail's variation mechanism (e.g. 3 different footstep clips). /// public sealed class SoundEntry { public uint WaveId { get; init; } // → Wave dat (0x0A000000..0x0A00FFFF) public int Priority { get; init; } // eviction ordering (0..7) public float Probability{ get; init; } // for entries with multiple alternatives public float VolumeBase { get; init; } // 0..1 multiplier applied before falloff public float PitchMin { get; init; } public float PitchMax { get; init; } public bool Loop { get; init; } public bool Is3D { get; init; } // 3D positional vs UI/music flat } /// /// Raw decoded PCM data from a Wave dat. Set by WaveDecoder at /// load time. Retail supports both PCM and MP3 source; we decode MP3 /// to PCM once at load (same as retail does for long clips). /// public sealed class WaveData { public int ChannelCount { get; init; } public int SampleRate { get; init; } public int BitsPerSample{ get; init; } // 8 or 16 public byte[] PcmBytes { get; init; } = Array.Empty(); public TimeSpan Duration { get; init; } } /// /// Falloff math (r05 §2). Retail is CPU-side inverse-square, NOT /// DirectSound3DBuffer. No doppler, no cone, no HRTF. /// public static class AudioFalloff { /// /// Attenuation factor based on distance. Retail uses pure inverse-square /// above a minimum-distance threshold. /// public static float AttenuationAt(float distanceMeters, float minDistance = 1.0f) { if (distanceMeters < minDistance) return 1.0f; float att = (minDistance * minDistance) / (distanceMeters * distanceMeters); return Math.Clamp(att, 0f, 1f); } /// /// Stereo pan from listener-relative X coord. ±1.0 fully panned. /// public static float PanFromRelative(float relativeX, float panRange = 20f) { if (panRange <= 0) return 0f; return Math.Clamp(relativeX / panRange, -1f, 1f); } } /// /// Interface the platform audio engine (AcDream.App layer) implements. /// Core defines the contract; App implements via Silk.NET.OpenAL. /// public interface IAudioEngine : IDisposable { /// Set master volume [0..1]. float MasterVolume { get; set; } float SfxVolume { get; set; } float MusicVolume { get; set; } float AmbientVolume{ get; set; } /// Update listener pose (called per frame from player position). void SetListener(float posX, float posY, float posZ, float forwardX, float forwardY, float forwardZ, float upX, float upY, float upZ); /// Play a 2D UI sound (no falloff). void PlayUi(SoundId id); /// Play a 3D sound at a world position. void Play3D(SoundId id, float x, float y, float z); /// Start a looped ambient sound (landblock-attached). int StartAmbient(SoundId id, float x, float y, float z); void StopAmbient(int handle); /// Start music (fades out previous if any). void PlayMusic(string resourceName, bool loop); void StopMusic(); } /// /// Cache of decoded waves + SoundTable lookups. Owned by the App-layer /// AudioEngine; Core exposes the interface. /// public interface ISoundCache { WaveData GetWave(uint waveId); IReadOnlyList GetSoundEntries(SoundId id); IReadOnlyList GetSoundEntries(uint soundTableId, SoundId id); }