SBK_BarDrive Library 2.1.0
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 {
84 _segsNum = min(n, _segmentCapacity);
85 _maxTracker = _segsNum > 0 ? _segsNum - 1 : 0;
86 }
87
93 bool update(uint32_t syncTime = millis())
94 {
95 _currentTime = syncTime;
96
98 return _queueActive;
99
100 if ((this->*_currentFunc)())
101 {
102 if (_loop)
103 {
104 if (_skipPending)
105 _isLoopingNow = false;
106 else
107 {
108 _isLoopingNow = true; // mark it so user can react
109 _init = true;
110 }
111 }
112 else
113 {
114 if (_queueActive && _queueCount > 0)
116 else
117 {
118 _isLoopingNow = false;
119 _isRunning = false;
120 _currentFunc = nullptr;
121 _queueActive = false;
123 }
124 }
125 }
126 return _isRunning;
127 }
128
131 {
132 _init = true;
133 return *this;
134 }
135
138 {
139 _isPaused = true;
140 return *this;
141 }
142
145 {
146 _isPaused = false;
147 return *this;
148 }
149
152 {
153 _isPaused = false;
154 _isRunning = false;
155 _loop = false;
156 _isLoopingNow = false;
157 _skipPending = false;
158 _currentFunc = nullptr;
159 _animLogicSet = false;
160 _init = true;
161 clearQueue();
162 _barMeter.clear();
163 return *this;
164 }
165
175 {
176 if (!_currentFunc)
177 return *this;
178
180 {
183 _queueCount++;
184 }
185 else
186 {
187 _queueOverflow = true;
188 }
189
190 _isRunning = false;
191 _currentFunc = nullptr;
192 return *this;
193 }
194
197 {
198 if (_queueCount > 0)
199 {
200 _queueActive = true;
202 }
203 return *this;
204 }
205
208 {
209 _queueHead = 0;
210 _queueTail = 0;
211 _queueCount = 0;
213 _queueActive = false;
214 _queueOverflow = false;
215 return *this;
216 }
217
225 {
226 if (!_isRunning || !_currentFunc)
227 return *this;
228
229 _loop = false;
230 _isPaused = false;
231 _isLoopingNow = false;
232 _skipPending = false;
233
234 if (_queueActive && _queueCount > 0)
236 else
237 {
238 _isRunning = false;
239 _currentFunc = nullptr;
240 _queueActive = false;
242 }
243 return *this;
244 }
245
247 uint8_t queuedAnimations() const { return _queueCount; }
248
250 bool isQueueRunning() const { return _queueActive; }
251
253 uint8_t currentQueueIndex() const { return _currentQueueIndex; }
254
256 bool isQueueIndexPlaying(uint8_t index) const
257 {
258 return _queueActive && _currentQueueIndex == index;
259 }
260
262 bool queueOverflowed() const { return _queueOverflow; }
263
266 {
267 _loop = true;
268 return *this;
269 }
270
273 {
274 _loop = false;
275 return *this;
276 }
277
279 SBK_BarMeterAnimations &setDir(bool newDirection)
280 {
281 _animRenderDirIsReversed = newDirection;
282 _animDirSet = true;
283 return *this;
284 }
285
293
301
309
315 {
317 return *this;
319 _animLogicSet = true;
320 return *this;
321 }
322
329 {
331 return *this;
333 _animLogicSet = true;
334 return *this;
335 }
336
343 {
345 return *this;
347 _animLogicSet = true;
348 return *this;
349 }
350
357 {
359 return *this;
361 _animLogicSet = false;
362 return *this;
363 }
364
367 {
369 return *this;
370 }
371
374 {
376 return *this;
377 }
378
380 bool isRunning() const { return _isRunning; }
381
383 bool isPaused() const { return _isPaused; }
384
386 bool isLoopEnabled() const { return _loop; }
387
390 {
391 _updateIntv1 = max(static_cast<uint16_t>(1), interval);
392 return *this;
393 }
394
397 {
398 if (_skipPending == true)
399 _skipPending = false;
400 else
401 {
402 if (_isLoopingNow)
403 {
404 _isLoopingNow = false; // auto-clear after read
405 return true;
406 }
407 }
408 return false;
409 }
410
413 {
415 return true;
416 else
417 return false;
418 }
419
422
425 {
427 return true;
428 else
429 return false;
430 }
431
434
435 // Animation starters
440 SBK_BarMeterAnimations &setAll(bool onff) { return onff ? setAllOn() : setAllOff(); }
441
446 {
448 _isRunning = true;
449 _init = true;
450 return *this;
451 }
452
457 {
459 _isRunning = true;
460 _init = true;
461 return *this;
462 }
463
468 SBK_BarMeterAnimations &wait(uint16_t duration)
469 {
470 _updateIntv1 = duration;
471 _isRunning = true;
473 _init = true;
474 return *this;
475 }
476
483 SBK_BarMeterAnimations &blinkPixel(uint8_t index, uint16_t interval = 500, uint8_t blinkCount = 0)
484 {
485 return blinkPixel(index, interval, interval, blinkCount);
486 }
487
495 SBK_BarMeterAnimations &blinkPixel(uint8_t index, uint16_t onInterval, uint16_t offInterval, uint8_t blinkCount)
496 {
497 _param1 = index;
498 _param2 = blinkCount;
499 _updateIntv1 = max(static_cast<uint16_t>(1), onInterval);
500 _updateIntv2 = max(static_cast<uint16_t>(1), offInterval);
502 _usePtr = false;
504 _isRunning = true;
505 _init = true;
506 return *this;
507 }
508
516 SBK_BarMeterAnimations &fillUpDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0)
517 {
521 _usePtr = false;
522 _valuePtr2 = nullptr;
523 _valuePtr1 = nullptr;
524 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
525 _isRunning = true;
526 uint8_t steps = max(1, _maxTracker - _minTracker + 1);
527 _updateIntv1 = max(5, duration / steps);
529 return *this;
530 }
531
539 SBK_BarMeterAnimations &fillUpIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
540 {
544 _usePtr = true;
545 _valuePtr1 = maxPercentPtr;
546 _valuePtr2 = minPercentPtr;
548 _isRunning = true;
549 _updateIntv1 = max(5, updateIntv);
551 return *this;
552 }
553
561 SBK_BarMeterAnimations &fillUpIntv(uint16_t updateIntv, uint8_t maxPercent = 100, uint8_t minPercent = 0)
562 {
566 _usePtr = false;
567 _valuePtr2 = nullptr;
568 _valuePtr1 = nullptr;
569 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
570 _isRunning = true;
571 _updateIntv1 = max(5, updateIntv);
573 return *this;
574 }
575
583 SBK_BarMeterAnimations &fillDownDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0)
584 {
588 _usePtr = false;
589 _valuePtr2 = nullptr;
590 _valuePtr1 = nullptr;
591 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
592 _isRunning = true;
593 uint8_t steps = max(1, _maxTracker - _minTracker + 1);
594 _updateIntv1 = max(5, duration / steps);
596 return *this;
597 }
598
606 SBK_BarMeterAnimations &fillDownIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
607 {
611 _usePtr = true;
612 _valuePtr1 = maxPercentPtr;
613 _valuePtr2 = minPercentPtr;
615 _isRunning = true;
616 uint8_t steps = max(1, _maxTracker - _minTracker + 1);
617 _updateIntv1 = max(5, updateIntv);
619 return *this;
620 }
621
629 SBK_BarMeterAnimations &fillDownIntv(uint16_t updateIntv, uint8_t maxPercent = 100, uint8_t minPercent = 0)
630 {
634 _usePtr = false;
635 _valuePtr2 = nullptr;
636 _valuePtr1 = nullptr;
637 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
638 _isRunning = true;
639 _updateIntv1 = max(5, updateIntv);
641 return *this;
642 }
643
651 SBK_BarMeterAnimations &emptyDownDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0)
652 {
656 _usePtr = false;
657 _valuePtr2 = nullptr;
658 _valuePtr1 = nullptr;
659 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
660 _isRunning = true;
661 uint8_t steps = max(1, _maxTracker - _minTracker + 1);
662 return emptyDownIntv(duration / steps, maxPercent, minPercent);
663 }
664
672 SBK_BarMeterAnimations &emptyDownIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
673 {
677 _usePtr = true;
678 _valuePtr1 = maxPercentPtr;
679 _valuePtr2 = minPercentPtr;
681 _isRunning = true;
682 _updateIntv1 = max(5, updateIntv);
684 return *this;
685 }
686
694 SBK_BarMeterAnimations &emptyDownIntv(uint16_t updateIntv, uint8_t maxPercent = 100, uint8_t minPercent = 0)
695 {
699 _usePtr = false;
700 _valuePtr2 = nullptr;
701 _valuePtr1 = nullptr;
702 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
703 _isRunning = true;
704 _updateIntv1 = max(5, updateIntv);
706 return *this;
707 }
708
716 SBK_BarMeterAnimations &emptyUpDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0)
717 {
721 _usePtr = false;
722 _valuePtr2 = nullptr;
723 _valuePtr1 = nullptr;
724 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
725 _isRunning = true;
726 uint8_t steps = max(1, _maxTracker - _minTracker + 1);
727 _updateIntv1 = max(5, duration / steps);
729 return *this;
730 }
731
739 SBK_BarMeterAnimations &emptyUpIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
740 {
744 _usePtr = true;
745 _valuePtr1 = maxPercentPtr;
746 _valuePtr2 = minPercentPtr;
748 _isRunning = true;
749 _updateIntv1 = max(5, updateIntv);
751 return *this;
752 }
753
761 SBK_BarMeterAnimations &emptyUpIntv(uint16_t updateIntv, uint8_t maxPercent = 100, uint8_t minPercent = 0)
762 {
766 _usePtr = false;
767 _valuePtr2 = nullptr;
768 _valuePtr1 = nullptr;
769 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
770 _isRunning = true;
771 _updateIntv1 = max(5, updateIntv);
773 return *this;
774 }
775
784 SBK_BarMeterAnimations &bounceFillUpDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0, uint16_t fillIntv = 0)
785 {
788 _usePtr = false;
789 _valuePtr2 = nullptr;
790 _valuePtr1 = nullptr;
791 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
792 uint8_t steps = max(1, _maxTracker - _minTracker + 1);
793 uint16_t emptyIntv = fillIntv;
794 if (fillIntv == 0)
795 {
796 fillIntv = max(5, duration) / (2 * steps);
797 emptyIntv = fillIntv;
798 }
799 else
800 {
801 emptyIntv = max(5, duration - fillIntv * steps) / steps;
802 }
803 _updateIntv1 = fillIntv; // Starting interval
804 _updateIntv2 = _updateIntv1; // Fill intv
805 _updateIntv3 = emptyIntv; // Empty intv
806 _sequenceState = 0;
807 _isRunning = true;
809 return *this;
810 }
811
820 SBK_BarMeterAnimations &bounceFillUpIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
821 {
824 _valuePtr1 = maxPercentPtr;
825 _valuePtr2 = minPercentPtr;
826 _usePtr = true;
828 _updateIntv1 = max(5, fillIntv); // Starting interval
829 _updateIntv2 = _updateIntv1; // Fill intv
830 _updateIntv3 = max(5, emptyIntv); // Empty intv
831 _sequenceState = 0;
832 _isRunning = true;
834 return *this;
835 }
836
845 SBK_BarMeterAnimations &bounceFillUpIntv(uint16_t fillIntv = 10, uint16_t emptyIntv = 10, uint8_t maxPercent = 100, uint8_t minPercent = 0)
846 {
850 _usePtr = false;
851 _valuePtr2 = nullptr;
852 _valuePtr1 = nullptr;
853 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
854 _updateIntv1 = max(5, fillIntv); // Starting interval
855 _updateIntv2 = _updateIntv1; // Fill intv
856 _updateIntv3 = max(5, emptyIntv); // Empty intv
857 _sequenceState = 0;
858 _isRunning = true;
860 return *this;
861 }
862
871 SBK_BarMeterAnimations &bounceFillDownDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0, uint16_t fillIntv = 0)
872 {
876 _usePtr = false;
877 _valuePtr2 = nullptr;
878 _valuePtr1 = nullptr;
879 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
880 uint8_t steps = max(1, _maxTracker - _minTracker + 1);
881 uint16_t emptyIntv = fillIntv;
882 if (fillIntv == 0)
883 {
884 fillIntv = max(5, duration) / (2 * steps);
885 emptyIntv = fillIntv;
886 }
887 else
888 {
889 emptyIntv = max(5, duration - fillIntv * steps) / steps;
890 }
891 _updateIntv1 = fillIntv; // Starting interval
892 _updateIntv2 = _updateIntv1; // Fill intv
893 _updateIntv3 = emptyIntv; // Empty intv
894 _sequenceState = 0;
895 _isRunning = true;
897 return *this;
898 }
899
908 SBK_BarMeterAnimations &bounceFillDownIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
909 {
913 _valuePtr1 = maxPercentPtr;
914 _valuePtr2 = minPercentPtr;
915 _usePtr = true;
917 _updateIntv1 = max(5, fillIntv); // Starting interval
918 _updateIntv2 = _updateIntv1; // Fill intv
919 _updateIntv3 = max(5, emptyIntv); // Empty intv
920 _sequenceState = 0;
921 _isRunning = true;
923 return *this;
924 }
925
934 SBK_BarMeterAnimations &bounceFillDownIntv(uint16_t fillIntv = 10, uint16_t emptyIntv = 10, uint8_t maxPercent = 100, uint8_t minPercent = 0)
935 {
939 _usePtr = false;
940 _valuePtr2 = nullptr;
941 _valuePtr1 = nullptr;
942 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
943 _updateIntv1 = max(5, fillIntv); // Starting interval
944 _updateIntv2 = _updateIntv1; // Fill intv
945 _updateIntv3 = max(5, emptyIntv); // Empty intv
946 _sequenceState = 0;
947 _isRunning = true;
949 return *this;
950 }
951
960 SBK_BarMeterAnimations &bounceFillFromCenterDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0, uint16_t fillIntv = 0)
961 {
965 _mirrorHalfRangeDir = false;
966 _usePtr = false;
967 _valuePtr2 = nullptr;
968 _valuePtr1 = nullptr;
969 _mapMinMaxTracker(minPercent, maxPercent, (_segsNum / 2) - 1, 0);
970 uint8_t steps = max(1, _maxTracker - _minTracker + 1);
971 uint16_t emptyIntv = fillIntv;
972 if (fillIntv == 0)
973 {
974 fillIntv = max(5, duration) / (2 * steps);
975 emptyIntv = fillIntv;
976 }
977 else
978 {
979 emptyIntv = max(5, duration - fillIntv * steps) / steps;
980 }
981 _updateIntv1 = fillIntv; // Starting interval
982 _updateIntv2 = _updateIntv1; // Fill intv
983 _updateIntv3 = emptyIntv; // Empty intv
984 _sequenceState = 0;
985 _isRunning = true;
987 return *this;
988 }
989
998 SBK_BarMeterAnimations &bounceFillFromCenterIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
999 {
1003 _mirrorHalfRangeDir = false;
1004 _valuePtr1 = maxPercentPtr;
1005 _valuePtr2 = minPercentPtr;
1006 _usePtr = true;
1007 _mapMinMaxTrackerFromPtr(minPercentPtr, maxPercentPtr, (_segsNum / 2) - 1, 0);
1008 _updateIntv1 = fillIntv; // Starting interval
1009 _updateIntv2 = _updateIntv1; // Fill intv
1010 _updateIntv3 = emptyIntv; // Empty intv
1011 _sequenceState = 0;
1012 _isRunning = true;
1014 return *this;
1015 }
1016
1025 SBK_BarMeterAnimations &bounceFillFromCenterIntv(uint16_t fillIntv = 25, uint16_t emptyIntv = 25, uint8_t maxPercent = 100, uint8_t minPercent = 0)
1026 {
1030 _mirrorHalfRangeDir = false;
1031 _usePtr = false;
1032 _valuePtr2 = nullptr;
1033 _valuePtr1 = nullptr;
1034 _mapMinMaxTracker(minPercent, maxPercent, (_segsNum / 2) - 1, 0);
1035 _updateIntv1 = fillIntv; // Starting interval
1036 _updateIntv2 = _updateIntv1; // Fill intv
1037 _updateIntv3 = emptyIntv; // Empty intv
1038 _sequenceState = 0;
1039 _isRunning = true;
1041 return *this;
1042 }
1043
1052 SBK_BarMeterAnimations &bounceFillFromEdgesDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0, uint16_t fillIntv = 0)
1053 {
1057 _mirrorHalfRangeDir = true;
1058 _usePtr = false;
1059 _valuePtr2 = nullptr;
1060 _valuePtr1 = nullptr;
1061 _mapMinMaxTracker(minPercent, maxPercent, (_segsNum / 2) - 1, 0);
1062 uint8_t steps = max(1, _maxTracker - _minTracker + 1);
1063 uint16_t emptyIntv = fillIntv;
1064 if (fillIntv == 0)
1065 {
1066 fillIntv = max(5, duration) / (2 * steps);
1067 emptyIntv = fillIntv;
1068 }
1069 else
1070 {
1071 emptyIntv = max(5, duration - fillIntv * steps) / steps;
1072 }
1073 _updateIntv1 = fillIntv; // Starting interval
1074 _updateIntv2 = _updateIntv1; // Fill intv
1075 _updateIntv3 = emptyIntv; // Empty intv
1076 _sequenceState = 0;
1077 _isRunning = true;
1079 return *this;
1080 }
1081
1090 SBK_BarMeterAnimations &bounceFillFromEdgesIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
1091 {
1095 _mirrorHalfRangeDir = true;
1096 _valuePtr1 = maxPercentPtr;
1097 _valuePtr2 = minPercentPtr;
1098 _usePtr = true;
1099 _mapMinMaxTrackerFromPtr(minPercentPtr, maxPercentPtr, (_segsNum / 2) - 1, 0);
1100 _updateIntv1 = fillIntv; // Starting interval
1101 _updateIntv2 = _updateIntv1; // Fill intv
1102 _updateIntv3 = emptyIntv; // Empty intv
1103 _sequenceState = 0;
1104 _isRunning = true;
1106 return *this;
1107 }
1108
1117 SBK_BarMeterAnimations &bounceFillFromEdgesIntv(uint16_t fillIntv = 25, uint16_t emptyIntv = 25, uint8_t maxPercent = 100, uint8_t minPercent = 0)
1118 {
1122 _mirrorHalfRangeDir = true;
1123 _usePtr = false;
1124 _valuePtr2 = nullptr;
1125 _valuePtr1 = nullptr;
1126 _mapMinMaxTracker(minPercent, maxPercent, (_segsNum / 2) - 1, 0);
1127 _updateIntv1 = fillIntv; // Starting interval
1128 _updateIntv2 = _updateIntv1; // Fill intv
1129 _updateIntv3 = emptyIntv; // Empty intv
1130 _sequenceState = 0;
1131 _isRunning = true;
1133 return *this;
1134 }
1135
1141 SBK_BarMeterAnimations &beatPulse(uint8_t *bpmPtr) // bouncing from bottom (maybe like a volume meter with the music)
1142 {
1144 _updateIntv1 = 60000UL / (bpmPtr ? max(1, *bpmPtr) : 116);
1145 _param2 = min(35 * (_segsNum - 1) / 100, 255); // MIN_BASE_LEVEL
1146 _param3 = min(67 * (_segsNum - 1) / 100, 255); // MIN_PEAK_LEVEL
1147 _param4 = 150; // PEAK_HOLD_TIME
1148 _usePtr = true;
1152
1153 _isRunning = true;
1155 _init = true;
1156 return *this;
1157 }
1158
1164 SBK_BarMeterAnimations &beatPulse(uint8_t bpm = 116) // bouncing from bottom (maybe like a volume meter with the music)
1165 {
1166 _param1 = max(1, bpm);
1167 _updateIntv1 = 60000UL / _param1;
1168 _param2 = min(35 * (_segsNum - 1) / 100, 255); // MIN_BASE_LEVEL
1169 _param3 = min(67 * (_segsNum - 1) / 100, 255); // MIN_PEAK_LEVEL
1170 _param4 = 150; // PEAK_HOLD_TIME
1171 _valuePtr1 = nullptr;
1172 _usePtr = false;
1176
1177 _isRunning = true;
1179 _init = true;
1180 return *this;
1181 }
1182
1191 SBK_BarMeterAnimations &explodingBlocks(uint16_t intv = 50, uint8_t blockLength = 2, uint8_t blockSpacing = 1, uint8_t numBlocks = 0)
1192 {
1193 _updateIntv1 = max(5, intv);
1196 _param3 = numBlocks;
1197 _param4 = ((_segsNum / 2) / (blockLength + blockSpacing)) + 2;
1198 _param4 = constrain(_param4, 2, 32);
1199
1203 _isRunning = true;
1205 return *this;
1206 }
1207
1216 SBK_BarMeterAnimations &collidingBlocks(uint16_t intv = 50, uint8_t blockLength = 2, uint8_t blockSpacing = 1, uint8_t numBlocks = 0)
1217 {
1218
1219 _updateIntv1 = max(5, intv);
1222 _param3 = numBlocks;
1223 _param4 = ((_segsNum / 2) / (blockLength + blockSpacing)) + 2;
1224 _param4 = constrain(_param4, 2, 32);
1225
1229 _isRunning = true;
1231 return *this;
1232 }
1233
1242 SBK_BarMeterAnimations &scrollingUpBlocks(uint16_t intv = 50, uint8_t blockLength = 2, uint8_t blockSpacing = 1, uint8_t numBlocks = 0)
1243 {
1244 _updateIntv1 = max(5, intv);
1247 _param3 = numBlocks;
1249 _param4 = constrain(_param4, 2, 64);
1250
1254 _isRunning = true;
1256 return *this;
1257 }
1258
1267 SBK_BarMeterAnimations &scrollingDownBlocks(uint16_t intv = 50, uint8_t blockLength = 2, uint8_t blockSpacing = 1, uint8_t numBlocks = 0)
1268 {
1269 _updateIntv1 = max(5, intv);
1272 _param3 = numBlocks;
1274 _param4 = constrain(_param4, 2, 64);
1275
1279 _isRunning = true;
1281 return *this;
1282 }
1283
1291 SBK_BarMeterAnimations &downStackingBlocks(uint16_t intv = 50, uint8_t blockLength = 1, uint8_t blockSpacing = 0)
1292 {
1296 _updateIntv1 = max(5, intv);
1299 _param3 = 0; // requested number of blocks
1300 _param4 = 1; // aka maximum blocks number
1301 if (_init)
1303 _isRunning = true;
1305 return *this;
1306 }
1307
1315 SBK_BarMeterAnimations &upUnstackingBlocks(uint16_t intv = 50, uint8_t blockLength = 1, uint8_t blockSpacing = 0)
1316 {
1320 _updateIntv1 = max(5, intv);
1323 _param3 = 0; // aka requested number of blocks
1324 _param4 = 1; // aka maximum blocks number
1325 if (_init)
1327 _isRunning = true;
1329 return *this;
1330 }
1331
1339 SBK_BarMeterAnimations &upStackingBlocks(uint16_t intv = 50, uint8_t blockLength = 1, uint8_t blockSpacing = 0)
1340 {
1344 _updateIntv1 = max(5, intv);
1347 _param3 = 0; // aka requested number of blocks
1348 _param4 = 1; // aka maximum blocks number
1349 if (_init)
1351 _isRunning = true;
1353 return *this;
1354 }
1355
1363 SBK_BarMeterAnimations &downUnstackingBlocks(uint16_t intv = 50, uint8_t blockLength = 1, uint8_t blockSpacing = 0)
1364 {
1368 _updateIntv1 = max(5, intv);
1371 _param3 = 0; // aka requested number of blocks
1372 _param4 = 1; // aka maximum blocks number
1373 if (_init)
1375 _isRunning = true;
1377 return *this;
1378 }
1379
1390 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)
1391 {
1392
1393 _valuePtr1 = sigPtr;
1394 _updateIntv1 = max(10, updateIntv);
1395 _minMap = max(0, minMap);
1396 _maxMap = max(0, maxMap);
1398 _param1 = constrain(smoothingFactor, 0, 100);
1399 _updateIntv2 = samplingIntv;
1400
1404 _isRunning = true;
1405 _init = true;
1407 return *this;
1408 }
1409
1420 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)
1421 {
1422 _valuePtr1 = sigPtr;
1423 _updateIntv1 = max(10, updateIntv);
1424 _minMap = max(0, minMap);
1425 _maxMap = max(0, maxMap);
1427 _param1 = constrain(smoothingFactor, 0, 100);
1428 _updateIntv2 = samplingIntv;
1429
1433 _isRunning = true;
1434 _init = true;
1436 return *this;
1437 }
1438
1451 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)
1452 {
1453 _valuePtr1 = sig1Ptr;
1454 _updateIntv1 = max(10, updateIntv);
1455 _valuePtr2 = sig2Ptr ? sig2Ptr : sig1Ptr;
1456 _minMap = max(0, minMap);
1457 _maxMap = max(0, maxMap);
1459 _param1 = constrain(smoothingFactor, 0, 100);
1460 _updateIntv2 = samplingIntv;
1461
1465 //_mirrorHalfRangeDir = false;
1466 _isRunning = true;
1467 _init = true;
1469 return *this;
1470 }
1471
1484 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)
1485 {
1486 _valuePtr1 = sig1Ptr;
1487 _updateIntv1 = max(10, updateIntv);
1488 _valuePtr2 = sig2Ptr ? sig2Ptr : sig1Ptr;
1489 _minMap = max(0, minMap);
1490 _maxMap = max(0, maxMap);
1492 _param1 = constrain(smoothingFactor, 0, 100);
1493 _updateIntv2 = samplingIntv;
1494
1498 //_mirrorHalfRangeDir = true;
1499 _isRunning = true;
1500 _init = true;
1502 return *this;
1503 }
1504
1520 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)
1521 {
1522
1523 _valuePtr1 = sigPtr;
1524 _updateIntv3 = max(20, peakHoldTime);
1525 _updateIntv1 = max(10, updateIntv);
1526 _minMap = max(0, minMap);
1527 _maxMap = max(0, maxMap);
1529 _param1 = constrain(smoothingFactor, 0, 100);
1530 _updateIntv2 = samplingIntv;
1531
1535 _isRunning = true;
1536 _init = true;
1538 return *this;
1539 }
1540
1546 SBK_BarMeterAnimations &randomFill(uint16_t interval = 30)
1547 {
1548 _updateIntv1 = interval;
1549
1553 _isRunning = true;
1555 _init = true;
1556 return *this;
1557 }
1558
1564 SBK_BarMeterAnimations &randomEmpty(uint16_t interval = 30)
1565 {
1566 _updateIntv1 = interval;
1567
1571 _isRunning = true;
1573 _init = true;
1574 return *this;
1575 }
1576
1577protected:
1578 BarMeterT &_barMeter;
1579 uint8_t _segsNum = 0;
1580
1581 // Active animation update function
1584
1587 {
1589 uint16_t updateIntv1 = 10, updateIntv2 = 10, updateIntv3 = 10;
1590 uint16_t minMap = 0, maxMap = 1023;
1591 const void *valuePtr1 = nullptr;
1592 const void *valuePtr2 = nullptr;
1593 int8_t minTracker = 0, maxTracker = 1;
1594 uint8_t param1 = 0, param2 = 0, param3 = 0, param4 = 0, param5 = 0;
1595 bool loop = false;
1600 bool animLogicSet = false;
1603 bool animDirSet = false;
1604 bool usePtr = false;
1606 };
1608 uint8_t _queueHead = 0;
1609 uint8_t _queueTail = 0;
1610 uint8_t _queueCount = 0;
1612 bool _queueActive = false;
1613 bool _queueOverflow = false;
1614
1615 // Control flags
1616 bool _init = true; // true = init function
1617 bool _isRunning = false;
1618 bool _isPaused = false;
1619 bool _loop = false;
1620 bool _isLoopingNow = false;
1626 bool _animLogicSet = false;
1627 bool _skipPending = false;
1630 bool _animDirSet = false;
1631 bool _usePtr = false;
1633
1634 // Time trackings
1635 uint32_t _currentTime = 0;
1637
1638 // Animations update interval trackers
1639 uint16_t _updateIntv1 = 10, _updateIntv2 = 10, _updateIntv3 = 10;
1640
1641 // Animations trackers
1642 uint8_t _sequenceState = 0;
1644 int8_t _minTracker = 0, _maxTracker = 1;
1645 uint8_t _param1 = 0, _param2 = 0, _param3 = 0, _param4 = 0, _param5 = 0;
1647 uint16_t _minMap = 0, _maxMap = 1023;
1648 uint8_t _counter1 = 0, _counter2 = 0;
1649 // Generic live-value pointers. Their type is determined by the active animation.
1650 const void *_valuePtr1 = nullptr, *_valuePtr2 = nullptr;
1651 // Blocks related helpers
1652 struct Block
1653 {
1654 Block() : position(-1), active(false) {}
1655 int8_t position;
1657 };
1658 static constexpr uint8_t MAX_BLOCK_CAPACITY = 64;
1660 uint8_t _segmentCapacity = 0;
1661 uint8_t _blockCapacity = 0;
1662 uint8_t _randomCursor = 0;
1663
1693
1694 void _loadQueueEntry(const QueueEntry &entry)
1695 {
1696 _currentFunc = entry.function;
1697 _updateIntv1 = entry.updateIntv1;
1698 _updateIntv2 = entry.updateIntv2;
1699 _updateIntv3 = entry.updateIntv3;
1700 _minMap = entry.minMap;
1701 _maxMap = entry.maxMap;
1702 _valuePtr1 = entry.valuePtr1;
1703 _valuePtr2 = entry.valuePtr2;
1704 _minTracker = entry.minTracker;
1705 _maxTracker = entry.maxTracker;
1706 _param1 = entry.param1;
1707 _param2 = entry.param2;
1708 _param3 = entry.param3;
1709 _param4 = entry.param4;
1710 _param5 = entry.param5;
1711 _loop = entry.loop;
1720 _animDirSet = entry.animDirSet;
1721 _usePtr = entry.usePtr;
1723
1724 _init = true;
1725 _isRunning = _currentFunc != nullptr;
1726 _isPaused = false;
1727 _isLoopingNow = false;
1728 _skipPending = false;
1729 _sequenceState = 0;
1731 _counter1 = _counter2 = 0;
1734 }
1735
1737 {
1738 if (_queueCount == 0)
1739 {
1740 _queueActive = false;
1741 _isRunning = false;
1742 _currentFunc = nullptr;
1744 return;
1745 }
1746
1750 _queueCount--;
1751 }
1752
1754 {
1755 _segmentCapacity = _barMeter.getSegsNum();
1757 _maxTracker = _segsNum > 0 ? _segsNum - 1 : 0;
1758
1759 const uint16_t requestedCapacity = static_cast<uint16_t>(_segmentCapacity) + 2U;
1760 _blockCapacity = min(requestedCapacity,
1761 static_cast<uint16_t>(MAX_BLOCK_CAPACITY));
1762 const uint8_t randomOrderBlocks =
1763 (_segmentCapacity + sizeof(Block) - 1U) / sizeof(Block);
1764 const uint8_t workspaceBlocks = max(_blockCapacity, randomOrderBlocks);
1765 if (workspaceBlocks > 0)
1766 _animationWorkspace = new Block[workspaceBlocks];
1767 }
1768
1769 uint8_t *_randomOrder()
1770 {
1771 return reinterpret_cast<uint8_t *>(_animationWorkspace);
1772 }
1773
1774 void _resetBlocks(uint8_t &blockCapacity)
1775 {
1777 {
1778 blockCapacity = 0;
1779 return;
1780 }
1781 blockCapacity = min(blockCapacity, _blockCapacity);
1782 for (uint8_t i = 0; i < blockCapacity; ++i)
1784 }
1785
1786 // Animation helpers
1787 static inline void _normalizePercentRange(uint8_t &minP, uint8_t &maxP)
1788 {
1789 minP = constrain(minP, 0, 100);
1790 maxP = constrain(maxP, 0, 100);
1791 if (minP > maxP)
1792 {
1793 uint8_t t = maxP;
1794 maxP = minP;
1795 minP = t;
1796 }
1797 if (minP == maxP)
1798 {
1799 if (maxP < 100)
1800 maxP = minP + 1;
1801 else
1802 minP--;
1803 }
1804 }
1805 const uint8_t *_percentMaxPtr() const { return static_cast<const uint8_t *>(_valuePtr1); }
1806 const uint8_t *_percentMinPtr() const { return static_cast<const uint8_t *>(_valuePtr2); }
1807 const uint8_t *_liveBpmPtr() const { return static_cast<const uint8_t *>(_valuePtr1); }
1808 const uint16_t *_signalPtr1() const { return static_cast<const uint16_t *>(_valuePtr1); }
1809 const uint16_t *_signalPtr2() const { return static_cast<const uint16_t *>(_valuePtr2); }
1810
1811 inline void _mapMinMaxTrackerFromPtr(const uint8_t *minPercPtr, const uint8_t *maxPercPtr, int8_t minR, uint8_t maxR)
1812 {
1813 if (!_usePtr)
1814 return;
1815 uint8_t minP = minPercPtr ? *minPercPtr : 0;
1816 uint8_t maxP = maxPercPtr ? *maxPercPtr : 100;
1817 _normalizePercentRange(minP, maxP);
1818 _minTracker = map(minP, 0, 100, minR, maxR);
1819 _maxTracker = map(maxP, 0, 100, minR, maxR);
1820 }
1821 inline void _mapMinMaxTracker(uint8_t minP, uint8_t maxP, uint8_t minR, uint8_t maxR)
1822 {
1823 _normalizePercentRange(minP, maxP);
1824 _minTracker = map(minP, 0, 100, minR, maxR);
1825 _maxTracker = map(maxP, 0, 100, minR, maxR);
1826 }
1827 inline uint8_t _corrPixelToDir(uint8_t pixel)
1828 {
1829 return _animRenderDirIsReversed ? (_segsNum - 1) - pixel : pixel;
1830 }
1831 inline uint8_t _corrPixelToDirForHalfRange(uint8_t pixel)
1832 {
1834 return pixel;
1835
1836 const uint8_t half = _segsNum / 2;
1837 return abs((half - 1) - pixel);
1838 }
1839 static inline void _correctSwapOrEqualMinMax(uint16_t &minVal, uint16_t &maxVal)
1840 {
1841 if (minVal > maxVal)
1842 {
1843 uint16_t temp = maxVal;
1844 maxVal = minVal;
1845 minVal = temp;
1846 }
1847 if (minVal == maxVal) // avoid division by zero in map()
1848 {
1849 if (maxVal < 65535)
1850 {
1851 maxVal = minVal + 1;
1852 }
1853 else
1854 {
1855 minVal--;
1856 }
1857 }
1858 }
1859 static inline uint8_t _getMappedSignal(uint16_t sig, uint16_t minM, uint16_t maxM, uint8_t minR, uint8_t maxR)
1860 {
1861 uint8_t mapped = map(sig, minM, maxM, minR, maxR);
1862 return constrain(mapped, minR, maxR); // avoid edge overshoot
1863 }
1864
1865 // Animation update functions
1866
1868 {
1869 _init = false;
1870 for (uint8_t i = 0; i < _segsNum; i++)
1871 _barMeter.setPixel(i, true);
1872 return true;
1873 }
1875 {
1876 _init = false;
1877 _barMeter.clear();
1878 return true;
1879 }
1880
1881 bool _wait()
1882 {
1883 if (_init)
1884 {
1885 _init = false;
1887 }
1889 }
1890
1892 {
1893 const uint8_t index = _param1;
1894 const uint8_t blinkCount = _param2;
1895
1896 if (index >= _segsNum)
1897 return true;
1898
1899 if (_init)
1900 {
1901 _init = false;
1902 _sequenceState = 1; // ON phase
1903 _counter1 = 0;
1905 _barMeter.setPixel(index, true);
1906 return false;
1907 }
1908
1909 const uint16_t interval = _sequenceState ? _updateIntv1 : _updateIntv2;
1910 if (_currentTime - _lastUpdate1 < interval)
1911 return false;
1912
1914 if (_sequenceState)
1915 {
1916 _sequenceState = 0;
1917 _barMeter.setPixel(index, false);
1918 _counter1++;
1919 return blinkCount > 0 && _counter1 >= blinkCount;
1920 }
1921
1922 _sequenceState = 1;
1923 _barMeter.setPixel(index, true);
1924 return false;
1925 }
1926
1928 {
1930
1931 if (_init)
1932 {
1933 _init = false;
1934
1935 if (!_animLogicSet)
1937
1938 // Pre-fill for emptyDown mode
1940 {
1942 for (uint8_t i = 0; i < _segsNum; ++i)
1943 {
1944 if (i <= _maxTracker)
1945 _barMeter.setPixel(_corrPixelToDir(i), true);
1946 else
1947 _barMeter.setPixel(_corrPixelToDir(i), false);
1948 }
1949 }
1950 else // Pre-fill fillUp, light up to _minTracker inclusively
1951 {
1953 for (uint8_t i = 0; i < _segsNum; ++i)
1954 {
1955 if (i <= _minTracker)
1956 _barMeter.setPixel(_corrPixelToDir(i), true);
1957 else
1958 _barMeter.setPixel(_corrPixelToDir(i), false);
1959 }
1960 }
1961 return false;
1962 }
1963
1965 {
1967
1968 // Clamp if inverted logic and tracker is beyond maxTracker
1971
1972 // Clamp in normal logic mode
1975 }
1976
1978 {
1980
1982 {
1984 {
1985 _barMeter.setPixel(_corrPixelToDir(_ledTracker1), false);
1986 _ledTracker1--;
1987 }
1988 else
1989 return true; // now done!
1990 }
1991 else
1992 {
1994 {
1995 _barMeter.setPixel(_corrPixelToDir(_ledTracker1), true);
1996 _ledTracker1++;
1997 }
1998 else
1999 return true; // done lighting up all
2000 }
2001 }
2002 return false;
2003 }
2004
2006 {
2007 switch (_sequenceState)
2008 {
2009 case 0: // Fill Up
2010
2011 _skipPending = false;
2012 if (_fillOrEmpty())
2013 {
2015 _sequenceState = 1;
2016 _updateIntv1 = _updateIntv3; // Empty update interval
2017 }
2018 return false;
2019
2020 case 1: // Fill Down
2021
2022 _skipPending = true;
2023 if (_fillOrEmpty())
2024 {
2026 _sequenceState = 0;
2027 _updateIntv1 = _updateIntv2; // Fill up update interval
2028 return true;
2029 }
2030 return false;
2031 }
2032
2033 return true; // Shouldn’t reach here
2034 }
2035
2037 {
2038 const uint8_t center = _segsNum / 2;
2040
2041 if (_init)
2042 {
2043 _init = false;
2044
2045 if (!_animLogicSet)
2047
2049 {
2051
2052 // Start fully lit
2053 for (uint8_t i = 0; i < center; ++i)
2054 {
2055 if (i >= _maxTracker)
2056 {
2057 _barMeter.setPixel(_corrPixelToDirForHalfRange(i), true);
2058 _barMeter.setPixel((_segsNum - 1) - _corrPixelToDirForHalfRange(i), true);
2059 }
2060 else
2061 {
2062 _barMeter.setPixel(_corrPixelToDirForHalfRange(i), false);
2063 _barMeter.setPixel((_segsNum - 1) - _corrPixelToDirForHalfRange(i), false);
2064 }
2065 }
2066 }
2067 else
2068 {
2069
2071
2072 // Start with minimum fill
2073 for (uint8_t i = 0; i < center; ++i)
2074 {
2075 if (i > _minTracker)
2076 {
2077 _barMeter.setPixel(_corrPixelToDirForHalfRange(i), true);
2078 _barMeter.setPixel((_segsNum - 1) - _corrPixelToDirForHalfRange(i), true);
2079 }
2080 else
2081 {
2082 _barMeter.setPixel(_corrPixelToDirForHalfRange(i), false);
2083 _barMeter.setPixel((_segsNum - 1) - _corrPixelToDirForHalfRange(i), false);
2084 }
2085 }
2086 }
2087 return false;
2088 }
2089
2091 {
2094 {
2095 if (_ledTracker1 >= _maxTracker && _ledTracker1 >= 0)
2096 {
2099 _ledTracker1--;
2100 }
2101 else
2102 return true; // finished filling
2103 }
2104 else
2105 {
2106 if (_ledTracker1 <= _minTracker && _ledTracker1 < center)
2107 {
2110 _ledTracker1++;
2111 }
2112 else
2113 return true; // finished clearing
2114 }
2115 }
2116
2117 return false;
2118 }
2119
2121 {
2122 switch (_sequenceState)
2123 {
2124 case 0: // Fill from center
2125
2126 _skipPending = true;
2128 {
2130 _updateIntv1 = _updateIntv3; // Empty update interval
2131 _sequenceState = 1;
2132 }
2133 return false;
2134
2135 case 1: // Empty to center
2136
2137 _skipPending = false;
2139 {
2141 _updateIntv1 = _updateIntv2; // Empty update interval
2142 _sequenceState = 0;
2143 return true; // one full bounce cycle completed
2144 }
2145 return false;
2146 }
2147
2148 return true; // should not happen
2149 }
2150
2151#define bpm _param1
2152#define MIN_BASE_LEVEL _param2
2153#define MIN_PEAK_LEVEL _param3
2154#define PEAK_HOLD_TIME _param4
2155#define bpmPtr _liveBpmPtr()
2156#define currentLevel _ledTracker1
2157#define peakLevel _ledTracker2
2158#define beat _updateIntv1
2159#define lastBeat _lastUpdate1
2160#define lastRandomUpdate _lastUpdate2
2161#define prevPeakUpdate _lastUpdate3
2162 bool _beatPulse() // bouncing from bottom (maybe like a volume meter with the music)
2163 {
2164 static int8_t randomOffset;
2165 static bool isPeak = false;
2166
2167 if (_init)
2168 {
2169 _init = false;
2170
2171 if (!_animLogicSet)
2173
2174 _setAllOff();
2177 }
2178
2179 // Update at bpm pulse
2180 if (_currentTime - lastBeat >= beat)
2181 {
2183 isPeak = !isPeak; // Toggle between peak and base level
2184 }
2185
2186 // Smooth transition between base and peak levels
2187 if (isPeak && currentLevel <= MIN_PEAK_LEVEL)
2188 {
2189 currentLevel += random(3, 5); // Gradual increase
2190 }
2191 else if (!isPeak && currentLevel >= MIN_BASE_LEVEL)
2192 {
2193 currentLevel -= random(0, 4); // Gradual decrease
2194 }
2195
2196 // Randomized additional LEDs on top at a random interval
2197 if (_currentTime - lastRandomUpdate >= uint16_t(random(50, 300)))
2198 {
2200 randomOffset = random(-4, 4); // 0 to 5 extra LEDs
2201 }
2202
2203 uint8_t finalLevel = currentLevel + randomOffset;
2204 finalLevel = constrain(finalLevel, 0, _segsNum);
2205 if (finalLevel > _segsNum)
2206 finalLevel = _segsNum;
2207 if (finalLevel < 0)
2208 finalLevel = 0;
2209
2210 // Update peak level
2211 if (finalLevel > peakLevel)
2212 {
2213 peakLevel = min(finalLevel, _segsNum - 1);
2214 prevPeakUpdate = _currentTime; // Reset decay timer
2215 }
2216 else if (_currentTime - prevPeakUpdate >= PEAK_HOLD_TIME && peakLevel > finalLevel)
2217 {
2218 peakLevel--; // Slowly drop peak
2219 prevPeakUpdate = _currentTime; // Reset decay timer
2220 }
2221
2222 // Update LED states
2223 for (uint8_t i = 0; i < _segsNum; i++)
2224 {
2225 _barMeter.setPixel(_corrPixelToDir(i), (i < finalLevel));
2226 }
2227
2228 // Ensure peak LED stays on
2229 if (peakLevel < _segsNum)
2230 {
2231 _barMeter.setPixel(_corrPixelToDir(peakLevel), true);
2232 }
2233 return false; // Continuous pulse animation, never auto-terminates
2234 }
2235
2237 {
2238 bpm = bpmPtr ? max(1, *bpmPtr) : 116;
2239 beat = 60000UL / bpm;
2240 return _beatPulse();
2241 }
2242#undef bpm
2243#undef MIN_BASE_LEVEL
2244#undef MIN_PEAK_LEVEL
2245#undef PEAK_HOLD_TIME
2246#undef bpmPtr
2247#undef currentLevel
2248#undef peakLevel
2249#undef beat
2250#undef lastBeat
2251#undef lastRandomUpdate
2252#undef prevPeakUpdate
2253
2254#define blockLength _param1
2255#define blockSpacing _param2
2256#define requestedNumBlocks _param3
2257#define maxBlocks _param4
2258#define emitIndex _param5
2259#define emittedBlocksCount _counter1
2260#define emitCooldown _counter2
2261 void _emitBlock(int8_t pos)
2262 {
2263 if (!_animationWorkspace || maxBlocks < 1)
2264 return;
2265
2266 if (emitCooldown > 0)
2267 {
2268 emitCooldown--;
2269 return;
2270 }
2271
2273 return;
2274
2275 for (uint8_t i = 0; i < maxBlocks; ++i)
2276 {
2277 uint8_t idx = (emitIndex + i) % maxBlocks;
2278 Block &b = _animationWorkspace[idx];
2279
2280 if (!b.active)
2281 {
2282 b.position = pos;
2283 b.active = true;
2285
2287 emitIndex = (emitIndex + 1) % maxBlocks;
2288 return;
2289 }
2290 }
2291 }
2292
2293 static inline int8_t _calculateSwitchPosition(int8_t pos, uint8_t blockL, uint8_t range)
2294 {
2295 // Compute switched block head position
2296 return (range - 1) - pos + (blockL - 1);
2297 }
2298
2300 {
2301 const int8_t emitInterval = blockLength + blockSpacing;
2302
2303 int8_t closestSwp = -127; // Track the nearest visible block from new side
2304
2305 for (uint8_t i = 0; i < maxBlocks; ++i)
2306 {
2307 Block &b = _animationWorkspace[i];
2308
2309 if (!b.active)
2310 continue;
2311
2312 int8_t p = b.position; // head position before switching
2313
2314 // Step 1: compute switched block head position
2315 int8_t swp = _calculateSwitchPosition(p, blockLength, range);
2316
2317 b.position = swp;
2318
2319 // Step 2: if out of visible range, deactivate block
2320 if (swp < 0)
2321 {
2322 b.active = false;
2323 continue;
2324 }
2325
2326 // // Step 3: Keep the first (closest to emit side) visible block
2327 if (closestSwp == -127 || swp < closestSwp)
2328 closestSwp = swp;
2329 }
2330
2331 if (closestSwp >= 0)
2332 return (emitInterval - 1) - closestSwp;
2333 else
2334 return 0; // fallback if no visible block found
2335 }
2336
2338 {
2339 const uint8_t center = _segsNum / 2;
2340
2341 if (_init)
2342 {
2343 _init = false;
2345
2347 emitCooldown = 0;
2348
2349 if (!_animLogicSet)
2351
2353 return false;
2354 }
2355
2357 {
2361 }
2362
2364 {
2366 // _frameCounter++;
2367 _barMeter.clear();
2368
2370 _emitBlock(-1); // The new block moves to position 0 on its first update.
2371
2372 for (uint8_t i = 0; i < maxBlocks; ++i)
2373 {
2374 Block &b = _animationWorkspace[i];
2375 if (!b.active)
2376 continue;
2377
2378 b.position++;
2379
2380 // Only render to lower half (0 to _centerTracker - 1)
2381 uint16_t clamped = max(0, (int16_t)b.position + 1);
2382 uint8_t pixelsVisible = min(blockLength, (uint8_t)clamped);
2383 for (uint8_t j = 0; j < pixelsVisible; ++j)
2384 {
2385 int8_t pos = _animRenderLogicIsInverted ? center - 1 - b.position : b.position;
2386 int8_t tailOffset = _animRenderLogicIsInverted ? pos + j : pos - j;
2387
2388 if (tailOffset < 0)
2389 continue;
2390
2391 int8_t idx = tailOffset;
2392 if (idx >= center)
2393 continue;
2394
2395 int8_t mirrorIdx = _segsNum - 1 - idx;
2396
2397 if (idx >= 0 && idx < _segsNum)
2398 {
2399 _barMeter.setPixel(idx, true);
2400 }
2401 if (mirrorIdx != idx && mirrorIdx >= 0 && mirrorIdx < _segsNum)
2402 {
2403 _barMeter.setPixel(mirrorIdx, true);
2404 }
2405 }
2406
2407 // Deactivate block if head has passed the visual range
2408 if (b.position >= center - 1 + blockLength)
2409 b.active = false;
2410 }
2411 }
2412 // The animation ends after emission stops and all active blocks have cleared.
2414 {
2415 bool anyActive = false;
2416 for (uint8_t i = 0; i < maxBlocks; ++i)
2417 {
2418 if (_animationWorkspace[i].active)
2419 {
2420 anyActive = true;
2421 break;
2422 }
2423 }
2424 if (!anyActive)
2425 return true; // animation complete
2426 }
2427
2428 return false;
2429 }
2430
2432 {
2433 if (_init)
2434 {
2435 _init = false;
2437
2439 emitCooldown = 0;
2440
2441 if (!_animLogicSet)
2443
2445 return false;
2446 }
2447
2449 {
2453 }
2454
2456 {
2458 _setAllOff();
2459
2461 _emitBlock(-1); // Start fully off-screen left
2462
2463 for (uint8_t i = 0; i < maxBlocks; ++i)
2464 {
2465 Block &b = _animationWorkspace[i];
2466 if (!b.active)
2467 continue;
2468
2469 b.position++;
2470
2471 for (uint8_t j = 0; j < blockLength; ++j)
2472 {
2473 int8_t pos = _animRenderLogicIsInverted ? (_segsNum - 1) - b.position : b.position;
2474 int8_t tailOffset = _animRenderLogicIsInverted ? pos + j : pos - j;
2475
2476 if (tailOffset < 0)
2477 continue;
2478
2479 int8_t idx = tailOffset;
2480 if (idx >= _segsNum)
2481 continue;
2482
2483 if (idx >= 0 && idx < _segsNum)
2484 _barMeter.setPixel(_corrPixelToDir(idx), true);
2485 }
2486
2487 // Deactivate block if head has passed the visual range
2488 if (b.position >= (_segsNum - 1) + blockLength)
2489 b.active = false;
2490 }
2491 }
2492
2494 {
2495 for (uint8_t i = 0; i < maxBlocks; ++i)
2496 {
2497 if (_animationWorkspace[i].active)
2498 return false;
2499 }
2500 return true;
2501 }
2502
2503 return false;
2504 }
2505
2506#define stackLevel _ledTracker1
2508 {
2509 const uint8_t blockInterval = blockLength + blockSpacing;
2510 bool hasActive = false; // active block tracker
2511
2512 if (_init)
2513 {
2514 _init = false;
2515
2516 if (!_animLogicSet)
2518
2519 maxBlocks = 1;
2521
2522 emitIndex = 0;
2523 emitCooldown = 0;
2524
2525 stackLevel = 0;
2527 {
2528 _barMeter.clear();
2529 // Falling blocks
2530 // stackLevel = 0;
2531 }
2532 else
2533 {
2534 // Flying upward - fill pattern first
2535 while (stackLevel < _segsNum)
2536 stackLevel += blockInterval;
2537 for (uint8_t i = 0; i < stackLevel; i++)
2538 {
2539 if ((i % blockInterval) < blockLength)
2540 _barMeter.setPixel(_corrPixelToDir(i), true);
2541 else
2542 _barMeter.setPixel(_corrPixelToDir(i), false);
2543 }
2544 }
2545
2546 return false;
2547 }
2548
2550 {
2553 stackLevel += blockInterval;
2554 else
2555 stackLevel -= blockInterval;
2556 }
2557
2559 {
2561
2562 // Only clear pixels of active blocks, not the whole bar
2563 for (uint8_t i = 0; i < maxBlocks; ++i)
2564 {
2565 Block &b = _animationWorkspace[i];
2566 if (!b.active)
2567 continue;
2568
2569 for (uint8_t j = 0; j < blockLength; ++j)
2570 {
2571 int8_t seg = b.position + j;
2572 if (seg >= 0 && seg < _segsNum)
2573 _barMeter.setPixel(_corrPixelToDir(seg), false);
2574 }
2575 }
2576
2577 // Emit one block if none active
2578 for (uint8_t i = 0; i < maxBlocks; ++i)
2579 {
2580 if (_animationWorkspace[i].active)
2581 {
2582 hasActive = true;
2583 break;
2584 }
2585 }
2586 if (!hasActive)
2587 {
2588 emitCooldown = 0; // emit blocks on demand
2590 _emitBlock(_segsNum); // fall from top
2591 else if (_animRenderLogicIsInverted && stackLevel >= 0)
2592 _emitBlock(stackLevel - blockInterval);
2593 }
2594
2595 // Update and draw all active blocks
2596 for (uint8_t i = 0; i < maxBlocks; ++i)
2597 {
2598 Block &b = _animationWorkspace[i];
2599 if (!b.active)
2600 continue;
2601
2602 // Clear trailing pixel of previous frame
2603 int8_t clearPos = b.position;
2604 if (clearPos >= 0 && clearPos < _segsNum)
2605 _barMeter.setPixel(_corrPixelToDir(clearPos), false);
2606
2607 // Move block
2609 b.position--;
2610 else
2611 b.position++;
2612
2613 for (uint8_t j = 0; j < blockLength; ++j)
2614 {
2615 int8_t seg = b.position + j;
2616 if (seg >= 0 && seg < _segsNum)
2617 _barMeter.setPixel(_corrPixelToDir(seg), true);
2618 }
2619
2621 {
2622 if (b.position <= stackLevel)
2623 {
2624 stackLevel += blockInterval;
2625 b.active = false;
2626 }
2627 }
2628 else
2629 {
2630 if (b.position >= _segsNum)
2631 {
2632 stackLevel -= blockInterval;
2633 b.active = false;
2634 }
2635 }
2636 }
2637
2638 // Draw base for stacking
2639 if (stackLevel == 0)
2640 {
2641 _barMeter.setPixel(_corrPixelToDir(0), false);
2642 }
2644 {
2645 for (int8_t i = 0; i < stackLevel - blockInterval; ++i)
2646 {
2647 if ((i % blockInterval) < blockLength)
2648 _barMeter.setPixel(_corrPixelToDir(i), true);
2649 else
2650 _barMeter.setPixel(_corrPixelToDir(i), false);
2651 }
2652 }
2653 else
2654 {
2655 for (uint8_t i = 0; i < stackLevel - blockInterval; i++)
2656 {
2657 if ((i % blockInterval) < blockLength)
2658 _barMeter.setPixel(_corrPixelToDir(i), true);
2659 else
2660 _barMeter.setPixel(_corrPixelToDir(i), false);
2661 }
2662 }
2664 {
2665 if (stackLevel >= _segsNum - 1 && !hasActive)
2666 return true;
2667 }
2668 else
2669 {
2670 if (stackLevel <= 0 && !hasActive)
2671 return true;
2672 }
2673 }
2674 return false;
2675 }
2676#undef stackLevel
2677#undef blockLength
2678#undef blockSpacing
2679#undef requestedNumBlocks
2680#undef maxBlocks
2681#undef emitIndex
2682#undef emittedBlocksCount
2683#undef emitCooldown
2684
2685#define smoothingFactor _param1
2687 {
2688 if (!_signalPtr1())
2689 {
2690 _setAllOff();
2691 return true;
2692 }
2693
2694 if (_init)
2695 {
2696 _init = false;
2697
2698 if (!_animLogicSet)
2700
2703 _setAllOff();
2704 return false;
2705 }
2706
2707 // Signal sampling
2709 {
2710 uint16_t raw = *_signalPtr1();
2713 }
2714
2716 {
2718
2720
2721 for (uint8_t i = 0; i < _segsNum; i++)
2722 {
2723 _barMeter.setPixel(_corrPixelToDir(i), i < level);
2724 }
2725 }
2726 return false; // continuous animation
2727 }
2729 {
2730 if (!_signalPtr1())
2731 {
2732 _setAllOff();
2733 return true;
2734 }
2735
2736 if (_init)
2737 {
2738 _init = false;
2739
2740 if (!_animLogicSet)
2742
2745 _setAllOff();
2746 return false;
2747 }
2748
2749 // Signal sampling
2751 {
2752 uint16_t raw = *_signalPtr1();
2755 }
2756
2758 {
2760
2762 uint8_t pointer = _getMappedSignal(*_signalPtr1(), _minMap, _maxMap, 0, _segsNum);
2763
2764 // First, fill up to moving average
2765 for (uint8_t i = 0; i < _segsNum; i++)
2766 {
2767 _barMeter.setPixel(_corrPixelToDir(i), i < avg);
2768 }
2769
2770 // Then selectively clear area around pointer
2771 if (pointer < avg && pointer > 0)
2772 _barMeter.setPixel(_corrPixelToDir(pointer - 1), false); // clear just below if pointer is inside the fill
2773
2774 if (pointer < avg - 2)
2775 _barMeter.setPixel(_corrPixelToDir(pointer + 1), false); // clear just above
2776
2777 // Finally, show the pointer
2778 _barMeter.setPixel(_corrPixelToDir(pointer), true);
2779 }
2780 return false; // continuous animation
2781 }
2783 {
2784 if (!_signalPtr1() && !_signalPtr2())
2785 {
2786 _setAllOff();
2787 return true;
2788 }
2789
2790 if (_init)
2791 {
2792 _init = false;
2793
2794 if (!_animLogicSet)
2796
2798 if (_signalPtr2())
2801 _setAllOff();
2802 return false;
2803 }
2804
2807
2808 // Signals sampling
2810 {
2812
2813 uint16_t raw1 = *_signalPtr1();
2814 _smoothedValue1 = (uint16_t)(((uint32_t)smoothingFactor * raw1 + (uint32_t)(100 - smoothingFactor) * _smoothedValue1) / 100);
2815
2816 if (_signalPtr2())
2817 {
2818 uint16_t raw2 = *_signalPtr2();
2819 _smoothedValue2 = (uint16_t)(((uint32_t)smoothingFactor * raw2 + (uint32_t)(100 - smoothingFactor) * _smoothedValue2) / 100);
2820 }
2821 }
2822
2824 {
2826
2827 uint16_t raw1 = *_signalPtr1();
2828 _smoothedValue1 = (uint16_t)(((uint32_t)smoothingFactor * raw1 + (uint32_t)(100 - smoothingFactor) * _smoothedValue1) / 100);
2829 uint8_t level1 = _getMappedSignal(_smoothedValue1, _minMap, _maxMap, 0, _segsNum / 2);
2830
2831 uint8_t level2 = level1;
2832 if (_signalPtr2())
2834
2835 for (uint8_t i = 0; i < _segsNum; i++)
2836 {
2837 //_barMeter.setPixel(_corrPixelToDirForHalfRange(i), i >= (_segsNum / 2 - 1) - level1 && i <= ((_segsNum / 2) + level2));
2839 _barMeter.setPixel(i, i < (_segsNum / 2 - 1) - level1 || i > ((_segsNum / 2) + level2));
2840 else
2841 _barMeter.setPixel(i, i >= (_segsNum / 2 - 1) - level1 && i <= ((_segsNum / 2) + level2));
2842 }
2843 }
2844 return false; // continuous animation
2845 }
2846
2847#define currentLevel _ledTracker1
2848#define peakLevel _ledTracker3
2849#define baseUpdate _updateIntv1
2850#define lastBaseUpdate _lastUpdate1
2851#define peakHoldTime _updateIntv3
2852#define lastPeakUpdate _lastUpdate3
2853 bool _followSignalFloatingPeak() // bouncing from bottom (maybe like a volume meter with the music)
2854 {
2855
2856 if (!_signalPtr1())
2857 {
2858 _setAllOff();
2859 return true;
2860 }
2861
2862 if (_init)
2863 {
2864 _init = false;
2865
2866 if (!_animLogicSet)
2868
2869 _setAllOff();
2873 currentLevel = 0;
2874 peakLevel = 0;
2875 return false;
2876 }
2877
2878 // Signal smoothing update
2880 {
2882
2883 uint16_t raw = *_signalPtr1();
2886 }
2887
2888 // Pixel + peak update
2890 {
2892
2893 // Peak logic
2894 if (currentLevel > peakLevel)
2895 {
2896 peakLevel = min(currentLevel, _segsNum - 1);
2897 lastPeakUpdate = _currentTime; // Reset decay timer
2898 }
2900 {
2901 peakLevel--; // Slowly drop peak
2902 lastPeakUpdate = _currentTime; // Reset decay timer
2903 }
2904
2905 // Draw bar
2906 for (uint8_t i = 0; i < _segsNum; i++)
2907 {
2908 _barMeter.setPixel(_corrPixelToDir(i), (i <= currentLevel));
2909 }
2910
2911 // Draw peak
2912 if (peakLevel < _segsNum)
2913 {
2914 _barMeter.setPixel(_corrPixelToDir(peakLevel), true);
2915 }
2916 }
2917 return false; // Continuous pulse animation, never auto-terminates
2918 }
2919#undef currentLevel
2920#undef peakLevel
2921#undef baseUpdate
2922#undef lastBaseUpdate
2923#undef peakHoldTime
2924#undef lastPeakUpdate
2925
2926#undef smoothingFactor
2927
2929 {
2930 uint8_t *pixelOrder = _randomOrder();
2931 if (_segsNum == 0 || !pixelOrder)
2932 return true;
2933
2934 if (_init)
2935 {
2936 _init = false;
2937
2938 if (!_animLogicSet)
2940
2942 _setAllOn();
2943 else
2944 _setAllOff();
2945
2946 for (uint8_t i = 0; i < _segsNum; ++i)
2947 pixelOrder[i] = i;
2948
2949 for (uint8_t i = _segsNum - 1; i > 0; --i)
2950 {
2951 uint8_t j = random(0, i + 1);
2952 uint8_t tmp = pixelOrder[i];
2953 pixelOrder[i] = pixelOrder[j];
2954 pixelOrder[j] = tmp;
2955 }
2956
2957 _randomCursor = 0;
2959
2960 return false;
2961 }
2962
2964 {
2966
2967 uint8_t retries = 0;
2968 while (_randomCursor < _segsNum && retries++ < _segsNum - 1)
2969 {
2970 uint8_t seg = pixelOrder[_randomCursor];
2971 bool currentState = _barMeter.getPixelState(seg);
2972 bool shouldChange = (!_AnimInitLogicIsInverted && !currentState) || (_AnimInitLogicIsInverted && currentState);
2973
2974 if (shouldChange)
2975 {
2976 _barMeter.setPixel(seg, !_AnimInitLogicIsInverted);
2977 _randomCursor++;
2978 break; // Change only one pixel per interval
2979 }
2980 else
2981 {
2982 _randomCursor++; // Move on to next even if it doesn't need change
2983 }
2984 }
2985 }
2986 if (_randomCursor >= _segsNum)
2987 {
2988 return true;
2989 }
2990 return false;
2991 }
2992};
#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 & resume()
Resume animation after pause.
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.
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 & 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.
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)
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.
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.
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 & 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.
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.
SBK_BarMeterAnimations & pause()
Pause animation progression.
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 the current pixel states.
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
void _loadQueueEntry(const QueueEntry &entry)
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 & 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 the first queued animation, if any.
static void _normalizePercentRange(uint8_t &minP, uint8_t &maxP)
SBK_BarMeterAnimations & setAllOn()
Set all pixels ON.
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,...
void _captureQueueEntry(QueueEntry &entry)
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 & 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 all pending queued animations without stopping the current animation.
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 & 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 isNonInvertingLogicAnim
uint8_t param4
uint8_t param3
uint16_t maxMap
uint16_t updateIntv2
const void * valuePtr2
const void * valuePtr1
bool animInitDirIsReversed
AnimUpdateFn function
bool mirrorHalfRangeDir
uint8_t param5
bool animDirSet
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