using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace MuzikaGromche.Via; interface ILightshow { /// /// Override or reset brightness (value) for flickering animation. /// void SetBrightnessOverride(byte? brightness); void SetColor(byte hue, byte saturation, byte value); } class Animations { private static CancellationTokenSource? cts; public static void Flicker(ILightshow lightshow) { if (cts != null) { cts.Cancel(); } cts = new(); _ = Task.Run(async () => await FlickerAsync(lightshow, cts.Token), cts.Token); } static async ValueTask FlickerAsync(ILightshow lightshow, CancellationToken cancellationToken) { await foreach (var on in FlickerAnimationAsync()) { if (cancellationToken.IsCancellationRequested) { break; } byte brightness = on ? (byte)0xFF : (byte)0x00; lightshow.SetBrightnessOverride(brightness); } lightshow.SetBrightnessOverride(null); } // Timestamps (in frames of 60 fps) of state switches, starting with 0 => ON. private static readonly int[] MansionWallLampFlicker = [ 0, 4, 8, 26, 28, 32, 37, 41, 42, 58, 60, 71, ]; public static async IAsyncEnumerable FlickerAnimationAsync() { bool lastState = false; int lastFrame = 0; const int initialMillisecondsDelay = 4 * 1000 / 60; await Task.Delay(initialMillisecondsDelay); foreach (int frame in MansionWallLampFlicker) { // convert difference in frames into milliseconds int millisecondsDelay = (int)((frame - lastFrame) / 60f * 1000f); lastState = !lastState; lastFrame = frame; await Task.Delay(millisecondsDelay); yield return lastState; } } }