#nullable enable using T3.Core.Animation; using T3.Core.DataTypes.DataSet; using T3CoreDataClip = T3.Core.DataTypes.DataSet.DataClip; namespace Lib.io.data; /// /// Samples one channel of a at a point in time: the value of /// the last event at or before the sample time. Complements SimulateIoData — instead /// of replaying events through the simulated IO bus, this reads channel values directly, /// which suits driving parameters from CC curves or recorded OSC without a MidiInput setup. /// /// /// The sample time defaults to the playhead mapped through the clip's /// (source seconds, the unit events are /// stored in). OverrideTime allows procedural access at any source time. /// The per-frame path is allocation-free: channel resolution is cached and invalidated by /// reference / string comparison; sampling is a binary search on the event list. /// [Guid("c0d57acb-29cc-489b-89b0-f016968f2f4c")] internal sealed class SampleFloatFromDataClip : Instance, IStatusProvider, ICustomDropdownHolder { [Output(Guid = "f29fc9be-8689-4894-a3e1-6b60fdbdae55", DirtyFlagTrigger = DirtyFlagTrigger.Animated)] public readonly Slot Result = new(); [Output(Guid = "25469a27-04e8-49bb-b685-241d9de90451", DirtyFlagTrigger = DirtyFlagTrigger.Animated)] public readonly Slot WasHit = new(); public SampleFloatFromDataClip() { Result.UpdateAction += Update; WasHit.UpdateAction += Update; } private void Update(EvaluationContext context) { var clip = Clip.GetValue(context); var channelPath = Channel.GetValue(context); var fallback = DefaultValue.GetValue(context); if (!DataClipSampling.TryResolveChannel(clip, channelPath, ref _resolvedChannel, ref _resolvedSet, ref _resolvedPath, out _errorMessageForStatus)) { Result.Value = fallback; WasHit.Value = false; return; } var time = DataClipSampling.GetSampleTime(context, clip!, UseTimeOverride.GetValue(context), OverrideTime.GetValue(context)); if (DataClipSampling.TrySampleLastValueAtTime(_resolvedChannel!, time, out var value)) { Result.Value = value; } else { Result.Value = fallback; } WasHit.Value = _hitDetector.Update(Playback.RunTimeInSecs, time, _resolvedChannel!); } #region channel dropdown string ICustomDropdownHolder.GetValueForInput(Guid inputId) => Channel.Value ?? string.Empty; IEnumerable ICustomDropdownHolder.GetOptionsForInput(Guid inputId) => DataClipSampling.GetChannelOptions(inputId, Channel.Id, Clip.Value); void ICustomDropdownHolder.HandleResultForInput(Guid inputId, string? selected, bool isAListItem) { if (selected != null) Channel.SetTypedInputValue(selected); } #endregion IStatusProvider.StatusLevel IStatusProvider.GetStatusLevel() => string.IsNullOrEmpty(_errorMessageForStatus) ? IStatusProvider.StatusLevel.Success : IStatusProvider.StatusLevel.Warning; string IStatusProvider.GetStatusMessage() => _errorMessageForStatus; [Input(Guid = "b69fbaf8-57f7-4b37-b23f-50e1aa0cd4d5")] public readonly InputSlot Clip = new(); [Input(Guid = "237ae57a-2aba-4a6f-9ed0-049635a1e04c")] public readonly InputSlot Channel = new(); [Input(Guid = "10066199-4e20-4f04-af17-5afe359de431")] public readonly InputSlot DefaultValue = new(); [Input(Guid = "61b5ef56-4a32-4026-a0c0-0b0c7b8ee1b1")] public readonly InputSlot OverrideTime = new(); [Input(Guid = "5c470e20-fe8e-461e-9333-05e82ff348f0")] public readonly InputSlot UseTimeOverride = new(); private DataChannel? _resolvedChannel; private DataSet? _resolvedSet; private string? _resolvedPath; private DataClipSampling.HitDetector _hitDetector; private string _errorMessageForStatus = string.Empty; }