193 lines
8.2 KiB
C#
193 lines
8.2 KiB
C#
#nullable enable
|
|
using T3.Core.Animation;
|
|
using T3.Core.Audio;
|
|
using T3.Core.Utils;
|
|
using T3.Editor.Gui.Interaction.Timing;
|
|
|
|
namespace T3.Editor.Gui.Windows.RenderExport;
|
|
|
|
internal static class RenderTiming
|
|
{
|
|
public struct Runtime
|
|
{
|
|
public double TimingOverhang; // leftover time due to ms granularity
|
|
public bool AudioRecording;
|
|
}
|
|
|
|
public static void ApplyTimeRange( RenderSettings renderSettings)
|
|
{
|
|
switch (renderSettings.TimeRange)
|
|
{
|
|
case RenderSettings.TimeRanges.Custom:
|
|
case RenderSettings.TimeRanges.Continuous:
|
|
// No end to derive — Continuous runs until the user stops it; Custom keeps the user's values.
|
|
break;
|
|
|
|
case RenderSettings.TimeRanges.Loop:
|
|
{
|
|
var playback = Playback.Current;
|
|
var startInSeconds = playback.SecondsFromBars(playback.LoopRange.Start);
|
|
var endInSeconds = playback.SecondsFromBars(playback.LoopRange.End);
|
|
renderSettings.StartInBars = (float)SecondsToReferenceTime(startInSeconds, renderSettings.TimeReference, renderSettings.FrameRate);
|
|
renderSettings.EndInBars = (float)SecondsToReferenceTime(endInSeconds, renderSettings.TimeReference, renderSettings.FrameRate);
|
|
break;
|
|
}
|
|
|
|
case RenderSettings.TimeRanges.Soundtrack:
|
|
{
|
|
if (PlaybackUtils.TryFindingSoundtrack(out var handle, out _))
|
|
{
|
|
var playback = Playback.Current;
|
|
var clip = handle.Clip;
|
|
renderSettings.StartInBars = (float)SecondsToReferenceTime(playback.SecondsFromBars(clip.TimeRange.Start), renderSettings.TimeReference, renderSettings.FrameRate);
|
|
if (clip.TimeRange.End > clip.TimeRange.Start)
|
|
renderSettings.EndInBars = (float)SecondsToReferenceTime(playback.SecondsFromBars(clip.TimeRange.End), renderSettings.TimeReference, renderSettings.FrameRate);
|
|
else
|
|
renderSettings.EndInBars = (float)SecondsToReferenceTime(clip.LengthInSeconds, renderSettings.TimeReference, renderSettings.FrameRate);
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
throw new ArgumentOutOfRangeException(nameof(renderSettings.TimeRange), renderSettings.TimeRange, null);
|
|
}
|
|
}
|
|
|
|
public static double ConvertReferenceTime(double time, RenderSettings.TimeReferences oldRef, RenderSettings.TimeReferences newRef, float fps)
|
|
{
|
|
if (oldRef == newRef)
|
|
return time;
|
|
|
|
var seconds = ReferenceTimeToSeconds(time, oldRef, fps);
|
|
return SecondsToReferenceTime(seconds, newRef, fps);
|
|
}
|
|
|
|
public static double ConvertFps(double time, double oldFps, double newFps)
|
|
{
|
|
if (oldFps == 0 || newFps == 0) return time;
|
|
return time / oldFps * newFps;
|
|
}
|
|
|
|
public static double ReferenceTimeToSeconds(double time, RenderSettings.TimeReferences references, float fps)
|
|
{
|
|
var playback = Playback.Current;
|
|
switch (references)
|
|
{
|
|
case RenderSettings.TimeReferences.Bars:
|
|
return playback.SecondsFromBars(time);
|
|
case RenderSettings.TimeReferences.Seconds:
|
|
return time;
|
|
case RenderSettings.TimeReferences.Frames:
|
|
return fps != 0 ? time / fps : time / 60.0;
|
|
default:
|
|
return time;
|
|
}
|
|
}
|
|
|
|
public static double SecondsToReferenceTime(double seconds, RenderSettings.TimeReferences references, float fps)
|
|
{
|
|
var playback = Playback.Current;
|
|
switch (references)
|
|
{
|
|
case RenderSettings.TimeReferences.Bars:
|
|
return playback.BarsFromSeconds(seconds);
|
|
case RenderSettings.TimeReferences.Seconds:
|
|
return seconds;
|
|
case RenderSettings.TimeReferences.Frames:
|
|
return fps != 0 ? seconds * fps : seconds * 60.0;
|
|
default:
|
|
return seconds;
|
|
}
|
|
}
|
|
|
|
public static int ComputeFrameCount(in RenderSettings s)
|
|
{
|
|
var start = ReferenceTimeToSeconds(s.StartInBars, s.TimeReference, s.FrameRate);
|
|
var end = ReferenceTimeToSeconds(s.EndInBars, s.TimeReference, s.FrameRate);
|
|
return (int)Math.Round((end - start) * s.FrameRate);
|
|
}
|
|
|
|
public static void SetPlaybackTimeForFrame(RenderProcess.ExportSession session)
|
|
{
|
|
// get playback settings
|
|
var composition = T3.Editor.UiModel.ProjectHandling.ProjectView.Focused?.CompositionInstance;
|
|
if (composition == null)
|
|
{
|
|
Log.Warning("Can't find focused composition instance.");
|
|
return;
|
|
}
|
|
|
|
PlaybackUtils.FindCompositionSettingsForInstance(composition, out var instanceWithSettings, out var projectSettings);
|
|
|
|
// change global settings before calculating times
|
|
Playback.Current.Bpm = projectSettings.Playback.Bpm;
|
|
Playback.Current.PlaybackSpeed = 0.0;
|
|
Playback.Current.Settings = projectSettings;
|
|
Playback.Current.FrameSpeedFactor = session.Settings.FrameRate / 60.0f;
|
|
|
|
// time range
|
|
var startSecs = ReferenceTimeToSeconds(session.Settings.StartInBars, session.Settings.TimeReference, session.Settings.FrameRate);
|
|
|
|
var oldSecs = Playback.Current.TimeInSecs;
|
|
|
|
if (session.Settings.TimeRange == RenderSettings.TimeRanges.Continuous)
|
|
{
|
|
// Open-ended: advance steadily from the start, there is no end to interpolate toward.
|
|
Playback.Current.TimeInSecs = startSecs + session.FrameIndex / session.Settings.FrameRate;
|
|
}
|
|
else
|
|
{
|
|
var endSecs = startSecs + Math.Max(session.FrameCount - 1, 0) / session.Settings.FrameRate;
|
|
var progress = session.FrameCount <= 1 ? 0.0 : session.FrameIndex / (double)(session.FrameCount - 1);
|
|
Playback.Current.TimeInSecs = MathUtils.Lerp(startSecs, endSecs, progress);
|
|
}
|
|
var adaptedDelta = Math.Max(Playback.Current.TimeInSecs - oldSecs + session.Runtime.TimingOverhang, 0.0);
|
|
|
|
// audio clip for preview
|
|
if (projectSettings.TryGetMainSoundtrack(instanceWithSettings, out var soundtrack))
|
|
AudioEngine.UseSoundtrackClip(soundtrack, Playback.Current.TimeInSecs);
|
|
|
|
if (!session.Runtime.AudioRecording)
|
|
{
|
|
session.Runtime.TimingOverhang = 0.0;
|
|
adaptedDelta = 1.0 / session.Settings.FrameRate;
|
|
|
|
Playback.Current.IsRenderingToFile = true;
|
|
Playback.Current.PlaybackSpeed = 1.0;
|
|
|
|
AudioRendering.PrepareRecording(Playback.Current, session.Settings.FrameRate);
|
|
|
|
var requestedEndSecs = ReferenceTimeToSeconds(session.Settings.EndInBars, session.Settings.TimeReference, session.Settings.FrameRate);
|
|
var actualEndSecs = startSecs + session.FrameCount / session.Settings.FrameRate;
|
|
|
|
Log.Gated.VideoRender($"Requested recording from {startSecs:0.0000} to {requestedEndSecs:0.0000} seconds");
|
|
Log.Gated.VideoRender($"Actually recording from {startSecs:0.0000} to {actualEndSecs:0.0000} seconds due to frame raster");
|
|
Log.Gated.VideoRender($"Using {Playback.Current.Bpm} bpm");
|
|
|
|
session.Runtime.AudioRecording = true;
|
|
}
|
|
|
|
// update audio engine (respect looping etc.)
|
|
Playback.Current.Update();
|
|
|
|
var bufferLengthMs = (int)Math.Floor(1000.0 * adaptedDelta);
|
|
session.Runtime.TimingOverhang = adaptedDelta - bufferLengthMs / 1000.0;
|
|
session.Runtime.TimingOverhang = Math.Max(session.Runtime.TimingOverhang, 0.0);
|
|
|
|
AudioEngine.CompleteFrame(Playback.Current, bufferLengthMs / 1000.0);
|
|
}
|
|
|
|
public static void ReleasePlaybackTime(ref RenderSettings s, ref Runtime rt)
|
|
{
|
|
AudioRendering.EndRecording(Playback.Current, s.FrameRate);
|
|
|
|
Playback.Current.TimeInSecs = ReferenceTimeToSeconds(s.EndInBars, s.TimeReference, s.FrameRate);
|
|
Playback.Current.IsRenderingToFile = false;
|
|
Playback.Current.PlaybackSpeed = 0.0;
|
|
Playback.Current.FrameSpeedFactor = 1.0; // could use actual display frame rate
|
|
Playback.Current.Update();
|
|
|
|
rt.AudioRecording = false;
|
|
rt.TimingOverhang = 0.0;
|
|
}
|
|
} |