using T3.Core.Animation; using T3.Core.Utils; namespace Lib.numbers.anim.time; [Guid("31ab98ec-5e79-4667-9a85-2fb168f41fa1")] internal sealed class AbletonLinkSync : Instance, IStatusProvider { [Output(Guid = "e1cfd42c-81fa-4820-91df-f1bad27b3a7f", DirtyFlagTrigger = DirtyFlagTrigger.Animated)] public readonly Slot Result = new(); [Output(Guid = "D6E453E6-1D3F-4765-A427-DD9967BFBC34", DirtyFlagTrigger = DirtyFlagTrigger.Animated)] public readonly Slot Tempo = new(); [Output(Guid = "DEAB643C-F608-4DA9-A1C2-298449D17BC0", DirtyFlagTrigger = DirtyFlagTrigger.Animated)] public readonly Slot IsConnected = new(); public AbletonLinkSync() { TryInitialize(); Result.UpdateAction += Update; Tempo.UpdateAction += Update; } private void Update(EvaluationContext context) { var isConnected = _nativeLinkInstance != IntPtr.Zero; IsConnected.Value = isConnected; if (!isConnected) return; if (MathUtils.WasTriggered(TriggerStartPlaying.GetValue(context), ref _triggerStart)) { StartPlaying(); TriggerStartPlaying.SetTypedInputValue(false); } if (MathUtils.WasTriggered(TriggerStopPlaying.GetValue(context), ref _triggerStop)) { StopPlaying(); TriggerStopPlaying.SetTypedInputValue(false); } var needsReconnected = false; if (MathUtils.WasTriggered(TriggerReconnect.GetValue(context), ref _triggerReconnected)) { needsReconnected = true; TriggerReconnect.SetTypedInputValue(false); } UpdateLink(out var beat, out var phase, out var tempo, out var quantum, out var time, out var peerCount); if (_startMeasure == 0) { _startMeasure = Math.Floor(beat / quantum) * quantum; _startMeasure = 0; } var pauseIfDisconnected = PauseIfDisconnected.GetValue(context); var pauseResults = pauseIfDisconnected && peerCount == 0; if (!pauseResults) { Tempo.Value = (float)tempo; Result.Value = OutputType.GetEnumValue(context) switch { ReturnTypes.Bars => (float)(beat / quantum - _startMeasure), ReturnTypes.Phase => (float)phase, ReturnTypes.Beats => (float)(beat - _startMeasure), ReturnTypes.Time => (float)(time / 1000), ReturnTypes.Quantum => (float)quantum, _ => 0 }; } if (peerCount != _peerCount) { Log.Debug("Number of Ableton Link Peers changed to " + peerCount); _peerCount = peerCount; } _lastErrorMessage = peerCount == 0 ? "No peers connected" : null; if (AutoConnect.GetValue(context) && peerCount == 0 && Playback.RunTimeInSecs - _lastReconnectTime > 3) { needsReconnected = true; } if (needsReconnected) { _lastReconnectTime = Playback.RunTimeInSecs; Destroy(); TryInitialize(); } Tempo.DirtyFlag.Clear(); Result.DirtyFlag.Clear(); } private double _startMeasure; private double _lastReconnectTime; private void TryInitialize() { lock (_lock) { if (_initialized) return; // Log.Debug("Initializing AbletonLink..."); try { _nativeLinkInstance = CreateAbletonLink(); Setup(InitialTempo); EnableStartStopSync(true); } catch (Exception e) { var message = "Failed to initialize AbletonLink. Probably can't find dll. Did you run install.bat? " + e.Message; Log.Warning(message); _lastErrorMessage = message; } _initialized = true; } } private void Destroy() { lock (_lock) { if (!_initialized) return; if (_nativeLinkInstance == IntPtr.Zero) return; // Log.Debug("Destroying Ableton Link..."); DestroyAbletonLink(_nativeLinkInstance); _nativeLinkInstance = IntPtr.Zero; _initialized = false; } } private static int _peerCount; [Input(Guid = "CD977DBC-3340-4542-8E0F-01BDE3882D6A", MappedType = typeof(ReturnTypes))] public readonly InputSlot OutputType = new(); [Input(Guid = "3DC1FC6B-D17F-4939-A1E6-6D3DB042AF36")] public readonly InputSlot TriggerStartPlaying = new(); [Input(Guid = "BF2CA9E8-6365-453F-B8C3-2428CCD9D23D")] public readonly InputSlot TriggerStopPlaying = new(); [Input(Guid = "F4E75E75-5113-4F22-A1DD-B897A5745F1D")] public readonly InputSlot TriggerReconnect = new(); [Input(Guid = "4DA6EBCC-0312-4846-88B0-A0682A59F97F")] public readonly InputSlot AutoConnect = new(); [Input(Guid = "ECF5D5AB-BB9F-4C48-9B33-8827E94DD286")] public readonly InputSlot PauseIfDisconnected = new(); #region use wrapper [DllImport("AbletonLinkDLL")] private static extern IntPtr CreateAbletonLink(); [DllImport("AbletonLinkDLL")] private static extern void DestroyAbletonLink(IntPtr ptr); protected override void Dispose(bool isDisposing) { if (!isDisposing) return; Destroy(); // Log.Debug("Disposing AbletonLink"); // if (_nativeLinkInstance != IntPtr.Zero) // { // DestroyAbletonLink(_nativeLinkInstance); // _nativeLinkInstance = IntPtr.Zero; // } } [DllImport("AbletonLinkDLL")] private static extern void setup(IntPtr ptr, double bpm); private static void Setup(double bpm) { setup(_nativeLinkInstance, bpm); } [DllImport("AbletonLinkDLL")] private static extern void setTempo(IntPtr ptr, double bpm); [DllImport("AbletonLinkDLL")] private static extern double tempo(IntPtr ptr); public double LinkTempo { get => tempo(_nativeLinkInstance); set => setTempo(_nativeLinkInstance, value); } [DllImport("AbletonLinkDLL")] private static extern void setQuantum(IntPtr ptr, double quantum); [DllImport("AbletonLinkDLL")] private static extern double quantum(IntPtr ptr); public double Quantum { get => quantum(_nativeLinkInstance); set => setQuantum(_nativeLinkInstance, value); } [DllImport("AbletonLinkDLL")] private static extern void forceBeatAtTime(IntPtr ptr, double beat); public void ForceBeatAtTime(double beat) { forceBeatAtTime(_nativeLinkInstance, beat); } [DllImport("AbletonLinkDLL")] private static extern void requestBeatAtTime(IntPtr ptr, double beat); public void RequestBeatAtTime(double beat) { requestBeatAtTime(_nativeLinkInstance, beat); } [DllImport("AbletonLinkDLL")] private static extern void enable(IntPtr ptr, bool bEnable); [DllImport("AbletonLinkDLL")] private static extern bool isEnabled(IntPtr ptr); public static bool Enabled { get => isEnabled(_nativeLinkInstance); set => enable(_nativeLinkInstance, value); } [DllImport("AbletonLinkDLL")] private static extern void enableStartStopSync(IntPtr ptr, bool bEnable); public static void EnableStartStopSync(bool enable) { enableStartStopSync(_nativeLinkInstance, enable); } [DllImport("AbletonLinkDLL")] private static extern void startPlaying(IntPtr ptr); public static void StartPlaying() { startPlaying(_nativeLinkInstance); } [DllImport("AbletonLinkDLL")] private static extern void stopPlaying(IntPtr ptr); public static void StopPlaying() { stopPlaying(_nativeLinkInstance); } [DllImport("AbletonLinkDLL")] private static extern bool isPlaying(IntPtr ptr); public bool IsPlaying => isPlaying(_nativeLinkInstance); [DllImport("AbletonLinkDLL")] private static extern int numPeers(IntPtr ptr); private int NumPeers => numPeers(_nativeLinkInstance); [DllImport("AbletonLinkDLL")] private static extern void update(IntPtr ptr, out double rbeat, out double rphase, out double rtempo, out double rquantum, out double rtime, out int rnumPeers); public static void UpdateLink(out double beat, out double phase, out double tempo, out double quantum, out double time, out int numPeers) { update(_nativeLinkInstance, out beat, out phase, out tempo, out quantum, out time, out numPeers); } #endregion private enum ReturnTypes { Bars, Phase, Beats, Time, Quantum, } // private static volatile AbletonLink singletonInstance; private static IntPtr _nativeLinkInstance = IntPtr.Zero; private bool _triggerStart; private bool _triggerStop; private const double InitialTempo = 120.0; private static bool _initialized; private readonly object _lock = new(); private bool _triggerReconnected; private string _lastErrorMessage = null; public IStatusProvider.StatusLevel GetStatusLevel() { return string.IsNullOrEmpty(_lastErrorMessage) ? IStatusProvider.StatusLevel.Success : IStatusProvider.StatusLevel.Warning; } public string GetStatusMessage() { return _lastErrorMessage; } }