#nullable enable using System.Diagnostics.CodeAnalysis; using ImGuiNET; using SharpDX.Direct3D11; using T3.Core.Animation; using T3.Core.Audio; using T3.Core.DataTypes; using T3.Core.DataTypes.Vector; using T3.Core.Operator; using T3.Core.Operator.Interfaces; using T3.Core.Resource; using T3.Editor.Gui.Audio; using T3.Editor.Gui.Styling; using Texture2D = T3.Core.DataTypes.Texture2D; namespace T3.Editor.Gui.Windows.TimeLine.TimeClips; /// /// Draws the waveform image inside an [AudioClip] op-clip body. Mirrors /// : calls for /// every op-clip, and this no-ops unless the op is an with a file set. /// internal static class AudioClipBodyRenderer { public static void TryDraw(Instance instance, TimeClip timeClip, Vector2 bodyMin, Vector2 bodyMax, ImDrawListPtr drawList) { // Op-type gate: only [AudioClip] (and only when it exposes a file path). if (instance is not IAudioClipProvider provider) return; if (instance is not IDescriptiveFilename descriptive) return; var path = descriptive.SourcePathSlot.TypedInputValue.Value; if (string.IsNullOrEmpty(path)) return; var bodyWidth = bodyMax.X - bodyMin.X; var bodyHeight = bodyMax.Y - bodyMin.Y; if (bodyWidth < 3 || bodyHeight < 4) return; if (!TryGetWaveformSrv(path, instance, out var srv)) return; drawList.PushClipRect(bodyMin, bodyMax, true); // The body maps linearly to the clip's source window [SourceRange.Start, SourceRange.End] // (converted to seconds via BPM); the waveform image spans the whole file [0, length]. Draw // the image only across the body sub-rect where file content actually exists — so a clip // longer than its source isn't stretched, and a trimmed clip shows just its used slice. // File length comes from the played clip when available, else an async probe (so the crop // works immediately, not only after the clip plays once). Full-stretch fallback until known. var lengthSecs = provider.SourceLengthInSeconds; if (lengthSecs <= 0.0001) AudioClipDurationCache.TryGetDurationSecs(path, instance, out lengthSecs); var playback = Playback.Current; var drawn = false; if (lengthSecs > 0.0001 && playback != null) { var sourceStartSecs = playback.SecondsFromBars(timeClip.SourceRange.Start); var sourceEndSecs = playback.SecondsFromBars(timeClip.SourceRange.End); var sourceSpanSecs = sourceEndSecs - sourceStartSecs; var visibleStartSecs = Math.Max(sourceStartSecs, 0.0); var visibleEndSecs = Math.Min(sourceEndSecs, lengthSecs); if (sourceSpanSecs > 0.0001 && visibleEndSecs > visibleStartSecs) { var x0 = (float)((visibleStartSecs - sourceStartSecs) / sourceSpanSecs); var x1 = (float)((visibleEndSecs - sourceStartSecs) / sourceSpanSecs); var u0 = (float)(visibleStartSecs / lengthSecs); var u1 = (float)(visibleEndSecs / lengthSecs); drawList.AddImage((IntPtr)srv, new Vector2(bodyMin.X + x0 * bodyWidth + 1, bodyMin.Y + 1), new Vector2(bodyMin.X + x1 * bodyWidth - 1, bodyMax.Y - 1), new Vector2(u0, 0), new Vector2(u1, 1), UiColors.ForegroundFull.Fade(1f)); drawn = true; } } if (!drawn) { drawList.AddImage((IntPtr)srv, bodyMin + new Vector2(1, 1), bodyMax - new Vector2(1, 1), Vector2.Zero, Vector2.One, UiColors.ForegroundFull.Fade(0.5f)); } drawList.PopClipRect(); } /// /// (Cached) shader-resource view of the file's waveform image. First call per asset kicks off a /// background generation via ; later calls return the loaded SRV. /// private static bool TryGetWaveformSrv(string assetPath, Instance owner, [NotNullWhen(true)] out ShaderResourceView? srv) { srv = null; var handle = new AudioClipResourceHandle(new TimelineAudioClip { AssetPath = assetPath }, owner); if (!AudioImageFactory.TryGetOrCreateImagePathForClip(handle, out var imagePath)) return false; if (_imageCache.TryGetValue(imagePath, out var entry)) { srv = entry.Srv; return srv is { IsDisposed: false }; } var resource = ResourceManager.CreateTextureResource(imagePath, owner); ShaderResourceView? newSrv = null; resource.Value?.CreateShaderResourceView(ref newSrv, imagePath); _imageCache[imagePath] = new CachedImage(resource, newSrv); srv = newSrv; return srv is { IsDisposed: false }; } private readonly record struct CachedImage(Resource Resource, ShaderResourceView? Srv); // Lifetime: entries live for the editor session (same as the waveform cache in the old item renderer). private static readonly Dictionary _imageCache = new(); }