SBK_BarDrive Library 2.1.2
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 {
101 }
102
105 {
108 {
109 // Stop producing blocks at the deadline, but keep updating until
110 // every block already in flight has naturally left the display.
112 _loop = false;
113 }
115 }
116
119 {
120 if (_queueActive)
122 else
123 {
124 _isRunning = false;
125 _isPaused = false;
126 _loop = false;
127 _isLoopingNow = false;
128 _currentFunc = nullptr;
129 _init = true;
131 }
132 return _isRunning;
133 }
134
136 return _queueActive;
137
138 if ((this->*_currentFunc)())
139 {
140 if (_loop)
141 {
142 if (_skipPending)
143 _isLoopingNow = false;
144 else
145 {
146 _isLoopingNow = true; // mark it so user can react
147 _init = true;
148 }
149 }
150 else
151 {
152 if (_queueActive && _queueCount > 0)
154 else
155 {
156 _isLoopingNow = false;
157 _isRunning = false;
158 _currentFunc = nullptr;
159 _queueActive = false;
163 }
164 }
165 }
166 return _isRunning;
167 }
168
171 {
172 _init = true;
173 return *this;
174 }
175
178 {
179 _isPaused = true;
180 return *this;
181 }
182
185 {
186 _isPaused = false;
187 return *this;
188 }
189
192 {
193 _isPaused = false;
194 _isRunning = false;
195 _loop = false;
196 _isLoopingNow = false;
197 _skipPending = false;
198 _currentFunc = nullptr;
199 _animLogicSet = false;
200 _init = true;
205 clearQueue();
206 _barMeter.clear();
207 return *this;
208 }
209
219 {
220 if (!_currentFunc)
221 return *this;
222
224 {
227 _queueCount++;
228 }
229 else
230 {
231 _queueOverflow = true;
232 }
233
234 _isRunning = false;
235 _currentFunc = nullptr;
238 return *this;
239 }
240
249 {
250 if (!_currentFunc)
251 return *this;
252
254 {
255 _captureQueueEntry(_queue[_queueTail], true, duration);
257 _queueCount++;
258 }
259 else
260 {
261 _queueOverflow = true;
262 }
263
264 _isRunning = false;
265 _currentFunc = nullptr;
268 return *this;
269 }
270
280 SBK_BarMeterAnimations &forTime(uint32_t duration)
281 {
282 if (_currentFunc)
283 {
284 _animationDuration = duration;
285 _queueEntryDuration = duration;
287 }
288 else if (_queueCount > 0)
289 {
290 const uint8_t previousIndex =
292 _queue[previousIndex].duration = duration;
293 }
294 return *this;
295 }
296
306 {
307 if (_queueCount == 0)
308 return *this;
309
311 {
312 _queueOverflow = true;
313 return *this;
314 }
315
316 const uint8_t previousIndex =
318 QueueEntry &entry = _queue[_queueTail];
319 entry = _queue[previousIndex];
320 entry.loop = _loop;
323 entry.resetRuntimeState = false;
324 entry.duration = 0;
325 entry.blockEmissionStopAfter = 0;
326
328 _queueCount++;
329 return *this;
330 }
331
334 {
335 if (_queueCount > 0)
336 {
337 _queueActive = true;
339 }
340 return *this;
341 }
342
345 {
346 _queueHead = 0;
347 _queueTail = 0;
348 _queueCount = 0;
350 _queueActive = false;
351 _queueOverflow = false;
352 return *this;
353 }
354
362 {
363 if (!_isRunning || !_currentFunc)
364 return *this;
365
366 _loop = false;
367 _isPaused = false;
368 _isLoopingNow = false;
369 _skipPending = false;
370
371 if (_queueActive && _queueCount > 0)
373 else
374 {
375 _isRunning = false;
376 _currentFunc = nullptr;
377 _queueActive = false;
379 }
380 return *this;
381 }
382
384 uint8_t queuedAnimations() const { return _queueCount; }
385
387 bool isQueueRunning() const { return _queueActive; }
388
390 uint8_t currentQueueIndex() const { return _currentQueueIndex; }
391
393 bool isQueueIndexPlaying(uint8_t index) const
394 {
395 return _queueActive && _currentQueueIndex == index;
396 }
397
399 bool queueOverflowed() const { return _queueOverflow; }
400
403 {
405 return *this;
406 _loop = true;
407 return *this;
408 }
409
412 {
413 _loop = false;
414 return *this;
415 }
416
418 SBK_BarMeterAnimations &setDir(bool newDirection)
419 {
420 _animRenderDirIsReversed = newDirection;
421 _animDirSet = true;
422 return *this;
423 }
424
432
440
448
454 {
456 return *this;
458 _animLogicSet = true;
459 return *this;
460 }
461
468 {
470 return *this;
472 _animLogicSet = true;
473 return *this;
474 }
475
482 {
484 return *this;
486 _animLogicSet = true;
487 return *this;
488 }
489
496 {
498 return *this;
500 _animLogicSet = false;
501 return *this;
502 }
503
506 {
508 return *this;
509 }
510
513 {
515 return *this;
516 }
517
529 {
530 if (_currentFunc)
531 {
532 _blockEmissionStopAfter = duration;
535 }
536 else if (_queueCount > 0)
537 {
538 const uint8_t previousIndex =
540 _queue[previousIndex].blockEmissionStopAfter = duration;
541 }
542 return *this;
543 }
544
546 bool isRunning() const { return _isRunning; }
547
549 bool isPaused() const { return _isPaused; }
550
552 bool isLoopEnabled() const { return _loop; }
553
556 {
557 _updateIntv1 = max(static_cast<uint16_t>(1), interval);
558 return *this;
559 }
560
563 {
564 if (_skipPending == true)
565 _skipPending = false;
566 else
567 {
568 if (_isLoopingNow)
569 {
570 _isLoopingNow = false; // auto-clear after read
571 return true;
572 }
573 }
574 return false;
575 }
576
579 {
581 return true;
582 else
583 return false;
584 }
585
588
591 {
593 return true;
594 else
595 return false;
596 }
597
600
601 // Animation starters
606 SBK_BarMeterAnimations &setAll(bool onff) { return onff ? setAllOn() : setAllOff(); }
607
612 {
614 _isRunning = true;
615 _init = true;
616 return *this;
617 }
618
623 {
625 _isRunning = true;
626 _init = true;
627 return *this;
628 }
629
634 SBK_BarMeterAnimations &wait(uint16_t duration)
635 {
636 _updateIntv1 = duration;
637 _loop = false;
638 _isRunning = true;
640 _init = true;
641 return *this;
642 }
643
650 SBK_BarMeterAnimations &blinkPixel(uint8_t index, uint16_t interval = 500, uint8_t blinkCount = 0)
651 {
652 return blinkPixel(index, interval, interval, blinkCount);
653 }
654
662 SBK_BarMeterAnimations &blinkPixel(uint8_t index, uint16_t onInterval, uint16_t offInterval, uint8_t blinkCount)
663 {
664 _param1 = index;
665 _param2 = blinkCount;
666 _updateIntv1 = max(static_cast<uint16_t>(1), onInterval);
667 _updateIntv2 = max(static_cast<uint16_t>(1), offInterval);
669 _usePtr = false;
671 _isRunning = true;
672 _init = true;
673 return *this;
674 }
675
683 SBK_BarMeterAnimations &fillUpDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0)
684 {
688 _usePtr = false;
689 _valuePtr2 = nullptr;
690 _valuePtr1 = nullptr;
691 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
692 _isRunning = true;
693 uint8_t steps = max(1, _maxTracker - _minTracker + 1);
694 _updateIntv1 = max(5, duration / steps);
696 return *this;
697 }
698
706 SBK_BarMeterAnimations &fillUpIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
707 {
711 _usePtr = true;
712 _valuePtr1 = maxPercentPtr;
713 _valuePtr2 = minPercentPtr;
715 _isRunning = true;
716 _updateIntv1 = max(5, updateIntv);
718 return *this;
719 }
720
728 SBK_BarMeterAnimations &fillUpIntv(uint16_t updateIntv, uint8_t maxPercent = 100, uint8_t minPercent = 0)
729 {
733 _usePtr = false;
734 _valuePtr2 = nullptr;
735 _valuePtr1 = nullptr;
736 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
737 _isRunning = true;
738 _updateIntv1 = max(5, updateIntv);
740 return *this;
741 }
742
750 SBK_BarMeterAnimations &fillDownDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0)
751 {
755 _usePtr = false;
756 _valuePtr2 = nullptr;
757 _valuePtr1 = nullptr;
758 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
759 _isRunning = true;
760 uint8_t steps = max(1, _maxTracker - _minTracker + 1);
761 _updateIntv1 = max(5, duration / steps);
763 return *this;
764 }
765
773 SBK_BarMeterAnimations &fillDownIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
774 {
778 _usePtr = true;
779 _valuePtr1 = maxPercentPtr;
780 _valuePtr2 = minPercentPtr;
782 _isRunning = true;
783 uint8_t steps = max(1, _maxTracker - _minTracker + 1);
784 _updateIntv1 = max(5, updateIntv);
786 return *this;
787 }
788
796 SBK_BarMeterAnimations &fillDownIntv(uint16_t updateIntv, uint8_t maxPercent = 100, uint8_t minPercent = 0)
797 {
801 _usePtr = false;
802 _valuePtr2 = nullptr;
803 _valuePtr1 = nullptr;
804 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
805 _isRunning = true;
806 _updateIntv1 = max(5, updateIntv);
808 return *this;
809 }
810
818 SBK_BarMeterAnimations &emptyDownDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0)
819 {
823 _usePtr = false;
824 _valuePtr2 = nullptr;
825 _valuePtr1 = nullptr;
826 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
827 _isRunning = true;
828 uint8_t steps = max(1, _maxTracker - _minTracker + 1);
829 return emptyDownIntv(duration / steps, maxPercent, minPercent);
830 }
831
839 SBK_BarMeterAnimations &emptyDownIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
840 {
844 _usePtr = true;
845 _valuePtr1 = maxPercentPtr;
846 _valuePtr2 = minPercentPtr;
848 _isRunning = true;
849 _updateIntv1 = max(5, updateIntv);
851 return *this;
852 }
853
861 SBK_BarMeterAnimations &emptyDownIntv(uint16_t updateIntv, uint8_t maxPercent = 100, uint8_t minPercent = 0)
862 {
866 _usePtr = false;
867 _valuePtr2 = nullptr;
868 _valuePtr1 = nullptr;
869 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
870 _isRunning = true;
871 _updateIntv1 = max(5, updateIntv);
873 return *this;
874 }
875
883 SBK_BarMeterAnimations &emptyUpDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0)
884 {
888 _usePtr = false;
889 _valuePtr2 = nullptr;
890 _valuePtr1 = nullptr;
891 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
892 _isRunning = true;
893 uint8_t steps = max(1, _maxTracker - _minTracker + 1);
894 _updateIntv1 = max(5, duration / steps);
896 return *this;
897 }
898
906 SBK_BarMeterAnimations &emptyUpIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
907 {
911 _usePtr = true;
912 _valuePtr1 = maxPercentPtr;
913 _valuePtr2 = minPercentPtr;
915 _isRunning = true;
916 _updateIntv1 = max(5, updateIntv);
918 return *this;
919 }
920
928 SBK_BarMeterAnimations &emptyUpIntv(uint16_t updateIntv, uint8_t maxPercent = 100, uint8_t minPercent = 0)
929 {
933 _usePtr = false;
934 _valuePtr2 = nullptr;
935 _valuePtr1 = nullptr;
936 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
937 _isRunning = true;
938 _updateIntv1 = max(5, updateIntv);
940 return *this;
941 }
942
951 SBK_BarMeterAnimations &bounceFillUpDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0, uint16_t fillIntv = 0)
952 {
955 _usePtr = false;
956 _valuePtr2 = nullptr;
957 _valuePtr1 = nullptr;
958 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
959 uint8_t steps = max(1, _maxTracker - _minTracker + 1);
960 uint16_t emptyIntv = fillIntv;
961 if (fillIntv == 0)
962 {
963 fillIntv = max(5, duration) / (2 * steps);
964 emptyIntv = fillIntv;
965 }
966 else
967 {
968 emptyIntv = max(5, duration - fillIntv * steps) / steps;
969 }
970 _updateIntv1 = fillIntv; // Starting interval
971 _updateIntv2 = _updateIntv1; // Fill intv
972 _updateIntv3 = emptyIntv; // Empty intv
973 _sequenceState = 0;
974 _isRunning = true;
976 return *this;
977 }
978
987 SBK_BarMeterAnimations &bounceFillUpIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
988 {
991 _valuePtr1 = maxPercentPtr;
992 _valuePtr2 = minPercentPtr;
993 _usePtr = true;
995 _updateIntv1 = max(5, fillIntv); // Starting interval
996 _updateIntv2 = _updateIntv1; // Fill intv
997 _updateIntv3 = max(5, emptyIntv); // Empty intv
998 _sequenceState = 0;
999 _isRunning = true;
1001 return *this;
1002 }
1003
1012 SBK_BarMeterAnimations &bounceFillUpIntv(uint16_t fillIntv = 10, uint16_t emptyIntv = 10, uint8_t maxPercent = 100, uint8_t minPercent = 0)
1013 {
1017 _usePtr = false;
1018 _valuePtr2 = nullptr;
1019 _valuePtr1 = nullptr;
1020 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
1021 _updateIntv1 = max(5, fillIntv); // Starting interval
1022 _updateIntv2 = _updateIntv1; // Fill intv
1023 _updateIntv3 = max(5, emptyIntv); // Empty intv
1024 _sequenceState = 0;
1025 _isRunning = true;
1027 return *this;
1028 }
1029
1038 SBK_BarMeterAnimations &bounceFillDownDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0, uint16_t fillIntv = 0)
1039 {
1043 _usePtr = false;
1044 _valuePtr2 = nullptr;
1045 _valuePtr1 = nullptr;
1046 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
1047 uint8_t steps = max(1, _maxTracker - _minTracker + 1);
1048 uint16_t emptyIntv = fillIntv;
1049 if (fillIntv == 0)
1050 {
1051 fillIntv = max(5, duration) / (2 * steps);
1052 emptyIntv = fillIntv;
1053 }
1054 else
1055 {
1056 emptyIntv = max(5, duration - fillIntv * steps) / steps;
1057 }
1058 _updateIntv1 = fillIntv; // Starting interval
1059 _updateIntv2 = _updateIntv1; // Fill intv
1060 _updateIntv3 = emptyIntv; // Empty intv
1061 _sequenceState = 0;
1062 _isRunning = true;
1064 return *this;
1065 }
1066
1075 SBK_BarMeterAnimations &bounceFillDownIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
1076 {
1080 _valuePtr1 = maxPercentPtr;
1081 _valuePtr2 = minPercentPtr;
1082 _usePtr = true;
1084 _updateIntv1 = max(5, fillIntv); // Starting interval
1085 _updateIntv2 = _updateIntv1; // Fill intv
1086 _updateIntv3 = max(5, emptyIntv); // Empty intv
1087 _sequenceState = 0;
1088 _isRunning = true;
1090 return *this;
1091 }
1092
1101 SBK_BarMeterAnimations &bounceFillDownIntv(uint16_t fillIntv = 10, uint16_t emptyIntv = 10, uint8_t maxPercent = 100, uint8_t minPercent = 0)
1102 {
1106 _usePtr = false;
1107 _valuePtr2 = nullptr;
1108 _valuePtr1 = nullptr;
1109 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
1110 _updateIntv1 = max(5, fillIntv); // Starting interval
1111 _updateIntv2 = _updateIntv1; // Fill intv
1112 _updateIntv3 = max(5, emptyIntv); // Empty intv
1113 _sequenceState = 0;
1114 _isRunning = true;
1116 return *this;
1117 }
1118
1127 SBK_BarMeterAnimations &bounceFillFromCenterDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0, uint16_t fillIntv = 0)
1128 {
1132 _mirrorHalfRangeDir = false;
1133 _usePtr = false;
1134 _valuePtr2 = nullptr;
1135 _valuePtr1 = nullptr;
1136 _mapMinMaxTracker(minPercent, maxPercent, (_segsNum / 2) - 1, 0);
1137 uint8_t steps = max(1, _maxTracker - _minTracker + 1);
1138 uint16_t emptyIntv = fillIntv;
1139 if (fillIntv == 0)
1140 {
1141 fillIntv = max(5, duration) / (2 * steps);
1142 emptyIntv = fillIntv;
1143 }
1144 else
1145 {
1146 emptyIntv = max(5, duration - fillIntv * steps) / steps;
1147 }
1148 _updateIntv1 = fillIntv; // Starting interval
1149 _updateIntv2 = _updateIntv1; // Fill intv
1150 _updateIntv3 = emptyIntv; // Empty intv
1151 _sequenceState = 0;
1152 _isRunning = true;
1154 return *this;
1155 }
1156
1165 SBK_BarMeterAnimations &bounceFillFromCenterIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
1166 {
1170 _mirrorHalfRangeDir = false;
1171 _valuePtr1 = maxPercentPtr;
1172 _valuePtr2 = minPercentPtr;
1173 _usePtr = true;
1174 _mapMinMaxTrackerFromPtr(minPercentPtr, maxPercentPtr, (_segsNum / 2) - 1, 0);
1175 _updateIntv1 = fillIntv; // Starting interval
1176 _updateIntv2 = _updateIntv1; // Fill intv
1177 _updateIntv3 = emptyIntv; // Empty intv
1178 _sequenceState = 0;
1179 _isRunning = true;
1181 return *this;
1182 }
1183
1192 SBK_BarMeterAnimations &bounceFillFromCenterIntv(uint16_t fillIntv = 25, uint16_t emptyIntv = 25, uint8_t maxPercent = 100, uint8_t minPercent = 0)
1193 {
1197 _mirrorHalfRangeDir = false;
1198 _usePtr = false;
1199 _valuePtr2 = nullptr;
1200 _valuePtr1 = nullptr;
1201 _mapMinMaxTracker(minPercent, maxPercent, (_segsNum / 2) - 1, 0);
1202 _updateIntv1 = fillIntv; // Starting interval
1203 _updateIntv2 = _updateIntv1; // Fill intv
1204 _updateIntv3 = emptyIntv; // Empty intv
1205 _sequenceState = 0;
1206 _isRunning = true;
1208 return *this;
1209 }
1210
1219 SBK_BarMeterAnimations &bounceFillFromEdgesDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0, uint16_t fillIntv = 0)
1220 {
1224 _mirrorHalfRangeDir = true;
1225 _usePtr = false;
1226 _valuePtr2 = nullptr;
1227 _valuePtr1 = nullptr;
1228 _mapMinMaxTracker(minPercent, maxPercent, (_segsNum / 2) - 1, 0);
1229 uint8_t steps = max(1, _maxTracker - _minTracker + 1);
1230 uint16_t emptyIntv = fillIntv;
1231 if (fillIntv == 0)
1232 {
1233 fillIntv = max(5, duration) / (2 * steps);
1234 emptyIntv = fillIntv;
1235 }
1236 else
1237 {
1238 emptyIntv = max(5, duration - fillIntv * steps) / steps;
1239 }
1240 _updateIntv1 = fillIntv; // Starting interval
1241 _updateIntv2 = _updateIntv1; // Fill intv
1242 _updateIntv3 = emptyIntv; // Empty intv
1243 _sequenceState = 0;
1244 _isRunning = true;
1246 return *this;
1247 }
1248
1257 SBK_BarMeterAnimations &bounceFillFromEdgesIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
1258 {
1262 _mirrorHalfRangeDir = true;
1263 _valuePtr1 = maxPercentPtr;
1264 _valuePtr2 = minPercentPtr;
1265 _usePtr = true;
1266 _mapMinMaxTrackerFromPtr(minPercentPtr, maxPercentPtr, (_segsNum / 2) - 1, 0);
1267 _updateIntv1 = fillIntv; // Starting interval
1268 _updateIntv2 = _updateIntv1; // Fill intv
1269 _updateIntv3 = emptyIntv; // Empty intv
1270 _sequenceState = 0;
1271 _isRunning = true;
1273 return *this;
1274 }
1275
1284 SBK_BarMeterAnimations &bounceFillFromEdgesIntv(uint16_t fillIntv = 25, uint16_t emptyIntv = 25, uint8_t maxPercent = 100, uint8_t minPercent = 0)
1285 {
1289 _mirrorHalfRangeDir = true;
1290 _usePtr = false;
1291 _valuePtr2 = nullptr;
1292 _valuePtr1 = nullptr;
1293 _mapMinMaxTracker(minPercent, maxPercent, (_segsNum / 2) - 1, 0);
1294 _updateIntv1 = fillIntv; // Starting interval
1295 _updateIntv2 = _updateIntv1; // Fill intv
1296 _updateIntv3 = emptyIntv; // Empty intv
1297 _sequenceState = 0;
1298 _isRunning = true;
1300 return *this;
1301 }
1302
1308 SBK_BarMeterAnimations &beatPulse(uint8_t *bpmPtr) // bouncing from bottom (maybe like a volume meter with the music)
1309 {
1311 _updateIntv1 = 60000UL / (bpmPtr ? max(1, *bpmPtr) : 116);
1312 _param2 = min(35 * (_segsNum - 1) / 100, 255); // MIN_BASE_LEVEL
1313 _param3 = min(67 * (_segsNum - 1) / 100, 255); // MIN_PEAK_LEVEL
1314 _param4 = 150; // PEAK_HOLD_TIME
1315 _usePtr = true;
1319
1320 _isRunning = true;
1322 _init = true;
1323 return *this;
1324 }
1325
1331 SBK_BarMeterAnimations &beatPulse(uint8_t bpm = 116) // bouncing from bottom (maybe like a volume meter with the music)
1332 {
1333 _param1 = max(1, bpm);
1334 _updateIntv1 = 60000UL / _param1;
1335 _param2 = min(35 * (_segsNum - 1) / 100, 255); // MIN_BASE_LEVEL
1336 _param3 = min(67 * (_segsNum - 1) / 100, 255); // MIN_PEAK_LEVEL
1337 _param4 = 150; // PEAK_HOLD_TIME
1338 _valuePtr1 = nullptr;
1339 _usePtr = false;
1343
1344 _isRunning = true;
1346 _init = true;
1347 return *this;
1348 }
1349
1358 SBK_BarMeterAnimations &explodingBlocks(uint16_t intv = 50, uint8_t blockLength = 2, uint8_t blockSpacing = 1, uint8_t numBlocks = 0)
1359 {
1360 _updateIntv1 = max(5, intv);
1363 _param3 = numBlocks;
1364 _param4 = ((_segsNum / 2) / (blockLength + blockSpacing)) + 2;
1365 _param4 = constrain(_param4, 2, 32);
1366
1370 _isRunning = true;
1372 return *this;
1373 }
1374
1383 SBK_BarMeterAnimations &collidingBlocks(uint16_t intv = 50, uint8_t blockLength = 2, uint8_t blockSpacing = 1, uint8_t numBlocks = 0)
1384 {
1385
1386 _updateIntv1 = max(5, intv);
1389 _param3 = numBlocks;
1390 _param4 = ((_segsNum / 2) / (blockLength + blockSpacing)) + 2;
1391 _param4 = constrain(_param4, 2, 32);
1392
1396 _isRunning = true;
1398 return *this;
1399 }
1400
1409 SBK_BarMeterAnimations &scrollingUpBlocks(uint16_t intv = 50, uint8_t blockLength = 2, uint8_t blockSpacing = 1, uint8_t numBlocks = 0)
1410 {
1411 _updateIntv1 = max(5, intv);
1414 _param3 = numBlocks;
1416 _param4 = constrain(_param4, 2, 64);
1417
1421 _isRunning = true;
1423 return *this;
1424 }
1425
1434 SBK_BarMeterAnimations &scrollingDownBlocks(uint16_t intv = 50, uint8_t blockLength = 2, uint8_t blockSpacing = 1, uint8_t numBlocks = 0)
1435 {
1436 _updateIntv1 = max(5, intv);
1439 _param3 = numBlocks;
1441 _param4 = constrain(_param4, 2, 64);
1442
1446 _isRunning = true;
1448 return *this;
1449 }
1450
1458 SBK_BarMeterAnimations &downStackingBlocks(uint16_t intv = 50, uint8_t blockLength = 1, uint8_t blockSpacing = 0)
1459 {
1463 _updateIntv1 = max(5, intv);
1466 _param3 = 0; // requested number of blocks
1467 _param4 = 1; // aka maximum blocks number
1468 if (_init)
1470 _isRunning = true;
1472 return *this;
1473 }
1474
1482 SBK_BarMeterAnimations &upUnstackingBlocks(uint16_t intv = 50, uint8_t blockLength = 1, uint8_t blockSpacing = 0)
1483 {
1487 _updateIntv1 = max(5, intv);
1490 _param3 = 0; // aka requested number of blocks
1491 _param4 = 1; // aka maximum blocks number
1492 if (_init)
1494 _isRunning = true;
1496 return *this;
1497 }
1498
1506 SBK_BarMeterAnimations &upStackingBlocks(uint16_t intv = 50, uint8_t blockLength = 1, uint8_t blockSpacing = 0)
1507 {
1511 _updateIntv1 = max(5, intv);
1514 _param3 = 0; // aka requested number of blocks
1515 _param4 = 1; // aka maximum blocks number
1516 if (_init)
1518 _isRunning = true;
1520 return *this;
1521 }
1522
1530 SBK_BarMeterAnimations &downUnstackingBlocks(uint16_t intv = 50, uint8_t blockLength = 1, uint8_t blockSpacing = 0)
1531 {
1535 _updateIntv1 = max(5, intv);
1538 _param3 = 0; // aka requested number of blocks
1539 _param4 = 1; // aka maximum blocks number
1540 if (_init)
1542 _isRunning = true;
1544 return *this;
1545 }
1546
1557 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)
1558 {
1559
1560 _valuePtr1 = sigPtr;
1561 _updateIntv1 = max(10, updateIntv);
1562 _minMap = max(0, minMap);
1563 _maxMap = max(0, maxMap);
1565 _param1 = constrain(smoothingFactor, 0, 100);
1566 _updateIntv2 = samplingIntv;
1567
1571 _isRunning = true;
1572 _init = true;
1574 return *this;
1575 }
1576
1587 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)
1588 {
1589 _valuePtr1 = sigPtr;
1590 _updateIntv1 = max(10, updateIntv);
1591 _minMap = max(0, minMap);
1592 _maxMap = max(0, maxMap);
1594 _param1 = constrain(smoothingFactor, 0, 100);
1595 _updateIntv2 = samplingIntv;
1596
1600 _isRunning = true;
1601 _init = true;
1603 return *this;
1604 }
1605
1618 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)
1619 {
1620 _valuePtr1 = sig1Ptr;
1621 _updateIntv1 = max(10, updateIntv);
1622 _valuePtr2 = sig2Ptr ? sig2Ptr : sig1Ptr;
1623 _minMap = max(0, minMap);
1624 _maxMap = max(0, maxMap);
1626 _param1 = constrain(smoothingFactor, 0, 100);
1627 _updateIntv2 = samplingIntv;
1628
1632 //_mirrorHalfRangeDir = false;
1633 _isRunning = true;
1634 _init = true;
1636 return *this;
1637 }
1638
1651 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)
1652 {
1653 _valuePtr1 = sig1Ptr;
1654 _updateIntv1 = max(10, updateIntv);
1655 _valuePtr2 = sig2Ptr ? sig2Ptr : sig1Ptr;
1656 _minMap = max(0, minMap);
1657 _maxMap = max(0, maxMap);
1659 _param1 = constrain(smoothingFactor, 0, 100);
1660 _updateIntv2 = samplingIntv;
1661
1665 //_mirrorHalfRangeDir = true;
1666 _isRunning = true;
1667 _init = true;
1669 return *this;
1670 }
1671
1687 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)
1688 {
1689
1690 _valuePtr1 = sigPtr;
1691 _updateIntv3 = max(20, peakHoldTime);
1692 _updateIntv1 = max(10, updateIntv);
1693 _minMap = max(0, minMap);
1694 _maxMap = max(0, maxMap);
1696 _param1 = constrain(smoothingFactor, 0, 100);
1697 _updateIntv2 = samplingIntv;
1698
1702 _isRunning = true;
1703 _init = true;
1705 return *this;
1706 }
1707
1713 SBK_BarMeterAnimations &randomFill(uint16_t interval = 30)
1714 {
1715 _updateIntv1 = interval;
1716
1720 _isRunning = true;
1722 _init = true;
1723 return *this;
1724 }
1725
1731 SBK_BarMeterAnimations &randomEmpty(uint16_t interval = 30)
1732 {
1733 _updateIntv1 = interval;
1734
1738 _isRunning = true;
1740 _init = true;
1741 return *this;
1742 }
1743
1744protected:
1745 BarMeterT &_barMeter;
1746 uint8_t _segsNum = 0;
1747
1748 // Active animation update function
1751
1754 {
1756 uint16_t updateIntv1 = 10, updateIntv2 = 10, updateIntv3 = 10;
1757 uint16_t minMap = 0, maxMap = 1023;
1758 const void *valuePtr1 = nullptr;
1759 const void *valuePtr2 = nullptr;
1760 int8_t minTracker = 0, maxTracker = 1;
1761 uint8_t param1 = 0, param2 = 0, param3 = 0, param4 = 0, param5 = 0;
1762 bool loop = false;
1767 bool animLogicSet = false;
1770 bool animDirSet = false;
1771 bool usePtr = false;
1774 uint32_t duration = 0;
1776 };
1778 uint8_t _queueHead = 0;
1779 uint8_t _queueTail = 0;
1780 uint8_t _queueCount = 0;
1782 bool _queueActive = false;
1783 bool _queueOverflow = false;
1788
1789 // Control flags
1790 bool _init = true; // true = init function
1791 bool _isRunning = false;
1792 bool _isPaused = false;
1793 bool _loop = false;
1794 bool _isLoopingNow = false;
1800 bool _animLogicSet = false;
1801 bool _skipPending = false;
1804 bool _animDirSet = false;
1805 bool _usePtr = false;
1809
1810 // Time trackings
1811 uint32_t _currentTime = 0;
1813
1814 // Animations update interval trackers
1815 uint16_t _updateIntv1 = 10, _updateIntv2 = 10, _updateIntv3 = 10;
1816
1817 // Animations trackers
1818 uint8_t _sequenceState = 0;
1820 int8_t _minTracker = 0, _maxTracker = 1;
1821 uint8_t _param1 = 0, _param2 = 0, _param3 = 0, _param4 = 0, _param5 = 0;
1823 uint16_t _minMap = 0, _maxMap = 1023;
1824 uint8_t _counter1 = 0, _counter2 = 0;
1825 // Generic live-value pointers. Their type is determined by the active animation.
1826 const void *_valuePtr1 = nullptr, *_valuePtr2 = nullptr;
1827 // Blocks related helpers
1828 struct Block
1829 {
1830 Block() : position(-1), active(false) {}
1831 int8_t position;
1833 };
1834 static constexpr uint8_t MAX_BLOCK_CAPACITY = 64;
1836 uint8_t _segmentCapacity = 0;
1837 uint8_t _blockCapacity = 0;
1838 uint8_t _randomCursor = 0;
1839
1840 void _captureQueueEntry(QueueEntry &entry, bool resetRuntimeState,
1841 uint32_t duration)
1842 {
1843 entry.function = _currentFunc;
1844 entry.updateIntv1 = _updateIntv1;
1845 entry.updateIntv2 = _updateIntv2;
1846 entry.updateIntv3 = _updateIntv3;
1847 entry.minMap = _minMap;
1848 entry.maxMap = _maxMap;
1849 entry.valuePtr1 = _valuePtr1;
1850 entry.valuePtr2 = _valuePtr2;
1851 entry.minTracker = _minTracker;
1852 entry.maxTracker = _maxTracker;
1853 entry.param1 = _param1;
1854 entry.param2 = _param2;
1855 entry.param3 = _param3;
1856 entry.param4 = _param4;
1857 entry.param5 = _param5;
1858 entry.loop = _loop;
1866 entry.animDirSet = _animDirSet;
1867 entry.usePtr = _usePtr;
1869 entry.resetRuntimeState = resetRuntimeState;
1870 entry.duration = duration;
1872 }
1873
1874 void _loadQueueEntry(const QueueEntry &entry, bool forceReset = false)
1875 {
1876 const bool resetRuntimeState = forceReset || entry.resetRuntimeState;
1877 const bool previousRenderLogic = _animRenderLogicIsInverted;
1878
1879 _currentFunc = entry.function;
1880 _updateIntv1 = entry.updateIntv1;
1881 _updateIntv2 = entry.updateIntv2;
1882 _updateIntv3 = entry.updateIntv3;
1883 _minMap = entry.minMap;
1884 _maxMap = entry.maxMap;
1885 _valuePtr1 = entry.valuePtr1;
1886 _valuePtr2 = entry.valuePtr2;
1887 _minTracker = entry.minTracker;
1888 _maxTracker = entry.maxTracker;
1889 _param1 = entry.param1;
1890 _param2 = entry.param2;
1891 _param3 = entry.param3;
1892 _param4 = entry.param4;
1893 if (resetRuntimeState)
1894 _param5 = entry.param5;
1895 _loop = entry.loop;
1898 (!resetRuntimeState && !entry.animLogicSet)
1901 _prevAnimRenderLogic = resetRuntimeState
1903 : previousRenderLogic;
1909 _animDirSet = entry.animDirSet;
1910 _usePtr = entry.usePtr;
1915
1916 _isRunning = _currentFunc != nullptr;
1917 _isPaused = false;
1918 _isLoopingNow = false;
1919 _skipPending = false;
1920 if (resetRuntimeState)
1921 {
1922 _init = true;
1923 _sequenceState = 0;
1925 _counter1 = _counter2 = 0;
1928 }
1929 else
1930 {
1931 _init = false;
1932 }
1933 }
1934
1935 void _startNextQueuedAnimation(bool forceReset = false)
1936 {
1937 if (_queueCount == 0)
1938 {
1939 _queueActive = false;
1940 _isRunning = false;
1941 _currentFunc = nullptr;
1944 return;
1945 }
1946
1948 _loadQueueEntry(_queue[_queueHead], forceReset);
1950 _queueCount--;
1951 }
1952
1954 {
1955 _segmentCapacity = _barMeter.getSegsNum();
1957 _maxTracker = _segsNum > 0 ? _segsNum - 1 : 0;
1958
1959 const uint16_t requestedCapacity = static_cast<uint16_t>(_segmentCapacity) + 2U;
1960 _blockCapacity = min(requestedCapacity,
1961 static_cast<uint16_t>(MAX_BLOCK_CAPACITY));
1962 const uint8_t randomOrderBlocks =
1963 (_segmentCapacity + sizeof(Block) - 1U) / sizeof(Block);
1964 const uint8_t workspaceBlocks = max(_blockCapacity, randomOrderBlocks);
1965 if (workspaceBlocks > 0)
1966 _animationWorkspace = new Block[workspaceBlocks];
1967 }
1968
1969 uint8_t *_randomOrder()
1970 {
1971 return reinterpret_cast<uint8_t *>(_animationWorkspace);
1972 }
1973
1974 void _resetBlocks(uint8_t &blockCapacity)
1975 {
1977 {
1978 blockCapacity = 0;
1979 return;
1980 }
1981 blockCapacity = min(blockCapacity, _blockCapacity);
1982 for (uint8_t i = 0; i < blockCapacity; ++i)
1984 }
1985
1986 // Animation helpers
1987 static inline void _normalizePercentRange(uint8_t &minP, uint8_t &maxP)
1988 {
1989 minP = constrain(minP, 0, 100);
1990 maxP = constrain(maxP, 0, 100);
1991 if (minP > maxP)
1992 {
1993 uint8_t t = maxP;
1994 maxP = minP;
1995 minP = t;
1996 }
1997 if (minP == maxP)
1998 {
1999 if (maxP < 100)
2000 maxP = minP + 1;
2001 else
2002 minP--;
2003 }
2004 }
2005 const uint8_t *_percentMaxPtr() const { return static_cast<const uint8_t *>(_valuePtr1); }
2006 const uint8_t *_percentMinPtr() const { return static_cast<const uint8_t *>(_valuePtr2); }
2007 const uint8_t *_liveBpmPtr() const { return static_cast<const uint8_t *>(_valuePtr1); }
2008 const uint16_t *_signalPtr1() const { return static_cast<const uint16_t *>(_valuePtr1); }
2009 const uint16_t *_signalPtr2() const { return static_cast<const uint16_t *>(_valuePtr2); }
2010
2011 inline void _mapMinMaxTrackerFromPtr(const uint8_t *minPercPtr, const uint8_t *maxPercPtr, int8_t minR, uint8_t maxR)
2012 {
2013 if (!_usePtr)
2014 return;
2015 uint8_t minP = minPercPtr ? *minPercPtr : 0;
2016 uint8_t maxP = maxPercPtr ? *maxPercPtr : 100;
2017 _normalizePercentRange(minP, maxP);
2018 _minTracker = map(minP, 0, 100, minR, maxR);
2019 _maxTracker = map(maxP, 0, 100, minR, maxR);
2020 }
2021 inline void _mapMinMaxTracker(uint8_t minP, uint8_t maxP, uint8_t minR, uint8_t maxR)
2022 {
2023 _normalizePercentRange(minP, maxP);
2024 _minTracker = map(minP, 0, 100, minR, maxR);
2025 _maxTracker = map(maxP, 0, 100, minR, maxR);
2026 }
2027 inline uint8_t _corrPixelToDir(uint8_t pixel)
2028 {
2029 return _animRenderDirIsReversed ? (_segsNum - 1) - pixel : pixel;
2030 }
2031 inline uint8_t _corrPixelToDirForHalfRange(uint8_t pixel)
2032 {
2034 return pixel;
2035
2036 const uint8_t half = _segsNum / 2;
2037 return abs((half - 1) - pixel);
2038 }
2039 static inline void _correctSwapOrEqualMinMax(uint16_t &minVal, uint16_t &maxVal)
2040 {
2041 if (minVal > maxVal)
2042 {
2043 uint16_t temp = maxVal;
2044 maxVal = minVal;
2045 minVal = temp;
2046 }
2047 if (minVal == maxVal) // avoid division by zero in map()
2048 {
2049 if (maxVal < 65535)
2050 {
2051 maxVal = minVal + 1;
2052 }
2053 else
2054 {
2055 minVal--;
2056 }
2057 }
2058 }
2059 static inline uint8_t _getMappedSignal(uint16_t sig, uint16_t minM, uint16_t maxM, uint8_t minR, uint8_t maxR)
2060 {
2061 uint8_t mapped = map(sig, minM, maxM, minR, maxR);
2062 return constrain(mapped, minR, maxR); // avoid edge overshoot
2063 }
2064
2065 // Animation update functions
2066
2068 {
2069 _init = false;
2070 for (uint8_t i = 0; i < _segsNum; i++)
2071 _barMeter.setPixel(i, true);
2072 return true;
2073 }
2075 {
2076 _init = false;
2077 _barMeter.clear();
2078 return true;
2079 }
2080
2081 bool _wait()
2082 {
2083 if (_init)
2084 {
2085 _init = false;
2087 }
2089 }
2090
2092 {
2093 const uint8_t index = _param1;
2094 const uint8_t blinkCount = _param2;
2095
2096 if (index >= _segsNum)
2097 return true;
2098
2099 if (_init)
2100 {
2101 _init = false;
2102 _sequenceState = 1; // ON phase
2103 _counter1 = 0;
2105 _barMeter.setPixel(index, true);
2106 return false;
2107 }
2108
2109 const uint16_t interval = _sequenceState ? _updateIntv1 : _updateIntv2;
2110 if (_currentTime - _lastUpdate1 < interval)
2111 return false;
2112
2114 if (_sequenceState)
2115 {
2116 _sequenceState = 0;
2117 _barMeter.setPixel(index, false);
2118 _counter1++;
2119 return blinkCount > 0 && _counter1 >= blinkCount;
2120 }
2121
2122 _sequenceState = 1;
2123 _barMeter.setPixel(index, true);
2124 return false;
2125 }
2126
2128 {
2130
2131 if (_init)
2132 {
2133 _init = false;
2134
2135 if (!_animLogicSet)
2137
2138 // Pre-fill for emptyDown mode
2140 {
2142 for (uint8_t i = 0; i < _segsNum; ++i)
2143 {
2144 if (i <= _maxTracker)
2145 _barMeter.setPixel(_corrPixelToDir(i), true);
2146 else
2147 _barMeter.setPixel(_corrPixelToDir(i), false);
2148 }
2149 }
2150 else // Pre-fill fillUp, light up to _minTracker inclusively
2151 {
2153 for (uint8_t i = 0; i < _segsNum; ++i)
2154 {
2155 if (i <= _minTracker)
2156 _barMeter.setPixel(_corrPixelToDir(i), true);
2157 else
2158 _barMeter.setPixel(_corrPixelToDir(i), false);
2159 }
2160 }
2161 return false;
2162 }
2163
2165 {
2167
2168 // Clamp if inverted logic and tracker is beyond maxTracker
2171
2172 // Clamp in normal logic mode
2175 }
2176
2178 {
2180
2182 {
2184 {
2185 _barMeter.setPixel(_corrPixelToDir(_ledTracker1), false);
2186 _ledTracker1--;
2187 }
2188 else
2189 return true; // now done!
2190 }
2191 else
2192 {
2194 {
2195 _barMeter.setPixel(_corrPixelToDir(_ledTracker1), true);
2196 _ledTracker1++;
2197 }
2198 else
2199 return true; // done lighting up all
2200 }
2201 }
2202 return false;
2203 }
2204
2206 {
2207 switch (_sequenceState)
2208 {
2209 case 0: // Fill Up
2210
2211 _skipPending = false;
2212 if (_fillOrEmpty())
2213 {
2215 _sequenceState = 1;
2216 _updateIntv1 = _updateIntv3; // Empty update interval
2217 }
2218 return false;
2219
2220 case 1: // Fill Down
2221
2222 _skipPending = true;
2223 if (_fillOrEmpty())
2224 {
2226 _sequenceState = 0;
2227 _updateIntv1 = _updateIntv2; // Fill up update interval
2228 return true;
2229 }
2230 return false;
2231 }
2232
2233 return true; // Shouldn’t reach here
2234 }
2235
2237 {
2238 const uint8_t center = _segsNum / 2;
2240
2241 if (_init)
2242 {
2243 _init = false;
2244
2245 if (!_animLogicSet)
2247
2249 {
2251
2252 // Start fully lit
2253 for (uint8_t i = 0; i < center; ++i)
2254 {
2255 if (i >= _maxTracker)
2256 {
2257 _barMeter.setPixel(_corrPixelToDirForHalfRange(i), true);
2258 _barMeter.setPixel((_segsNum - 1) - _corrPixelToDirForHalfRange(i), true);
2259 }
2260 else
2261 {
2262 _barMeter.setPixel(_corrPixelToDirForHalfRange(i), false);
2263 _barMeter.setPixel((_segsNum - 1) - _corrPixelToDirForHalfRange(i), false);
2264 }
2265 }
2266 }
2267 else
2268 {
2269
2271
2272 // Start with minimum fill
2273 for (uint8_t i = 0; i < center; ++i)
2274 {
2275 if (i > _minTracker)
2276 {
2277 _barMeter.setPixel(_corrPixelToDirForHalfRange(i), true);
2278 _barMeter.setPixel((_segsNum - 1) - _corrPixelToDirForHalfRange(i), true);
2279 }
2280 else
2281 {
2282 _barMeter.setPixel(_corrPixelToDirForHalfRange(i), false);
2283 _barMeter.setPixel((_segsNum - 1) - _corrPixelToDirForHalfRange(i), false);
2284 }
2285 }
2286 }
2287 return false;
2288 }
2289
2291 {
2294 {
2295 if (_ledTracker1 >= _maxTracker && _ledTracker1 >= 0)
2296 {
2299 _ledTracker1--;
2300 }
2301 else
2302 return true; // finished filling
2303 }
2304 else
2305 {
2306 if (_ledTracker1 <= _minTracker && _ledTracker1 < center)
2307 {
2310 _ledTracker1++;
2311 }
2312 else
2313 return true; // finished clearing
2314 }
2315 }
2316
2317 return false;
2318 }
2319
2321 {
2322 switch (_sequenceState)
2323 {
2324 case 0: // Fill from center
2325
2326 _skipPending = true;
2328 {
2330 _updateIntv1 = _updateIntv3; // Empty update interval
2331 _sequenceState = 1;
2332 }
2333 return false;
2334
2335 case 1: // Empty to center
2336
2337 _skipPending = false;
2339 {
2341 _updateIntv1 = _updateIntv2; // Empty update interval
2342 _sequenceState = 0;
2343 return true; // one full bounce cycle completed
2344 }
2345 return false;
2346 }
2347
2348 return true; // should not happen
2349 }
2350
2351#define bpm _param1
2352#define MIN_BASE_LEVEL _param2
2353#define MIN_PEAK_LEVEL _param3
2354#define PEAK_HOLD_TIME _param4
2355#define bpmPtr _liveBpmPtr()
2356#define currentLevel _ledTracker1
2357#define peakLevel _ledTracker2
2358#define beat _updateIntv1
2359#define lastBeat _lastUpdate1
2360#define lastRandomUpdate _lastUpdate2
2361#define prevPeakUpdate _lastUpdate3
2362 bool _beatPulse() // bouncing from bottom (maybe like a volume meter with the music)
2363 {
2364 static int8_t randomOffset;
2365 static bool isPeak = false;
2366
2367 if (_init)
2368 {
2369 _init = false;
2370
2371 if (!_animLogicSet)
2373
2374 _setAllOff();
2377 }
2378
2379 // Update at bpm pulse
2380 if (_currentTime - lastBeat >= beat)
2381 {
2383 isPeak = !isPeak; // Toggle between peak and base level
2384 }
2385
2386 // Smooth transition between base and peak levels
2387 if (isPeak && currentLevel <= MIN_PEAK_LEVEL)
2388 {
2389 currentLevel += random(3, 5); // Gradual increase
2390 }
2391 else if (!isPeak && currentLevel >= MIN_BASE_LEVEL)
2392 {
2393 currentLevel -= random(0, 4); // Gradual decrease
2394 }
2395
2396 // Randomized additional LEDs on top at a random interval
2397 if (_currentTime - lastRandomUpdate >= uint16_t(random(50, 300)))
2398 {
2400 randomOffset = random(-4, 4); // 0 to 5 extra LEDs
2401 }
2402
2403 uint8_t finalLevel = currentLevel + randomOffset;
2404 finalLevel = constrain(finalLevel, 0, _segsNum);
2405 if (finalLevel > _segsNum)
2406 finalLevel = _segsNum;
2407 if (finalLevel < 0)
2408 finalLevel = 0;
2409
2410 // Update peak level
2411 if (finalLevel > peakLevel)
2412 {
2413 peakLevel = min(finalLevel, _segsNum - 1);
2414 prevPeakUpdate = _currentTime; // Reset decay timer
2415 }
2416 else if (_currentTime - prevPeakUpdate >= PEAK_HOLD_TIME && peakLevel > finalLevel)
2417 {
2418 peakLevel--; // Slowly drop peak
2419 prevPeakUpdate = _currentTime; // Reset decay timer
2420 }
2421
2422 // Update LED states
2423 for (uint8_t i = 0; i < _segsNum; i++)
2424 {
2425 _barMeter.setPixel(_corrPixelToDir(i), (i < finalLevel));
2426 }
2427
2428 // Ensure peak LED stays on
2429 if (peakLevel < _segsNum)
2430 {
2431 _barMeter.setPixel(_corrPixelToDir(peakLevel), true);
2432 }
2433 return false; // Continuous pulse animation, never auto-terminates
2434 }
2435
2437 {
2438 bpm = bpmPtr ? max(1, *bpmPtr) : 116;
2439 beat = 60000UL / bpm;
2440 return _beatPulse();
2441 }
2442#undef bpm
2443#undef MIN_BASE_LEVEL
2444#undef MIN_PEAK_LEVEL
2445#undef PEAK_HOLD_TIME
2446#undef bpmPtr
2447#undef currentLevel
2448#undef peakLevel
2449#undef beat
2450#undef lastBeat
2451#undef lastRandomUpdate
2452#undef prevPeakUpdate
2453
2454#define blockLength _param1
2455#define blockSpacing _param2
2456#define requestedNumBlocks _param3
2457#define maxBlocks _param4
2458#define emitIndex _param5
2459#define emittedBlocksCount _counter1
2460#define emitCooldown _counter2
2461 void _emitBlock(int8_t pos)
2462 {
2463 if (!_animationWorkspace || maxBlocks < 1)
2464 return;
2465
2466 if (emitCooldown > 0)
2467 {
2468 emitCooldown--;
2469 return;
2470 }
2471
2473 return;
2474
2475 for (uint8_t i = 0; i < maxBlocks; ++i)
2476 {
2477 uint8_t idx = (emitIndex + i) % maxBlocks;
2478 Block &b = _animationWorkspace[idx];
2479
2480 if (!b.active)
2481 {
2482 b.position = pos;
2483 b.active = true;
2485
2487 emitIndex = (emitIndex + 1) % maxBlocks;
2488 return;
2489 }
2490 }
2491 }
2492
2493 static inline int8_t _calculateSwitchPosition(int8_t pos, uint8_t blockL, uint8_t range)
2494 {
2495 // Compute switched block head position
2496 return (range - 1) - pos + (blockL - 1);
2497 }
2498
2500 {
2501 const int8_t emitInterval = blockLength + blockSpacing;
2502
2503 int8_t closestSwp = -127; // Track the nearest visible block from new side
2504
2505 for (uint8_t i = 0; i < maxBlocks; ++i)
2506 {
2507 Block &b = _animationWorkspace[i];
2508
2509 if (!b.active)
2510 continue;
2511
2512 int8_t p = b.position; // head position before switching
2513
2514 // Step 1: compute switched block head position
2515 int8_t swp = _calculateSwitchPosition(p, blockLength, range);
2516
2517 b.position = swp;
2518
2519 // Step 2: if out of visible range, deactivate block
2520 if (swp < 0)
2521 {
2522 b.active = false;
2523 continue;
2524 }
2525
2526 // // Step 3: Keep the first (closest to emit side) visible block
2527 if (closestSwp == -127 || swp < closestSwp)
2528 closestSwp = swp;
2529 }
2530
2531 if (closestSwp >= 0)
2532 return (emitInterval - 1) - closestSwp;
2533 else
2534 return 0; // fallback if no visible block found
2535 }
2536
2538 {
2539 const uint8_t center = _segsNum / 2;
2540
2541 if (_init)
2542 {
2543 _init = false;
2545
2547 emitCooldown = 0;
2548
2549 if (!_animLogicSet)
2551
2553 return false;
2554 }
2555
2557 {
2561 }
2562
2564 {
2566 // _frameCounter++;
2567 _barMeter.clear();
2568
2570 _emitBlock(-1); // The new block moves to position 0 on its first update.
2571
2572 for (uint8_t i = 0; i < maxBlocks; ++i)
2573 {
2574 Block &b = _animationWorkspace[i];
2575 if (!b.active)
2576 continue;
2577
2578 b.position++;
2579
2580 // Only render to lower half (0 to _centerTracker - 1)
2581 uint16_t clamped = max(0, (int16_t)b.position + 1);
2582 uint8_t pixelsVisible = min(blockLength, (uint8_t)clamped);
2583 for (uint8_t j = 0; j < pixelsVisible; ++j)
2584 {
2585 int8_t pos = _animRenderLogicIsInverted ? center - 1 - b.position : b.position;
2586 int8_t tailOffset = _animRenderLogicIsInverted ? pos + j : pos - j;
2587
2588 if (tailOffset < 0)
2589 continue;
2590
2591 int8_t idx = tailOffset;
2592 if (idx >= center)
2593 continue;
2594
2595 int8_t mirrorIdx = _segsNum - 1 - idx;
2596
2597 if (idx >= 0 && idx < _segsNum)
2598 {
2599 _barMeter.setPixel(idx, true);
2600 }
2601 if (mirrorIdx != idx && mirrorIdx >= 0 && mirrorIdx < _segsNum)
2602 {
2603 _barMeter.setPixel(mirrorIdx, true);
2604 }
2605 }
2606
2607 // Deactivate block if head has passed the visual range
2608 if (b.position >= center - 1 + blockLength)
2609 b.active = false;
2610 }
2611 }
2612 // The animation ends after emission stops and all active blocks have cleared.
2614 {
2615 bool anyActive = false;
2616 for (uint8_t i = 0; i < maxBlocks; ++i)
2617 {
2618 if (_animationWorkspace[i].active)
2619 {
2620 anyActive = true;
2621 break;
2622 }
2623 }
2624 if (!anyActive)
2625 return true; // animation complete
2626 }
2627
2628 return false;
2629 }
2630
2632 {
2633 if (_init)
2634 {
2635 _init = false;
2637
2639 emitCooldown = 0;
2640
2641 if (!_animLogicSet)
2643
2645 return false;
2646 }
2647
2649 {
2653 }
2654
2656 {
2658 _setAllOff();
2659
2661 _emitBlock(-1); // Start fully off-screen left
2662
2663 for (uint8_t i = 0; i < maxBlocks; ++i)
2664 {
2665 Block &b = _animationWorkspace[i];
2666 if (!b.active)
2667 continue;
2668
2669 b.position++;
2670
2671 for (uint8_t j = 0; j < blockLength; ++j)
2672 {
2673 int8_t pos = _animRenderLogicIsInverted ? (_segsNum - 1) - b.position : b.position;
2674 int8_t tailOffset = _animRenderLogicIsInverted ? pos + j : pos - j;
2675
2676 if (tailOffset < 0)
2677 continue;
2678
2679 int8_t idx = tailOffset;
2680 if (idx >= _segsNum)
2681 continue;
2682
2683 if (idx >= 0 && idx < _segsNum)
2684 _barMeter.setPixel(_corrPixelToDir(idx), true);
2685 }
2686
2687 // Deactivate block if head has passed the visual range
2688 if (b.position >= (_segsNum - 1) + blockLength)
2689 b.active = false;
2690 }
2691 }
2692
2694 {
2695 for (uint8_t i = 0; i < maxBlocks; ++i)
2696 {
2697 if (_animationWorkspace[i].active)
2698 return false;
2699 }
2700 return true;
2701 }
2702
2703 return false;
2704 }
2705
2706#define stackLevel _ledTracker1
2708 {
2709 const uint8_t blockInterval = blockLength + blockSpacing;
2710 bool hasActive = false; // active block tracker
2711
2712 if (_init)
2713 {
2714 _init = false;
2715
2716 if (!_animLogicSet)
2718
2719 maxBlocks = 1;
2721
2722 emitIndex = 0;
2723 emitCooldown = 0;
2724
2725 stackLevel = 0;
2727 {
2728 _barMeter.clear();
2729 // Falling blocks
2730 // stackLevel = 0;
2731 }
2732 else
2733 {
2734 // Flying upward - fill pattern first
2735 while (stackLevel < _segsNum)
2736 stackLevel += blockInterval;
2737 for (uint8_t i = 0; i < stackLevel; i++)
2738 {
2739 if ((i % blockInterval) < blockLength)
2740 _barMeter.setPixel(_corrPixelToDir(i), true);
2741 else
2742 _barMeter.setPixel(_corrPixelToDir(i), false);
2743 }
2744 }
2745
2746 return false;
2747 }
2748
2750 {
2753 stackLevel += blockInterval;
2754 else
2755 stackLevel -= blockInterval;
2756 }
2757
2759 {
2761
2762 // Only clear pixels of active blocks, not the whole bar
2763 for (uint8_t i = 0; i < maxBlocks; ++i)
2764 {
2765 Block &b = _animationWorkspace[i];
2766 if (!b.active)
2767 continue;
2768
2769 for (uint8_t j = 0; j < blockLength; ++j)
2770 {
2771 int8_t seg = b.position + j;
2772 if (seg >= 0 && seg < _segsNum)
2773 _barMeter.setPixel(_corrPixelToDir(seg), false);
2774 }
2775 }
2776
2777 // Emit one block if none active
2778 for (uint8_t i = 0; i < maxBlocks; ++i)
2779 {
2780 if (_animationWorkspace[i].active)
2781 {
2782 hasActive = true;
2783 break;
2784 }
2785 }
2786 if (!hasActive)
2787 {
2788 emitCooldown = 0; // emit blocks on demand
2790 _emitBlock(_segsNum); // fall from top
2791 else if (_animRenderLogicIsInverted && stackLevel >= 0)
2792 _emitBlock(stackLevel - blockInterval);
2793 }
2794
2795 // Update and draw all active blocks
2796 for (uint8_t i = 0; i < maxBlocks; ++i)
2797 {
2798 Block &b = _animationWorkspace[i];
2799 if (!b.active)
2800 continue;
2801
2802 // Clear trailing pixel of previous frame
2803 int8_t clearPos = b.position;
2804 if (clearPos >= 0 && clearPos < _segsNum)
2805 _barMeter.setPixel(_corrPixelToDir(clearPos), false);
2806
2807 // Move block
2809 b.position--;
2810 else
2811 b.position++;
2812
2813 for (uint8_t j = 0; j < blockLength; ++j)
2814 {
2815 int8_t seg = b.position + j;
2816 if (seg >= 0 && seg < _segsNum)
2817 _barMeter.setPixel(_corrPixelToDir(seg), true);
2818 }
2819
2821 {
2822 if (b.position <= stackLevel)
2823 {
2824 stackLevel += blockInterval;
2825 b.active = false;
2826 }
2827 }
2828 else
2829 {
2830 if (b.position >= _segsNum)
2831 {
2832 stackLevel -= blockInterval;
2833 b.active = false;
2834 }
2835 }
2836 }
2837
2838 // Draw base for stacking
2839 if (stackLevel == 0)
2840 {
2841 _barMeter.setPixel(_corrPixelToDir(0), false);
2842 }
2844 {
2845 for (int8_t i = 0; i < stackLevel - blockInterval; ++i)
2846 {
2847 if ((i % blockInterval) < blockLength)
2848 _barMeter.setPixel(_corrPixelToDir(i), true);
2849 else
2850 _barMeter.setPixel(_corrPixelToDir(i), false);
2851 }
2852 }
2853 else
2854 {
2855 for (uint8_t i = 0; i < stackLevel - blockInterval; i++)
2856 {
2857 if ((i % blockInterval) < blockLength)
2858 _barMeter.setPixel(_corrPixelToDir(i), true);
2859 else
2860 _barMeter.setPixel(_corrPixelToDir(i), false);
2861 }
2862 }
2864 {
2865 if (stackLevel >= _segsNum - 1 && !hasActive)
2866 return true;
2867 }
2868 else
2869 {
2870 if (stackLevel <= 0 && !hasActive)
2871 return true;
2872 }
2873 }
2874 return false;
2875 }
2876#undef stackLevel
2877#undef blockLength
2878#undef blockSpacing
2879#undef requestedNumBlocks
2880#undef maxBlocks
2881#undef emitIndex
2882#undef emittedBlocksCount
2883#undef emitCooldown
2884
2885#define smoothingFactor _param1
2887 {
2888 if (!_signalPtr1())
2889 {
2890 _setAllOff();
2891 return true;
2892 }
2893
2894 if (_init)
2895 {
2896 _init = false;
2897
2898 if (!_animLogicSet)
2900
2903 _setAllOff();
2904 return false;
2905 }
2906
2907 // Signal sampling
2909 {
2910 uint16_t raw = *_signalPtr1();
2913 }
2914
2916 {
2918
2920
2921 for (uint8_t i = 0; i < _segsNum; i++)
2922 {
2923 _barMeter.setPixel(_corrPixelToDir(i), i < level);
2924 }
2925 }
2926 return false; // continuous animation
2927 }
2929 {
2930 if (!_signalPtr1())
2931 {
2932 _setAllOff();
2933 return true;
2934 }
2935
2936 if (_init)
2937 {
2938 _init = false;
2939
2940 if (!_animLogicSet)
2942
2945 _setAllOff();
2946 return false;
2947 }
2948
2949 // Signal sampling
2951 {
2952 uint16_t raw = *_signalPtr1();
2955 }
2956
2958 {
2960
2962 uint8_t pointer = _getMappedSignal(*_signalPtr1(), _minMap, _maxMap, 0, _segsNum);
2963
2964 // First, fill up to moving average
2965 for (uint8_t i = 0; i < _segsNum; i++)
2966 {
2967 _barMeter.setPixel(_corrPixelToDir(i), i < avg);
2968 }
2969
2970 // Then selectively clear area around pointer
2971 if (pointer < avg && pointer > 0)
2972 _barMeter.setPixel(_corrPixelToDir(pointer - 1), false); // clear just below if pointer is inside the fill
2973
2974 if (pointer < avg - 2)
2975 _barMeter.setPixel(_corrPixelToDir(pointer + 1), false); // clear just above
2976
2977 // Finally, show the pointer
2978 _barMeter.setPixel(_corrPixelToDir(pointer), true);
2979 }
2980 return false; // continuous animation
2981 }
2983 {
2984 if (!_signalPtr1() && !_signalPtr2())
2985 {
2986 _setAllOff();
2987 return true;
2988 }
2989
2990 if (_init)
2991 {
2992 _init = false;
2993
2994 if (!_animLogicSet)
2996
2998 if (_signalPtr2())
3001 _setAllOff();
3002 return false;
3003 }
3004
3007
3008 // Signals sampling
3010 {
3012
3013 uint16_t raw1 = *_signalPtr1();
3014 _smoothedValue1 = (uint16_t)(((uint32_t)smoothingFactor * raw1 + (uint32_t)(100 - smoothingFactor) * _smoothedValue1) / 100);
3015
3016 if (_signalPtr2())
3017 {
3018 uint16_t raw2 = *_signalPtr2();
3019 _smoothedValue2 = (uint16_t)(((uint32_t)smoothingFactor * raw2 + (uint32_t)(100 - smoothingFactor) * _smoothedValue2) / 100);
3020 }
3021 }
3022
3024 {
3026
3027 uint16_t raw1 = *_signalPtr1();
3028 _smoothedValue1 = (uint16_t)(((uint32_t)smoothingFactor * raw1 + (uint32_t)(100 - smoothingFactor) * _smoothedValue1) / 100);
3029 uint8_t level1 = _getMappedSignal(_smoothedValue1, _minMap, _maxMap, 0, _segsNum / 2);
3030
3031 uint8_t level2 = level1;
3032 if (_signalPtr2())
3034
3035 for (uint8_t i = 0; i < _segsNum; i++)
3036 {
3037 //_barMeter.setPixel(_corrPixelToDirForHalfRange(i), i >= (_segsNum / 2 - 1) - level1 && i <= ((_segsNum / 2) + level2));
3039 _barMeter.setPixel(i, i < (_segsNum / 2 - 1) - level1 || i > ((_segsNum / 2) + level2));
3040 else
3041 _barMeter.setPixel(i, i >= (_segsNum / 2 - 1) - level1 && i <= ((_segsNum / 2) + level2));
3042 }
3043 }
3044 return false; // continuous animation
3045 }
3046
3047#define currentLevel _ledTracker1
3048#define peakLevel _ledTracker3
3049#define baseUpdate _updateIntv1
3050#define lastBaseUpdate _lastUpdate1
3051#define peakHoldTime _updateIntv3
3052#define lastPeakUpdate _lastUpdate3
3053 bool _followSignalFloatingPeak() // bouncing from bottom (maybe like a volume meter with the music)
3054 {
3055
3056 if (!_signalPtr1())
3057 {
3058 _setAllOff();
3059 return true;
3060 }
3061
3062 if (_init)
3063 {
3064 _init = false;
3065
3066 if (!_animLogicSet)
3068
3069 _setAllOff();
3073 currentLevel = 0;
3074 peakLevel = 0;
3075 return false;
3076 }
3077
3078 // Signal smoothing update
3080 {
3082
3083 uint16_t raw = *_signalPtr1();
3086 }
3087
3088 // Pixel + peak update
3090 {
3092
3093 // Peak logic
3094 if (currentLevel > peakLevel)
3095 {
3096 peakLevel = min(currentLevel, _segsNum - 1);
3097 lastPeakUpdate = _currentTime; // Reset decay timer
3098 }
3100 {
3101 peakLevel--; // Slowly drop peak
3102 lastPeakUpdate = _currentTime; // Reset decay timer
3103 }
3104
3105 // Draw bar
3106 for (uint8_t i = 0; i < _segsNum; i++)
3107 {
3108 _barMeter.setPixel(_corrPixelToDir(i), (i <= currentLevel));
3109 }
3110
3111 // Draw peak
3112 if (peakLevel < _segsNum)
3113 {
3114 _barMeter.setPixel(_corrPixelToDir(peakLevel), true);
3115 }
3116 }
3117 return false; // Continuous pulse animation, never auto-terminates
3118 }
3119#undef currentLevel
3120#undef peakLevel
3121#undef baseUpdate
3122#undef lastBaseUpdate
3123#undef peakHoldTime
3124#undef lastPeakUpdate
3125
3126#undef smoothingFactor
3127
3129 {
3130 uint8_t *pixelOrder = _randomOrder();
3131 if (_segsNum == 0 || !pixelOrder)
3132 return true;
3133
3134 if (_init)
3135 {
3136 _init = false;
3137
3138 if (!_animLogicSet)
3140
3142 _setAllOn();
3143 else
3144 _setAllOff();
3145
3146 for (uint8_t i = 0; i < _segsNum; ++i)
3147 pixelOrder[i] = i;
3148
3149 for (uint8_t i = _segsNum - 1; i > 0; --i)
3150 {
3151 uint8_t j = random(0, i + 1);
3152 uint8_t tmp = pixelOrder[i];
3153 pixelOrder[i] = pixelOrder[j];
3154 pixelOrder[j] = tmp;
3155 }
3156
3157 _randomCursor = 0;
3159
3160 return false;
3161 }
3162
3164 {
3166
3167 uint8_t retries = 0;
3168 while (_randomCursor < _segsNum && retries++ < _segsNum - 1)
3169 {
3170 uint8_t seg = pixelOrder[_randomCursor];
3171 bool currentState = _barMeter.getPixelState(seg);
3172 bool shouldChange = (!_AnimInitLogicIsInverted && !currentState) || (_AnimInitLogicIsInverted && currentState);
3173
3174 if (shouldChange)
3175 {
3176 _barMeter.setPixel(seg, !_AnimInitLogicIsInverted);
3177 _randomCursor++;
3178 break; // Change only one pixel per interval
3179 }
3180 else
3181 {
3182 _randomCursor++; // Move on to next even if it doesn't need change
3183 }
3184 }
3185 }
3186 if (_randomCursor >= _segsNum)
3187 {
3188 return true;
3189 }
3190 return false;
3191 }
3192};
#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.
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)
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.
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.
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 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 & 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.
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 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 & 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 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