// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "Subsystems/WorldSubsystem.h" #include "Quartz/AudioMixerClockManager.h" #include "Sound/QuartzQuantizationUtilities.h" #include "QuartzSubsystem.generated.h" // forwards namespace Audio { class FMixerDevice; class FQuartzClockManager; class FQuartzShareableCommandQueue; } class FQuartzTickableObject; class UQuartzClockHandle; using MetronomeCommandQueuePtr = TSharedPtr; struct FQuartzTickableObjectsManager : public FQuartLatencyTracker { public: AUDIOMIXER_API void Tick(float DeltaTime); AUDIOMIXER_API bool IsTickable() const; AUDIOMIXER_API void SubscribeToQuartzTick(FQuartzTickableObject* InObjectToTick); AUDIOMIXER_API void UnsubscribeFromQuartzTick(FQuartzTickableObject* InObjectToTick); private: // list of objects needing to be ticked by Quartz TArray QuartzTickSubscribers; // index to track the next subscriber to tick (if updates are being amortized across multiple UObject Ticks) int32 UpdateIndex{ 0 }; }; UCLASS(DisplayName = "Quartz", MinimalAPI) class UQuartzSubsystem : public UTickableWorldSubsystem { GENERATED_BODY() public: // ctor/dtor UQuartzSubsystem() = default; virtual ~UQuartzSubsystem() override = default; //~ Begin UWorldSubsystem Interface AUDIOMIXER_API virtual void Initialize(FSubsystemCollectionBase& Collection) override; AUDIOMIXER_API virtual void Deinitialize() override; AUDIOMIXER_API virtual bool DoesSupportWorldType(EWorldType::Type WorldType) const override; AUDIOMIXER_API void virtual BeginDestroy() override; //~ End UWorldSubsystem Interface //~ Begin FTickableGameObject Interface AUDIOMIXER_API virtual void Tick(float DeltaTime) override; AUDIOMIXER_API virtual bool IsTickableWhenPaused() const override; AUDIOMIXER_API virtual bool IsTickable() const override; AUDIOMIXER_API virtual TStatId GetStatId() const override; //~ End FTickableGameObject Interface // these calls are forwarded to the internal FQuartzTickableObjectsManager AUDIOMIXER_API void SubscribeToQuartzTick(FQuartzTickableObject* InObjectToTick); AUDIOMIXER_API void UnsubscribeFromQuartzTick(FQuartzTickableObject* InObjectToTick); // get C++ handle (proxy) to a clock if it exists AUDIOMIXER_API Audio::FQuartzClockProxy GetProxyForClock(FName ClockName) const; // allow an external clock (not ticked by the Audio Mixer or QuartzSubsystem) to be accessible via this subsystem AUDIOMIXER_API void AddProxyForExternalClock(const Audio::FQuartzClockProxy& InProxy); // static methods static AUDIOMIXER_API UQuartzSubsystem* Get(const UWorld* const World); // Helper functions for initializing quantized command initialization struct (to consolidate eyesore) static AUDIOMIXER_API Audio::FQuartzQuantizedRequestData CreateRequestDataForTickRateChange(UQuartzClockHandle* InClockHandle, const FOnQuartzCommandEventBP& InDelegate, const Audio::FQuartzClockTickRate& InNewTickRate, const FQuartzQuantizationBoundary& InQuantizationBoundary); static AUDIOMIXER_API Audio::FQuartzQuantizedRequestData CreateRequestDataForTransportReset(UQuartzClockHandle* InClockHandle, const FQuartzQuantizationBoundary& InQuantizationBoundary, const FOnQuartzCommandEventBP& InDelegate); static AUDIOMIXER_API Audio::FQuartzQuantizedRequestData CreateRequestDataForStartOtherClock(UQuartzClockHandle* InClockHandle, FName InClockToStart, const FQuartzQuantizationBoundary& InQuantizationBoundary, const FOnQuartzCommandEventBP& InDelegate); static AUDIOMIXER_API Audio::FQuartzQuantizedRequestData CreateRequestDataForSchedulePlaySound(UQuartzClockHandle* InClockHandle, const FOnQuartzCommandEventBP& InDelegate, const FQuartzQuantizationBoundary& InQuantizationBoundary); static AUDIOMIXER_API Audio::FQuartzQuantizedRequestData CreateRequestDataForQuantizedNotify(UQuartzClockHandle* InClockHandle, const FQuartzQuantizationBoundary& InQuantizationBoundary, const FOnQuartzCommandEventBP& InDelegate, float InMsOffset = 0.f); UFUNCTION(BlueprintCallable, Category = "Quartz Clock Handle") AUDIOMIXER_API bool IsQuartzEnabled(); // Clock Creation // create a new clock (or return handle if clock already exists) UFUNCTION(BlueprintCallable, Category = "Quartz Clock Handle", meta = (WorldContext = "WorldContextObject", AdvancedDisplay = "bUseAudioEngineClockManager")) AUDIOMIXER_API UQuartzClockHandle* CreateNewClock(const UObject* WorldContextObject, FName ClockName, FQuartzClockSettings InSettings, bool bOverrideSettingsIfClockExists = false, bool bUseAudioEngineClockManager = true); // delete an existing clock given its name UFUNCTION(BlueprintCallable, Category = "Quartz Clock Handle", meta = (WorldContext = "WorldContextObject")) AUDIOMIXER_API void DeleteClockByName(const UObject* WorldContextObject, FName ClockName); // delete an existing clock given its clock handle UFUNCTION(BlueprintCallable, Category = "Quartz Clock Handle", meta = (WorldContext = "WorldContextObject")) AUDIOMIXER_API void DeleteClockByHandle(const UObject* WorldContextObject, UPARAM(ref) UQuartzClockHandle*& InClockHandle); // get handle for existing clock UFUNCTION(BlueprintCallable, Category = "Quartz Clock Handle", meta = (WorldContext = "WorldContextObject")) AUDIOMIXER_API UQuartzClockHandle* GetHandleForClock(const UObject* WorldContextObject, FName ClockName); // returns true if the clock exists UFUNCTION(BlueprintCallable, Category = "Quartz Clock Handle", meta = (WorldContext = "WorldContextObject")) AUDIOMIXER_API bool DoesClockExist(const UObject* WorldContextObject, FName ClockName); // returns true if the clock is running UFUNCTION(BlueprintCallable, Category = "Quartz Clock Handle", meta = (WorldContext = "WorldContextObject")) AUDIOMIXER_API bool IsClockRunning(const UObject* WorldContextObject, FName ClockName); // Returns the duration in seconds of the given Quantization Type UFUNCTION(BlueprintCallable, Category = "Quartz Clock Handle", meta = (WorldContext = "WorldContextObject")) AUDIOMIXER_API float GetDurationOfQuantizationTypeInSeconds(const UObject* WorldContextObject, FName ClockName, const EQuartzCommandQuantization& QuantizationType, float Multiplier = 1.0f); // Retrieves a timestamp for the clock UFUNCTION(BlueprintCallable, Category = "Quartz Clock Handle", meta = (WorldContext = "WorldContextObject")) AUDIOMIXER_API FQuartzTransportTimeStamp GetCurrentClockTimestamp(const UObject* WorldContextObject, const FName& InClockName); // Returns the amount of time, in seconds, the clock has been running. Caution: due to latency, this will not be perfectly accurate UFUNCTION(BlueprintCallable, Category = "Quartz Clock Handle", meta = (WorldContext = "WorldContextObject")) AUDIOMIXER_API float GetEstimatedClockRunTime(const UObject* WorldContextObject, const FName& InClockName); // latency data (Game thread -> Audio Render Thread) UFUNCTION(BlueprintCallable, Category = "Quartz Subsystem", meta = (WorldContext = "WorldContextObject")) AUDIOMIXER_API float GetGameThreadToAudioRenderThreadAverageLatency(const UObject* WorldContextObject); UFUNCTION(BlueprintCallable, Category = "Quartz Subsystem", meta = (WorldContext = "WorldContextObject")) AUDIOMIXER_API float GetGameThreadToAudioRenderThreadMinLatency(const UObject* WorldContextObject); UFUNCTION(BlueprintCallable, Category = "Quartz Subsystem", meta = (WorldContext = "WorldContextObject")) AUDIOMIXER_API float GetGameThreadToAudioRenderThreadMaxLatency(const UObject* WorldContextObject); // latency data (Audio Render Thread -> Game thread) UFUNCTION(BlueprintCallable, Category = "Quartz Subsystem") AUDIOMIXER_API float GetAudioRenderThreadToGameThreadAverageLatency(); UFUNCTION(BlueprintCallable, Category = "Quartz Subsystem") AUDIOMIXER_API float GetAudioRenderThreadToGameThreadMinLatency(); UFUNCTION(BlueprintCallable, Category = "Quartz Subsystem") AUDIOMIXER_API float GetAudioRenderThreadToGameThreadMaxLatency(); // latency data (Round trip) UFUNCTION(BlueprintCallable, Category = "Quartz Subsystem", meta = (WorldContext = "WorldContextObject")) AUDIOMIXER_API float GetRoundTripAverageLatency(const UObject* WorldContextObject); UFUNCTION(BlueprintCallable, Category = "Quartz Subsystem", meta = (WorldContext = "WorldContextObject")) AUDIOMIXER_API float GetRoundTripMinLatency(const UObject* WorldContextObject); UFUNCTION(BlueprintCallable, Category = "Quartz Subsystem", meta = (WorldContext = "WorldContextObject")) AUDIOMIXER_API float GetRoundTripMaxLatency(const UObject* WorldContextObject); UFUNCTION(BlueprintCallable, Category = "Quartz Subsystem") AUDIOMIXER_API void SetQuartzSubsystemTickableWhenPaused(const bool bInTickableWhenPaused); // sharable to allow non-UObjects to un-subscribe if the Subsystem is going to outlive them AUDIOMIXER_API TWeakPtr GetTickableObjectManager() const; private: // deletes proxies to clocks that no longer exists AUDIOMIXER_API void PruneStaleProxies(); static AUDIOMIXER_API void PruneStaleProxiesInternal(TArray& ContainerToPrune); // sharable tickable object manager to allow for non-UObject subscription / un-subscription TSharedPtr TickableObjectManagerPtr { MakeShared() }; // Clock manager/proxy-related data that lives on the AudioDevice for persistence. TSharedPtr ClockManagerDataPtr { nullptr }; bool bTickEvenWhenPaused = false; // helpers AUDIOMIXER_API Audio::FQuartzClockProxy* FindProxyByName(const FName& ClockName); AUDIOMIXER_API Audio::FQuartzClockProxy const* FindProxyByName(const FName& ClockName) const; AUDIOMIXER_API Audio::FQuartzClockManager* GetClockManager(const UObject* WorldContextObject, bool bUseAudioEngineClockManager = true); }; // class UQuartzGameSubsystem