Files
2026-07-13 13:13:17 +08:00

307 lines
9.6 KiB
C#

using T3.Core.Animation;
using T3.Core.Utils;
namespace Lib.numbers.anim.time;
[Guid("31ab98ec-5e79-4667-9a85-2fb168f41fa1")]
internal sealed class AbletonLinkSync : Instance<AbletonLinkSync>, IStatusProvider
{
[Output(Guid = "e1cfd42c-81fa-4820-91df-f1bad27b3a7f", DirtyFlagTrigger = DirtyFlagTrigger.Animated)]
public readonly Slot<float> Result = new();
[Output(Guid = "D6E453E6-1D3F-4765-A427-DD9967BFBC34", DirtyFlagTrigger = DirtyFlagTrigger.Animated)]
public readonly Slot<float> Tempo = new();
[Output(Guid = "DEAB643C-F608-4DA9-A1C2-298449D17BC0", DirtyFlagTrigger = DirtyFlagTrigger.Animated)]
public readonly Slot<bool> 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<ReturnTypes>(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<int> OutputType = new();
[Input(Guid = "3DC1FC6B-D17F-4939-A1E6-6D3DB042AF36")]
public readonly InputSlot<bool> TriggerStartPlaying = new();
[Input(Guid = "BF2CA9E8-6365-453F-B8C3-2428CCD9D23D")]
public readonly InputSlot<bool> TriggerStopPlaying = new();
[Input(Guid = "F4E75E75-5113-4F22-A1DD-B897A5745F1D")]
public readonly InputSlot<bool> TriggerReconnect = new();
[Input(Guid = "4DA6EBCC-0312-4846-88B0-A0682A59F97F")]
public readonly InputSlot<bool> AutoConnect = new();
[Input(Guid = "ECF5D5AB-BB9F-4C48-9B33-8827E94DD286")]
public readonly InputSlot<bool> 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;
}
}