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. // ───────────────────────────────────────────────────────────────────── // SoundId moved to its own file on 2026-07-29, when the hand-curated // 23-member subset was replaced by retail's full 205-entry SoundType // catalog: src/AcDream.Core/Audio/SoundId.cs (same namespace). // A local `SoundEntry` scaffold class lived here until 2026-08-08 (Campaign A // slice A1). It had no implementers and no readers — the live path consumes // DatReaderWriter's `SoundEntry` directly — and four of its eight fields were // invented: `PitchMin`/`PitchMax` (retail never calls SetFrequency), `Loop` // (retail never sets the DirectSound loop flag; "looping" ambients are // re-fired one-shots), and `Is3D` (every retail gameplay buffer is created // with m_3D = 0). Its `Priority` was also typed `int` "0..7" where the dat // field is a float in [0,1]. The `ISoundCache` interface that returned it went // the same way. Evidence: // docs/research/2026-08-08-audio-retail-soundmanager-core.md, // docs/research/2026-08-08-audio-retail-dat-layer.md §1. /// /// 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; } } // An `AudioFalloff` helper lived here until 2026-08-08 (Campaign A slice A2). // It had the right shape but a 1-metre reference distance where retail's is 5, // a `PanFromRelative` that was linear in relative X over an invented 20-metre // range where retail pans by the SINE of a compass bearing, and no audibility // cutoff. Nothing ever called either method. Both are superseded by // `RetailSoundMixer`, which carries the byte-decoded retail math. /// /// Interface the platform audio engine (AcDream.App layer) implements. /// Core defines the contract; App implements via Silk.NET.OpenAL. /// public interface IAudioEngine : IDisposable { /// /// acdream's own master, on top of retail's two live knobs. Retail has /// three preferences — effect, ambient, and an interface one it registers /// and never reads — and NO master and no music knob. See AP-174. /// float MasterVolume { get; set; } /// Retail's effect_sound_volume. float SfxVolume { get; set; } /// Retail's ambient_sound_volume (applied twice — TS-65). float AmbientVolume{ get; set; } /// /// Update the listener pose. Retail's listener is SmartBox::viewer — /// the COLLIDED third-person camera Position, refreshed once per rendered /// frame (SmartBox::update_viewer @ 0x00453CE0), falling back /// to the player's own position when the camera sweep fails. Only two /// things are ever read out of it: the origin, for distance, and /// Frame::get_heading, for pan. There is no up vector, no velocity, /// and therefore no doppler and no elevation cue — which is why this takes /// a single compass heading rather than a forward/up basis. /// void SetListener(float posX, float posY, float posZ, float headingDegrees); /// 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); // A `StartAmbient(id, x, y, z)` / `StopAmbient(handle)` pair lived here // until 2026-08-08 (Campaign A slice A5). It modelled a LOOPING, // handle-owned ambient voice, which retail does not have: retail never sets // the DirectSound loop flag, and a "continuous" ambient is a one-shot // re-fired every min_rate seconds off an absolute-deadline queue, with a // fresh variant pick and crossfade volume each time. The implementation was // a stub that minted a handle and played nothing, while StopAmbient looked // up a source that was never created. `AmbientSoundController` + // `AmbientSoundScheduler` carry the real model. // A `PlayMusic(resourceName, loop)` / `StopMusic()` pair and a `MusicVolume` // knob lived here until 2026-08-08 (Campaign A slice A6). They modelled a // subsystem retail does not have. The EoR client links a complete winmm // midiStream player and never feeds it: `midiPlay` @ 0x00553390 has zero // callers (its only textual occurrences in the 65 MB decomp are its own // definition and its own queue drainer), both MIDI callbacks are statically // null, the string "music" appears zero times, `SoundType` has no music // member, `SoundManager::InitPrefs` registers no music preference, and the // retail install ships no .mid/.mp3/.wav at all. The string-keyed signature // was itself the tell — every other entry point in this interface is // DID-keyed. What players remember as music is the AdminEnvirons UI stinger // family (see `EnvironSoundCueMap`) and the intro AVI's audio track. // Evidence: docs/research/2026-08-08-audio-retail-music-absence.md. }