#nullable enable using System.Diagnostics.CodeAnalysis; using T3.Core.Animation; using T3.Core.Audio; using T3.Core.IO; using T3.Core.Operator; using T3.Core.Settings; using T3.Core.Resource; using T3.Editor.Gui.UiHelpers; using T3.Editor.UiModel.ProjectHandling; namespace T3.Editor.Gui.Interaction.Timing; public static class PlaybackUtils { public static BpmProvider BpmProvider = BpmProvider.Instance; public static TapProvider TapProvider = TapProvider.Instance; internal static void UpdatePlaybackAndSyncing() { var settings = FindCompositionSettings(out var audioComposition); WasapiAudioInput.StartFrame(settings); if (settings.Playback.AudioSource == CompositionSettings.AudioSources.ProjectSoundTrack) { // Register every clip in the composition's AudioClips list so the engine // can play multiple clips simultaneously. The engine's per-clip TimeRange // bound-check decides which clips are audible at the current playhead. var time = Playback.Current.TimeInSecs; foreach (var clip in settings.Playback.AudioClips) { if (string.IsNullOrEmpty(clip.AssetPath)) continue; AudioEngine.UseSoundtrackClip(clip.GetResourceHandle(audioComposition), time); } } // Op-backed audio clips ([AudioClip]) with AutoPlay register themselves here, independent of // the AudioSource mode. Coexists with the CompositionSettings layer clips above while the // op-backed audio-clip system is being migrated in. AudioClipCollector.RegisterAutoPlayClips(ProjectView.Focused?.CompositionInstance, Playback.Current.TimeInBars, Playback.Current.TimeInSecs); if (settings.Playback.AudioSource == CompositionSettings.AudioSources.ExternalDevice && settings.Playback.Syncing == CompositionSettings.SyncModes.Tapping) { Playback.Current = T3Ui.DefaultBeatTimingPlayback; if (Playback.Current.Settings is { Playback.Syncing: CompositionSettings.SyncModes.Tapping }) { if (TapProvider != null) { if(TapProvider.BeatTapTriggered) BeatTiming.TriggerSyncTap(); if (TapProvider.ResyncTriggered) BeatTiming.TriggerResyncMeasure(); } Playback.Current.Settings.Playback.Bpm = (float)Playback.Current.Bpm; // Process callback from [SetBpm] operator if (BpmProvider != null && BpmProvider.TryGetNewBpmRate(out var newBpmRate2)) { Log.Debug($" Setting new bpm rate {newBpmRate2}"); BeatTiming.SetBpmRate(newBpmRate2); } BeatTiming.Update(); } } else { Playback.Current = T3Ui.DefaultTimelinePlayback; } // Process callback from [SetBpm] operator if (BpmProvider != null && BpmProvider.TryGetNewBpmRate(out var newBpmRate)) { Log.Debug($" Applying {newBpmRate} BPM to settings"); settings.Playback.Bpm = newBpmRate; } Playback.Current.Bpm = settings.Playback.Bpm; Playback.Current.Update(UserSettings.Config.EnableIdleMotion); Playback.Current.Settings = settings; } private static CompositionSettings FindCompositionSettings(out IResourceConsumer? owner) { var composition = ProjectView.Focused?.CompositionInstance; if (composition != null && FindCompositionSettingsForInstance(composition, out var instance, out var settings)) { owner = instance; return settings; } owner = null; return _defaultCompositionSettings; } /// /// Scans the current composition path and its parents for a soundtrack /// internal static bool TryFindingSoundtrack([NotNullWhen(true)] out AudioClipResourceHandle? soundtrack, out IResourceConsumer? composition) { var settings = FindCompositionSettings(out composition); if (composition != null) return settings.TryGetMainSoundtrack(composition, out soundtrack); soundtrack = null; return false; } /// /// Try to find project settings for an instance by walking up the parent chain. /// /// false if falling back to default settings internal static bool FindCompositionSettingsForInstance(Instance startInstance, out Instance? instanceWithSettings, out CompositionSettings settings) { instanceWithSettings = startInstance; while (true) { if (instanceWithSettings == null) { settings = _defaultCompositionSettings; instanceWithSettings = null; return false; } settings = instanceWithSettings.Symbol.CompositionSettings; if (settings.Enabled) { return true; } instanceWithSettings = instanceWithSettings.Parent; } } private static readonly CompositionSettings _defaultCompositionSettings = new() { Enabled = false, Playback = new CompositionSettings.PlaybackConfig { Bpm = 120, AudioSource = CompositionSettings.AudioSources.ProjectSoundTrack, Syncing = CompositionSettings.SyncModes.Timeline, AudioInputDeviceName = string.Empty, AudioGainFactor = 1, AudioDecayFactor = 1, } }; }