#pragma once #include "CoreMinimal.h" #include "Core/DescentHandle.h" #include "Core/DescentWorldSubsystem.h" #include "DescentAudio.generated.h" class UAudioComponent; class USoundWaveProcedural; /** Sound (M9, requirement V3; DESIGN_UE.md section 8.4). Every sound is rendered in code when the world starts: no asset, no import step. */ namespace Descent::Audio { constexpr int32 SampleRate = 22050; constexpr double AirLowPassHz = 5200.0, TenderLowPassHz = 2400.0, SwimLowPassHz = 1300.0; THEDESCENT_API const TArray& LoopNames(); /** 16-bit mono PCM of a loop (4 s) or a one-shot. False for an unknown name. */ THEDESCENT_API bool Render(FName Name, TArray& Out); } struct FDescentLoopState { float Volume = 0.f, Pitch = 1.f; }; struct FDescentOneShot { FName Name; double SimTime = 0.0; float Volume = 1.f; }; /** Owns one audio component per loop and sets volume, pitch and low-pass every frame; plays one-shots. The `audio` state section shows what it does. */ UCLASS() class THEDESCENT_API UDescentAudioSubsystem : public UDescentWorldSubsystem { GENERATED_BODY() public: static UDescentAudioSubsystem* Get(const UWorld* World); virtual void OnWorldBeginPlay(UWorld& InWorld) override; virtual void Deinitialize() override; void OneShot(FName Name, float Volume = 1.f); const FDescentLoopState* FindLoop(FName Name) const { return Loops.Find(Name); } bool HadOneShot(FName Name) const; TMap Loops; TArray OneShots; // the last 20 double MasterLowPassHz = Descent::Audio::AirLowPassHz; bool bDevice = false; private: void Step(double Dt); void EnsureComponents(); TSharedPtr> Pcm(FName Name); TMap>> Cache; FDescentHandle StepHandle; int32 NextShot = 0; UPROPERTY(Transient) TObjectPtr Owner; UPROPERTY(Transient) TMap> LoopComps; UPROPERTY(Transient) TArray> ShotComps; UPROPERTY(Transient) TArray> ShotWaves; UPROPERTY(Transient) TArray> LoopWaves; };