IMovieSceneCustomClockSource - Level Sequencer Time Dilation differs between Auditioning and PIE

I have a custom clock source (IMovieSceneCustomClockSource) that we are using to maintain audio/visual sync.

There is some custom code to handle time dilated level sequences. It uses the “InPlayRate” value passed in from both IMovieSceneCustomClockSource::OnTick() and IMovieSceneCustomClockSource::OnRequestCurrentTime() to detect when time dilation is active, and modulate our external clock accordingly

Here is my code:

The play rate is getting toggled between 1.0f and the time dilated keyframe value when in PIE.

The issue seems to be that during PIE, MovieSceneSlomoSystem.cpp calls FSlomoUtil::ApplySlomo(PlaybackContext, TimeDilation);

This function early exits if we are in editor, but it overwrites WorldSettings->CinematicTimeDilation with a value of 1.f every other frame.

Given that IMovieSceneCustomClockSource is used only in the context of a level sequence - I want to use a value that only grabs time dilation relevant to the level sequencer tracks themselves. Is there any way to do this so I can ensure consistency between editor and PIE?

Here is a printout of values when auditioning in editor:

LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 1.000000, Cine Time Dilation: 1.000000, EffectiveTimeDilation: 1.000000
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 0.987175, Cine Time Dilation: 0.987175, EffectiveTimeDilation: 0.987175
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 0.957764, Cine Time Dilation: 0.957764, EffectiveTimeDilation: 0.957764
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 0.913971, Cine Time Dilation: 0.913971, EffectiveTimeDilation: 0.913971
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 0.863675, Cine Time Dilation: 0.863675, EffectiveTimeDilation: 0.863675
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 0.808126, Cine Time Dilation: 0.808126, EffectiveTimeDilation: 0.808126
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 0.752151, Cine Time Dilation: 0.752151, EffectiveTimeDilation: 0.752151
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 0.692744, Cine Time Dilation: 0.692744, EffectiveTimeDilation: 0.692744
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 0.640594, Cine Time Dilation: 0.640594, EffectiveTimeDilation: 0.640594
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 0.588514, Cine Time Dilation: 0.588514, EffectiveTimeDilation: 0.588514
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 0.542823, Cine Time Dilation: 0.542823, EffectiveTimeDilation: 0.542823

vs when printing the same values when calling this cinematic in PIE:

LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 1.000000, Cine Time Dilation: 1.000000, EffectiveTimeDilation: 1.000000
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 0.998525, Cine Time Dilation: 0.998525, EffectiveTimeDilation: 0.998525
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 1.000000, Cine Time Dilation: 1.000000, EffectiveTimeDilation: 1.000000
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 0.951400, Cine Time Dilation: 0.951400, EffectiveTimeDilation: 0.951400
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 1.000000, Cine Time Dilation: 1.000000, EffectiveTimeDilation: 1.000000
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 0.875000, Cine Time Dilation: 0.875000, EffectiveTimeDilation: 0.875000
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 1.000000, Cine Time Dilation: 1.000000, EffectiveTimeDilation: 1.000000
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 0.774600, Cine Time Dilation: 0.774600, EffectiveTimeDilation: 0.774600
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 1.000000, Cine Time Dilation: 1.000000, EffectiveTimeDilation: 1.000000
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 0.600000, Cine Time Dilation: 0.600000, EffectiveTimeDilation: 0.600000
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 1.000000, Cine Time Dilation: 1.000000, EffectiveTimeDilation: 1.000000
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 0.481600, Cine Time Dilation: 0.481600, EffectiveTimeDilation: 0.481600

Steps to Reproduce

  1. Create a IMovieSceneCustomClockSource Actor
  2. Add this clock source to your level sequence
  3. Create a time dilation track on this level sequence and keyframe an interpolation between 1 and 0.5
  4. Print out the “InPlayRate” value from IMovieSceneCustomClockSource::OnRequestCurrentTime() during auditioning in Editor and PIE
  5. Notice that these values are consistent with keyframing when auditioning in editor, but are throttled in PIE
  6. You can also print out WorldSettings->CinematicTimeDilation and observe the same issue

Here is my implementation of IMovieSceneCustomClockSource

void AMyCustomClockSource::OnTick(float DeltaSeconds, float InPlayRate)
{
	// Once we exit time dilation, capture the difference between audio and UE clock
	if (InPlayRate == 1.f && EnteredTimeDilation == true)	
		TimeDiffPostDilation = FMath::Abs(AudioTimeInSecs - (CurrentDilatedTime + ScaledDeltaSecs));

	// If time dilation is active, switch to tick-based tracking
	if (InPlayRate != 1.f)
	{
		if (!EnteredTimeDilation)
			CurrentDilatedTime	 = (AudioTimeInSecs - TimeDiffPostDilation);

		EnteredTimeDilation = true;
		ScaledDeltaSecs			+= (DeltaSeconds * InPlayRate);
	}
	else
	{
		EnteredTimeDilation = false;
		CurrentDilatedTime = 0.f;
		ScaledDeltaSecs = 0.f;
	}
}

FFrameTime AScreamCustomClockSource::OnRequestCurrentTime(const FQualifiedFrameTime& InCurrentTime, float InPlayRate)
{
	AudioTimeInSecs = AudioSystem::GetSoundPlaybackTimeInSeconds(SoundHandle);

	return CineTimeDilation == 1.f ?
		  (AudioTimeInSecs  - TimeDiffPostDilation)			* InCurrentTime.Rate:
		  (CurrentDilatedTime + ScaledDeltaSecs)				* InCurrentTime.Rate;
}




Hey there,

To validate your test, are you doing this when you have only the instance of the sequence playing in PIE and not an instance of your level sequencer open in the sequencer editor window?

Dustin

Hi [mention removed]​ - I’m calling the level sequence like so:

[Image Removed]

This sequence is not active in the sequence editor window when I call it.

Hi [mention removed]​ - I’ve been digging around in MovieSceneSlomoSystem.cpp to figure out why FSlomoUtil::ApplySlomo is overwriting the level sequencer play rate every other frame during PIE.

I am totally stuck here. Would you be able to help me?

Hey Steven. The input parameter to FMovieSceneTimeController::Tick is the PlayRate that is applied to the actual level sequence player (ie, UMovieSceneSequencePlayer::GetPlayRate) in PIE or in Game and has nothing to do with slomo. In a PIE or Game world the cinematic dilation is applied to all actor ticks (including the deltatime specified in UMovieSceneSequencePlayer::Update) so it is handled automatically without any additional play rate scaling required.

For editor worlds though, and by extension the Sequencer UI, there is no active ticking, and as such there is no CinematicTimeDilation. For this reason the Sequencer UI will factor in CinematicTimeDilation manually to its own PlayRate.

If you are using an Actor as the time controller then you can check its GetWorld()->WorldType to see if it is an Editor world to determine whether the play rate will have already accommodated the CinematicTimeDilation or not. For most Actor-based clocks we would normally use the actual Actor Tick function to progress the clock which would avoid needing to ever consider time dilation separately.

As for the time dilation being 1.0 in every other frame, I would check that you are not looking at 2 different clocks there: if you are running a dedicated server as part of PIE, or if the Sequencer UI is still open you may well be seeing 2 different clocks printing different values.

[mention removed]​ - thank you for the explanation! Apologies for the delayed response, I’ve been out sick.

We are using our external audio system as the clock source, which works great.

NetMode is “Standalone” and “Run under One Process” is checked - this game is a single player non-networked game, so the editor is not running any client/server logic. GetLocalRole() consistently returns ROLE_AUTHORITY for both PIE and editor auditioning.

The sequence window is not open while running in PIE. There does not seem to be any editor configuration that is causing this to happen.

I have a Level Sequence set up with Time dilation keyframed, as you can see in the attached screenshot.

[Image Removed]Here I am printing out “TimeDilation” from FSlomoUtil::ApplySlomo() as well as “InPlayRate” from MyCustomClock::OnRequestCurrentTime() - From Auditioning in the Sequencer. PlayRate and TimeDilation are consistent with each other.

LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 1.000000
LogTemp: Warning: XX: ApplySlomo: TimeDilation: 1.000000
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 1.000000
LogTemp: Warning: XX: ApplySlomo: TimeDilation: 0.994200
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 0.994200
LogTemp: Warning: XX: ApplySlomo: TimeDilation: 0.965016
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 0.965016
LogTemp: Warning: XX: ApplySlomo: TimeDilation: 0.915659
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 0.915659
LogTemp: Warning: XX: ApplySlomo: TimeDilation: 0.856737
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 0.856737
LogTemp: Warning: XX: ApplySlomo: TimeDilation: 0.797154
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 0.797154
LogTemp: Warning: XX: ApplySlomo: TimeDilation: 0.721761
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 0.721761
LogTemp: Warning: XX: ApplySlomo: TimeDilation: 0.655037
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 0.655037
LogTemp: Warning: XX: ApplySlomo: TimeDilation: 0.588296

When I call the same Level sequence (with the same custom clock actor in my level), from PIE I can see that FSlomoUtil::ApplySlomo() is applying a time dilation of 1.f every other frame.

LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 1.000000
LogTemp: Warning: XX: ApplySlomo: TimeDilation: 0.994200
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 0.994200
LogTemp: Warning: XX: ApplySlomo: TimeDilation: 1.000000
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 1.000000
LogTemp: Warning: XX: ApplySlomo: TimeDilation: 0.851775
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 0.851775
LogTemp: Warning: XX: ApplySlomo: TimeDilation: 1.000000
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 1.000000
LogTemp: Warning: XX: ApplySlomo: TimeDilation: 0.600000
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 0.600000
LogTemp: Warning: XX: ApplySlomo: TimeDilation: 1.000000
LogTemp: Warning: XXX: OnRequestCurrentTime() - PlayRate: 1.000000
LogTemp: Warning: XX: ApplySlomo: TimeDilation: 0.348225

This throttling is present in UMovieSceneSequencePlayer::Update(), where PlayRate *= World->GetWorldSettings()->GetEffectiveTimeDilation()

My custom clock needs to grab the Time Dilation value that is keyframed on the level sequencer track (as shown in the screenshot above). Is there any reliable way to get that value?

Your help is much appreciated, thank you so much for your time!

Bear in mind that time-dilation isn’t a perfect science and will always be 1 frame behind due to the nature of it feeding the CinematicTimeDilation that won’t be read until next frame. With that in mind, it might be enough to just use GetWorld()->GetWorldSettings()->CinematicTimeDilation directly if it’s ok to be a frame behind. If not, you will have to look at the UMovieScene data directly and root out the slomo track’s section and channel, then call FloatCurve.Evaluate to find the value at that time.

It’s also worth looking into Time Warp tracks instead of using slomo, if you want to only affect the sequence playrate and not the global tick rate for all actors.

[mention removed]​ - I’d like to make it clear - this is not an issue of the Time Dilation value being a frame behind.

The problem here is that the “InPlayRate” parameter that Unreal passes into IMovieSceneCustomClockSource::OnRequestCurrentTime() is being set back to 1.f every other frame, during an active time dilation track in the level sequencer. This behavior does not occur when testing the level sequence in the editor, which seems like a bug.

I’ve finally got it working by locating the SlomoTrack Section and querying the Time Dilation keyframes from it manually. This required some code to only modify the DeltaSeconds on my CustomClock when we are in an Editor World (since the DeltaTime is automatically scaled by Time Dilation when playing the game as you said above).