SBK_BarDrive Library 2.2.1
LED bar meter control and queued animations for Arduino
Loading...
Searching...
No Matches
SBK_BarMeterAnimations.h
Go to the documentation of this file.
1
32
33#pragma once
34
35#include <Arduino.h>
36
37// Maximum number of animations stored by each animation controller.
38// Define before including SBK_BarDrive.h to select a smaller or larger queue.
39#ifndef SBK_BARDRIVE_QUEUE_CAPACITY
40#define SBK_BARDRIVE_QUEUE_CAPACITY 4
41#endif
42
43#if SBK_BARDRIVE_QUEUE_CAPACITY < 1 || SBK_BARDRIVE_QUEUE_CAPACITY > 255
44#error "SBK_BARDRIVE_QUEUE_CAPACITY must be between 1 and 255"
45#endif
46
57template <typename BarMeterT>
59{
60public:
61 static constexpr uint8_t NO_QUEUE_INDEX = 0xFF;
62
67 explicit SBK_BarMeterAnimations(BarMeterT &barMeter) : _barMeter(barMeter)
68 {
70 }
71
76
82 void setSegsNum(uint8_t n)
83 {
85 _maxTracker = _segsNum > 0 ? _segsNum - 1 : 0;
86 }
87
93 bool update(uint32_t syncTime = millis())
94 {
95 // Keep all animation deadlines on a clock that excludes paused time.
96 if (_isPaused)
97 return _queueActive;
98
99 _currentTime = syncTime - _pausedTime;
100
102 {
105 }
106
109 {
112 {
113 // Stop producing blocks at the deadline, but keep updating until
114 // every block already in flight has naturally left the display.
116 _loop = false;
117 }
119 }
120
123 {
124 if (_queueActive)
126 else
127 {
128 _isRunning = false;
129 _isPaused = false;
130 _loop = false;
131 _isLoopingNow = false;
132 _currentFunc = nullptr;
133 _init = true;
135 }
136 return _isRunning;
137 }
138
140 return _queueActive;
141
142 if ((this->*_currentFunc)())
143 {
144 if (_loop)
145 {
146 if (_skipPending)
147 _isLoopingNow = false;
148 else
149 {
150 _isLoopingNow = true; // mark it so user can react
151 _init = true;
152 }
153 }
154 else
155 {
156 if (_queueActive)
158 else
159 {
160 _isLoopingNow = false;
161 _isRunning = false;
162 _currentFunc = nullptr;
163 _queueActive = false;
167 }
168 }
169 }
170 return _isRunning;
171 }
172
175 {
176 _init = true;
177 return *this;
178 }
179
184 SBK_BarMeterAnimations &pause(uint32_t syncTime = millis())
185 {
186 if (!_isPaused)
187 {
188 _pausedAt = syncTime;
189 _isPaused = true;
190 }
191 return *this;
192 }
193
198 SBK_BarMeterAnimations &resume(uint32_t syncTime = millis())
199 {
200 if (_isPaused)
201 {
202 _pausedTime += syncTime - _pausedAt;
203 _isPaused = false;
204 }
205 return *this;
206 }
207
210 {
211 resume();
212 _isRunning = false;
213 _loop = false;
214 _isLoopingNow = false;
215 _skipPending = false;
216 _currentFunc = nullptr;
217 _animLogicSet = false;
218 _init = true;
223 clearQueue();
224 _barMeter.clear();
225 return *this;
226 }
227
237 {
238 if (!_currentFunc)
239 return *this;
240
242 {
245 _queueCount++;
247 }
248 else
249 {
250 _queueOverflow = true;
251 }
252
253 _isRunning = false;
254 _currentFunc = nullptr;
257 return *this;
258 }
259
268 {
269 if (!_currentFunc)
270 return *this;
271
273 {
274 _captureQueueEntry(_queue[_queueTail], true, duration);
276 _queueCount++;
278 }
279 else
280 {
281 _queueOverflow = true;
282 }
283
284 _isRunning = false;
285 _currentFunc = nullptr;
288 return *this;
289 }
290
300 SBK_BarMeterAnimations &forTime(uint32_t duration)
301 {
302 if (_currentFunc)
303 {
304 _animationDuration = duration;
305 _queueEntryDuration = duration;
307 }
308 else if (_queueCount > 0)
309 {
310 const uint8_t previousIndex =
312 _queue[previousIndex].duration = duration;
313 }
314 return *this;
315 }
316
326 {
327 if (_queueCount == 0)
328 return *this;
329
331 {
332 _queueOverflow = true;
333 return *this;
334 }
335
336 const uint8_t previousIndex =
338 QueueEntry &entry = _queue[_queueTail];
339 entry = _queue[previousIndex];
340 entry.loop = _loop;
343 entry.resetRuntimeState = false;
344 entry.duration = 0;
345 entry.blockEmissionStopAfter = 0;
346
348 _queueCount++;
350 return *this;
351 }
352
355 {
356 _queueLoop = true;
357 return *this;
358 }
359
362 {
363 _queueLoop = false;
364 return *this;
365 }
366
369 {
370 if (_queueCount > 0)
371 {
372 if (!_queueActive)
374 _queueActive = true;
376 }
377 return *this;
378 }
379
382 {
383 _queueHead = 0;
384 _queueTail = 0;
385 _queueCount = 0;
387 _queueStart = 0;
388 _queueLoop = false;
390 _queueActive = false;
391 _queueOverflow = false;
392 return *this;
393 }
394
402 {
403 if (!_isRunning || !_currentFunc)
404 return *this;
405
406 _loop = false;
407 resume();
408 _isLoopingNow = false;
409 _skipPending = false;
410
411 if (_queueActive)
413 else
414 {
415 _isRunning = false;
416 _currentFunc = nullptr;
417 _queueActive = false;
419 }
420 return *this;
421 }
422
424 uint8_t queuedAnimations() const { return _queueCount; }
425
427 bool isQueueRunning() const { return _queueActive; }
428
430 uint8_t currentQueueIndex() const { return _currentQueueIndex; }
431
433 bool isQueueIndexPlaying(uint8_t index) const
434 {
435 return _queueActive && _currentQueueIndex == index;
436 }
437
439 bool queueOverflowed() const { return _queueOverflow; }
440
443 {
445 return *this;
446 _loop = true;
447 return *this;
448 }
449
452 {
453 _loop = false;
454 return *this;
455 }
456
458 SBK_BarMeterAnimations &setDir(bool newDirection)
459 {
460 _animRenderDirIsReversed = newDirection;
461 _animDirSet = true;
462 return *this;
463 }
464
472
480
488
494 {
496 return *this;
498 _animLogicSet = true;
499 return *this;
500 }
501
508 {
510 return *this;
512 _animLogicSet = true;
513 return *this;
514 }
515
522 {
524 return *this;
526 _animLogicSet = true;
527 return *this;
528 }
529
536 {
538 return *this;
540 _animLogicSet = false;
541 return *this;
542 }
543
546 {
548 return *this;
549 }
550
553 {
555 return *this;
556 }
557
569 {
570 if (_currentFunc)
571 {
572 _blockEmissionStopAfter = duration;
575 }
576 else if (_queueCount > 0)
577 {
578 const uint8_t previousIndex =
580 _queue[previousIndex].blockEmissionStopAfter = duration;
581 }
582 return *this;
583 }
584
594 {
595 if (_currentFunc)
596 _waitTrailingSpace = enabled;
597 else if (_queueCount > 0)
599 return *this;
600 }
601
603 bool isRunning() const { return _isRunning; }
604
606 bool isPaused() const { return _isPaused; }
607
609 bool isLoopEnabled() const { return _loop; }
610
613 {
614 _updateIntv1 = _maxValue(static_cast<uint16_t>(1), interval);
615 return *this;
616 }
617
620 {
621 if (_skipPending == true)
622 _skipPending = false;
623 else
624 {
625 if (_isLoopingNow)
626 {
627 _isLoopingNow = false; // auto-clear after read
628 return true;
629 }
630 }
631 return false;
632 }
633
636 {
638 return true;
639 else
640 return false;
641 }
642
645
648 {
650 return true;
651 else
652 return false;
653 }
654
657
658 // Animation starters
663 SBK_BarMeterAnimations &setAll(bool onff) { return onff ? setAllOn() : setAllOff(); }
664
669 {
671 _isRunning = true;
672 _init = true;
673 return *this;
674 }
675
680 {
682 _isRunning = true;
683 _init = true;
684 return *this;
685 }
686
691 SBK_BarMeterAnimations &wait(uint16_t duration)
692 {
693 _updateIntv1 = duration;
694 _loop = false;
695 _isRunning = true;
697 _init = true;
698 return *this;
699 }
700
707 SBK_BarMeterAnimations &blinkPixel(uint8_t index, uint16_t interval = 500, uint8_t blinkCount = 0)
708 {
709 return blinkPixel(index, interval, interval, blinkCount);
710 }
711
719 SBK_BarMeterAnimations &blinkPixel(uint8_t index, uint16_t onInterval, uint16_t offInterval, uint8_t blinkCount)
720 {
721 _param1 = index;
722 _param2 = blinkCount;
723 _updateIntv1 = _maxValue(static_cast<uint16_t>(1), onInterval);
724 _updateIntv2 = _maxValue(static_cast<uint16_t>(1), offInterval);
726 _usePtr = false;
728 _isRunning = true;
729 _init = true;
730 return *this;
731 }
732
740 SBK_BarMeterAnimations &fillUpDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0)
741 {
745 _usePtr = false;
746 _valuePtr2 = nullptr;
747 _valuePtr1 = nullptr;
748 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
749 _isRunning = true;
750 uint8_t steps = _maxValue(1, _maxTracker - _minTracker + 1);
751 _updateIntv1 = _maxValue(5, duration / steps);
753 return *this;
754 }
755
763 SBK_BarMeterAnimations &fillUpIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
764 {
768 _usePtr = true;
769 _valuePtr1 = maxPercentPtr;
770 _valuePtr2 = minPercentPtr;
772 _isRunning = true;
773 _updateIntv1 = _maxValue(5, updateIntv);
775 return *this;
776 }
777
785 SBK_BarMeterAnimations &fillUpIntv(uint16_t updateIntv, uint8_t maxPercent = 100, uint8_t minPercent = 0)
786 {
790 _usePtr = false;
791 _valuePtr2 = nullptr;
792 _valuePtr1 = nullptr;
793 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
794 _isRunning = true;
795 _updateIntv1 = _maxValue(5, updateIntv);
797 return *this;
798 }
799
807 SBK_BarMeterAnimations &fillDownDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0)
808 {
812 _usePtr = false;
813 _valuePtr2 = nullptr;
814 _valuePtr1 = nullptr;
815 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
816 _isRunning = true;
817 uint8_t steps = _maxValue(1, _maxTracker - _minTracker + 1);
818 _updateIntv1 = _maxValue(5, duration / steps);
820 return *this;
821 }
822
830 SBK_BarMeterAnimations &fillDownIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
831 {
835 _usePtr = true;
836 _valuePtr1 = maxPercentPtr;
837 _valuePtr2 = minPercentPtr;
839 _isRunning = true;
840 uint8_t steps = _maxValue(1, _maxTracker - _minTracker + 1);
841 _updateIntv1 = _maxValue(5, updateIntv);
843 return *this;
844 }
845
853 SBK_BarMeterAnimations &fillDownIntv(uint16_t updateIntv, uint8_t maxPercent = 100, uint8_t minPercent = 0)
854 {
858 _usePtr = false;
859 _valuePtr2 = nullptr;
860 _valuePtr1 = nullptr;
861 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
862 _isRunning = true;
863 _updateIntv1 = _maxValue(5, updateIntv);
865 return *this;
866 }
867
875 SBK_BarMeterAnimations &emptyDownDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0)
876 {
880 _usePtr = false;
881 _valuePtr2 = nullptr;
882 _valuePtr1 = nullptr;
883 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
884 _isRunning = true;
885 uint8_t steps = _maxValue(1, _maxTracker - _minTracker + 1);
886 return emptyDownIntv(duration / steps, maxPercent, minPercent);
887 }
888
896 SBK_BarMeterAnimations &emptyDownIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
897 {
901 _usePtr = true;
902 _valuePtr1 = maxPercentPtr;
903 _valuePtr2 = minPercentPtr;
905 _isRunning = true;
906 _updateIntv1 = _maxValue(5, updateIntv);
908 return *this;
909 }
910
918 SBK_BarMeterAnimations &emptyDownIntv(uint16_t updateIntv, uint8_t maxPercent = 100, uint8_t minPercent = 0)
919 {
923 _usePtr = false;
924 _valuePtr2 = nullptr;
925 _valuePtr1 = nullptr;
926 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
927 _isRunning = true;
928 _updateIntv1 = _maxValue(5, updateIntv);
930 return *this;
931 }
932
940 SBK_BarMeterAnimations &emptyUpDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0)
941 {
945 _usePtr = false;
946 _valuePtr2 = nullptr;
947 _valuePtr1 = nullptr;
948 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
949 _isRunning = true;
950 uint8_t steps = _maxValue(1, _maxTracker - _minTracker + 1);
951 _updateIntv1 = _maxValue(5, duration / steps);
953 return *this;
954 }
955
963 SBK_BarMeterAnimations &emptyUpIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
964 {
968 _usePtr = true;
969 _valuePtr1 = maxPercentPtr;
970 _valuePtr2 = minPercentPtr;
972 _isRunning = true;
973 _updateIntv1 = _maxValue(5, updateIntv);
975 return *this;
976 }
977
985 SBK_BarMeterAnimations &emptyUpIntv(uint16_t updateIntv, uint8_t maxPercent = 100, uint8_t minPercent = 0)
986 {
990 _usePtr = false;
991 _valuePtr2 = nullptr;
992 _valuePtr1 = nullptr;
993 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
994 _isRunning = true;
995 _updateIntv1 = _maxValue(5, updateIntv);
997 return *this;
998 }
999
1008 SBK_BarMeterAnimations &bounceFillUpDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0, uint16_t fillIntv = 0)
1009 {
1012 _usePtr = false;
1013 _valuePtr2 = nullptr;
1014 _valuePtr1 = nullptr;
1015 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
1016 uint8_t steps = _maxValue(1, _maxTracker - _minTracker + 1);
1017 uint16_t emptyIntv = fillIntv;
1018 if (fillIntv == 0)
1019 {
1020 fillIntv = _maxValue(5, duration) / (2 * steps);
1021 emptyIntv = fillIntv;
1022 }
1023 else
1024 {
1025 emptyIntv = _maxValue(5, duration - fillIntv * steps) / steps;
1026 }
1027 _updateIntv1 = fillIntv; // Starting interval
1028 _updateIntv2 = _updateIntv1; // Fill intv
1029 _updateIntv3 = emptyIntv; // Empty intv
1030 _sequenceState = 0;
1031 _isRunning = true;
1033 return *this;
1034 }
1035
1044 SBK_BarMeterAnimations &bounceFillUpIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
1045 {
1048 _valuePtr1 = maxPercentPtr;
1049 _valuePtr2 = minPercentPtr;
1050 _usePtr = true;
1052 _updateIntv1 = _maxValue(5, fillIntv); // Starting interval
1053 _updateIntv2 = _updateIntv1; // Fill intv
1054 _updateIntv3 = _maxValue(5, emptyIntv); // Empty intv
1055 _sequenceState = 0;
1056 _isRunning = true;
1058 return *this;
1059 }
1060
1069 SBK_BarMeterAnimations &bounceFillUpIntv(uint16_t fillIntv = 10, uint16_t emptyIntv = 10, uint8_t maxPercent = 100, uint8_t minPercent = 0)
1070 {
1074 _usePtr = false;
1075 _valuePtr2 = nullptr;
1076 _valuePtr1 = nullptr;
1077 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
1078 _updateIntv1 = _maxValue(5, fillIntv); // Starting interval
1079 _updateIntv2 = _updateIntv1; // Fill intv
1080 _updateIntv3 = _maxValue(5, emptyIntv); // Empty intv
1081 _sequenceState = 0;
1082 _isRunning = true;
1084 return *this;
1085 }
1086
1095 SBK_BarMeterAnimations &bounceFillDownDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0, uint16_t fillIntv = 0)
1096 {
1100 _usePtr = false;
1101 _valuePtr2 = nullptr;
1102 _valuePtr1 = nullptr;
1103 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
1104 uint8_t steps = _maxValue(1, _maxTracker - _minTracker + 1);
1105 uint16_t emptyIntv = fillIntv;
1106 if (fillIntv == 0)
1107 {
1108 fillIntv = _maxValue(5, duration) / (2 * steps);
1109 emptyIntv = fillIntv;
1110 }
1111 else
1112 {
1113 emptyIntv = _maxValue(5, duration - fillIntv * steps) / steps;
1114 }
1115 _updateIntv1 = fillIntv; // Starting interval
1116 _updateIntv2 = _updateIntv1; // Fill intv
1117 _updateIntv3 = emptyIntv; // Empty intv
1118 _sequenceState = 0;
1119 _isRunning = true;
1121 return *this;
1122 }
1123
1132 SBK_BarMeterAnimations &bounceFillDownIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
1133 {
1137 _valuePtr1 = maxPercentPtr;
1138 _valuePtr2 = minPercentPtr;
1139 _usePtr = true;
1141 _updateIntv1 = _maxValue(5, fillIntv); // Starting interval
1142 _updateIntv2 = _updateIntv1; // Fill intv
1143 _updateIntv3 = _maxValue(5, emptyIntv); // Empty intv
1144 _sequenceState = 0;
1145 _isRunning = true;
1147 return *this;
1148 }
1149
1158 SBK_BarMeterAnimations &bounceFillDownIntv(uint16_t fillIntv = 10, uint16_t emptyIntv = 10, uint8_t maxPercent = 100, uint8_t minPercent = 0)
1159 {
1163 _usePtr = false;
1164 _valuePtr2 = nullptr;
1165 _valuePtr1 = nullptr;
1166 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
1167 _updateIntv1 = _maxValue(5, fillIntv); // Starting interval
1168 _updateIntv2 = _updateIntv1; // Fill intv
1169 _updateIntv3 = _maxValue(5, emptyIntv); // Empty intv
1170 _sequenceState = 0;
1171 _isRunning = true;
1173 return *this;
1174 }
1175
1184 SBK_BarMeterAnimations &bounceFillFromCenterDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0, uint16_t fillIntv = 0)
1185 {
1189 _mirrorHalfRangeDir = false;
1190 _usePtr = false;
1191 _valuePtr2 = nullptr;
1192 _valuePtr1 = nullptr;
1193 _mapMinMaxTracker(minPercent, maxPercent, (_segsNum / 2) - 1, 0);
1194 uint8_t steps = _maxValue(1, _maxTracker - _minTracker + 1);
1195 uint16_t emptyIntv = fillIntv;
1196 if (fillIntv == 0)
1197 {
1198 fillIntv = _maxValue(5, duration) / (2 * steps);
1199 emptyIntv = fillIntv;
1200 }
1201 else
1202 {
1203 emptyIntv = _maxValue(5, duration - fillIntv * steps) / steps;
1204 }
1205 _updateIntv1 = fillIntv; // Starting interval
1206 _updateIntv2 = _updateIntv1; // Fill intv
1207 _updateIntv3 = emptyIntv; // Empty intv
1208 _sequenceState = 0;
1209 _isRunning = true;
1211 return *this;
1212 }
1213
1222 SBK_BarMeterAnimations &bounceFillFromCenterIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
1223 {
1227 _mirrorHalfRangeDir = false;
1228 _valuePtr1 = maxPercentPtr;
1229 _valuePtr2 = minPercentPtr;
1230 _usePtr = true;
1231 _mapMinMaxTrackerFromPtr(minPercentPtr, maxPercentPtr, (_segsNum / 2) - 1, 0);
1232 _updateIntv1 = fillIntv; // Starting interval
1233 _updateIntv2 = _updateIntv1; // Fill intv
1234 _updateIntv3 = emptyIntv; // Empty intv
1235 _sequenceState = 0;
1236 _isRunning = true;
1238 return *this;
1239 }
1240
1249 SBK_BarMeterAnimations &bounceFillFromCenterIntv(uint16_t fillIntv = 25, uint16_t emptyIntv = 25, uint8_t maxPercent = 100, uint8_t minPercent = 0)
1250 {
1254 _mirrorHalfRangeDir = false;
1255 _usePtr = false;
1256 _valuePtr2 = nullptr;
1257 _valuePtr1 = nullptr;
1258 _mapMinMaxTracker(minPercent, maxPercent, (_segsNum / 2) - 1, 0);
1259 _updateIntv1 = fillIntv; // Starting interval
1260 _updateIntv2 = _updateIntv1; // Fill intv
1261 _updateIntv3 = emptyIntv; // Empty intv
1262 _sequenceState = 0;
1263 _isRunning = true;
1265 return *this;
1266 }
1267
1276 SBK_BarMeterAnimations &bounceFillFromEdgesDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0, uint16_t fillIntv = 0)
1277 {
1281 _mirrorHalfRangeDir = true;
1282 _usePtr = false;
1283 _valuePtr2 = nullptr;
1284 _valuePtr1 = nullptr;
1285 _mapMinMaxTracker(minPercent, maxPercent, (_segsNum / 2) - 1, 0);
1286 uint8_t steps = _maxValue(1, _maxTracker - _minTracker + 1);
1287 uint16_t emptyIntv = fillIntv;
1288 if (fillIntv == 0)
1289 {
1290 fillIntv = _maxValue(5, duration) / (2 * steps);
1291 emptyIntv = fillIntv;
1292 }
1293 else
1294 {
1295 emptyIntv = _maxValue(5, duration - fillIntv * steps) / steps;
1296 }
1297 _updateIntv1 = fillIntv; // Starting interval
1298 _updateIntv2 = _updateIntv1; // Fill intv
1299 _updateIntv3 = emptyIntv; // Empty intv
1300 _sequenceState = 0;
1301 _isRunning = true;
1303 return *this;
1304 }
1305
1314 SBK_BarMeterAnimations &bounceFillFromEdgesIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
1315 {
1319 _mirrorHalfRangeDir = true;
1320 _valuePtr1 = maxPercentPtr;
1321 _valuePtr2 = minPercentPtr;
1322 _usePtr = true;
1323 _mapMinMaxTrackerFromPtr(minPercentPtr, maxPercentPtr, (_segsNum / 2) - 1, 0);
1324 _updateIntv1 = fillIntv; // Starting interval
1325 _updateIntv2 = _updateIntv1; // Fill intv
1326 _updateIntv3 = emptyIntv; // Empty intv
1327 _sequenceState = 0;
1328 _isRunning = true;
1330 return *this;
1331 }
1332
1341 SBK_BarMeterAnimations &bounceFillFromEdgesIntv(uint16_t fillIntv = 25, uint16_t emptyIntv = 25, uint8_t maxPercent = 100, uint8_t minPercent = 0)
1342 {
1346 _mirrorHalfRangeDir = true;
1347 _usePtr = false;
1348 _valuePtr2 = nullptr;
1349 _valuePtr1 = nullptr;
1350 _mapMinMaxTracker(minPercent, maxPercent, (_segsNum / 2) - 1, 0);
1351 _updateIntv1 = fillIntv; // Starting interval
1352 _updateIntv2 = _updateIntv1; // Fill intv
1353 _updateIntv3 = emptyIntv; // Empty intv
1354 _sequenceState = 0;
1355 _isRunning = true;
1357 return *this;
1358 }
1359
1365 SBK_BarMeterAnimations &beatPulse(uint8_t *bpmPtr) // bouncing from bottom (maybe like a volume meter with the music)
1366 {
1368 _updateIntv1 = 60000UL / (bpmPtr ? _maxValue(1, *bpmPtr) : 116);
1369 _param2 = _minValue(35 * (_segsNum - 1) / 100, 255); // MIN_BASE_LEVEL
1370 _param3 = _minValue(67 * (_segsNum - 1) / 100, 255); // MIN_PEAK_LEVEL
1371 _param4 = 150; // PEAK_HOLD_TIME
1372 _usePtr = true;
1376
1377 _isRunning = true;
1379 _init = true;
1380 return *this;
1381 }
1382
1388 SBK_BarMeterAnimations &beatPulse(uint8_t bpm = 116) // bouncing from bottom (maybe like a volume meter with the music)
1389 {
1390 _param1 = _maxValue(1, bpm);
1391 _updateIntv1 = 60000UL / _param1;
1392 _param2 = _minValue(35 * (_segsNum - 1) / 100, 255); // MIN_BASE_LEVEL
1393 _param3 = _minValue(67 * (_segsNum - 1) / 100, 255); // MIN_PEAK_LEVEL
1394 _param4 = 150; // PEAK_HOLD_TIME
1395 _valuePtr1 = nullptr;
1396 _usePtr = false;
1400
1401 _isRunning = true;
1403 _init = true;
1404 return *this;
1405 }
1406
1415 SBK_BarMeterAnimations &explodingBlocks(uint16_t intv = 50, uint8_t blockLength = 2, uint8_t blockSpacing = 1, uint8_t numBlocks = 0)
1416 {
1417 _waitTrailingSpace = false;
1418 _trailingSpacePending = false;
1419 _updateIntv1 = _maxValue(5, intv);
1422 _param3 = numBlocks;
1423 _param4 = ((_segsNum / 2) / (blockLength + blockSpacing)) + 2;
1424 _param4 = constrain(_param4, 2, 32);
1425
1429 _isRunning = true;
1431 return *this;
1432 }
1433
1442 SBK_BarMeterAnimations &collidingBlocks(uint16_t intv = 50, uint8_t blockLength = 2, uint8_t blockSpacing = 1, uint8_t numBlocks = 0)
1443 {
1444
1445 _waitTrailingSpace = false;
1446 _trailingSpacePending = false;
1447 _updateIntv1 = _maxValue(5, intv);
1450 _param3 = numBlocks;
1451 _param4 = ((_segsNum / 2) / (blockLength + blockSpacing)) + 2;
1452 _param4 = constrain(_param4, 2, 32);
1453
1457 _isRunning = true;
1459 return *this;
1460 }
1461
1470 SBK_BarMeterAnimations &scrollingUpBlocks(uint16_t intv = 50, uint8_t blockLength = 2, uint8_t blockSpacing = 1, uint8_t numBlocks = 0)
1471 {
1472 _waitTrailingSpace = false;
1473 _trailingSpacePending = false;
1474 _updateIntv1 = _maxValue(5, intv);
1477 _param3 = numBlocks;
1479 _param4 = constrain(_param4, 2, 64);
1480
1484 _isRunning = true;
1486 return *this;
1487 }
1488
1497 SBK_BarMeterAnimations &scrollingDownBlocks(uint16_t intv = 50, uint8_t blockLength = 2, uint8_t blockSpacing = 1, uint8_t numBlocks = 0)
1498 {
1499 _waitTrailingSpace = false;
1500 _trailingSpacePending = false;
1501 _updateIntv1 = _maxValue(5, intv);
1504 _param3 = numBlocks;
1506 _param4 = constrain(_param4, 2, 64);
1507
1511 _isRunning = true;
1513 return *this;
1514 }
1515
1523 SBK_BarMeterAnimations &downStackingBlocks(uint16_t intv = 50, uint8_t blockLength = 1, uint8_t blockSpacing = 0)
1524 {
1528 _updateIntv1 = _maxValue(5, intv);
1531 _param3 = 0; // requested number of blocks
1532 _param4 = 1; // aka maximum blocks number
1533 if (_init)
1535 _isRunning = true;
1537 return *this;
1538 }
1539
1547 SBK_BarMeterAnimations &upUnstackingBlocks(uint16_t intv = 50, uint8_t blockLength = 1, uint8_t blockSpacing = 0)
1548 {
1552 _updateIntv1 = _maxValue(5, intv);
1555 _param3 = 0; // aka requested number of blocks
1556 _param4 = 1; // aka maximum blocks number
1557 if (_init)
1559 _isRunning = true;
1561 return *this;
1562 }
1563
1571 SBK_BarMeterAnimations &upStackingBlocks(uint16_t intv = 50, uint8_t blockLength = 1, uint8_t blockSpacing = 0)
1572 {
1576 _updateIntv1 = _maxValue(5, intv);
1579 _param3 = 0; // aka requested number of blocks
1580 _param4 = 1; // aka maximum blocks number
1581 if (_init)
1583 _isRunning = true;
1585 return *this;
1586 }
1587
1595 SBK_BarMeterAnimations &downUnstackingBlocks(uint16_t intv = 50, uint8_t blockLength = 1, uint8_t blockSpacing = 0)
1596 {
1600 _updateIntv1 = _maxValue(5, intv);
1603 _param3 = 0; // aka requested number of blocks
1604 _param4 = 1; // aka maximum blocks number
1605 if (_init)
1607 _isRunning = true;
1609 return *this;
1610 }
1611
1622 SBK_BarMeterAnimations &followSignalSmooth(const uint16_t *sigPtr, uint16_t updateIntv = 100, uint16_t minMap = 0, uint16_t maxMap = 1023, uint8_t smoothingFactor = 30, uint16_t samplingIntv = 5)
1623 {
1624
1625 _valuePtr1 = sigPtr;
1626 _updateIntv1 = _maxValue(10, updateIntv);
1627 _minMap = _maxValue(0, minMap);
1628 _maxMap = _maxValue(0, maxMap);
1630 _param1 = constrain(smoothingFactor, 0, 100);
1631 _updateIntv2 = samplingIntv;
1632
1636 _isRunning = true;
1637 _init = true;
1639 return *this;
1640 }
1641
1652 SBK_BarMeterAnimations &followSignalWithPointer(const uint16_t *sigPtr, uint16_t updateIntv = 100, uint16_t minMap = 0, uint16_t maxMap = 1023, uint8_t smoothingFactor = 30, uint16_t samplingIntv = 5)
1653 {
1654 _valuePtr1 = sigPtr;
1655 _updateIntv1 = _maxValue(10, updateIntv);
1656 _minMap = _maxValue(0, minMap);
1657 _maxMap = _maxValue(0, maxMap);
1659 _param1 = constrain(smoothingFactor, 0, 100);
1660 _updateIntv2 = samplingIntv;
1661
1665 _isRunning = true;
1666 _init = true;
1668 return *this;
1669 }
1670
1683 SBK_BarMeterAnimations &followDualSignalFromCenter(const uint16_t *sig1Ptr, uint16_t updateIntv = 100, const uint16_t *sig2Ptr = nullptr, uint16_t minMap = 0, uint16_t maxMap = 1023, uint8_t smoothingFactor = 30, uint16_t samplingIntv = 5)
1684 {
1685 _valuePtr1 = sig1Ptr;
1686 _updateIntv1 = _maxValue(10, updateIntv);
1687 _valuePtr2 = sig2Ptr ? sig2Ptr : sig1Ptr;
1688 _minMap = _maxValue(0, minMap);
1689 _maxMap = _maxValue(0, maxMap);
1691 _param1 = constrain(smoothingFactor, 0, 100);
1692 _updateIntv2 = samplingIntv;
1693
1697 //_mirrorHalfRangeDir = false;
1698 _isRunning = true;
1699 _init = true;
1701 return *this;
1702 }
1703
1716 SBK_BarMeterAnimations &followDualSignalFromEdges(const uint16_t *sig1Ptr, uint16_t updateIntv = 100, const uint16_t *sig2Ptr = nullptr, uint16_t minMap = 0, uint16_t maxMap = 1023, uint8_t smoothingFactor = 30, uint16_t samplingIntv = 5)
1717 {
1718 _valuePtr1 = sig1Ptr;
1719 _updateIntv1 = _maxValue(10, updateIntv);
1720 _valuePtr2 = sig2Ptr ? sig2Ptr : sig1Ptr;
1721 _minMap = _maxValue(0, minMap);
1722 _maxMap = _maxValue(0, maxMap);
1724 _param1 = constrain(smoothingFactor, 0, 100);
1725 _updateIntv2 = samplingIntv;
1726
1730 //_mirrorHalfRangeDir = true;
1731 _isRunning = true;
1732 _init = true;
1734 return *this;
1735 }
1736
1752 SBK_BarMeterAnimations &followSignalFloatingPeak(const uint16_t *sigPtr, uint8_t peakHoldTime = 20, uint16_t updateIntv = 100, uint16_t minMap = 0, uint16_t maxMap = 1023, uint8_t smoothingFactor = 30, uint16_t samplingIntv = 5)
1753 {
1754
1755 _valuePtr1 = sigPtr;
1757 _updateIntv1 = _maxValue(10, updateIntv);
1758 _minMap = _maxValue(0, minMap);
1759 _maxMap = _maxValue(0, maxMap);
1761 _param1 = constrain(smoothingFactor, 0, 100);
1762 _updateIntv2 = samplingIntv;
1763
1767 _isRunning = true;
1768 _init = true;
1770 return *this;
1771 }
1772
1778 SBK_BarMeterAnimations &randomFill(uint16_t interval = 30)
1779 {
1780 _updateIntv1 = interval;
1781
1785 _isRunning = true;
1787 _init = true;
1788 return *this;
1789 }
1790
1796 SBK_BarMeterAnimations &randomEmpty(uint16_t interval = 30)
1797 {
1798 _updateIntv1 = interval;
1799
1803 _isRunning = true;
1805 _init = true;
1806 return *this;
1807 }
1808
1809protected:
1810 BarMeterT &_barMeter;
1811 uint8_t _segsNum = 0;
1812
1813 // Active animation update function
1816
1819 {
1821 uint16_t updateIntv1 = 10, updateIntv2 = 10, updateIntv3 = 10;
1822 uint16_t minMap = 0, maxMap = 1023;
1823 const void *valuePtr1 = nullptr;
1824 const void *valuePtr2 = nullptr;
1825 int8_t minTracker = 0, maxTracker = 1;
1826 uint8_t param1 = 0, param2 = 0, param3 = 0, param4 = 0, param5 = 0;
1827 bool loop = false;
1832 bool animLogicSet = false;
1835 bool animDirSet = false;
1836 bool usePtr = false;
1838 bool waitTrailingSpace = false;
1840 uint32_t duration = 0;
1842 };
1844 uint8_t _queueHead = 0;
1845 uint8_t _queueTail = 0;
1846 uint8_t _queueCount = 0;
1848 uint8_t _queueStart = 0;
1849 bool _queueLoop = false;
1851 bool _queueActive = false;
1852 bool _queueOverflow = false;
1857
1858 // Control flags
1859 bool _init = true; // true = init function
1860 bool _isRunning = false;
1861 bool _isPaused = false;
1862 bool _loop = false;
1863 bool _isLoopingNow = false;
1869 bool _animLogicSet = false;
1870 bool _skipPending = false;
1873 bool _animDirSet = false;
1874 bool _usePtr = false;
1880
1881 // Time trackings
1882 uint32_t _currentTime = 0;
1883 uint32_t _pausedAt = 0;
1884 uint32_t _pausedTime = 0;
1886
1887 // Animations update interval trackers
1888 uint16_t _updateIntv1 = 10, _updateIntv2 = 10, _updateIntv3 = 10;
1889
1890 // Animations trackers
1891 uint8_t _sequenceState = 0;
1893 int8_t _minTracker = 0, _maxTracker = 1;
1894 uint8_t _param1 = 0, _param2 = 0, _param3 = 0, _param4 = 0, _param5 = 0;
1896 uint16_t _minMap = 0, _maxMap = 1023;
1897 uint8_t _counter1 = 0, _counter2 = 0;
1898 // Generic live-value pointers. Their type is determined by the active animation.
1899 const void *_valuePtr1 = nullptr, *_valuePtr2 = nullptr;
1900 // Blocks related helpers
1901 struct Block
1902 {
1903 Block() : position(-1), active(false) {}
1904 int8_t position;
1906 };
1907 static constexpr uint8_t MAX_BLOCK_CAPACITY = 64;
1909 uint8_t _segmentCapacity = 0;
1910 uint8_t _blockCapacity = 0;
1911 uint8_t _randomCursor = 0;
1912
1913 void _captureQueueEntry(QueueEntry &entry, bool resetRuntimeState,
1914 uint32_t duration)
1915 {
1916 entry.function = _currentFunc;
1917 entry.updateIntv1 = _updateIntv1;
1918 entry.updateIntv2 = _updateIntv2;
1919 entry.updateIntv3 = _updateIntv3;
1920 entry.minMap = _minMap;
1921 entry.maxMap = _maxMap;
1922 entry.valuePtr1 = _valuePtr1;
1923 entry.valuePtr2 = _valuePtr2;
1924 entry.minTracker = _minTracker;
1925 entry.maxTracker = _maxTracker;
1926 entry.param1 = _param1;
1927 entry.param2 = _param2;
1928 entry.param3 = _param3;
1929 entry.param4 = _param4;
1930 entry.param5 = _param5;
1931 entry.loop = _loop;
1939 entry.animDirSet = _animDirSet;
1940 entry.usePtr = _usePtr;
1943 entry.resetRuntimeState = resetRuntimeState;
1944 entry.duration = duration;
1946 }
1947
1948 void _loadQueueEntry(const QueueEntry &entry, bool forceReset = false)
1949 {
1950 const bool resetRuntimeState = forceReset || entry.resetRuntimeState;
1951 const bool previousRenderLogic = _animRenderLogicIsInverted;
1952
1953 _currentFunc = entry.function;
1954 _updateIntv1 = entry.updateIntv1;
1955 _updateIntv2 = entry.updateIntv2;
1956 _updateIntv3 = entry.updateIntv3;
1957 _minMap = entry.minMap;
1958 _maxMap = entry.maxMap;
1959 _valuePtr1 = entry.valuePtr1;
1960 _valuePtr2 = entry.valuePtr2;
1961 _minTracker = entry.minTracker;
1962 _maxTracker = entry.maxTracker;
1963 _param1 = entry.param1;
1964 _param2 = entry.param2;
1965 _param3 = entry.param3;
1966 _param4 = entry.param4;
1967 if (resetRuntimeState)
1968 _param5 = entry.param5;
1969 _loop = entry.loop;
1972 (!resetRuntimeState && !entry.animLogicSet)
1975 _prevAnimRenderLogic = resetRuntimeState
1977 : previousRenderLogic;
1983 _animDirSet = entry.animDirSet;
1984 _usePtr = entry.usePtr;
1987 _trailingSpacePending = false;
1991
1992 _isRunning = _currentFunc != nullptr;
1993 resume();
1994 _isLoopingNow = false;
1995 _skipPending = false;
1996 if (resetRuntimeState)
1997 {
1998 _init = true;
1999 _sequenceState = 0;
2001 _counter1 = _counter2 = 0;
2004 }
2005 else
2006 {
2007 _init = false;
2008 }
2009 }
2010
2011 void _startNextQueuedAnimation(bool forceReset = false)
2012 {
2013 if (_queueCount == 0 && _queueLoop && _queueStoredCount > 0)
2014 {
2017 forceReset = true;
2018 }
2019 if (_queueCount == 0)
2020 {
2022 _queueActive = false;
2023 _isRunning = false;
2024 _currentFunc = nullptr;
2027 return;
2028 }
2029
2031 _loadQueueEntry(_queue[_queueHead], forceReset);
2033 _queueCount--;
2034 }
2035
2037 {
2038 _segmentCapacity = _barMeter.getSegsNum();
2040 _maxTracker = _segsNum > 0 ? _segsNum - 1 : 0;
2041
2042 const uint16_t requestedCapacity = static_cast<uint16_t>(_segmentCapacity) + 2U;
2043 _blockCapacity = _minValue(requestedCapacity,
2044 static_cast<uint16_t>(MAX_BLOCK_CAPACITY));
2045 const uint8_t randomOrderBlocks =
2046 (_segmentCapacity + sizeof(Block) - 1U) / sizeof(Block);
2047 const uint8_t workspaceBlocks = _maxValue(_blockCapacity, randomOrderBlocks);
2048 if (workspaceBlocks > 0)
2049 _animationWorkspace = new Block[workspaceBlocks];
2050 }
2051
2052 uint8_t *_randomOrder()
2053 {
2054 return reinterpret_cast<uint8_t *>(_animationWorkspace);
2055 }
2056
2057 void _resetBlocks(uint8_t &blockCapacity)
2058 {
2060 {
2061 blockCapacity = 0;
2062 return;
2063 }
2064 blockCapacity = _minValue(blockCapacity, _blockCapacity);
2065 for (uint8_t i = 0; i < blockCapacity; ++i)
2067 }
2068
2069 // Animation helpers
2070 // Arduino cores differ: AVR defines min/max macros, while ESP cores use
2071 // same-type templates. Preserve ordinary arithmetic promotion on all cores.
2072 template <typename A, typename B>
2073 static auto _minValue(A a, B b) -> decltype(true ? A() : B())
2074 {
2075 return a < b ? a : b;
2076 }
2077
2078 template <typename A, typename B>
2079 static auto _maxValue(A a, B b) -> decltype(true ? A() : B())
2080 {
2081 return a > b ? a : b;
2082 }
2083
2084 static inline void _normalizePercentRange(uint8_t &minP, uint8_t &maxP)
2085 {
2086 minP = constrain(minP, 0, 100);
2087 maxP = constrain(maxP, 0, 100);
2088 if (minP > maxP)
2089 {
2090 uint8_t t = maxP;
2091 maxP = minP;
2092 minP = t;
2093 }
2094 if (minP == maxP)
2095 {
2096 if (maxP < 100)
2097 maxP = minP + 1;
2098 else
2099 minP--;
2100 }
2101 }
2102 const uint8_t *_percentMaxPtr() const { return static_cast<const uint8_t *>(_valuePtr1); }
2103 const uint8_t *_percentMinPtr() const { return static_cast<const uint8_t *>(_valuePtr2); }
2104 const uint8_t *_liveBpmPtr() const { return static_cast<const uint8_t *>(_valuePtr1); }
2105 const uint16_t *_signalPtr1() const { return static_cast<const uint16_t *>(_valuePtr1); }
2106 const uint16_t *_signalPtr2() const { return static_cast<const uint16_t *>(_valuePtr2); }
2107
2108 inline void _mapMinMaxTrackerFromPtr(const uint8_t *minPercPtr, const uint8_t *maxPercPtr, int8_t minR, uint8_t maxR)
2109 {
2110 if (!_usePtr)
2111 return;
2112 uint8_t minP = minPercPtr ? *minPercPtr : 0;
2113 uint8_t maxP = maxPercPtr ? *maxPercPtr : 100;
2114 _normalizePercentRange(minP, maxP);
2115 _minTracker = map(minP, 0, 100, minR, maxR);
2116 _maxTracker = map(maxP, 0, 100, minR, maxR);
2117 }
2118 inline void _mapMinMaxTracker(uint8_t minP, uint8_t maxP, uint8_t minR, uint8_t maxR)
2119 {
2120 _normalizePercentRange(minP, maxP);
2121 _minTracker = map(minP, 0, 100, minR, maxR);
2122 _maxTracker = map(maxP, 0, 100, minR, maxR);
2123 }
2124 inline uint8_t _corrPixelToDir(uint8_t pixel)
2125 {
2126 return _animRenderDirIsReversed ? (_segsNum - 1) - pixel : pixel;
2127 }
2128 inline uint8_t _corrPixelToDirForHalfRange(uint8_t pixel)
2129 {
2131 return pixel;
2132
2133 const uint8_t half = _segsNum / 2;
2134 return abs((half - 1) - pixel);
2135 }
2136 static inline void _correctSwapOrEqualMinMax(uint16_t &minVal, uint16_t &maxVal)
2137 {
2138 if (minVal > maxVal)
2139 {
2140 uint16_t temp = maxVal;
2141 maxVal = minVal;
2142 minVal = temp;
2143 }
2144 if (minVal == maxVal) // avoid division by zero in map()
2145 {
2146 if (maxVal < 65535)
2147 {
2148 maxVal = minVal + 1;
2149 }
2150 else
2151 {
2152 minVal--;
2153 }
2154 }
2155 }
2156 static inline uint8_t _getMappedSignal(uint16_t sig, uint16_t minM, uint16_t maxM, uint8_t minR, uint8_t maxR)
2157 {
2158 uint8_t mapped = map(sig, minM, maxM, minR, maxR);
2159 return constrain(mapped, minR, maxR); // avoid edge overshoot
2160 }
2161
2162 // Animation update functions
2163
2165 {
2166 _init = false;
2167 for (uint8_t i = 0; i < _segsNum; i++)
2168 _barMeter.setPixel(i, true);
2169 return true;
2170 }
2172 {
2173 _init = false;
2174 _barMeter.clear();
2175 return true;
2176 }
2177
2178 bool _wait()
2179 {
2180 if (_init)
2181 {
2182 _init = false;
2184 }
2186 }
2187
2189 {
2190 const uint8_t index = _param1;
2191 const uint8_t blinkCount = _param2;
2192
2193 if (index >= _segsNum)
2194 return true;
2195
2196 if (_init)
2197 {
2198 _init = false;
2199 _sequenceState = 1; // ON phase
2200 _counter1 = 0;
2202 _barMeter.setPixel(index, true);
2203 return false;
2204 }
2205
2206 const uint16_t interval = _sequenceState ? _updateIntv1 : _updateIntv2;
2207 if (_currentTime - _lastUpdate1 < interval)
2208 return false;
2209
2211 if (_sequenceState)
2212 {
2213 _sequenceState = 0;
2214 _barMeter.setPixel(index, false);
2215 _counter1++;
2216 return blinkCount > 0 && _counter1 >= blinkCount;
2217 }
2218
2219 _sequenceState = 1;
2220 _barMeter.setPixel(index, true);
2221 return false;
2222 }
2223
2225 {
2227
2228 if (_init)
2229 {
2230 _init = false;
2231
2232 if (!_animLogicSet)
2234
2235 // Pre-fill for emptyDown mode
2237 {
2239 for (uint8_t i = 0; i < _segsNum; ++i)
2240 {
2241 if (i <= _maxTracker)
2242 _barMeter.setPixel(_corrPixelToDir(i), true);
2243 else
2244 _barMeter.setPixel(_corrPixelToDir(i), false);
2245 }
2246 }
2247 else // Pre-fill fillUp, light up to _minTracker inclusively
2248 {
2250 for (uint8_t i = 0; i < _segsNum; ++i)
2251 {
2252 if (i <= _minTracker)
2253 _barMeter.setPixel(_corrPixelToDir(i), true);
2254 else
2255 _barMeter.setPixel(_corrPixelToDir(i), false);
2256 }
2257 }
2258 return false;
2259 }
2260
2262 {
2264
2265 // Clamp if inverted logic and tracker is beyond maxTracker
2268
2269 // Clamp in normal logic mode
2272 }
2273
2275 {
2277
2279 {
2281 {
2282 _barMeter.setPixel(_corrPixelToDir(_ledTracker1), false);
2283 _ledTracker1--;
2284 }
2285 else
2286 return true; // now done!
2287 }
2288 else
2289 {
2291 {
2292 _barMeter.setPixel(_corrPixelToDir(_ledTracker1), true);
2293 _ledTracker1++;
2294 }
2295 else
2296 return true; // done lighting up all
2297 }
2298 }
2299 return false;
2300 }
2301
2303 {
2304 switch (_sequenceState)
2305 {
2306 case 0: // Fill Up
2307
2308 _skipPending = false;
2309 if (_fillOrEmpty())
2310 {
2312 _sequenceState = 1;
2313 _updateIntv1 = _updateIntv3; // Empty update interval
2314 }
2315 return false;
2316
2317 case 1: // Fill Down
2318
2319 _skipPending = true;
2320 if (_fillOrEmpty())
2321 {
2323 _sequenceState = 0;
2324 _updateIntv1 = _updateIntv2; // Fill up update interval
2325 return true;
2326 }
2327 return false;
2328 }
2329
2330 return true; // Shouldn’t reach here
2331 }
2332
2334 {
2335 const uint8_t center = _segsNum / 2;
2337
2338 if (_init)
2339 {
2340 _init = false;
2341
2342 if (!_animLogicSet)
2344
2346 {
2348
2349 // Start fully lit
2350 for (uint8_t i = 0; i < center; ++i)
2351 {
2352 if (i >= _maxTracker)
2353 {
2354 _barMeter.setPixel(_corrPixelToDirForHalfRange(i), true);
2355 _barMeter.setPixel((_segsNum - 1) - _corrPixelToDirForHalfRange(i), true);
2356 }
2357 else
2358 {
2359 _barMeter.setPixel(_corrPixelToDirForHalfRange(i), false);
2360 _barMeter.setPixel((_segsNum - 1) - _corrPixelToDirForHalfRange(i), false);
2361 }
2362 }
2363 }
2364 else
2365 {
2366
2368
2369 // Start with minimum fill
2370 for (uint8_t i = 0; i < center; ++i)
2371 {
2372 if (i > _minTracker)
2373 {
2374 _barMeter.setPixel(_corrPixelToDirForHalfRange(i), true);
2375 _barMeter.setPixel((_segsNum - 1) - _corrPixelToDirForHalfRange(i), true);
2376 }
2377 else
2378 {
2379 _barMeter.setPixel(_corrPixelToDirForHalfRange(i), false);
2380 _barMeter.setPixel((_segsNum - 1) - _corrPixelToDirForHalfRange(i), false);
2381 }
2382 }
2383 }
2384 return false;
2385 }
2386
2388 {
2390
2391 // A completed phase leaves the tracker one step past its endpoint.
2392 // Move it back into the active range before reversing direction.
2397 }
2398
2400 {
2403 {
2404 if (_ledTracker1 >= _maxTracker && _ledTracker1 >= 0)
2405 {
2408 _ledTracker1--;
2409 }
2410 else
2411 return true; // finished filling
2412 }
2413 else
2414 {
2415 if (_ledTracker1 <= _minTracker && _ledTracker1 < center)
2416 {
2419 _ledTracker1++;
2420 }
2421 else
2422 return true; // finished clearing
2423 }
2424 }
2425
2426 return false;
2427 }
2428
2430 {
2431 switch (_sequenceState)
2432 {
2433 case 0: // Fill from center
2434
2435 _skipPending = true;
2437 {
2439 _updateIntv1 = _updateIntv3; // Empty update interval
2440 _sequenceState = 1;
2441 }
2442 return false;
2443
2444 case 1: // Empty to center
2445
2446 _skipPending = false;
2448 {
2450 _updateIntv1 = _updateIntv2; // Empty update interval
2451 _sequenceState = 0;
2452 return true; // one full bounce cycle completed
2453 }
2454 return false;
2455 }
2456
2457 return true; // should not happen
2458 }
2459
2460#define bpm _param1
2461#define MIN_BASE_LEVEL _param2
2462#define MIN_PEAK_LEVEL _param3
2463#define PEAK_HOLD_TIME _param4
2464#define bpmPtr _liveBpmPtr()
2465#define currentLevel _ledTracker1
2466#define peakLevel _ledTracker2
2467#define beat _updateIntv1
2468#define lastBeat _lastUpdate1
2469#define lastRandomUpdate _lastUpdate2
2470#define prevPeakUpdate _lastUpdate3
2471 bool _beatPulse() // bouncing from bottom (maybe like a volume meter with the music)
2472 {
2473 static int8_t randomOffset;
2474 static bool isPeak = false;
2475
2476 if (_init)
2477 {
2478 _init = false;
2479
2480 if (!_animLogicSet)
2482
2483 _setAllOff();
2486 }
2487
2488 // Update at bpm pulse
2489 if (_currentTime - lastBeat >= beat)
2490 {
2492 isPeak = !isPeak; // Toggle between peak and base level
2493 }
2494
2495 // Smooth transition between base and peak levels
2496 if (isPeak && currentLevel <= MIN_PEAK_LEVEL)
2497 {
2498 currentLevel += random(3, 5); // Gradual increase
2499 }
2500 else if (!isPeak && currentLevel >= MIN_BASE_LEVEL)
2501 {
2502 currentLevel -= random(0, 4); // Gradual decrease
2503 }
2504
2505 // Randomized additional LEDs on top at a random interval
2506 if (_currentTime - lastRandomUpdate >= uint16_t(random(50, 300)))
2507 {
2509 randomOffset = random(-4, 4); // 0 to 5 extra LEDs
2510 }
2511
2512 uint8_t finalLevel = currentLevel + randomOffset;
2513 finalLevel = constrain(finalLevel, 0, _segsNum);
2514 if (finalLevel > _segsNum)
2515 finalLevel = _segsNum;
2516 if (finalLevel < 0)
2517 finalLevel = 0;
2518
2519 // Update peak level
2520 if (finalLevel > peakLevel)
2521 {
2522 peakLevel = _minValue(finalLevel, _segsNum - 1);
2523 prevPeakUpdate = _currentTime; // Reset decay timer
2524 }
2525 else if (_currentTime - prevPeakUpdate >= PEAK_HOLD_TIME && peakLevel > finalLevel)
2526 {
2527 peakLevel--; // Slowly drop peak
2528 prevPeakUpdate = _currentTime; // Reset decay timer
2529 }
2530
2531 // Update LED states
2532 for (uint8_t i = 0; i < _segsNum; i++)
2533 {
2534 _barMeter.setPixel(_corrPixelToDir(i), (i < finalLevel));
2535 }
2536
2537 // Ensure peak LED stays on
2538 if (peakLevel < _segsNum)
2539 {
2540 _barMeter.setPixel(_corrPixelToDir(peakLevel), true);
2541 }
2542 return false; // Continuous pulse animation, never auto-terminates
2543 }
2544
2546 {
2547 bpm = bpmPtr ? _maxValue(1, *bpmPtr) : 116;
2548 beat = 60000UL / bpm;
2549 return _beatPulse();
2550 }
2551#undef bpm
2552#undef MIN_BASE_LEVEL
2553#undef MIN_PEAK_LEVEL
2554#undef PEAK_HOLD_TIME
2555#undef bpmPtr
2556#undef currentLevel
2557#undef peakLevel
2558#undef beat
2559#undef lastBeat
2560#undef lastRandomUpdate
2561#undef prevPeakUpdate
2562
2563#define blockLength _param1
2564#define blockSpacing _param2
2565#define requestedNumBlocks _param3
2566#define maxBlocks _param4
2567#define emitIndex _param5
2568#define emittedBlocksCount _counter1
2569#define emitCooldown _counter2
2571 {
2573 {
2574 _trailingSpacePending = false;
2575 return false;
2576 }
2577 for (uint8_t i = 0; i < maxBlocks; ++i)
2578 {
2579 if (_animationWorkspace[i].active)
2580 {
2581 _trailingSpacePending = false;
2582 return false;
2583 }
2584 }
2585 if (!_waitTrailingSpace || blockSpacing == 0)
2586 return true;
2588 {
2589 // These block animations do not otherwise use the second timer.
2591 _trailingSpacePending = true;
2592 }
2593 return _currentTime - _lastUpdate2 >= static_cast<uint32_t>(blockSpacing) * _updateIntv1;
2594 }
2595
2596 void _emitBlock(int8_t pos)
2597 {
2598 if (!_animationWorkspace || maxBlocks < 1)
2599 return;
2600
2601 if (emitCooldown > 0)
2602 {
2603 emitCooldown--;
2604 return;
2605 }
2606
2608 return;
2609
2610 for (uint8_t i = 0; i < maxBlocks; ++i)
2611 {
2612 uint8_t idx = (emitIndex + i) % maxBlocks;
2613 Block &b = _animationWorkspace[idx];
2614
2615 if (!b.active)
2616 {
2617 b.position = pos;
2618 b.active = true;
2620
2622 emitIndex = (emitIndex + 1) % maxBlocks;
2623 return;
2624 }
2625 }
2626 }
2627
2628 static inline int8_t _calculateSwitchPosition(int8_t pos, uint8_t blockL, uint8_t range)
2629 {
2630 // Compute switched block head position
2631 return (range - 1) - pos + (blockL - 1);
2632 }
2633
2635 {
2636 const int8_t emitInterval = blockLength + blockSpacing;
2637
2638 int8_t closestSwp = -127; // Track the nearest visible block from new side
2639
2640 for (uint8_t i = 0; i < maxBlocks; ++i)
2641 {
2642 Block &b = _animationWorkspace[i];
2643
2644 if (!b.active)
2645 continue;
2646
2647 int8_t p = b.position; // head position before switching
2648
2649 // Step 1: compute switched block head position
2650 int8_t swp = _calculateSwitchPosition(p, blockLength, range);
2651
2652 b.position = swp;
2653
2654 // Step 2: if out of visible range, deactivate block
2655 if (swp < 0)
2656 {
2657 b.active = false;
2658 continue;
2659 }
2660
2661 // // Step 3: Keep the first (closest to emit side) visible block
2662 if (closestSwp == -127 || swp < closestSwp)
2663 closestSwp = swp;
2664 }
2665
2666 if (closestSwp >= 0)
2667 return (emitInterval - 1) - closestSwp;
2668 else
2669 return 0; // fallback if no visible block found
2670 }
2671
2673 {
2674 const uint8_t center = _segsNum / 2;
2675
2676 if (_init)
2677 {
2678 _init = false;
2679 _trailingSpacePending = false;
2681
2683 emitCooldown = 0;
2684
2685 if (!_animLogicSet)
2687
2689 return false;
2690 }
2691
2693 {
2697 }
2698
2700 {
2702 // _frameCounter++;
2703 _barMeter.clear();
2704
2706 _emitBlock(-1); // The new block moves to position 0 on its first update.
2707
2708 for (uint8_t i = 0; i < maxBlocks; ++i)
2709 {
2710 Block &b = _animationWorkspace[i];
2711 if (!b.active)
2712 continue;
2713
2714 b.position++;
2715
2716 // Only render to lower half (0 to _centerTracker - 1)
2717 uint16_t clamped = _maxValue(0, (int16_t)b.position + 1);
2718 uint8_t pixelsVisible = _minValue(blockLength, (uint8_t)clamped);
2719 for (uint8_t j = 0; j < pixelsVisible; ++j)
2720 {
2721 int8_t pos = _animRenderLogicIsInverted ? center - 1 - b.position : b.position;
2722 int8_t tailOffset = _animRenderLogicIsInverted ? pos + j : pos - j;
2723
2724 if (tailOffset < 0)
2725 continue;
2726
2727 int8_t idx = tailOffset;
2728 if (idx >= center)
2729 continue;
2730
2731 int8_t mirrorIdx = _segsNum - 1 - idx;
2732
2733 if (idx >= 0 && idx < _segsNum)
2734 {
2735 _barMeter.setPixel(idx, true);
2736 }
2737 if (mirrorIdx != idx && mirrorIdx >= 0 && mirrorIdx < _segsNum)
2738 {
2739 _barMeter.setPixel(mirrorIdx, true);
2740 }
2741 }
2742
2743 // Deactivate block if head has passed the visual range
2744 if (b.position >= center - 1 + blockLength)
2745 b.active = false;
2746 }
2747 }
2748 // The animation ends after emission stops and all active blocks have cleared.
2749 return _blocksComplete();
2750 }
2751
2753 {
2754 if (_init)
2755 {
2756 _init = false;
2757 _trailingSpacePending = false;
2759
2761 emitCooldown = 0;
2762
2763 if (!_animLogicSet)
2765
2767 return false;
2768 }
2769
2771 {
2775 }
2776
2778 {
2780 _setAllOff();
2781
2783 _emitBlock(-1); // Start fully off-screen left
2784
2785 for (uint8_t i = 0; i < maxBlocks; ++i)
2786 {
2787 Block &b = _animationWorkspace[i];
2788 if (!b.active)
2789 continue;
2790
2791 b.position++;
2792
2793 for (uint8_t j = 0; j < blockLength; ++j)
2794 {
2795 int8_t pos = _animRenderLogicIsInverted ? (_segsNum - 1) - b.position : b.position;
2796 int8_t tailOffset = _animRenderLogicIsInverted ? pos + j : pos - j;
2797
2798 if (tailOffset < 0)
2799 continue;
2800
2801 int8_t idx = tailOffset;
2802 if (idx >= _segsNum)
2803 continue;
2804
2805 if (idx >= 0 && idx < _segsNum)
2806 _barMeter.setPixel(_corrPixelToDir(idx), true);
2807 }
2808
2809 // Deactivate block if head has passed the visual range
2810 if (b.position >= (_segsNum - 1) + blockLength)
2811 b.active = false;
2812 }
2813 }
2814
2815 return _blocksComplete();
2816 }
2817
2818#define stackLevel _ledTracker1
2820 {
2821 const uint8_t blockInterval = blockLength + blockSpacing;
2822 bool hasActive = false; // active block tracker
2823
2824 if (_init)
2825 {
2826 _init = false;
2827
2828 if (!_animLogicSet)
2830
2831 maxBlocks = 1;
2833
2834 emitIndex = 0;
2835 emitCooldown = 0;
2836
2837 stackLevel = 0;
2839 {
2840 _barMeter.clear();
2841 // Falling blocks
2842 // stackLevel = 0;
2843 }
2844 else
2845 {
2846 // Flying upward - fill pattern first
2847 while (stackLevel < _segsNum)
2848 stackLevel += blockInterval;
2849 for (uint8_t i = 0; i < stackLevel; i++)
2850 {
2851 if ((i % blockInterval) < blockLength)
2852 _barMeter.setPixel(_corrPixelToDir(i), true);
2853 else
2854 _barMeter.setPixel(_corrPixelToDir(i), false);
2855 }
2856 }
2857
2858 return false;
2859 }
2860
2862 {
2865 stackLevel += blockInterval;
2866 else
2867 stackLevel -= blockInterval;
2868 }
2869
2871 {
2873
2874 // Only clear pixels of active blocks, not the whole bar
2875 for (uint8_t i = 0; i < maxBlocks; ++i)
2876 {
2877 Block &b = _animationWorkspace[i];
2878 if (!b.active)
2879 continue;
2880
2881 for (uint8_t j = 0; j < blockLength; ++j)
2882 {
2883 int8_t seg = b.position + j;
2884 if (seg >= 0 && seg < _segsNum)
2885 _barMeter.setPixel(_corrPixelToDir(seg), false);
2886 }
2887 }
2888
2889 // Emit one block if none active
2890 for (uint8_t i = 0; i < maxBlocks; ++i)
2891 {
2892 if (_animationWorkspace[i].active)
2893 {
2894 hasActive = true;
2895 break;
2896 }
2897 }
2898 if (!hasActive)
2899 {
2900 emitCooldown = 0; // emit blocks on demand
2902 _emitBlock(_segsNum); // fall from top
2903 else if (_animRenderLogicIsInverted && stackLevel >= 0)
2904 _emitBlock(stackLevel - blockInterval);
2905 }
2906
2907 // Update and draw all active blocks
2908 for (uint8_t i = 0; i < maxBlocks; ++i)
2909 {
2910 Block &b = _animationWorkspace[i];
2911 if (!b.active)
2912 continue;
2913
2914 // Clear trailing pixel of previous frame
2915 int8_t clearPos = b.position;
2916 if (clearPos >= 0 && clearPos < _segsNum)
2917 _barMeter.setPixel(_corrPixelToDir(clearPos), false);
2918
2919 // Move block
2921 b.position--;
2922 else
2923 b.position++;
2924
2925 for (uint8_t j = 0; j < blockLength; ++j)
2926 {
2927 int8_t seg = b.position + j;
2928 if (seg >= 0 && seg < _segsNum)
2929 _barMeter.setPixel(_corrPixelToDir(seg), true);
2930 }
2931
2933 {
2934 if (b.position <= stackLevel)
2935 {
2936 stackLevel += blockInterval;
2937 b.active = false;
2938 }
2939 }
2940 else
2941 {
2942 if (b.position >= _segsNum)
2943 {
2944 stackLevel -= blockInterval;
2945 b.active = false;
2946 }
2947 }
2948 }
2949
2950 // Draw base for stacking
2951 if (stackLevel == 0)
2952 {
2953 _barMeter.setPixel(_corrPixelToDir(0), false);
2954 }
2956 {
2957 for (int8_t i = 0; i < stackLevel - blockInterval; ++i)
2958 {
2959 if ((i % blockInterval) < blockLength)
2960 _barMeter.setPixel(_corrPixelToDir(i), true);
2961 else
2962 _barMeter.setPixel(_corrPixelToDir(i), false);
2963 }
2964 }
2965 else
2966 {
2967 for (uint8_t i = 0; i < stackLevel - blockInterval; i++)
2968 {
2969 if ((i % blockInterval) < blockLength)
2970 _barMeter.setPixel(_corrPixelToDir(i), true);
2971 else
2972 _barMeter.setPixel(_corrPixelToDir(i), false);
2973 }
2974 }
2976 {
2977 if (stackLevel >= _segsNum - 1 && !hasActive)
2978 return true;
2979 }
2980 else
2981 {
2982 if (stackLevel <= 0 && !hasActive)
2983 return true;
2984 }
2985 }
2986 return false;
2987 }
2988#undef stackLevel
2989#undef blockLength
2990#undef blockSpacing
2991#undef requestedNumBlocks
2992#undef maxBlocks
2993#undef emitIndex
2994#undef emittedBlocksCount
2995#undef emitCooldown
2996
2997#define smoothingFactor _param1
2999 {
3000 if (!_signalPtr1())
3001 {
3002 _setAllOff();
3003 return true;
3004 }
3005
3006 if (_init)
3007 {
3008 _init = false;
3009
3010 if (!_animLogicSet)
3012
3015 _setAllOff();
3016 return false;
3017 }
3018
3019 // Signal sampling
3021 {
3022 uint16_t raw = *_signalPtr1();
3025 }
3026
3028 {
3030
3032
3033 for (uint8_t i = 0; i < _segsNum; i++)
3034 {
3035 _barMeter.setPixel(_corrPixelToDir(i), i < level);
3036 }
3037 }
3038 return false; // continuous animation
3039 }
3041 {
3042 if (!_signalPtr1())
3043 {
3044 _setAllOff();
3045 return true;
3046 }
3047
3048 if (_init)
3049 {
3050 _init = false;
3051
3052 if (!_animLogicSet)
3054
3057 _setAllOff();
3058 return false;
3059 }
3060
3061 // Signal sampling
3063 {
3064 uint16_t raw = *_signalPtr1();
3067 }
3068
3070 {
3072
3074 uint8_t pointer = _getMappedSignal(*_signalPtr1(), _minMap, _maxMap, 0, _segsNum);
3075
3076 // First, fill up to moving average
3077 for (uint8_t i = 0; i < _segsNum; i++)
3078 {
3079 _barMeter.setPixel(_corrPixelToDir(i), i < avg);
3080 }
3081
3082 // Then selectively clear area around pointer
3083 if (pointer < avg && pointer > 0)
3084 _barMeter.setPixel(_corrPixelToDir(pointer - 1), false); // clear just below if pointer is inside the fill
3085
3086 if (pointer < avg - 2)
3087 _barMeter.setPixel(_corrPixelToDir(pointer + 1), false); // clear just above
3088
3089 // Finally, show the pointer
3090 _barMeter.setPixel(_corrPixelToDir(pointer), true);
3091 }
3092 return false; // continuous animation
3093 }
3095 {
3096 if (!_signalPtr1() && !_signalPtr2())
3097 {
3098 _setAllOff();
3099 return true;
3100 }
3101
3102 if (_init)
3103 {
3104 _init = false;
3105
3106 if (!_animLogicSet)
3108
3110 if (_signalPtr2())
3113 _setAllOff();
3114 return false;
3115 }
3116
3119
3120 // Signals sampling
3122 {
3124
3125 uint16_t raw1 = *_signalPtr1();
3126 _smoothedValue1 = (uint16_t)(((uint32_t)smoothingFactor * raw1 + (uint32_t)(100 - smoothingFactor) * _smoothedValue1) / 100);
3127
3128 if (_signalPtr2())
3129 {
3130 uint16_t raw2 = *_signalPtr2();
3131 _smoothedValue2 = (uint16_t)(((uint32_t)smoothingFactor * raw2 + (uint32_t)(100 - smoothingFactor) * _smoothedValue2) / 100);
3132 }
3133 }
3134
3136 {
3138
3139 uint16_t raw1 = *_signalPtr1();
3140 _smoothedValue1 = (uint16_t)(((uint32_t)smoothingFactor * raw1 + (uint32_t)(100 - smoothingFactor) * _smoothedValue1) / 100);
3141 uint8_t level1 = _getMappedSignal(_smoothedValue1, _minMap, _maxMap, 0, _segsNum / 2);
3142
3143 uint8_t level2 = level1;
3144 if (_signalPtr2())
3146
3147 for (uint8_t i = 0; i < _segsNum; i++)
3148 {
3149 //_barMeter.setPixel(_corrPixelToDirForHalfRange(i), i >= (_segsNum / 2 - 1) - level1 && i <= ((_segsNum / 2) + level2));
3151 _barMeter.setPixel(i, i < (_segsNum / 2 - 1) - level1 || i > ((_segsNum / 2) + level2));
3152 else
3153 _barMeter.setPixel(i, i >= (_segsNum / 2 - 1) - level1 && i <= ((_segsNum / 2) + level2));
3154 }
3155 }
3156 return false; // continuous animation
3157 }
3158
3159#define currentLevel _ledTracker1
3160#define peakLevel _ledTracker3
3161#define baseUpdate _updateIntv1
3162#define lastBaseUpdate _lastUpdate1
3163#define peakHoldTime _updateIntv3
3164#define lastPeakUpdate _lastUpdate3
3165 bool _followSignalFloatingPeak() // bouncing from bottom (maybe like a volume meter with the music)
3166 {
3167
3168 if (!_signalPtr1())
3169 {
3170 _setAllOff();
3171 return true;
3172 }
3173
3174 if (_init)
3175 {
3176 _init = false;
3177
3178 if (!_animLogicSet)
3180
3181 _setAllOff();
3185 currentLevel = 0;
3186 peakLevel = 0;
3187 return false;
3188 }
3189
3190 // Signal smoothing update
3192 {
3194
3195 uint16_t raw = *_signalPtr1();
3198 }
3199
3200 // Pixel + peak update
3202 {
3204
3205 // Peak logic
3206 if (currentLevel > peakLevel)
3207 {
3209 lastPeakUpdate = _currentTime; // Reset decay timer
3210 }
3212 {
3213 peakLevel--; // Slowly drop peak
3214 lastPeakUpdate = _currentTime; // Reset decay timer
3215 }
3216
3217 // Draw bar
3218 for (uint8_t i = 0; i < _segsNum; i++)
3219 {
3220 _barMeter.setPixel(_corrPixelToDir(i), (i <= currentLevel));
3221 }
3222
3223 // Draw peak
3224 if (peakLevel < _segsNum)
3225 {
3226 _barMeter.setPixel(_corrPixelToDir(peakLevel), true);
3227 }
3228 }
3229 return false; // Continuous pulse animation, never auto-terminates
3230 }
3231#undef currentLevel
3232#undef peakLevel
3233#undef baseUpdate
3234#undef lastBaseUpdate
3235#undef peakHoldTime
3236#undef lastPeakUpdate
3237
3238#undef smoothingFactor
3239
3241 {
3242 uint8_t *pixelOrder = _randomOrder();
3243 if (_segsNum == 0 || !pixelOrder)
3244 return true;
3245
3246 if (_init)
3247 {
3248 _init = false;
3249
3250 if (!_animLogicSet)
3252
3254 _setAllOn();
3255 else
3256 _setAllOff();
3257
3258 for (uint8_t i = 0; i < _segsNum; ++i)
3259 pixelOrder[i] = i;
3260
3261 for (uint8_t i = _segsNum - 1; i > 0; --i)
3262 {
3263 uint8_t j = random(0, i + 1);
3264 uint8_t tmp = pixelOrder[i];
3265 pixelOrder[i] = pixelOrder[j];
3266 pixelOrder[j] = tmp;
3267 }
3268
3269 _randomCursor = 0;
3271
3272 return false;
3273 }
3274
3276 {
3278
3279 uint8_t retries = 0;
3280 while (_randomCursor < _segsNum && retries++ < _segsNum - 1)
3281 {
3282 uint8_t seg = pixelOrder[_randomCursor];
3283 bool currentState = _barMeter.getPixelState(seg);
3284 bool shouldChange = (!_AnimInitLogicIsInverted && !currentState) || (_AnimInitLogicIsInverted && currentState);
3285
3286 if (shouldChange)
3287 {
3288 _barMeter.setPixel(seg, !_AnimInitLogicIsInverted);
3289 _randomCursor++;
3290 break; // Change only one pixel per interval
3291 }
3292 else
3293 {
3294 _randomCursor++; // Move on to next even if it doesn't need change
3295 }
3296 }
3297 }
3298 if (_randomCursor >= _segsNum)
3299 {
3300 return true;
3301 }
3302 return false;
3303 }
3304};
#define stackLevel
#define peakHoldTime
#define MIN_BASE_LEVEL
#define peakLevel
#define PEAK_HOLD_TIME
#define emittedBlocksCount
#define emitIndex
#define lastBaseUpdate
#define beat
#define lastRandomUpdate
#define lastPeakUpdate
#define maxBlocks
#define smoothingFactor
#define currentLevel
#define blockLength
#define SBK_BARDRIVE_QUEUE_CAPACITY
#define lastBeat
#define requestedNumBlocks
#define prevPeakUpdate
#define emitCooldown
#define blockSpacing
#define bpm
#define bpmPtr
#define MIN_PEAK_LEVEL
bool isNonInvertingLogicAnim()
Return true if the current animation does not support logic inversion.
SBK_BarMeterAnimations & fillUpIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr=nullptr)
Start animation that fills the bar upwards at a fixed interval with live percent tracking.
SBK_BarMeterAnimations & beatPulse(uint8_t bpm=116)
Start a beat pulse animation synchronized to a constant BPM.
SBK_BarMeterAnimations & bounceFillUpIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr=nullptr)
Start animation that fills/empties (bounces) bar upward with live range.
SBK_BarMeterAnimations & fillDownDur(uint16_t duration, uint8_t maxPercent=100, uint8_t minPercent=0)
Start animation that fills the bar downwards over a specified duration.
SBK_BarMeterAnimations & fillUpIntv(uint16_t updateIntv, uint8_t maxPercent=100, uint8_t minPercent=0)
Start animation that fills the bar upwards at a fixed interval.
SBK_BarMeterAnimations & emptyUpIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr=nullptr)
Start animation that empties the bar from bottom to top at fixed intervals with live range.
SBK_BarMeterAnimations & loop()
Set animation to automatically loop when complete.
uint8_t currentQueueIndex() const
Return the physical queue slot currently playing, or NO_QUEUE_INDEX.
SBK_BarMeterAnimations & bounceFillFromEdgesDur(uint16_t duration, uint8_t maxPercent=100, uint8_t minPercent=0, uint16_t fillIntv=0)
Bounce fill from both edges inward with fixed range.
SBK_BarMeterAnimations & waitTrailingSpace(bool enabled=true)
Optionally wait one block spacing after the final block clears.
static int8_t _calculateSwitchPosition(int8_t pos, uint8_t blockL, uint8_t range)
SBK_BarMeterAnimations & setDir(bool newDirection)
Explicitly set the animation rendering direction.
SBK_BarMeterAnimations & bounceFillUpDur(uint16_t duration, uint8_t maxPercent=100, uint8_t minPercent=0, uint16_t fillIntv=0)
Start animation that fills/empties (bounces) bar upward with fixed range.
SBK_BarMeterAnimations & beatPulse(uint8_t *bpmPtr)
Start a beat pulse animation synchronized to a live BPM value.
SBK_BarMeterAnimations(BarMeterT &barMeter)
Construct an animation controller linked to a bar meter.
SBK_BarMeterAnimations & fillDownIntv(uint16_t updateIntv, uint8_t maxPercent=100, uint8_t minPercent=0)
Start animation that fills the bar downwards at a fixed interval.
SBK_BarMeterAnimations & toggleLogic()
Toggle rendering logic (inverted vs normal), example fill logic become empty logic.
SBK_BarMeterAnimations & loopQueue()
Repeat the entire queue after its final entry completes.
SBK_BarMeterAnimations & followSignalWithPointer(const uint16_t *sigPtr, uint16_t updateIntv=100, uint16_t minMap=0, uint16_t maxMap=1023, uint8_t smoothingFactor=30, uint16_t samplingIntv=5)
Follow signal with smoothing and display a pointer.
SBK_BarMeterAnimations & toggleDir()
Toggle the current animation rendering direction.
SBK_BarMeterAnimations & emptyDownIntv(uint16_t updateIntv, uint8_t maxPercent=100, uint8_t minPercent=0)
Start animation that empties the bar from top to bottom at fixed intervals.
void _loadQueueEntry(const QueueEntry &entry, bool forceReset=false)
const uint16_t * _signalPtr1() const
SBK_BarMeterAnimations & reverseDir()
Reverse the rendering direction compared to the original one.
void _mapMinMaxTracker(uint8_t minP, uint8_t maxP, uint8_t minR, uint8_t maxR)
SBK_BarMeterAnimations & pause(uint32_t syncTime=millis())
Freeze animation progression and all queue/animation timers.
void _mapMinMaxTrackerFromPtr(const uint8_t *minPercPtr, const uint8_t *maxPercPtr, int8_t minR, uint8_t maxR)
SBK_BarMeterAnimations & bounceFillDownDur(uint16_t duration, uint8_t maxPercent=100, uint8_t minPercent=0, uint16_t fillIntv=0)
Start animation that fills/empties (bounces) bar downward.
SBK_BarMeterAnimations & setUpdateInterval(uint16_t interval)
Change the primary update interval without restarting the animation.
SBK_BarMeterAnimations & followSignalSmooth(const uint16_t *sigPtr, uint16_t updateIntv=100, uint16_t minMap=0, uint16_t maxMap=1023, uint8_t smoothingFactor=30, uint16_t samplingIntv=5)
Follow analog signal with smoothing for fill animation.
static constexpr uint8_t MAX_BLOCK_CAPACITY
const uint8_t * _liveBpmPtr() const
SBK_BarMeterAnimations & followSignalFloatingPeak(const uint16_t *sigPtr, uint8_t peakHoldTime=20, uint16_t updateIntv=100, uint16_t minMap=0, uint16_t maxMap=1023, uint8_t smoothingFactor=30, uint16_t samplingIntv=5)
Follow an analog signal with smoothing and floating peak indicator.
static auto _minValue(A a, B b) -> decltype(true ? A() :B())
const uint16_t * _signalPtr2() const
SBK_BarMeterAnimations & setAll(bool onff)
Set all pixels to the specified state.
SBK_BarMeterAnimations & downStackingBlocks(uint16_t intv=50, uint8_t blockLength=1, uint8_t blockSpacing=0)
Drop blocks from top and stack from bottom up.
void _resetBlocks(uint8_t &blockCapacity)
uint8_t _corrPixelToDirForHalfRange(uint8_t pixel)
const uint8_t * _percentMaxPtr() const
SBK_BarMeterAnimations & emptyDownDur(uint16_t duration, uint8_t maxPercent=100, uint8_t minPercent=0)
Start animation that empties the bar from top to bottom over a duration.
SBK_BarMeterAnimations & blinkPixel(uint8_t index, uint16_t interval=500, uint8_t blinkCount=0)
Blink one pixel with equal ON and OFF intervals.
bool queueOverflowed() const
Return true if an enqueue operation exceeded the configured capacity.
static constexpr uint8_t QUEUE_CAPACITY
SBK_BarMeterAnimations & setLogic(bool newLogic)
Set rendering logic (true = inverted logic, false = normal logic), example fill logic become empty lo...
SBK_BarMeterAnimations & downUnstackingBlocks(uint16_t intv=50, uint8_t blockLength=1, uint8_t blockSpacing=0)
Drop blocks from top and unstack from bottom up.
bool isQueueRunning() const
Return true while an animation queue is being processed.
void _startNextQueuedAnimation(bool forceReset=false)
SBK_BarMeterAnimations & resetDir()
Reset direction to the original setting.
SBK_BarMeterAnimations & setAllOff()
Set all pixels OFF.
SBK_BarMeterAnimations & animInit()
Mark animation as ready to reinitialize in next update cycle.
SBK_BarMeterAnimations & randomEmpty(uint16_t interval=30)
Randomly turn off pixels until the bar is empty.
bool isQueueIndexPlaying(uint8_t index) const
Return true when the requested physical queue slot is currently playing.
SBK_BarMeterAnimations & stopBlockEmission()
Stop emitting new blocks during compatible block animations.
SBK_BarMeterAnimations & blinkPixel(uint8_t index, uint16_t onInterval, uint16_t offInterval, uint8_t blinkCount)
Blink one pixel with independent ON and OFF intervals.
SBK_BarMeterAnimations & upStackingBlocks(uint16_t intv=50, uint8_t blockLength=1, uint8_t blockSpacing=0)
Launch blocks from bottom and stack at the top.
bool isLogicInverted()
Returns true if current logic is different from original logic.
SBK_BarMeterAnimations & enqueue()
Add the currently configured animation to the fixed-capacity queue.
SBK_BarMeterAnimations & enqueueReverseAnim()
Queue a state-preserving logic reversal of the preceding entry.
SBK_BarMeterAnimations & followDualSignalFromCenter(const uint16_t *sig1Ptr, uint16_t updateIntv=100, const uint16_t *sig2Ptr=nullptr, uint16_t minMap=0, uint16_t maxMap=1023, uint8_t smoothingFactor=30, uint16_t samplingIntv=5)
Smoothly fills the lower half and upper half of the bar meter independently from center,...
SBK_BarMeterAnimations & explodingBlocks(uint16_t intv=50, uint8_t blockLength=2, uint8_t blockSpacing=1, uint8_t numBlocks=0)
Emit mirrored blocks from center outward.
bool animPendingLoop()
Detect whether animation has completed and is about to loop.
void _captureQueueEntry(QueueEntry &entry, bool resetRuntimeState, uint32_t duration)
static void _correctSwapOrEqualMinMax(uint16_t &minVal, uint16_t &maxVal)
bool isBlockEmissionEnabled() const
Query if block emission is enabled.
SBK_BarMeterAnimations & noLoop()
Disable auto-looping.
SBK_BarMeterAnimations & emptyDownIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr=nullptr)
Start animation that empties the bar from top to bottom at fixed intervals with live range.
SBK_BarMeterAnimations & stop()
Terminate the animation, loop, and queue, then clear mapped pixels.
int8_t _calculateSwitchedEmitTickCounter(uint8_t range)
SBK_BarMeterAnimations & upUnstackingBlocks(uint16_t intv=50, uint8_t blockLength=1, uint8_t blockSpacing=0)
Launch blocks upward and unstack from top.
SBK_BarMeterAnimations & wait(uint16_t duration)
Wait without blocking or changing pixel states; waits never loop.
uint8_t _corrPixelToDir(uint8_t pixel)
SBK_BarMeterAnimations & bounceFillFromCenterIntv(uint16_t fillIntv=25, uint16_t emptyIntv=25, uint8_t maxPercent=100, uint8_t minPercent=0)
Bounce fill from center outward with fixed range.
SBK_BarMeterAnimations & bounceFillFromCenterIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr=nullptr)
Bounce fill from center outward with live percent range tracking.
const uint8_t * _percentMinPtr() const
bool update(uint32_t syncTime=millis())
Advance the animation logic based on the current millis().
SBK_BarMeterAnimations & collidingBlocks(uint16_t intv=50, uint8_t blockLength=2, uint8_t blockSpacing=1, uint8_t numBlocks=0)
Emit mirrored blocks from edge to center.
QueueEntry _queue[QUEUE_CAPACITY]
SBK_BarMeterAnimations & noLoopQueue()
Disable repetition; finish the current queue pass before stopping.
SBK_BarMeterAnimations & emptyUpDur(uint16_t duration, uint8_t maxPercent=100, uint8_t minPercent=0)
Start animation that empties the bar from bottom to top over a duration.
SBK_BarMeterAnimations & resetLogic()
Reset logic to initial (default) value.
bool isLoopEnabled() const
Query if animation loop is enabled.
void setSegsNum(uint8_t n)
Set the total number of segments the animation logic should handle.
SBK_BarMeterAnimations & startQueue()
Start queued playback using the current queue-loop setting.
static void _normalizePercentRange(uint8_t &minP, uint8_t &maxP)
SBK_BarMeterAnimations & setAllOn()
Set all pixels ON.
SBK_BarMeterAnimations & stopBlockEmissionAfter(uint32_t duration)
Stop new block emission after an animation has run for a duration.
bool(SBK_BarMeterAnimations::*)() AnimUpdateFn
bool isRunning() const
Query if animation is currently active.
SBK_BarMeterAnimations & bounceFillDownIntv(uint16_t fillIntv=10, uint16_t emptyIntv=10, uint8_t maxPercent=100, uint8_t minPercent=0)
Start downwardfill/empty (bounce) with fixed percent range.
SBK_BarMeterAnimations & invertLogic()
Reverse logic compared to initial state Logic refer to behavior, example : fill/empty,...
SBK_BarMeterAnimations & scrollingDownBlocks(uint16_t intv=50, uint8_t blockLength=2, uint8_t blockSpacing=1, uint8_t numBlocks=0)
Scroll blocks downward.
SBK_BarMeterAnimations & emptyUpIntv(uint16_t updateIntv, uint8_t maxPercent=100, uint8_t minPercent=0)
Start animation that empties the bar from bottom to top at fixed intervals.
SBK_BarMeterAnimations & bounceFillDownIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr=nullptr)
Start downward fill/empty (bounce) with live percent range tracking.
SBK_BarMeterAnimations & forTime(uint32_t duration)
Limit a direct or queued animation to a maximum run time.
SBK_BarMeterAnimations & bounceFillFromCenterDur(uint16_t duration, uint8_t maxPercent=100, uint8_t minPercent=0, uint16_t fillIntv=0)
Bounce fill from center outward with fixed range.
SBK_BarMeterAnimations & skipCurrent()
Abandon the active animation and immediately start the next queue entry.
SBK_BarMeterAnimations & fillDownIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr=nullptr)
Start animation that fills the bar downwards at a fixed interval with live percent tracking.
static uint8_t _getMappedSignal(uint16_t sig, uint16_t minM, uint16_t maxM, uint8_t minR, uint8_t maxR)
SBK_BarMeterAnimations & resumeBlockEmission()
Resume emitting new blocks during compatible block animations.
SBK_BarMeterAnimations & clearQueue()
Discard the queue and disable queue looping without stopping the current animation.
static auto _maxValue(A a, B b) -> decltype(true ? A() :B())
SBK_BarMeterAnimations & randomFill(uint16_t interval=30)
Randomly light up pixels until the bar is full.
uint8_t queuedAnimations() const
Return the number of animations waiting behind the active one.
SBK_BarMeterAnimations & bounceFillUpIntv(uint16_t fillIntv=10, uint16_t emptyIntv=10, uint8_t maxPercent=100, uint8_t minPercent=0)
Start animation that fills/empties (bounces) bar upward with fixed range.
SBK_BarMeterAnimations & fillUpDur(uint16_t duration, uint8_t maxPercent=100, uint8_t minPercent=0)
Start animation that fills the bar upwards over a specified duration.
SBK_BarMeterAnimations & bounceFillFromEdgesIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr=nullptr)
Bounce fill from both edges inward with live percent range tracking.
static constexpr uint8_t NO_QUEUE_INDEX
SBK_BarMeterAnimations & scrollingUpBlocks(uint16_t intv=50, uint8_t blockLength=2, uint8_t blockSpacing=1, uint8_t numBlocks=0)
Scroll blocks upward.
SBK_BarMeterAnimations & resume(uint32_t syncTime=millis())
Resume with the same playback time remaining as when paused.
SBK_BarMeterAnimations & enqueueFor(uint32_t duration)
Queue the configured animation for a fixed amount of time.
SBK_BarMeterAnimations & followDualSignalFromEdges(const uint16_t *sig1Ptr, uint16_t updateIntv=100, const uint16_t *sig2Ptr=nullptr, uint16_t minMap=0, uint16_t maxMap=1023, uint8_t smoothingFactor=30, uint16_t samplingIntv=5)
Smoothly fills the lower half and upper half of the bar meter independently from center,...
bool isDirectionReversed()
Returns true if direction is reversed compared to the original.
SBK_BarMeterAnimations & bounceFillFromEdgesIntv(uint16_t fillIntv=25, uint16_t emptyIntv=25, uint8_t maxPercent=100, uint8_t minPercent=0)
Bounce fill from both edges inward with fix range.
bool isPaused() const
Query if animation is paused.
bool animInitLogicIsInverted
bool resetRuntimeState
bool waitTrailingSpace
bool isNonInvertingLogicAnim
uint8_t param4
uint8_t param3
uint16_t maxMap
uint32_t blockEmissionStopAfter
uint16_t updateIntv2
const void * valuePtr2
const void * valuePtr1
bool animInitDirIsReversed
AnimUpdateFn function
bool mirrorHalfRangeDir
uint8_t param5
bool animDirSet
uint32_t duration
bool animRenderDirIsReversed
bool animLogicSet
bool usePtr
int8_t minTracker
bool animRenderLogicIsInverted
bool loop
uint8_t param1
uint16_t minMap
int8_t maxTracker
uint8_t param2
uint16_t updateIntv1
uint16_t updateIntv3
bool emittingBlocksEnabled