using System.Collections.Concurrent; namespace Substation; public class State { public readonly object Lock = new(); public bool DirtyA, DirtyB; public int DesiredA, DesiredB; public byte[]? LoopFreqA, LoopIntA; public byte[]? LoopFreqB, LoopIntB; public readonly ConcurrentQueue StreamA = new(); public readonly ConcurrentQueue StreamB = new(); public int ActualA, ActualB; public int LastSentA, LastSentB; public int LastIntensityA, LastIntensityB; public byte[]? LastFreqA, LastIntA, LastFreqB, LastIntB; public bool LastActiveA, LastActiveB; public int MaxA = 200; public int MaxB = 200; public LimitMode LimitModeA = LimitMode.Clamp; public LimitMode LimitModeB = LimitMode.Clamp; public bool SwapChannels; public void SetLimitA(int max, LimitMode mode) { lock (Lock) { MaxA = Math.Clamp(max, 0, 200); LimitModeA = mode; DirtyA = true; } } public void SetLimitB(int max, LimitMode mode) { lock (Lock) { MaxB = Math.Clamp(max, 0, 200); LimitModeB = mode; DirtyB = true; } } public bool IsSignaling { get { lock (Lock) { bool hasPattern = LoopFreqA != null || LoopFreqB != null || !StreamA.IsEmpty || !StreamB.IsEmpty; bool hasStrength = DesiredA > 0 || DesiredB > 0; return hasPattern && hasStrength; } } } public void SetStrength(char ch, int value) { lock (Lock) { var clamped = Math.Clamp(value, 0, 200); if (ch == 'A') { DesiredA = clamped; DirtyA = true; } else { DesiredB = clamped; DirtyB = true; } } } public void SetLoop(char ch, byte[] freq, byte[] intensity) { lock (Lock) { if (ch == 'A') { LoopFreqA = freq; LoopIntA = intensity; } else { LoopFreqB = freq; LoopIntB = intensity; } } } public void EnqueueStream(char ch, IEnumerable frames) { var queue = ch == 'A' ? StreamA : StreamB; foreach (var f in frames) queue.Enqueue(f); } public void Stop(char ch) { lock (Lock) { if (ch == 'A') { LoopFreqA = null; LoopIntA = null; } else { LoopFreqB = null; LoopIntB = null; } } var queue = ch == 'A' ? StreamA : StreamB; while (queue.TryDequeue(out _)) { } } public (StrengthMode modeA, byte valA, StrengthMode modeB, byte valB, byte[] freqA, byte[] intA, byte[] freqB, byte[] intB) ConsumeTick() { lock (Lock) { var modeA = DirtyA ? StrengthMode.Abs : StrengthMode.None; var modeB = DirtyB ? StrengthMode.Abs : StrengthMode.None; byte ApplyLimit(int desired, int max, LimitMode mode) => mode == LimitMode.Scale ? (byte)(desired * max / 200) : (byte)Math.Clamp(desired, 0, max); var valA = ApplyLimit(DesiredA, MaxA, LimitModeA); var valB = ApplyLimit(DesiredB, MaxB, LimitModeB); LastSentA = valA; LastSentB = valB; DirtyA = false; DirtyB = false; bool hasA = !StreamA.IsEmpty || (LoopFreqA != null && LoopIntA != null); bool hasB = !StreamB.IsEmpty || (LoopFreqB != null && LoopIntB != null); var (freqA, intA) = PopWave(StreamA, LoopFreqA, LoopIntA); var (freqB, intB) = PopWave(StreamB, LoopFreqB, LoopIntB); LastActiveA = hasA; LastActiveB = hasB; LastFreqA = freqA; LastIntA = intA; LastFreqB = freqB; LastIntB = intB; LastIntensityA = hasA ? (intA[0] + intA[1] + intA[2] + intA[3]) / 4 : 0; LastIntensityB = hasB ? (intB[0] + intB[1] + intB[2] + intB[3]) / 4 : 0; if (SwapChannels) { (valA, valB) = (valB, valA); (modeA, modeB) = (modeB, modeA); (freqA, freqB) = (freqB, freqA); (intA, intB) = (intB, intA); (LastSentA, LastSentB) = (LastSentB, LastSentA); (LastFreqA, LastFreqB) = (LastFreqB, LastFreqA); (LastIntA, LastIntB) = (LastIntB, LastIntA); (LastActiveA, LastActiveB) = (LastActiveB, LastActiveA); (LastIntensityA, LastIntensityB) = (LastIntensityB, LastIntensityA); } return (modeA, valA, modeB, valB, freqA, intA, freqB, intB); } } static (byte[] freq, byte[] intensity) PopWave( ConcurrentQueue stream, byte[]? loopFreq, byte[]? loopInt) { if (stream.TryDequeue(out var frame)) return (frame.Freq, frame.Intensity); if (loopFreq != null && loopInt != null) return (loopFreq, loopInt); return (B0.ChannelOff, B0.IntensityOff); } }