Files
UnrealEngineUWP/Engine/Source/Runtime/AudioMixer/Private/Quartz/AudioMixerClock.cpp
bob tellez c7b8c96656 Merging CL#20037848
[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]
2022-05-04 03:16:13 -04:00

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