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);
}