You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
[Backout] - CL20029769, 20034044, and 20035018. [FYI] Maxwell.Hayes Original CL Desc ----------------------------------------------------------------- Decoupling Clock Handles' clock access from QuartzSubsystem / FAudioDevice #jira UE-147373, UE-147374 #rb Aaron.McLeran, Buzz.Burrowes #preflight 62715f699d6c2f8f5b23fae2 #ROBOMERGE-AUTHOR: bob.tellez #ROBOMERGE-SOURCE: CL 20037851 via CL 20037859 via CL 20037860 #ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v943-19904690) [CL 20038591 by bob tellez in ue5-main branch]
464 lines
12 KiB
C++
464 lines
12 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Quartz/AudioMixerClock.h"
|
|
#include "Quartz/AudioMixerClockManager.h"
|
|
#include "AudioMixerSourceManager.h"
|
|
|
|
|
|
static float HeadlessClockSampleRateCvar = 100000.f;
|
|
FAutoConsoleVariableRef CVarHeadlessClockSampleRate(
|
|
TEXT("au.Quartz.HeadlessClockSampleRate"),
|
|
HeadlessClockSampleRateCvar,
|
|
TEXT("Sample rate to use for Quartz Clocks/Metronomes when no Mixer Device is present.\n")
|
|
TEXT("0: Not Enabled, 1: Enabled"),
|
|
ECVF_Default);
|
|
|
|
namespace Audio
|
|
{
|
|
FQuartzClock::FQuartzClock(const FName& InName, const FQuartzClockSettings& InClockSettings, FQuartzClockManager* InOwningClockManagerPtr)
|
|
: Metronome(InClockSettings.TimeSignature)
|
|
, OwningClockManagerPtr(InOwningClockManagerPtr)
|
|
, Name(InName)
|
|
, bIsRunning(false)
|
|
, bIgnoresFlush(InClockSettings.bIgnoreLevelChange)
|
|
{
|
|
|
|
FMixerDevice* MixerDevice = GetMixerDevice();
|
|
|
|
if (MixerDevice)
|
|
{
|
|
Metronome.SetSampleRate(MixerDevice->GetSampleRate());
|
|
}
|
|
else
|
|
{
|
|
Metronome.SetSampleRate(HeadlessClockSampleRateCvar);
|
|
}
|
|
}
|
|
|
|
FQuartzClock::~FQuartzClock()
|
|
{
|
|
Shutdown();
|
|
}
|
|
|
|
void FQuartzClock::ChangeTickRate(FQuartzClockTickRate InNewTickRate, int32 NumFramesLeft)
|
|
{
|
|
FMixerDevice* MixerDevice = GetMixerDevice();
|
|
|
|
if (MixerDevice)
|
|
{
|
|
InNewTickRate.SetSampleRate(MixerDevice->GetSampleRate());
|
|
}
|
|
else
|
|
{
|
|
InNewTickRate.SetSampleRate(HeadlessClockSampleRateCvar);
|
|
}
|
|
|
|
Metronome.SetTickRate(InNewTickRate, NumFramesLeft);
|
|
FQuartzClockTickRate CurrentTickRate = Metronome.GetTickRate();
|
|
|
|
// ratio between new and old rates
|
|
const double Ratio = InNewTickRate.GetFramesPerTick() / CurrentTickRate.GetFramesPerTick();
|
|
|
|
// adjust time-till-fire for existing commands
|
|
for (auto& Command : PendingCommands)
|
|
{
|
|
Command.NumFramesUntilExec = NumFramesLeft + Ratio * (Command.NumFramesUntilExec - NumFramesLeft);
|
|
}
|
|
|
|
for (auto& Command : ClockAlteringPendingCommands)
|
|
{
|
|
Command.NumFramesUntilExec = NumFramesLeft + Ratio * (Command.NumFramesUntilExec - NumFramesLeft);
|
|
}
|
|
}
|
|
|
|
void FQuartzClock::ChangeTimeSignature(const FQuartzTimeSignature& InNewTimeSignature)
|
|
{
|
|
// TODO: what does this do to pending events waiting for the beat? (maybe nothing is reasonable?)
|
|
Metronome.SetTimeSignature(InNewTimeSignature);
|
|
}
|
|
|
|
void FQuartzClock::Resume()
|
|
{
|
|
if (bIsRunning == false)
|
|
{
|
|
for (auto& Command : PendingCommands)
|
|
{
|
|
// Update countdown time to each quantized command
|
|
Command.Command->OnClockStarted();
|
|
}
|
|
|
|
for (auto& Command : ClockAlteringPendingCommands)
|
|
{
|
|
// Update countdown time to each quantized command
|
|
Command.Command->OnClockStarted();
|
|
}
|
|
}
|
|
|
|
bIsRunning = true;
|
|
}
|
|
|
|
void FQuartzClock::Stop(bool CancelPendingEvents)
|
|
{
|
|
bIsRunning = false;
|
|
Metronome.ResetTransport();
|
|
TickDelayLengthInFrames = 0;
|
|
|
|
if (CancelPendingEvents)
|
|
{
|
|
for (auto& Command : PendingCommands)
|
|
{
|
|
Command.Command->Cancel();
|
|
}
|
|
|
|
for (auto& Command : ClockAlteringPendingCommands)
|
|
{
|
|
Command.Command->Cancel();
|
|
}
|
|
|
|
PendingCommands.Reset();
|
|
ClockAlteringPendingCommands.Reset();
|
|
}
|
|
}
|
|
|
|
void FQuartzClock::Pause()
|
|
{
|
|
if (bIsRunning)
|
|
{
|
|
for (auto& Command : PendingCommands)
|
|
{
|
|
// Update countdown time to each quantized command
|
|
Command.Command->OnClockPaused();
|
|
}
|
|
|
|
for (auto& Command : ClockAlteringPendingCommands)
|
|
{
|
|
// Update countdown time to each quantized command
|
|
Command.Command->OnClockPaused();
|
|
}
|
|
}
|
|
|
|
bIsRunning = false;
|
|
}
|
|
|
|
void FQuartzClock::Restart(bool bPause)
|
|
{
|
|
bIsRunning = !bPause;
|
|
TickDelayLengthInFrames = 0;
|
|
}
|
|
|
|
void FQuartzClock::Shutdown()
|
|
{
|
|
for (auto& PendingCommand : PendingCommands)
|
|
{
|
|
PendingCommand.Command->Cancel();
|
|
}
|
|
|
|
for (auto& PendingCommand : ClockAlteringPendingCommands)
|
|
{
|
|
PendingCommand.Command->Cancel();
|
|
}
|
|
|
|
PendingCommands.Reset();
|
|
ClockAlteringPendingCommands.Reset();
|
|
}
|
|
|
|
void FQuartzClock::LowResolutionTick(float InDeltaTimeSeconds)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(QuartzClock::Tick_LowRes);
|
|
Tick(static_cast<int32>(InDeltaTimeSeconds * Metronome.GetTickRate().GetSampleRate()));
|
|
}
|
|
|
|
void FQuartzClock::Tick(int32 InNumFramesUntilNextTick)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(QuartzClock::Tick);
|
|
|
|
if (!bIsRunning)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (TickDelayLengthInFrames >= InNumFramesUntilNextTick)
|
|
{
|
|
TickDelayLengthInFrames -= InNumFramesUntilNextTick;
|
|
return;
|
|
}
|
|
|
|
const int32 FramesOfLatency = (ThreadLatencyInMilliseconds / 1000) * Metronome.GetTickRate().GetSampleRate();
|
|
|
|
if (TickDelayLengthInFrames == 0)
|
|
{
|
|
TickInternal(InNumFramesUntilNextTick, ClockAlteringPendingCommands, FramesOfLatency); // (process things like BPM changes first)
|
|
TickInternal(InNumFramesUntilNextTick, PendingCommands, FramesOfLatency);
|
|
}
|
|
else
|
|
{
|
|
TickInternal(TickDelayLengthInFrames, ClockAlteringPendingCommands, FramesOfLatency);
|
|
TickInternal(TickDelayLengthInFrames, PendingCommands, FramesOfLatency);
|
|
|
|
TickInternal(InNumFramesUntilNextTick - TickDelayLengthInFrames, ClockAlteringPendingCommands, FramesOfLatency, TickDelayLengthInFrames);
|
|
TickInternal(InNumFramesUntilNextTick - TickDelayLengthInFrames, PendingCommands, FramesOfLatency, TickDelayLengthInFrames);
|
|
}
|
|
|
|
|
|
Metronome.Tick(InNumFramesUntilNextTick, FramesOfLatency);
|
|
}
|
|
|
|
void FQuartzClock::TickInternal(int32 InNumFramesUntilNextTick, TArray<PendingCommand>& CommandsToTick, int32 FramesOfLatency, int32 FramesOfDelay)
|
|
{
|
|
bool bHaveCommandsToRemove = false;
|
|
|
|
// Update all pending commands
|
|
for (PendingCommand& PendingCommand : CommandsToTick)
|
|
{
|
|
// Time to notify game thread?
|
|
if (PendingCommand.NumFramesUntilExec < FramesOfLatency)
|
|
{
|
|
PendingCommand.Command->AboutToStart();
|
|
}
|
|
|
|
// Time To execute?
|
|
if (PendingCommand.NumFramesUntilExec < InNumFramesUntilNextTick)
|
|
{
|
|
PendingCommand.Command->OnFinalCallback(PendingCommand.NumFramesUntilExec + FramesOfDelay);
|
|
PendingCommand.Command.Reset();
|
|
bHaveCommandsToRemove = true;
|
|
}
|
|
else // not yet executing
|
|
{
|
|
PendingCommand.NumFramesUntilExec -= InNumFramesUntilNextTick;
|
|
}
|
|
}
|
|
|
|
// clean up executed commands
|
|
if (bHaveCommandsToRemove)
|
|
{
|
|
for (int32 i = CommandsToTick.Num() - 1; i >= 0; --i)
|
|
{
|
|
if (!CommandsToTick[i].Command.IsValid())
|
|
{
|
|
CommandsToTick.RemoveAtSwap(i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FQuartzClock::SetSampleRate(float InNewSampleRate)
|
|
{
|
|
if (FMath::IsNearlyEqual(InNewSampleRate, Metronome.GetTickRate().GetSampleRate()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// update Tick Rate
|
|
Metronome.SetSampleRate(InNewSampleRate);
|
|
|
|
// TODO: update the deadlines of all our new events
|
|
}
|
|
|
|
bool FQuartzClock::IgnoresFlush()
|
|
{
|
|
return bIgnoresFlush;
|
|
}
|
|
|
|
bool FQuartzClock::DoesMatchSettings(const FQuartzClockSettings& InClockSettings) const
|
|
{
|
|
return Metronome.GetTimeSignature() == InClockSettings.TimeSignature;
|
|
}
|
|
|
|
void FQuartzClock::SubscribeToTimeDivision(MetronomeCommandQueuePtr InListenerQueue, EQuartzCommandQuantization InQuantizationBoundary)
|
|
{
|
|
Metronome.SubscribeToTimeDivision(InListenerQueue, InQuantizationBoundary);
|
|
}
|
|
|
|
void FQuartzClock::SubscribeToAllTimeDivisions(MetronomeCommandQueuePtr InListenerQueue)
|
|
{
|
|
Metronome.SubscribeToAllTimeDivisions(InListenerQueue);
|
|
}
|
|
|
|
void FQuartzClock::UnsubscribeFromTimeDivision(MetronomeCommandQueuePtr InListenerQueue, EQuartzCommandQuantization InQuantizationBoundary)
|
|
{
|
|
Metronome.UnsubscribeFromTimeDivision(InListenerQueue, InQuantizationBoundary);
|
|
}
|
|
|
|
void FQuartzClock::UnsubscribeFromAllTimeDivisions(MetronomeCommandQueuePtr InListenerQueue)
|
|
{
|
|
Metronome.UnsubscribeFromAllTimeDivisions(InListenerQueue);
|
|
}
|
|
|
|
void FQuartzClock::AddQuantizedCommand(FQuartzQuantizationBoundary InQuantizationBondary, TSharedPtr<IQuartzQuantizedCommand> InNewEvent)
|
|
{
|
|
if (!ensure(InNewEvent.IsValid()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!bIsRunning && InQuantizationBondary.bCancelCommandIfClockIsNotRunning)
|
|
{
|
|
InNewEvent->Cancel();
|
|
return;
|
|
}
|
|
|
|
if (InQuantizationBondary.bResetClockOnQueued)
|
|
{
|
|
Stop(/* clear pending events = */true);
|
|
Restart(!bIsRunning);
|
|
}
|
|
|
|
if (!bIsRunning && InQuantizationBondary.bResumeClockOnQueued)
|
|
{
|
|
Resume();
|
|
}
|
|
|
|
|
|
int32 FramesUntilExec = 0;
|
|
|
|
// if this is unquantized, execute immediately (even if the clock is paused)
|
|
if (InQuantizationBondary.Quantization == EQuartzCommandQuantization::None)
|
|
{
|
|
InNewEvent->AboutToStart();
|
|
InNewEvent->OnFinalCallback(0);
|
|
return;
|
|
}
|
|
|
|
// get number of frames until event (assuming we are at frame 0)
|
|
FramesUntilExec = FMath::RoundToInt(Metronome.GetFramesUntilBoundary(InQuantizationBondary)); // query metronome (round result to int)
|
|
FramesUntilExec = FMath::Max(0, InNewEvent->OverrideFramesUntilExec(FramesUntilExec)); // allow command to override the deadline (clamp result)
|
|
|
|
// if this is going to execute on the next tick, warn Game Thread Subscribers as soon as possible
|
|
if (FramesUntilExec == 0)
|
|
{
|
|
InNewEvent->AboutToStart();
|
|
}
|
|
|
|
// add to pending commands list, execute OnQueued()
|
|
if (InNewEvent->IsClockAltering())
|
|
{
|
|
ClockAlteringPendingCommands.Emplace(PendingCommand(MoveTemp(InNewEvent), FramesUntilExec));
|
|
}
|
|
else
|
|
{
|
|
PendingCommands.Emplace(PendingCommand(MoveTemp(InNewEvent), FramesUntilExec));
|
|
}
|
|
}
|
|
|
|
bool FQuartzClock::CancelQuantizedCommand(TSharedPtr<IQuartzQuantizedCommand> InCommandPtr)
|
|
{
|
|
if (InCommandPtr->IsClockAltering())
|
|
{
|
|
return CancelQuantizedCommandInternal(InCommandPtr, ClockAlteringPendingCommands);
|
|
}
|
|
|
|
return CancelQuantizedCommandInternal(InCommandPtr, PendingCommands);
|
|
}
|
|
|
|
bool FQuartzClock::HasPendingEvents() const
|
|
{
|
|
// if container has any events in it.
|
|
return (NumPendingEvents() > 0);
|
|
}
|
|
|
|
int32 FQuartzClock::NumPendingEvents() const
|
|
{
|
|
return PendingCommands.Num() + ClockAlteringPendingCommands.Num();
|
|
}
|
|
|
|
bool FQuartzClock::IsRunning()
|
|
{
|
|
return bIsRunning;
|
|
}
|
|
|
|
float FQuartzClock::GetDurationOfQuantizationTypeInSeconds(const EQuartzCommandQuantization& QuantizationType, float Multiplier)
|
|
{
|
|
// if this is unquantized, return 0
|
|
if (QuantizationType == EQuartzCommandQuantization::None)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
FQuartzClockTickRate TickRate = Metronome.GetTickRate();
|
|
|
|
// get number of frames until the relevant quantization event
|
|
double FramesUntilExec = TickRate.GetFramesPerDuration(QuantizationType);
|
|
|
|
//Translate frames to seconds
|
|
double SampleRate = TickRate.GetSampleRate();
|
|
|
|
if (!FMath::IsNearlyZero(SampleRate))
|
|
{
|
|
return (FramesUntilExec * Multiplier) / SampleRate;
|
|
}
|
|
else //Handle potential divide by zero
|
|
{
|
|
return INDEX_NONE;
|
|
}
|
|
}
|
|
|
|
FQuartzTransportTimeStamp FQuartzClock::GetCurrentTimestamp()
|
|
{
|
|
FQuartzTransportTimeStamp CurrentTimeStamp = Metronome.GetTimeStamp();
|
|
|
|
return CurrentTimeStamp;
|
|
}
|
|
|
|
float FQuartzClock::GetEstimatedRunTime()
|
|
{
|
|
return (float)Metronome.GetTimeSinceStart();
|
|
}
|
|
|
|
FMixerDevice* FQuartzClock::GetMixerDevice()
|
|
{
|
|
checkSlow(OwningClockManagerPtr);
|
|
if (OwningClockManagerPtr)
|
|
{
|
|
return OwningClockManagerPtr->GetMixerDevice();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
FMixerSourceManager* FQuartzClock::GetSourceManager()
|
|
{
|
|
FMixerDevice* MixerDevice = GetMixerDevice();
|
|
|
|
checkSlow(MixerDevice);
|
|
if (MixerDevice)
|
|
{
|
|
return MixerDevice->GetSourceManager();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
FQuartzClockManager* FQuartzClock::GetClockManager()
|
|
{
|
|
checkSlow(OwningClockManagerPtr);
|
|
if (OwningClockManagerPtr)
|
|
{
|
|
return OwningClockManagerPtr;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void FQuartzClock::ResetTransport()
|
|
{
|
|
Metronome.ResetTransport();
|
|
}
|
|
|
|
bool FQuartzClock::CancelQuantizedCommandInternal(TSharedPtr<IQuartzQuantizedCommand> InCommandPtr, TArray<PendingCommand>& CommandsToTick)
|
|
{
|
|
for (int32 i = CommandsToTick.Num() - 1; i >= 0; --i)
|
|
{
|
|
PendingCommand& PendingCommand = CommandsToTick[i];
|
|
|
|
if (PendingCommand.Command == InCommandPtr)
|
|
{
|
|
PendingCommand.Command->Cancel();
|
|
CommandsToTick.RemoveAtSwap(i);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
} // namespace Audio
|