SBK_BarDrive Library 2.2.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 {
85 _maxTracker = _segsNum > 0 ? _segsNum - 1 : 0;
86 }
87
93 bool update(uint32_t syncTime = millis())
94 {
95 // Keep all animation deadlines on a clock that excludes paused time.
96 if (_isPaused)
97 return _queueActive;
98
99 _currentTime = syncTime - _pausedTime;
100
102 {
105 }
106
109 {
112 {
113 // Stop producing blocks at the deadline, but keep updating until
114 // every block already in flight has naturally left the display.
116 _loop = false;
117 }
119 }
120
123 {
124 if (_queueActive)
126 else
127 {
128 _isRunning = false;
129 _isPaused = false;
130 _loop = false;
131 _isLoopingNow = false;
132 _currentFunc = nullptr;
133 _init = true;
135 }
136 return _isRunning;
137 }
138
140 return _queueActive;
141
142 if ((this->*_currentFunc)())
143 {
144 if (_loop)
145 {
146 if (_skipPending)
147 _isLoopingNow = false;
148 else
149 {
150 _isLoopingNow = true; // mark it so user can react
151 _init = true;
152 }
153 }
154 else
155 {
156 if (_queueActive)
158 else
159 {
160 _isLoopingNow = false;
161 _isRunning = false;
162 _currentFunc = nullptr;
163 _queueActive = false;
167 }
168 }
169 }
170 return _isRunning;
171 }
172
175 {
176 _init = true;
177 return *this;
178 }
179
184 SBK_BarMeterAnimations &pause(uint32_t syncTime = millis())
185 {
186 if (!_isPaused)
187 {
188 _pausedAt = syncTime;
189 _isPaused = true;
190 }
191 return *this;
192 }
193
198 SBK_BarMeterAnimations &resume(uint32_t syncTime = millis())
199 {
200 if (_isPaused)
201 {
202 _pausedTime += syncTime - _pausedAt;
203 _isPaused = false;
204 }
205 return *this;
206 }
207
210 {
211 resume();
212 _isRunning = false;
213 _loop = false;
214 _isLoopingNow = false;
215 _skipPending = false;
216 _currentFunc = nullptr;
217 _animLogicSet = false;
218 _init = true;
223 clearQueue();
224 _barMeter.clear();
225 return *this;
226 }
227
237 {
238 if (!_currentFunc)
239 return *this;
240
242 {
245 _queueCount++;
247 }
248 else
249 {
250 _queueOverflow = true;
251 }
252
253 _isRunning = false;
254 _currentFunc = nullptr;
257 return *this;
258 }
259
268 {
269 if (!_currentFunc)
270 return *this;
271
273 {
274 _captureQueueEntry(_queue[_queueTail], true, duration);
276 _queueCount++;
278 }
279 else
280 {
281 _queueOverflow = true;
282 }
283
284 _isRunning = false;
285 _currentFunc = nullptr;
288 return *this;
289 }
290
300 SBK_BarMeterAnimations &forTime(uint32_t duration)
301 {
302 if (_currentFunc)
303 {
304 _animationDuration = duration;
305 _queueEntryDuration = duration;
307 }
308 else if (_queueCount > 0)
309 {
310 const uint8_t previousIndex =
312 _queue[previousIndex].duration = duration;
313 }
314 return *this;
315 }
316
326 {
327 if (_queueCount == 0)
328 return *this;
329
331 {
332 _queueOverflow = true;
333 return *this;
334 }
335
336 const uint8_t previousIndex =
338 QueueEntry &entry = _queue[_queueTail];
339 entry = _queue[previousIndex];
340 entry.loop = _loop;
343 entry.resetRuntimeState = false;
344 entry.duration = 0;
345 entry.blockEmissionStopAfter = 0;
346
348 _queueCount++;
350 return *this;
351 }
352
355 {
356 _queueLoop = true;
357 return *this;
358 }
359
362 {
363 _queueLoop = false;
364 return *this;
365 }
366
369 {
370 if (_queueCount > 0)
371 {
372 if (!_queueActive)
374 _queueActive = true;
376 }
377 return *this;
378 }
379
382 {
383 _queueHead = 0;
384 _queueTail = 0;
385 _queueCount = 0;
387 _queueStart = 0;
388 _queueLoop = false;
390 _queueActive = false;
391 _queueOverflow = false;
392 return *this;
393 }
394
402 {
403 if (!_isRunning || !_currentFunc)
404 return *this;
405
406 _loop = false;
407 resume();
408 _isLoopingNow = false;
409 _skipPending = false;
410
411 if (_queueActive)
413 else
414 {
415 _isRunning = false;
416 _currentFunc = nullptr;
417 _queueActive = false;
419 }
420 return *this;
421 }
422
424 uint8_t queuedAnimations() const { return _queueCount; }
425
427 bool isQueueRunning() const { return _queueActive; }
428
430 uint8_t currentQueueIndex() const { return _currentQueueIndex; }
431
433 bool isQueueIndexPlaying(uint8_t index) const
434 {
435 return _queueActive && _currentQueueIndex == index;
436 }
437
439 bool queueOverflowed() const { return _queueOverflow; }
440
443 {
445 return *this;
446 _loop = true;
447 return *this;
448 }
449
452 {
453 _loop = false;
454 return *this;
455 }
456
458 SBK_BarMeterAnimations &setDir(bool newDirection)
459 {
460 _animRenderDirIsReversed = newDirection;
461 _animDirSet = true;
462 return *this;
463 }
464
472
480
488
494 {
496 return *this;
498 _animLogicSet = true;
499 return *this;
500 }
501
508 {
510 return *this;
512 _animLogicSet = true;
513 return *this;
514 }
515
522 {
524 return *this;
526 _animLogicSet = true;
527 return *this;
528 }
529
536 {
538 return *this;
540 _animLogicSet = false;
541 return *this;
542 }
543
546 {
548 return *this;
549 }
550
553 {
555 return *this;
556 }
557
569 {
570 if (_currentFunc)
571 {
572 _blockEmissionStopAfter = duration;
575 }
576 else if (_queueCount > 0)
577 {
578 const uint8_t previousIndex =
580 _queue[previousIndex].blockEmissionStopAfter = duration;
581 }
582 return *this;
583 }
584
586 bool isRunning() const { return _isRunning; }
587
589 bool isPaused() const { return _isPaused; }
590
592 bool isLoopEnabled() const { return _loop; }
593
596 {
597 _updateIntv1 = _maxValue(static_cast<uint16_t>(1), interval);
598 return *this;
599 }
600
603 {
604 if (_skipPending == true)
605 _skipPending = false;
606 else
607 {
608 if (_isLoopingNow)
609 {
610 _isLoopingNow = false; // auto-clear after read
611 return true;
612 }
613 }
614 return false;
615 }
616
619 {
621 return true;
622 else
623 return false;
624 }
625
628
631 {
633 return true;
634 else
635 return false;
636 }
637
640
641 // Animation starters
646 SBK_BarMeterAnimations &setAll(bool onff) { return onff ? setAllOn() : setAllOff(); }
647
652 {
654 _isRunning = true;
655 _init = true;
656 return *this;
657 }
658
663 {
665 _isRunning = true;
666 _init = true;
667 return *this;
668 }
669
674 SBK_BarMeterAnimations &wait(uint16_t duration)
675 {
676 _updateIntv1 = duration;
677 _loop = false;
678 _isRunning = true;
680 _init = true;
681 return *this;
682 }
683
690 SBK_BarMeterAnimations &blinkPixel(uint8_t index, uint16_t interval = 500, uint8_t blinkCount = 0)
691 {
692 return blinkPixel(index, interval, interval, blinkCount);
693 }
694
702 SBK_BarMeterAnimations &blinkPixel(uint8_t index, uint16_t onInterval, uint16_t offInterval, uint8_t blinkCount)
703 {
704 _param1 = index;
705 _param2 = blinkCount;
706 _updateIntv1 = _maxValue(static_cast<uint16_t>(1), onInterval);
707 _updateIntv2 = _maxValue(static_cast<uint16_t>(1), offInterval);
709 _usePtr = false;
711 _isRunning = true;
712 _init = true;
713 return *this;
714 }
715
723 SBK_BarMeterAnimations &fillUpDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0)
724 {
728 _usePtr = false;
729 _valuePtr2 = nullptr;
730 _valuePtr1 = nullptr;
731 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
732 _isRunning = true;
733 uint8_t steps = _maxValue(1, _maxTracker - _minTracker + 1);
734 _updateIntv1 = _maxValue(5, duration / steps);
736 return *this;
737 }
738
746 SBK_BarMeterAnimations &fillUpIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
747 {
751 _usePtr = true;
752 _valuePtr1 = maxPercentPtr;
753 _valuePtr2 = minPercentPtr;
755 _isRunning = true;
756 _updateIntv1 = _maxValue(5, updateIntv);
758 return *this;
759 }
760
768 SBK_BarMeterAnimations &fillUpIntv(uint16_t updateIntv, uint8_t maxPercent = 100, uint8_t minPercent = 0)
769 {
773 _usePtr = false;
774 _valuePtr2 = nullptr;
775 _valuePtr1 = nullptr;
776 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
777 _isRunning = true;
778 _updateIntv1 = _maxValue(5, updateIntv);
780 return *this;
781 }
782
790 SBK_BarMeterAnimations &fillDownDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0)
791 {
795 _usePtr = false;
796 _valuePtr2 = nullptr;
797 _valuePtr1 = nullptr;
798 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
799 _isRunning = true;
800 uint8_t steps = _maxValue(1, _maxTracker - _minTracker + 1);
801 _updateIntv1 = _maxValue(5, duration / steps);
803 return *this;
804 }
805
813 SBK_BarMeterAnimations &fillDownIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
814 {
818 _usePtr = true;
819 _valuePtr1 = maxPercentPtr;
820 _valuePtr2 = minPercentPtr;
822 _isRunning = true;
823 uint8_t steps = _maxValue(1, _maxTracker - _minTracker + 1);
824 _updateIntv1 = _maxValue(5, updateIntv);
826 return *this;
827 }
828
836 SBK_BarMeterAnimations &fillDownIntv(uint16_t updateIntv, uint8_t maxPercent = 100, uint8_t minPercent = 0)
837 {
841 _usePtr = false;
842 _valuePtr2 = nullptr;
843 _valuePtr1 = nullptr;
844 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
845 _isRunning = true;
846 _updateIntv1 = _maxValue(5, updateIntv);
848 return *this;
849 }
850
858 SBK_BarMeterAnimations &emptyDownDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0)
859 {
863 _usePtr = false;
864 _valuePtr2 = nullptr;
865 _valuePtr1 = nullptr;
866 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
867 _isRunning = true;
868 uint8_t steps = _maxValue(1, _maxTracker - _minTracker + 1);
869 return emptyDownIntv(duration / steps, maxPercent, minPercent);
870 }
871
879 SBK_BarMeterAnimations &emptyDownIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
880 {
884 _usePtr = true;
885 _valuePtr1 = maxPercentPtr;
886 _valuePtr2 = minPercentPtr;
888 _isRunning = true;
889 _updateIntv1 = _maxValue(5, updateIntv);
891 return *this;
892 }
893
901 SBK_BarMeterAnimations &emptyDownIntv(uint16_t updateIntv, uint8_t maxPercent = 100, uint8_t minPercent = 0)
902 {
906 _usePtr = false;
907 _valuePtr2 = nullptr;
908 _valuePtr1 = nullptr;
909 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
910 _isRunning = true;
911 _updateIntv1 = _maxValue(5, updateIntv);
913 return *this;
914 }
915
923 SBK_BarMeterAnimations &emptyUpDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0)
924 {
928 _usePtr = false;
929 _valuePtr2 = nullptr;
930 _valuePtr1 = nullptr;
931 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
932 _isRunning = true;
933 uint8_t steps = _maxValue(1, _maxTracker - _minTracker + 1);
934 _updateIntv1 = _maxValue(5, duration / steps);
936 return *this;
937 }
938
946 SBK_BarMeterAnimations &emptyUpIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
947 {
951 _usePtr = true;
952 _valuePtr1 = maxPercentPtr;
953 _valuePtr2 = minPercentPtr;
955 _isRunning = true;
956 _updateIntv1 = _maxValue(5, updateIntv);
958 return *this;
959 }
960
968 SBK_BarMeterAnimations &emptyUpIntv(uint16_t updateIntv, uint8_t maxPercent = 100, uint8_t minPercent = 0)
969 {
973 _usePtr = false;
974 _valuePtr2 = nullptr;
975 _valuePtr1 = nullptr;
976 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
977 _isRunning = true;
978 _updateIntv1 = _maxValue(5, updateIntv);
980 return *this;
981 }
982
991 SBK_BarMeterAnimations &bounceFillUpDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0, uint16_t fillIntv = 0)
992 {
995 _usePtr = false;
996 _valuePtr2 = nullptr;
997 _valuePtr1 = nullptr;
998 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
999 uint8_t steps = _maxValue(1, _maxTracker - _minTracker + 1);
1000 uint16_t emptyIntv = fillIntv;
1001 if (fillIntv == 0)
1002 {
1003 fillIntv = _maxValue(5, duration) / (2 * steps);
1004 emptyIntv = fillIntv;
1005 }
1006 else
1007 {
1008 emptyIntv = _maxValue(5, duration - fillIntv * steps) / steps;
1009 }
1010 _updateIntv1 = fillIntv; // Starting interval
1011 _updateIntv2 = _updateIntv1; // Fill intv
1012 _updateIntv3 = emptyIntv; // Empty intv
1013 _sequenceState = 0;
1014 _isRunning = true;
1016 return *this;
1017 }
1018
1027 SBK_BarMeterAnimations &bounceFillUpIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
1028 {
1031 _valuePtr1 = maxPercentPtr;
1032 _valuePtr2 = minPercentPtr;
1033 _usePtr = true;
1035 _updateIntv1 = _maxValue(5, fillIntv); // Starting interval
1036 _updateIntv2 = _updateIntv1; // Fill intv
1037 _updateIntv3 = _maxValue(5, emptyIntv); // Empty intv
1038 _sequenceState = 0;
1039 _isRunning = true;
1041 return *this;
1042 }
1043
1052 SBK_BarMeterAnimations &bounceFillUpIntv(uint16_t fillIntv = 10, uint16_t emptyIntv = 10, uint8_t maxPercent = 100, uint8_t minPercent = 0)
1053 {
1057 _usePtr = false;
1058 _valuePtr2 = nullptr;
1059 _valuePtr1 = nullptr;
1060 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
1061 _updateIntv1 = _maxValue(5, fillIntv); // Starting interval
1062 _updateIntv2 = _updateIntv1; // Fill intv
1063 _updateIntv3 = _maxValue(5, emptyIntv); // Empty intv
1064 _sequenceState = 0;
1065 _isRunning = true;
1067 return *this;
1068 }
1069
1078 SBK_BarMeterAnimations &bounceFillDownDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0, uint16_t fillIntv = 0)
1079 {
1083 _usePtr = false;
1084 _valuePtr2 = nullptr;
1085 _valuePtr1 = nullptr;
1086 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
1087 uint8_t steps = _maxValue(1, _maxTracker - _minTracker + 1);
1088 uint16_t emptyIntv = fillIntv;
1089 if (fillIntv == 0)
1090 {
1091 fillIntv = _maxValue(5, duration) / (2 * steps);
1092 emptyIntv = fillIntv;
1093 }
1094 else
1095 {
1096 emptyIntv = _maxValue(5, duration - fillIntv * steps) / steps;
1097 }
1098 _updateIntv1 = fillIntv; // Starting interval
1099 _updateIntv2 = _updateIntv1; // Fill intv
1100 _updateIntv3 = emptyIntv; // Empty intv
1101 _sequenceState = 0;
1102 _isRunning = true;
1104 return *this;
1105 }
1106
1115 SBK_BarMeterAnimations &bounceFillDownIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
1116 {
1120 _valuePtr1 = maxPercentPtr;
1121 _valuePtr2 = minPercentPtr;
1122 _usePtr = true;
1124 _updateIntv1 = _maxValue(5, fillIntv); // Starting interval
1125 _updateIntv2 = _updateIntv1; // Fill intv
1126 _updateIntv3 = _maxValue(5, emptyIntv); // Empty intv
1127 _sequenceState = 0;
1128 _isRunning = true;
1130 return *this;
1131 }
1132
1141 SBK_BarMeterAnimations &bounceFillDownIntv(uint16_t fillIntv = 10, uint16_t emptyIntv = 10, uint8_t maxPercent = 100, uint8_t minPercent = 0)
1142 {
1146 _usePtr = false;
1147 _valuePtr2 = nullptr;
1148 _valuePtr1 = nullptr;
1149 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
1150 _updateIntv1 = _maxValue(5, fillIntv); // Starting interval
1151 _updateIntv2 = _updateIntv1; // Fill intv
1152 _updateIntv3 = _maxValue(5, emptyIntv); // Empty intv
1153 _sequenceState = 0;
1154 _isRunning = true;
1156 return *this;
1157 }
1158
1167 SBK_BarMeterAnimations &bounceFillFromCenterDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0, uint16_t fillIntv = 0)
1168 {
1172 _mirrorHalfRangeDir = false;
1173 _usePtr = false;
1174 _valuePtr2 = nullptr;
1175 _valuePtr1 = nullptr;
1176 _mapMinMaxTracker(minPercent, maxPercent, (_segsNum / 2) - 1, 0);
1177 uint8_t steps = _maxValue(1, _maxTracker - _minTracker + 1);
1178 uint16_t emptyIntv = fillIntv;
1179 if (fillIntv == 0)
1180 {
1181 fillIntv = _maxValue(5, duration) / (2 * steps);
1182 emptyIntv = fillIntv;
1183 }
1184 else
1185 {
1186 emptyIntv = _maxValue(5, duration - fillIntv * steps) / steps;
1187 }
1188 _updateIntv1 = fillIntv; // Starting interval
1189 _updateIntv2 = _updateIntv1; // Fill intv
1190 _updateIntv3 = emptyIntv; // Empty intv
1191 _sequenceState = 0;
1192 _isRunning = true;
1194 return *this;
1195 }
1196
1205 SBK_BarMeterAnimations &bounceFillFromCenterIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
1206 {
1210 _mirrorHalfRangeDir = false;
1211 _valuePtr1 = maxPercentPtr;
1212 _valuePtr2 = minPercentPtr;
1213 _usePtr = true;
1214 _mapMinMaxTrackerFromPtr(minPercentPtr, maxPercentPtr, (_segsNum / 2) - 1, 0);
1215 _updateIntv1 = fillIntv; // Starting interval
1216 _updateIntv2 = _updateIntv1; // Fill intv
1217 _updateIntv3 = emptyIntv; // Empty intv
1218 _sequenceState = 0;
1219 _isRunning = true;
1221 return *this;
1222 }
1223
1232 SBK_BarMeterAnimations &bounceFillFromCenterIntv(uint16_t fillIntv = 25, uint16_t emptyIntv = 25, uint8_t maxPercent = 100, uint8_t minPercent = 0)
1233 {
1237 _mirrorHalfRangeDir = false;
1238 _usePtr = false;
1239 _valuePtr2 = nullptr;
1240 _valuePtr1 = nullptr;
1241 _mapMinMaxTracker(minPercent, maxPercent, (_segsNum / 2) - 1, 0);
1242 _updateIntv1 = fillIntv; // Starting interval
1243 _updateIntv2 = _updateIntv1; // Fill intv
1244 _updateIntv3 = emptyIntv; // Empty intv
1245 _sequenceState = 0;
1246 _isRunning = true;
1248 return *this;
1249 }
1250
1259 SBK_BarMeterAnimations &bounceFillFromEdgesDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0, uint16_t fillIntv = 0)
1260 {
1264 _mirrorHalfRangeDir = true;
1265 _usePtr = false;
1266 _valuePtr2 = nullptr;
1267 _valuePtr1 = nullptr;
1268 _mapMinMaxTracker(minPercent, maxPercent, (_segsNum / 2) - 1, 0);
1269 uint8_t steps = _maxValue(1, _maxTracker - _minTracker + 1);
1270 uint16_t emptyIntv = fillIntv;
1271 if (fillIntv == 0)
1272 {
1273 fillIntv = _maxValue(5, duration) / (2 * steps);
1274 emptyIntv = fillIntv;
1275 }
1276 else
1277 {
1278 emptyIntv = _maxValue(5, duration - fillIntv * steps) / steps;
1279 }
1280 _updateIntv1 = fillIntv; // Starting interval
1281 _updateIntv2 = _updateIntv1; // Fill intv
1282 _updateIntv3 = emptyIntv; // Empty intv
1283 _sequenceState = 0;
1284 _isRunning = true;
1286 return *this;
1287 }
1288
1297 SBK_BarMeterAnimations &bounceFillFromEdgesIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
1298 {
1302 _mirrorHalfRangeDir = true;
1303 _valuePtr1 = maxPercentPtr;
1304 _valuePtr2 = minPercentPtr;
1305 _usePtr = true;
1306 _mapMinMaxTrackerFromPtr(minPercentPtr, maxPercentPtr, (_segsNum / 2) - 1, 0);
1307 _updateIntv1 = fillIntv; // Starting interval
1308 _updateIntv2 = _updateIntv1; // Fill intv
1309 _updateIntv3 = emptyIntv; // Empty intv
1310 _sequenceState = 0;
1311 _isRunning = true;
1313 return *this;
1314 }
1315
1324 SBK_BarMeterAnimations &bounceFillFromEdgesIntv(uint16_t fillIntv = 25, uint16_t emptyIntv = 25, uint8_t maxPercent = 100, uint8_t minPercent = 0)
1325 {
1329 _mirrorHalfRangeDir = true;
1330 _usePtr = false;
1331 _valuePtr2 = nullptr;
1332 _valuePtr1 = nullptr;
1333 _mapMinMaxTracker(minPercent, maxPercent, (_segsNum / 2) - 1, 0);
1334 _updateIntv1 = fillIntv; // Starting interval
1335 _updateIntv2 = _updateIntv1; // Fill intv
1336 _updateIntv3 = emptyIntv; // Empty intv
1337 _sequenceState = 0;
1338 _isRunning = true;
1340 return *this;
1341 }
1342
1348 SBK_BarMeterAnimations &beatPulse(uint8_t *bpmPtr) // bouncing from bottom (maybe like a volume meter with the music)
1349 {
1351 _updateIntv1 = 60000UL / (bpmPtr ? _maxValue(1, *bpmPtr) : 116);
1352 _param2 = _minValue(35 * (_segsNum - 1) / 100, 255); // MIN_BASE_LEVEL
1353 _param3 = _minValue(67 * (_segsNum - 1) / 100, 255); // MIN_PEAK_LEVEL
1354 _param4 = 150; // PEAK_HOLD_TIME
1355 _usePtr = true;
1359
1360 _isRunning = true;
1362 _init = true;
1363 return *this;
1364 }
1365
1371 SBK_BarMeterAnimations &beatPulse(uint8_t bpm = 116) // bouncing from bottom (maybe like a volume meter with the music)
1372 {
1373 _param1 = _maxValue(1, bpm);
1374 _updateIntv1 = 60000UL / _param1;
1375 _param2 = _minValue(35 * (_segsNum - 1) / 100, 255); // MIN_BASE_LEVEL
1376 _param3 = _minValue(67 * (_segsNum - 1) / 100, 255); // MIN_PEAK_LEVEL
1377 _param4 = 150; // PEAK_HOLD_TIME
1378 _valuePtr1 = nullptr;
1379 _usePtr = false;
1383
1384 _isRunning = true;
1386 _init = true;
1387 return *this;
1388 }
1389
1398 SBK_BarMeterAnimations &explodingBlocks(uint16_t intv = 50, uint8_t blockLength = 2, uint8_t blockSpacing = 1, uint8_t numBlocks = 0)
1399 {
1400 _updateIntv1 = _maxValue(5, intv);
1403 _param3 = numBlocks;
1404 _param4 = ((_segsNum / 2) / (blockLength + blockSpacing)) + 2;
1405 _param4 = constrain(_param4, 2, 32);
1406
1410 _isRunning = true;
1412 return *this;
1413 }
1414
1423 SBK_BarMeterAnimations &collidingBlocks(uint16_t intv = 50, uint8_t blockLength = 2, uint8_t blockSpacing = 1, uint8_t numBlocks = 0)
1424 {
1425
1426 _updateIntv1 = _maxValue(5, intv);
1429 _param3 = numBlocks;
1430 _param4 = ((_segsNum / 2) / (blockLength + blockSpacing)) + 2;
1431 _param4 = constrain(_param4, 2, 32);
1432
1436 _isRunning = true;
1438 return *this;
1439 }
1440
1449 SBK_BarMeterAnimations &scrollingUpBlocks(uint16_t intv = 50, uint8_t blockLength = 2, uint8_t blockSpacing = 1, uint8_t numBlocks = 0)
1450 {
1451 _updateIntv1 = _maxValue(5, intv);
1454 _param3 = numBlocks;
1456 _param4 = constrain(_param4, 2, 64);
1457
1461 _isRunning = true;
1463 return *this;
1464 }
1465
1474 SBK_BarMeterAnimations &scrollingDownBlocks(uint16_t intv = 50, uint8_t blockLength = 2, uint8_t blockSpacing = 1, uint8_t numBlocks = 0)
1475 {
1476 _updateIntv1 = _maxValue(5, intv);
1479 _param3 = numBlocks;
1481 _param4 = constrain(_param4, 2, 64);
1482
1486 _isRunning = true;
1488 return *this;
1489 }
1490
1498 SBK_BarMeterAnimations &downStackingBlocks(uint16_t intv = 50, uint8_t blockLength = 1, uint8_t blockSpacing = 0)
1499 {
1503 _updateIntv1 = _maxValue(5, intv);
1506 _param3 = 0; // requested number of blocks
1507 _param4 = 1; // aka maximum blocks number
1508 if (_init)
1510 _isRunning = true;
1512 return *this;
1513 }
1514
1522 SBK_BarMeterAnimations &upUnstackingBlocks(uint16_t intv = 50, uint8_t blockLength = 1, uint8_t blockSpacing = 0)
1523 {
1527 _updateIntv1 = _maxValue(5, intv);
1530 _param3 = 0; // aka requested number of blocks
1531 _param4 = 1; // aka maximum blocks number
1532 if (_init)
1534 _isRunning = true;
1536 return *this;
1537 }
1538
1546 SBK_BarMeterAnimations &upStackingBlocks(uint16_t intv = 50, uint8_t blockLength = 1, uint8_t blockSpacing = 0)
1547 {
1551 _updateIntv1 = _maxValue(5, intv);
1554 _param3 = 0; // aka requested number of blocks
1555 _param4 = 1; // aka maximum blocks number
1556 if (_init)
1558 _isRunning = true;
1560 return *this;
1561 }
1562
1570 SBK_BarMeterAnimations &downUnstackingBlocks(uint16_t intv = 50, uint8_t blockLength = 1, uint8_t blockSpacing = 0)
1571 {
1575 _updateIntv1 = _maxValue(5, intv);
1578 _param3 = 0; // aka requested number of blocks
1579 _param4 = 1; // aka maximum blocks number
1580 if (_init)
1582 _isRunning = true;
1584 return *this;
1585 }
1586
1597 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)
1598 {
1599
1600 _valuePtr1 = sigPtr;
1601 _updateIntv1 = _maxValue(10, updateIntv);
1602 _minMap = _maxValue(0, minMap);
1603 _maxMap = _maxValue(0, maxMap);
1605 _param1 = constrain(smoothingFactor, 0, 100);
1606 _updateIntv2 = samplingIntv;
1607
1611 _isRunning = true;
1612 _init = true;
1614 return *this;
1615 }
1616
1627 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)
1628 {
1629 _valuePtr1 = sigPtr;
1630 _updateIntv1 = _maxValue(10, updateIntv);
1631 _minMap = _maxValue(0, minMap);
1632 _maxMap = _maxValue(0, maxMap);
1634 _param1 = constrain(smoothingFactor, 0, 100);
1635 _updateIntv2 = samplingIntv;
1636
1640 _isRunning = true;
1641 _init = true;
1643 return *this;
1644 }
1645
1658 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)
1659 {
1660 _valuePtr1 = sig1Ptr;
1661 _updateIntv1 = _maxValue(10, updateIntv);
1662 _valuePtr2 = sig2Ptr ? sig2Ptr : sig1Ptr;
1663 _minMap = _maxValue(0, minMap);
1664 _maxMap = _maxValue(0, maxMap);
1666 _param1 = constrain(smoothingFactor, 0, 100);
1667 _updateIntv2 = samplingIntv;
1668
1672 //_mirrorHalfRangeDir = false;
1673 _isRunning = true;
1674 _init = true;
1676 return *this;
1677 }
1678
1691 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)
1692 {
1693 _valuePtr1 = sig1Ptr;
1694 _updateIntv1 = _maxValue(10, updateIntv);
1695 _valuePtr2 = sig2Ptr ? sig2Ptr : sig1Ptr;
1696 _minMap = _maxValue(0, minMap);
1697 _maxMap = _maxValue(0, maxMap);
1699 _param1 = constrain(smoothingFactor, 0, 100);
1700 _updateIntv2 = samplingIntv;
1701
1705 //_mirrorHalfRangeDir = true;
1706 _isRunning = true;
1707 _init = true;
1709 return *this;
1710 }
1711
1727 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)
1728 {
1729
1730 _valuePtr1 = sigPtr;
1732 _updateIntv1 = _maxValue(10, updateIntv);
1733 _minMap = _maxValue(0, minMap);
1734 _maxMap = _maxValue(0, maxMap);
1736 _param1 = constrain(smoothingFactor, 0, 100);
1737 _updateIntv2 = samplingIntv;
1738
1742 _isRunning = true;
1743 _init = true;
1745 return *this;
1746 }
1747
1753 SBK_BarMeterAnimations &randomFill(uint16_t interval = 30)
1754 {
1755 _updateIntv1 = interval;
1756
1760 _isRunning = true;
1762 _init = true;
1763 return *this;
1764 }
1765
1771 SBK_BarMeterAnimations &randomEmpty(uint16_t interval = 30)
1772 {
1773 _updateIntv1 = interval;
1774
1778 _isRunning = true;
1780 _init = true;
1781 return *this;
1782 }
1783
1784protected:
1785 BarMeterT &_barMeter;
1786 uint8_t _segsNum = 0;
1787
1788 // Active animation update function
1791
1794 {
1796 uint16_t updateIntv1 = 10, updateIntv2 = 10, updateIntv3 = 10;
1797 uint16_t minMap = 0, maxMap = 1023;
1798 const void *valuePtr1 = nullptr;
1799 const void *valuePtr2 = nullptr;
1800 int8_t minTracker = 0, maxTracker = 1;
1801 uint8_t param1 = 0, param2 = 0, param3 = 0, param4 = 0, param5 = 0;
1802 bool loop = false;
1807 bool animLogicSet = false;
1810 bool animDirSet = false;
1811 bool usePtr = false;
1814 uint32_t duration = 0;
1816 };
1818 uint8_t _queueHead = 0;
1819 uint8_t _queueTail = 0;
1820 uint8_t _queueCount = 0;
1822 uint8_t _queueStart = 0;
1823 bool _queueLoop = false;
1825 bool _queueActive = false;
1826 bool _queueOverflow = false;
1831
1832 // Control flags
1833 bool _init = true; // true = init function
1834 bool _isRunning = false;
1835 bool _isPaused = false;
1836 bool _loop = false;
1837 bool _isLoopingNow = false;
1843 bool _animLogicSet = false;
1844 bool _skipPending = false;
1847 bool _animDirSet = false;
1848 bool _usePtr = false;
1852
1853 // Time trackings
1854 uint32_t _currentTime = 0;
1855 uint32_t _pausedAt = 0;
1856 uint32_t _pausedTime = 0;
1858
1859 // Animations update interval trackers
1860 uint16_t _updateIntv1 = 10, _updateIntv2 = 10, _updateIntv3 = 10;
1861
1862 // Animations trackers
1863 uint8_t _sequenceState = 0;
1865 int8_t _minTracker = 0, _maxTracker = 1;
1866 uint8_t _param1 = 0, _param2 = 0, _param3 = 0, _param4 = 0, _param5 = 0;
1868 uint16_t _minMap = 0, _maxMap = 1023;
1869 uint8_t _counter1 = 0, _counter2 = 0;
1870 // Generic live-value pointers. Their type is determined by the active animation.
1871 const void *_valuePtr1 = nullptr, *_valuePtr2 = nullptr;
1872 // Blocks related helpers
1873 struct Block
1874 {
1875 Block() : position(-1), active(false) {}
1876 int8_t position;
1878 };
1879 static constexpr uint8_t MAX_BLOCK_CAPACITY = 64;
1881 uint8_t _segmentCapacity = 0;
1882 uint8_t _blockCapacity = 0;
1883 uint8_t _randomCursor = 0;
1884
1885 void _captureQueueEntry(QueueEntry &entry, bool resetRuntimeState,
1886 uint32_t duration)
1887 {
1888 entry.function = _currentFunc;
1889 entry.updateIntv1 = _updateIntv1;
1890 entry.updateIntv2 = _updateIntv2;
1891 entry.updateIntv3 = _updateIntv3;
1892 entry.minMap = _minMap;
1893 entry.maxMap = _maxMap;
1894 entry.valuePtr1 = _valuePtr1;
1895 entry.valuePtr2 = _valuePtr2;
1896 entry.minTracker = _minTracker;
1897 entry.maxTracker = _maxTracker;
1898 entry.param1 = _param1;
1899 entry.param2 = _param2;
1900 entry.param3 = _param3;
1901 entry.param4 = _param4;
1902 entry.param5 = _param5;
1903 entry.loop = _loop;
1911 entry.animDirSet = _animDirSet;
1912 entry.usePtr = _usePtr;
1914 entry.resetRuntimeState = resetRuntimeState;
1915 entry.duration = duration;
1917 }
1918
1919 void _loadQueueEntry(const QueueEntry &entry, bool forceReset = false)
1920 {
1921 const bool resetRuntimeState = forceReset || entry.resetRuntimeState;
1922 const bool previousRenderLogic = _animRenderLogicIsInverted;
1923
1924 _currentFunc = entry.function;
1925 _updateIntv1 = entry.updateIntv1;
1926 _updateIntv2 = entry.updateIntv2;
1927 _updateIntv3 = entry.updateIntv3;
1928 _minMap = entry.minMap;
1929 _maxMap = entry.maxMap;
1930 _valuePtr1 = entry.valuePtr1;
1931 _valuePtr2 = entry.valuePtr2;
1932 _minTracker = entry.minTracker;
1933 _maxTracker = entry.maxTracker;
1934 _param1 = entry.param1;
1935 _param2 = entry.param2;
1936 _param3 = entry.param3;
1937 _param4 = entry.param4;
1938 if (resetRuntimeState)
1939 _param5 = entry.param5;
1940 _loop = entry.loop;
1943 (!resetRuntimeState && !entry.animLogicSet)
1946 _prevAnimRenderLogic = resetRuntimeState
1948 : previousRenderLogic;
1954 _animDirSet = entry.animDirSet;
1955 _usePtr = entry.usePtr;
1960
1961 _isRunning = _currentFunc != nullptr;
1962 resume();
1963 _isLoopingNow = false;
1964 _skipPending = false;
1965 if (resetRuntimeState)
1966 {
1967 _init = true;
1968 _sequenceState = 0;
1970 _counter1 = _counter2 = 0;
1973 }
1974 else
1975 {
1976 _init = false;
1977 }
1978 }
1979
1980 void _startNextQueuedAnimation(bool forceReset = false)
1981 {
1982 if (_queueCount == 0 && _queueLoop && _queueStoredCount > 0)
1983 {
1986 forceReset = true;
1987 }
1988 if (_queueCount == 0)
1989 {
1991 _queueActive = false;
1992 _isRunning = false;
1993 _currentFunc = nullptr;
1996 return;
1997 }
1998
2000 _loadQueueEntry(_queue[_queueHead], forceReset);
2002 _queueCount--;
2003 }
2004
2006 {
2007 _segmentCapacity = _barMeter.getSegsNum();
2009 _maxTracker = _segsNum > 0 ? _segsNum - 1 : 0;
2010
2011 const uint16_t requestedCapacity = static_cast<uint16_t>(_segmentCapacity) + 2U;
2012 _blockCapacity = _minValue(requestedCapacity,
2013 static_cast<uint16_t>(MAX_BLOCK_CAPACITY));
2014 const uint8_t randomOrderBlocks =
2015 (_segmentCapacity + sizeof(Block) - 1U) / sizeof(Block);
2016 const uint8_t workspaceBlocks = _maxValue(_blockCapacity, randomOrderBlocks);
2017 if (workspaceBlocks > 0)
2018 _animationWorkspace = new Block[workspaceBlocks];
2019 }
2020
2021 uint8_t *_randomOrder()
2022 {
2023 return reinterpret_cast<uint8_t *>(_animationWorkspace);
2024 }
2025
2026 void _resetBlocks(uint8_t &blockCapacity)
2027 {
2029 {
2030 blockCapacity = 0;
2031 return;
2032 }
2033 blockCapacity = _minValue(blockCapacity, _blockCapacity);
2034 for (uint8_t i = 0; i < blockCapacity; ++i)
2036 }
2037
2038 // Animation helpers
2039 // Arduino cores differ: AVR defines min/max macros, while ESP cores use
2040 // same-type templates. Preserve ordinary arithmetic promotion on all cores.
2041 template <typename A, typename B>
2042 static auto _minValue(A a, B b) -> decltype(true ? A() : B())
2043 {
2044 return a < b ? a : b;
2045 }
2046
2047 template <typename A, typename B>
2048 static auto _maxValue(A a, B b) -> decltype(true ? A() : B())
2049 {
2050 return a > b ? a : b;
2051 }
2052
2053 static inline void _normalizePercentRange(uint8_t &minP, uint8_t &maxP)
2054 {
2055 minP = constrain(minP, 0, 100);
2056 maxP = constrain(maxP, 0, 100);
2057 if (minP > maxP)
2058 {
2059 uint8_t t = maxP;
2060 maxP = minP;
2061 minP = t;
2062 }
2063 if (minP == maxP)
2064 {
2065 if (maxP < 100)
2066 maxP = minP + 1;
2067 else
2068 minP--;
2069 }
2070 }
2071 const uint8_t *_percentMaxPtr() const { return static_cast<const uint8_t *>(_valuePtr1); }
2072 const uint8_t *_percentMinPtr() const { return static_cast<const uint8_t *>(_valuePtr2); }
2073 const uint8_t *_liveBpmPtr() const { return static_cast<const uint8_t *>(_valuePtr1); }
2074 const uint16_t *_signalPtr1() const { return static_cast<const uint16_t *>(_valuePtr1); }
2075 const uint16_t *_signalPtr2() const { return static_cast<const uint16_t *>(_valuePtr2); }
2076
2077 inline void _mapMinMaxTrackerFromPtr(const uint8_t *minPercPtr, const uint8_t *maxPercPtr, int8_t minR, uint8_t maxR)
2078 {
2079 if (!_usePtr)
2080 return;
2081 uint8_t minP = minPercPtr ? *minPercPtr : 0;
2082 uint8_t maxP = maxPercPtr ? *maxPercPtr : 100;
2083 _normalizePercentRange(minP, maxP);
2084 _minTracker = map(minP, 0, 100, minR, maxR);
2085 _maxTracker = map(maxP, 0, 100, minR, maxR);
2086 }
2087 inline void _mapMinMaxTracker(uint8_t minP, uint8_t maxP, uint8_t minR, uint8_t maxR)
2088 {
2089 _normalizePercentRange(minP, maxP);
2090 _minTracker = map(minP, 0, 100, minR, maxR);
2091 _maxTracker = map(maxP, 0, 100, minR, maxR);
2092 }
2093 inline uint8_t _corrPixelToDir(uint8_t pixel)
2094 {
2095 return _animRenderDirIsReversed ? (_segsNum - 1) - pixel : pixel;
2096 }
2097 inline uint8_t _corrPixelToDirForHalfRange(uint8_t pixel)
2098 {
2100 return pixel;
2101
2102 const uint8_t half = _segsNum / 2;
2103 return abs((half - 1) - pixel);
2104 }
2105 static inline void _correctSwapOrEqualMinMax(uint16_t &minVal, uint16_t &maxVal)
2106 {
2107 if (minVal > maxVal)
2108 {
2109 uint16_t temp = maxVal;
2110 maxVal = minVal;
2111 minVal = temp;
2112 }
2113 if (minVal == maxVal) // avoid division by zero in map()
2114 {
2115 if (maxVal < 65535)
2116 {
2117 maxVal = minVal + 1;
2118 }
2119 else
2120 {
2121 minVal--;
2122 }
2123 }
2124 }
2125 static inline uint8_t _getMappedSignal(uint16_t sig, uint16_t minM, uint16_t maxM, uint8_t minR, uint8_t maxR)
2126 {
2127 uint8_t mapped = map(sig, minM, maxM, minR, maxR);
2128 return constrain(mapped, minR, maxR); // avoid edge overshoot
2129 }
2130
2131 // Animation update functions
2132
2134 {
2135 _init = false;
2136 for (uint8_t i = 0; i < _segsNum; i++)
2137 _barMeter.setPixel(i, true);
2138 return true;
2139 }
2141 {
2142 _init = false;
2143 _barMeter.clear();
2144 return true;
2145 }
2146
2147 bool _wait()
2148 {
2149 if (_init)
2150 {
2151 _init = false;
2153 }
2155 }
2156
2158 {
2159 const uint8_t index = _param1;
2160 const uint8_t blinkCount = _param2;
2161
2162 if (index >= _segsNum)
2163 return true;
2164
2165 if (_init)
2166 {
2167 _init = false;
2168 _sequenceState = 1; // ON phase
2169 _counter1 = 0;
2171 _barMeter.setPixel(index, true);
2172 return false;
2173 }
2174
2175 const uint16_t interval = _sequenceState ? _updateIntv1 : _updateIntv2;
2176 if (_currentTime - _lastUpdate1 < interval)
2177 return false;
2178
2180 if (_sequenceState)
2181 {
2182 _sequenceState = 0;
2183 _barMeter.setPixel(index, false);
2184 _counter1++;
2185 return blinkCount > 0 && _counter1 >= blinkCount;
2186 }
2187
2188 _sequenceState = 1;
2189 _barMeter.setPixel(index, true);
2190 return false;
2191 }
2192
2194 {
2196
2197 if (_init)
2198 {
2199 _init = false;
2200
2201 if (!_animLogicSet)
2203
2204 // Pre-fill for emptyDown mode
2206 {
2208 for (uint8_t i = 0; i < _segsNum; ++i)
2209 {
2210 if (i <= _maxTracker)
2211 _barMeter.setPixel(_corrPixelToDir(i), true);
2212 else
2213 _barMeter.setPixel(_corrPixelToDir(i), false);
2214 }
2215 }
2216 else // Pre-fill fillUp, light up to _minTracker inclusively
2217 {
2219 for (uint8_t i = 0; i < _segsNum; ++i)
2220 {
2221 if (i <= _minTracker)
2222 _barMeter.setPixel(_corrPixelToDir(i), true);
2223 else
2224 _barMeter.setPixel(_corrPixelToDir(i), false);
2225 }
2226 }
2227 return false;
2228 }
2229
2231 {
2233
2234 // Clamp if inverted logic and tracker is beyond maxTracker
2237
2238 // Clamp in normal logic mode
2241 }
2242
2244 {
2246
2248 {
2250 {
2251 _barMeter.setPixel(_corrPixelToDir(_ledTracker1), false);
2252 _ledTracker1--;
2253 }
2254 else
2255 return true; // now done!
2256 }
2257 else
2258 {
2260 {
2261 _barMeter.setPixel(_corrPixelToDir(_ledTracker1), true);
2262 _ledTracker1++;
2263 }
2264 else
2265 return true; // done lighting up all
2266 }
2267 }
2268 return false;
2269 }
2270
2272 {
2273 switch (_sequenceState)
2274 {
2275 case 0: // Fill Up
2276
2277 _skipPending = false;
2278 if (_fillOrEmpty())
2279 {
2281 _sequenceState = 1;
2282 _updateIntv1 = _updateIntv3; // Empty update interval
2283 }
2284 return false;
2285
2286 case 1: // Fill Down
2287
2288 _skipPending = true;
2289 if (_fillOrEmpty())
2290 {
2292 _sequenceState = 0;
2293 _updateIntv1 = _updateIntv2; // Fill up update interval
2294 return true;
2295 }
2296 return false;
2297 }
2298
2299 return true; // Shouldn’t reach here
2300 }
2301
2303 {
2304 const uint8_t center = _segsNum / 2;
2306
2307 if (_init)
2308 {
2309 _init = false;
2310
2311 if (!_animLogicSet)
2313
2315 {
2317
2318 // Start fully lit
2319 for (uint8_t i = 0; i < center; ++i)
2320 {
2321 if (i >= _maxTracker)
2322 {
2323 _barMeter.setPixel(_corrPixelToDirForHalfRange(i), true);
2324 _barMeter.setPixel((_segsNum - 1) - _corrPixelToDirForHalfRange(i), true);
2325 }
2326 else
2327 {
2328 _barMeter.setPixel(_corrPixelToDirForHalfRange(i), false);
2329 _barMeter.setPixel((_segsNum - 1) - _corrPixelToDirForHalfRange(i), false);
2330 }
2331 }
2332 }
2333 else
2334 {
2335
2337
2338 // Start with minimum fill
2339 for (uint8_t i = 0; i < center; ++i)
2340 {
2341 if (i > _minTracker)
2342 {
2343 _barMeter.setPixel(_corrPixelToDirForHalfRange(i), true);
2344 _barMeter.setPixel((_segsNum - 1) - _corrPixelToDirForHalfRange(i), true);
2345 }
2346 else
2347 {
2348 _barMeter.setPixel(_corrPixelToDirForHalfRange(i), false);
2349 _barMeter.setPixel((_segsNum - 1) - _corrPixelToDirForHalfRange(i), false);
2350 }
2351 }
2352 }
2353 return false;
2354 }
2355
2357 {
2359
2360 // A completed phase leaves the tracker one step past its endpoint.
2361 // Move it back into the active range before reversing direction.
2366 }
2367
2369 {
2372 {
2373 if (_ledTracker1 >= _maxTracker && _ledTracker1 >= 0)
2374 {
2377 _ledTracker1--;
2378 }
2379 else
2380 return true; // finished filling
2381 }
2382 else
2383 {
2384 if (_ledTracker1 <= _minTracker && _ledTracker1 < center)
2385 {
2388 _ledTracker1++;
2389 }
2390 else
2391 return true; // finished clearing
2392 }
2393 }
2394
2395 return false;
2396 }
2397
2399 {
2400 switch (_sequenceState)
2401 {
2402 case 0: // Fill from center
2403
2404 _skipPending = true;
2406 {
2408 _updateIntv1 = _updateIntv3; // Empty update interval
2409 _sequenceState = 1;
2410 }
2411 return false;
2412
2413 case 1: // Empty to center
2414
2415 _skipPending = false;
2417 {
2419 _updateIntv1 = _updateIntv2; // Empty update interval
2420 _sequenceState = 0;
2421 return true; // one full bounce cycle completed
2422 }
2423 return false;
2424 }
2425
2426 return true; // should not happen
2427 }
2428
2429#define bpm _param1
2430#define MIN_BASE_LEVEL _param2
2431#define MIN_PEAK_LEVEL _param3
2432#define PEAK_HOLD_TIME _param4
2433#define bpmPtr _liveBpmPtr()
2434#define currentLevel _ledTracker1
2435#define peakLevel _ledTracker2
2436#define beat _updateIntv1
2437#define lastBeat _lastUpdate1
2438#define lastRandomUpdate _lastUpdate2
2439#define prevPeakUpdate _lastUpdate3
2440 bool _beatPulse() // bouncing from bottom (maybe like a volume meter with the music)
2441 {
2442 static int8_t randomOffset;
2443 static bool isPeak = false;
2444
2445 if (_init)
2446 {
2447 _init = false;
2448
2449 if (!_animLogicSet)
2451
2452 _setAllOff();
2455 }
2456
2457 // Update at bpm pulse
2458 if (_currentTime - lastBeat >= beat)
2459 {
2461 isPeak = !isPeak; // Toggle between peak and base level
2462 }
2463
2464 // Smooth transition between base and peak levels
2465 if (isPeak && currentLevel <= MIN_PEAK_LEVEL)
2466 {
2467 currentLevel += random(3, 5); // Gradual increase
2468 }
2469 else if (!isPeak && currentLevel >= MIN_BASE_LEVEL)
2470 {
2471 currentLevel -= random(0, 4); // Gradual decrease
2472 }
2473
2474 // Randomized additional LEDs on top at a random interval
2475 if (_currentTime - lastRandomUpdate >= uint16_t(random(50, 300)))
2476 {
2478 randomOffset = random(-4, 4); // 0 to 5 extra LEDs
2479 }
2480
2481 uint8_t finalLevel = currentLevel + randomOffset;
2482 finalLevel = constrain(finalLevel, 0, _segsNum);
2483 if (finalLevel > _segsNum)
2484 finalLevel = _segsNum;
2485 if (finalLevel < 0)
2486 finalLevel = 0;
2487
2488 // Update peak level
2489 if (finalLevel > peakLevel)
2490 {
2491 peakLevel = _minValue(finalLevel, _segsNum - 1);
2492 prevPeakUpdate = _currentTime; // Reset decay timer
2493 }
2494 else if (_currentTime - prevPeakUpdate >= PEAK_HOLD_TIME && peakLevel > finalLevel)
2495 {
2496 peakLevel--; // Slowly drop peak
2497 prevPeakUpdate = _currentTime; // Reset decay timer
2498 }
2499
2500 // Update LED states
2501 for (uint8_t i = 0; i < _segsNum; i++)
2502 {
2503 _barMeter.setPixel(_corrPixelToDir(i), (i < finalLevel));
2504 }
2505
2506 // Ensure peak LED stays on
2507 if (peakLevel < _segsNum)
2508 {
2509 _barMeter.setPixel(_corrPixelToDir(peakLevel), true);
2510 }
2511 return false; // Continuous pulse animation, never auto-terminates
2512 }
2513
2515 {
2516 bpm = bpmPtr ? _maxValue(1, *bpmPtr) : 116;
2517 beat = 60000UL / bpm;
2518 return _beatPulse();
2519 }
2520#undef bpm
2521#undef MIN_BASE_LEVEL
2522#undef MIN_PEAK_LEVEL
2523#undef PEAK_HOLD_TIME
2524#undef bpmPtr
2525#undef currentLevel
2526#undef peakLevel
2527#undef beat
2528#undef lastBeat
2529#undef lastRandomUpdate
2530#undef prevPeakUpdate
2531
2532#define blockLength _param1
2533#define blockSpacing _param2
2534#define requestedNumBlocks _param3
2535#define maxBlocks _param4
2536#define emitIndex _param5
2537#define emittedBlocksCount _counter1
2538#define emitCooldown _counter2
2539 void _emitBlock(int8_t pos)
2540 {
2541 if (!_animationWorkspace || maxBlocks < 1)
2542 return;
2543
2544 if (emitCooldown > 0)
2545 {
2546 emitCooldown--;
2547 return;
2548 }
2549
2551 return;
2552
2553 for (uint8_t i = 0; i < maxBlocks; ++i)
2554 {
2555 uint8_t idx = (emitIndex + i) % maxBlocks;
2556 Block &b = _animationWorkspace[idx];
2557
2558 if (!b.active)
2559 {
2560 b.position = pos;
2561 b.active = true;
2563
2565 emitIndex = (emitIndex + 1) % maxBlocks;
2566 return;
2567 }
2568 }
2569 }
2570
2571 static inline int8_t _calculateSwitchPosition(int8_t pos, uint8_t blockL, uint8_t range)
2572 {
2573 // Compute switched block head position
2574 return (range - 1) - pos + (blockL - 1);
2575 }
2576
2578 {
2579 const int8_t emitInterval = blockLength + blockSpacing;
2580
2581 int8_t closestSwp = -127; // Track the nearest visible block from new side
2582
2583 for (uint8_t i = 0; i < maxBlocks; ++i)
2584 {
2585 Block &b = _animationWorkspace[i];
2586
2587 if (!b.active)
2588 continue;
2589
2590 int8_t p = b.position; // head position before switching
2591
2592 // Step 1: compute switched block head position
2593 int8_t swp = _calculateSwitchPosition(p, blockLength, range);
2594
2595 b.position = swp;
2596
2597 // Step 2: if out of visible range, deactivate block
2598 if (swp < 0)
2599 {
2600 b.active = false;
2601 continue;
2602 }
2603
2604 // // Step 3: Keep the first (closest to emit side) visible block
2605 if (closestSwp == -127 || swp < closestSwp)
2606 closestSwp = swp;
2607 }
2608
2609 if (closestSwp >= 0)
2610 return (emitInterval - 1) - closestSwp;
2611 else
2612 return 0; // fallback if no visible block found
2613 }
2614
2616 {
2617 const uint8_t center = _segsNum / 2;
2618
2619 if (_init)
2620 {
2621 _init = false;
2623
2625 emitCooldown = 0;
2626
2627 if (!_animLogicSet)
2629
2631 return false;
2632 }
2633
2635 {
2639 }
2640
2642 {
2644 // _frameCounter++;
2645 _barMeter.clear();
2646
2648 _emitBlock(-1); // The new block moves to position 0 on its first update.
2649
2650 for (uint8_t i = 0; i < maxBlocks; ++i)
2651 {
2652 Block &b = _animationWorkspace[i];
2653 if (!b.active)
2654 continue;
2655
2656 b.position++;
2657
2658 // Only render to lower half (0 to _centerTracker - 1)
2659 uint16_t clamped = _maxValue(0, (int16_t)b.position + 1);
2660 uint8_t pixelsVisible = _minValue(blockLength, (uint8_t)clamped);
2661 for (uint8_t j = 0; j < pixelsVisible; ++j)
2662 {
2663 int8_t pos = _animRenderLogicIsInverted ? center - 1 - b.position : b.position;
2664 int8_t tailOffset = _animRenderLogicIsInverted ? pos + j : pos - j;
2665
2666 if (tailOffset < 0)
2667 continue;
2668
2669 int8_t idx = tailOffset;
2670 if (idx >= center)
2671 continue;
2672
2673 int8_t mirrorIdx = _segsNum - 1 - idx;
2674
2675 if (idx >= 0 && idx < _segsNum)
2676 {
2677 _barMeter.setPixel(idx, true);
2678 }
2679 if (mirrorIdx != idx && mirrorIdx >= 0 && mirrorIdx < _segsNum)
2680 {
2681 _barMeter.setPixel(mirrorIdx, true);
2682 }
2683 }
2684
2685 // Deactivate block if head has passed the visual range
2686 if (b.position >= center - 1 + blockLength)
2687 b.active = false;
2688 }
2689 }
2690 // The animation ends after emission stops and all active blocks have cleared.
2692 {
2693 bool anyActive = false;
2694 for (uint8_t i = 0; i < maxBlocks; ++i)
2695 {
2696 if (_animationWorkspace[i].active)
2697 {
2698 anyActive = true;
2699 break;
2700 }
2701 }
2702 if (!anyActive)
2703 return true; // animation complete
2704 }
2705
2706 return false;
2707 }
2708
2710 {
2711 if (_init)
2712 {
2713 _init = false;
2715
2717 emitCooldown = 0;
2718
2719 if (!_animLogicSet)
2721
2723 return false;
2724 }
2725
2727 {
2731 }
2732
2734 {
2736 _setAllOff();
2737
2739 _emitBlock(-1); // Start fully off-screen left
2740
2741 for (uint8_t i = 0; i < maxBlocks; ++i)
2742 {
2743 Block &b = _animationWorkspace[i];
2744 if (!b.active)
2745 continue;
2746
2747 b.position++;
2748
2749 for (uint8_t j = 0; j < blockLength; ++j)
2750 {
2751 int8_t pos = _animRenderLogicIsInverted ? (_segsNum - 1) - b.position : b.position;
2752 int8_t tailOffset = _animRenderLogicIsInverted ? pos + j : pos - j;
2753
2754 if (tailOffset < 0)
2755 continue;
2756
2757 int8_t idx = tailOffset;
2758 if (idx >= _segsNum)
2759 continue;
2760
2761 if (idx >= 0 && idx < _segsNum)
2762 _barMeter.setPixel(_corrPixelToDir(idx), true);
2763 }
2764
2765 // Deactivate block if head has passed the visual range
2766 if (b.position >= (_segsNum - 1) + blockLength)
2767 b.active = false;
2768 }
2769 }
2770
2772 {
2773 for (uint8_t i = 0; i < maxBlocks; ++i)
2774 {
2775 if (_animationWorkspace[i].active)
2776 return false;
2777 }
2778 return true;
2779 }
2780
2781 return false;
2782 }
2783
2784#define stackLevel _ledTracker1
2786 {
2787 const uint8_t blockInterval = blockLength + blockSpacing;
2788 bool hasActive = false; // active block tracker
2789
2790 if (_init)
2791 {
2792 _init = false;
2793
2794 if (!_animLogicSet)
2796
2797 maxBlocks = 1;
2799
2800 emitIndex = 0;
2801 emitCooldown = 0;
2802
2803 stackLevel = 0;
2805 {
2806 _barMeter.clear();
2807 // Falling blocks
2808 // stackLevel = 0;
2809 }
2810 else
2811 {
2812 // Flying upward - fill pattern first
2813 while (stackLevel < _segsNum)
2814 stackLevel += blockInterval;
2815 for (uint8_t i = 0; i < stackLevel; i++)
2816 {
2817 if ((i % blockInterval) < blockLength)
2818 _barMeter.setPixel(_corrPixelToDir(i), true);
2819 else
2820 _barMeter.setPixel(_corrPixelToDir(i), false);
2821 }
2822 }
2823
2824 return false;
2825 }
2826
2828 {
2831 stackLevel += blockInterval;
2832 else
2833 stackLevel -= blockInterval;
2834 }
2835
2837 {
2839
2840 // Only clear pixels of active blocks, not the whole bar
2841 for (uint8_t i = 0; i < maxBlocks; ++i)
2842 {
2843 Block &b = _animationWorkspace[i];
2844 if (!b.active)
2845 continue;
2846
2847 for (uint8_t j = 0; j < blockLength; ++j)
2848 {
2849 int8_t seg = b.position + j;
2850 if (seg >= 0 && seg < _segsNum)
2851 _barMeter.setPixel(_corrPixelToDir(seg), false);
2852 }
2853 }
2854
2855 // Emit one block if none active
2856 for (uint8_t i = 0; i < maxBlocks; ++i)
2857 {
2858 if (_animationWorkspace[i].active)
2859 {
2860 hasActive = true;
2861 break;
2862 }
2863 }
2864 if (!hasActive)
2865 {
2866 emitCooldown = 0; // emit blocks on demand
2868 _emitBlock(_segsNum); // fall from top
2869 else if (_animRenderLogicIsInverted && stackLevel >= 0)
2870 _emitBlock(stackLevel - blockInterval);
2871 }
2872
2873 // Update and draw all active blocks
2874 for (uint8_t i = 0; i < maxBlocks; ++i)
2875 {
2876 Block &b = _animationWorkspace[i];
2877 if (!b.active)
2878 continue;
2879
2880 // Clear trailing pixel of previous frame
2881 int8_t clearPos = b.position;
2882 if (clearPos >= 0 && clearPos < _segsNum)
2883 _barMeter.setPixel(_corrPixelToDir(clearPos), false);
2884
2885 // Move block
2887 b.position--;
2888 else
2889 b.position++;
2890
2891 for (uint8_t j = 0; j < blockLength; ++j)
2892 {
2893 int8_t seg = b.position + j;
2894 if (seg >= 0 && seg < _segsNum)
2895 _barMeter.setPixel(_corrPixelToDir(seg), true);
2896 }
2897
2899 {
2900 if (b.position <= stackLevel)
2901 {
2902 stackLevel += blockInterval;
2903 b.active = false;
2904 }
2905 }
2906 else
2907 {
2908 if (b.position >= _segsNum)
2909 {
2910 stackLevel -= blockInterval;
2911 b.active = false;
2912 }
2913 }
2914 }
2915
2916 // Draw base for stacking
2917 if (stackLevel == 0)
2918 {
2919 _barMeter.setPixel(_corrPixelToDir(0), false);
2920 }
2922 {
2923 for (int8_t i = 0; i < stackLevel - blockInterval; ++i)
2924 {
2925 if ((i % blockInterval) < blockLength)
2926 _barMeter.setPixel(_corrPixelToDir(i), true);
2927 else
2928 _barMeter.setPixel(_corrPixelToDir(i), false);
2929 }
2930 }
2931 else
2932 {
2933 for (uint8_t i = 0; i < stackLevel - blockInterval; i++)
2934 {
2935 if ((i % blockInterval) < blockLength)
2936 _barMeter.setPixel(_corrPixelToDir(i), true);
2937 else
2938 _barMeter.setPixel(_corrPixelToDir(i), false);
2939 }
2940 }
2942 {
2943 if (stackLevel >= _segsNum - 1 && !hasActive)
2944 return true;
2945 }
2946 else
2947 {
2948 if (stackLevel <= 0 && !hasActive)
2949 return true;
2950 }
2951 }
2952 return false;
2953 }
2954#undef stackLevel
2955#undef blockLength
2956#undef blockSpacing
2957#undef requestedNumBlocks
2958#undef maxBlocks
2959#undef emitIndex
2960#undef emittedBlocksCount
2961#undef emitCooldown
2962
2963#define smoothingFactor _param1
2965 {
2966 if (!_signalPtr1())
2967 {
2968 _setAllOff();
2969 return true;
2970 }
2971
2972 if (_init)
2973 {
2974 _init = false;
2975
2976 if (!_animLogicSet)
2978
2981 _setAllOff();
2982 return false;
2983 }
2984
2985 // Signal sampling
2987 {
2988 uint16_t raw = *_signalPtr1();
2991 }
2992
2994 {
2996
2998
2999 for (uint8_t i = 0; i < _segsNum; i++)
3000 {
3001 _barMeter.setPixel(_corrPixelToDir(i), i < level);
3002 }
3003 }
3004 return false; // continuous animation
3005 }
3007 {
3008 if (!_signalPtr1())
3009 {
3010 _setAllOff();
3011 return true;
3012 }
3013
3014 if (_init)
3015 {
3016 _init = false;
3017
3018 if (!_animLogicSet)
3020
3023 _setAllOff();
3024 return false;
3025 }
3026
3027 // Signal sampling
3029 {
3030 uint16_t raw = *_signalPtr1();
3033 }
3034
3036 {
3038
3040 uint8_t pointer = _getMappedSignal(*_signalPtr1(), _minMap, _maxMap, 0, _segsNum);
3041
3042 // First, fill up to moving average
3043 for (uint8_t i = 0; i < _segsNum; i++)
3044 {
3045 _barMeter.setPixel(_corrPixelToDir(i), i < avg);
3046 }
3047
3048 // Then selectively clear area around pointer
3049 if (pointer < avg && pointer > 0)
3050 _barMeter.setPixel(_corrPixelToDir(pointer - 1), false); // clear just below if pointer is inside the fill
3051
3052 if (pointer < avg - 2)
3053 _barMeter.setPixel(_corrPixelToDir(pointer + 1), false); // clear just above
3054
3055 // Finally, show the pointer
3056 _barMeter.setPixel(_corrPixelToDir(pointer), true);
3057 }
3058 return false; // continuous animation
3059 }
3061 {
3062 if (!_signalPtr1() && !_signalPtr2())
3063 {
3064 _setAllOff();
3065 return true;
3066 }
3067
3068 if (_init)
3069 {
3070 _init = false;
3071
3072 if (!_animLogicSet)
3074
3076 if (_signalPtr2())
3079 _setAllOff();
3080 return false;
3081 }
3082
3085
3086 // Signals sampling
3088 {
3090
3091 uint16_t raw1 = *_signalPtr1();
3092 _smoothedValue1 = (uint16_t)(((uint32_t)smoothingFactor * raw1 + (uint32_t)(100 - smoothingFactor) * _smoothedValue1) / 100);
3093
3094 if (_signalPtr2())
3095 {
3096 uint16_t raw2 = *_signalPtr2();
3097 _smoothedValue2 = (uint16_t)(((uint32_t)smoothingFactor * raw2 + (uint32_t)(100 - smoothingFactor) * _smoothedValue2) / 100);
3098 }
3099 }
3100
3102 {
3104
3105 uint16_t raw1 = *_signalPtr1();
3106 _smoothedValue1 = (uint16_t)(((uint32_t)smoothingFactor * raw1 + (uint32_t)(100 - smoothingFactor) * _smoothedValue1) / 100);
3107 uint8_t level1 = _getMappedSignal(_smoothedValue1, _minMap, _maxMap, 0, _segsNum / 2);
3108
3109 uint8_t level2 = level1;
3110 if (_signalPtr2())
3112
3113 for (uint8_t i = 0; i < _segsNum; i++)
3114 {
3115 //_barMeter.setPixel(_corrPixelToDirForHalfRange(i), i >= (_segsNum / 2 - 1) - level1 && i <= ((_segsNum / 2) + level2));
3117 _barMeter.setPixel(i, i < (_segsNum / 2 - 1) - level1 || i > ((_segsNum / 2) + level2));
3118 else
3119 _barMeter.setPixel(i, i >= (_segsNum / 2 - 1) - level1 && i <= ((_segsNum / 2) + level2));
3120 }
3121 }
3122 return false; // continuous animation
3123 }
3124
3125#define currentLevel _ledTracker1
3126#define peakLevel _ledTracker3
3127#define baseUpdate _updateIntv1
3128#define lastBaseUpdate _lastUpdate1
3129#define peakHoldTime _updateIntv3
3130#define lastPeakUpdate _lastUpdate3
3131 bool _followSignalFloatingPeak() // bouncing from bottom (maybe like a volume meter with the music)
3132 {
3133
3134 if (!_signalPtr1())
3135 {
3136 _setAllOff();
3137 return true;
3138 }
3139
3140 if (_init)
3141 {
3142 _init = false;
3143
3144 if (!_animLogicSet)
3146
3147 _setAllOff();
3151 currentLevel = 0;
3152 peakLevel = 0;
3153 return false;
3154 }
3155
3156 // Signal smoothing update
3158 {
3160
3161 uint16_t raw = *_signalPtr1();
3164 }
3165
3166 // Pixel + peak update
3168 {
3170
3171 // Peak logic
3172 if (currentLevel > peakLevel)
3173 {
3175 lastPeakUpdate = _currentTime; // Reset decay timer
3176 }
3178 {
3179 peakLevel--; // Slowly drop peak
3180 lastPeakUpdate = _currentTime; // Reset decay timer
3181 }
3182
3183 // Draw bar
3184 for (uint8_t i = 0; i < _segsNum; i++)
3185 {
3186 _barMeter.setPixel(_corrPixelToDir(i), (i <= currentLevel));
3187 }
3188
3189 // Draw peak
3190 if (peakLevel < _segsNum)
3191 {
3192 _barMeter.setPixel(_corrPixelToDir(peakLevel), true);
3193 }
3194 }
3195 return false; // Continuous pulse animation, never auto-terminates
3196 }
3197#undef currentLevel
3198#undef peakLevel
3199#undef baseUpdate
3200#undef lastBaseUpdate
3201#undef peakHoldTime
3202#undef lastPeakUpdate
3203
3204#undef smoothingFactor
3205
3207 {
3208 uint8_t *pixelOrder = _randomOrder();
3209 if (_segsNum == 0 || !pixelOrder)
3210 return true;
3211
3212 if (_init)
3213 {
3214 _init = false;
3215
3216 if (!_animLogicSet)
3218
3220 _setAllOn();
3221 else
3222 _setAllOff();
3223
3224 for (uint8_t i = 0; i < _segsNum; ++i)
3225 pixelOrder[i] = i;
3226
3227 for (uint8_t i = _segsNum - 1; i > 0; --i)
3228 {
3229 uint8_t j = random(0, i + 1);
3230 uint8_t tmp = pixelOrder[i];
3231 pixelOrder[i] = pixelOrder[j];
3232 pixelOrder[j] = tmp;
3233 }
3234
3235 _randomCursor = 0;
3237
3238 return false;
3239 }
3240
3242 {
3244
3245 uint8_t retries = 0;
3246 while (_randomCursor < _segsNum && retries++ < _segsNum - 1)
3247 {
3248 uint8_t seg = pixelOrder[_randomCursor];
3249 bool currentState = _barMeter.getPixelState(seg);
3250 bool shouldChange = (!_AnimInitLogicIsInverted && !currentState) || (_AnimInitLogicIsInverted && currentState);
3251
3252 if (shouldChange)
3253 {
3254 _barMeter.setPixel(seg, !_AnimInitLogicIsInverted);
3255 _randomCursor++;
3256 break; // Change only one pixel per interval
3257 }
3258 else
3259 {
3260 _randomCursor++; // Move on to next even if it doesn't need change
3261 }
3262 }
3263 }
3264 if (_randomCursor >= _segsNum)
3265 {
3266 return true;
3267 }
3268 return false;
3269 }
3270};
#define stackLevel
#define peakHoldTime
#define MIN_BASE_LEVEL
#define peakLevel
#define PEAK_HOLD_TIME
#define emittedBlocksCount
#define emitIndex
#define lastBaseUpdate
#define beat
#define lastRandomUpdate
#define lastPeakUpdate
#define maxBlocks
#define smoothingFactor
#define currentLevel
#define blockLength
#define SBK_BARDRIVE_QUEUE_CAPACITY
#define lastBeat
#define requestedNumBlocks
#define prevPeakUpdate
#define emitCooldown
#define blockSpacing
#define bpm
#define bpmPtr
#define MIN_PEAK_LEVEL
bool isNonInvertingLogicAnim()
Return true if the current animation does not support logic inversion.
SBK_BarMeterAnimations & fillUpIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr=nullptr)
Start animation that fills the bar upwards at a fixed interval with live percent tracking.
SBK_BarMeterAnimations & beatPulse(uint8_t bpm=116)
Start a beat pulse animation synchronized to a constant BPM.
SBK_BarMeterAnimations & bounceFillUpIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr=nullptr)
Start animation that fills/empties (bounces) bar upward with live range.
SBK_BarMeterAnimations & fillDownDur(uint16_t duration, uint8_t maxPercent=100, uint8_t minPercent=0)
Start animation that fills the bar downwards over a specified duration.
SBK_BarMeterAnimations & fillUpIntv(uint16_t updateIntv, uint8_t maxPercent=100, uint8_t minPercent=0)
Start animation that fills the bar upwards at a fixed interval.
SBK_BarMeterAnimations & emptyUpIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr=nullptr)
Start animation that empties the bar from bottom to top at fixed intervals with live range.
SBK_BarMeterAnimations & loop()
Set animation to automatically loop when complete.
uint8_t currentQueueIndex() const
Return the physical queue slot currently playing, or NO_QUEUE_INDEX.
SBK_BarMeterAnimations & bounceFillFromEdgesDur(uint16_t duration, uint8_t maxPercent=100, uint8_t minPercent=0, uint16_t fillIntv=0)
Bounce fill from both edges inward with fixed range.
static int8_t _calculateSwitchPosition(int8_t pos, uint8_t blockL, uint8_t range)
SBK_BarMeterAnimations & setDir(bool newDirection)
Explicitly set the animation rendering direction.
SBK_BarMeterAnimations & bounceFillUpDur(uint16_t duration, uint8_t maxPercent=100, uint8_t minPercent=0, uint16_t fillIntv=0)
Start animation that fills/empties (bounces) bar upward with fixed range.
SBK_BarMeterAnimations & beatPulse(uint8_t *bpmPtr)
Start a beat pulse animation synchronized to a live BPM value.
SBK_BarMeterAnimations(BarMeterT &barMeter)
Construct an animation controller linked to a bar meter.
SBK_BarMeterAnimations & fillDownIntv(uint16_t updateIntv, uint8_t maxPercent=100, uint8_t minPercent=0)
Start animation that fills the bar downwards at a fixed interval.
SBK_BarMeterAnimations & toggleLogic()
Toggle rendering logic (inverted vs normal), example fill logic become empty logic.
SBK_BarMeterAnimations & loopQueue()
Repeat the entire queue after its final entry completes.
SBK_BarMeterAnimations & followSignalWithPointer(const uint16_t *sigPtr, uint16_t updateIntv=100, uint16_t minMap=0, uint16_t maxMap=1023, uint8_t smoothingFactor=30, uint16_t samplingIntv=5)
Follow signal with smoothing and display a pointer.
SBK_BarMeterAnimations & toggleDir()
Toggle the current animation rendering direction.
SBK_BarMeterAnimations & emptyDownIntv(uint16_t updateIntv, uint8_t maxPercent=100, uint8_t minPercent=0)
Start animation that empties the bar from top to bottom at fixed intervals.
void _loadQueueEntry(const QueueEntry &entry, bool forceReset=false)
const uint16_t * _signalPtr1() const
SBK_BarMeterAnimations & reverseDir()
Reverse the rendering direction compared to the original one.
void _mapMinMaxTracker(uint8_t minP, uint8_t maxP, uint8_t minR, uint8_t maxR)
SBK_BarMeterAnimations & pause(uint32_t syncTime=millis())
Freeze animation progression and all queue/animation timers.
void _mapMinMaxTrackerFromPtr(const uint8_t *minPercPtr, const uint8_t *maxPercPtr, int8_t minR, uint8_t maxR)
SBK_BarMeterAnimations & bounceFillDownDur(uint16_t duration, uint8_t maxPercent=100, uint8_t minPercent=0, uint16_t fillIntv=0)
Start animation that fills/empties (bounces) bar downward.
SBK_BarMeterAnimations & setUpdateInterval(uint16_t interval)
Change the primary update interval without restarting the animation.
SBK_BarMeterAnimations & followSignalSmooth(const uint16_t *sigPtr, uint16_t updateIntv=100, uint16_t minMap=0, uint16_t maxMap=1023, uint8_t smoothingFactor=30, uint16_t samplingIntv=5)
Follow analog signal with smoothing for fill animation.
static constexpr uint8_t MAX_BLOCK_CAPACITY
const uint8_t * _liveBpmPtr() const
SBK_BarMeterAnimations & followSignalFloatingPeak(const uint16_t *sigPtr, uint8_t peakHoldTime=20, uint16_t updateIntv=100, uint16_t minMap=0, uint16_t maxMap=1023, uint8_t smoothingFactor=30, uint16_t samplingIntv=5)
Follow an analog signal with smoothing and floating peak indicator.
static auto _minValue(A a, B b) -> decltype(true ? A() :B())
const uint16_t * _signalPtr2() const
SBK_BarMeterAnimations & setAll(bool onff)
Set all pixels to the specified state.
SBK_BarMeterAnimations & downStackingBlocks(uint16_t intv=50, uint8_t blockLength=1, uint8_t blockSpacing=0)
Drop blocks from top and stack from bottom up.
void _resetBlocks(uint8_t &blockCapacity)
uint8_t _corrPixelToDirForHalfRange(uint8_t pixel)
const uint8_t * _percentMaxPtr() const
SBK_BarMeterAnimations & emptyDownDur(uint16_t duration, uint8_t maxPercent=100, uint8_t minPercent=0)
Start animation that empties the bar from top to bottom over a duration.
SBK_BarMeterAnimations & blinkPixel(uint8_t index, uint16_t interval=500, uint8_t blinkCount=0)
Blink one pixel with equal ON and OFF intervals.
bool queueOverflowed() const
Return true if an enqueue operation exceeded the configured capacity.
static constexpr uint8_t QUEUE_CAPACITY
SBK_BarMeterAnimations & setLogic(bool newLogic)
Set rendering logic (true = inverted logic, false = normal logic), example fill logic become empty lo...
SBK_BarMeterAnimations & downUnstackingBlocks(uint16_t intv=50, uint8_t blockLength=1, uint8_t blockSpacing=0)
Drop blocks from top and unstack from bottom up.
bool isQueueRunning() const
Return true while an animation queue is being processed.
void _startNextQueuedAnimation(bool forceReset=false)
SBK_BarMeterAnimations & resetDir()
Reset direction to the original setting.
SBK_BarMeterAnimations & setAllOff()
Set all pixels OFF.
SBK_BarMeterAnimations & animInit()
Mark animation as ready to reinitialize in next update cycle.
SBK_BarMeterAnimations & randomEmpty(uint16_t interval=30)
Randomly turn off pixels until the bar is empty.
bool isQueueIndexPlaying(uint8_t index) const
Return true when the requested physical queue slot is currently playing.
SBK_BarMeterAnimations & stopBlockEmission()
Stop emitting new blocks during compatible block animations.
SBK_BarMeterAnimations & blinkPixel(uint8_t index, uint16_t onInterval, uint16_t offInterval, uint8_t blinkCount)
Blink one pixel with independent ON and OFF intervals.
SBK_BarMeterAnimations & upStackingBlocks(uint16_t intv=50, uint8_t blockLength=1, uint8_t blockSpacing=0)
Launch blocks from bottom and stack at the top.
bool isLogicInverted()
Returns true if current logic is different from original logic.
SBK_BarMeterAnimations & enqueue()
Add the currently configured animation to the fixed-capacity queue.
SBK_BarMeterAnimations & enqueueReverseAnim()
Queue a state-preserving logic reversal of the preceding entry.
SBK_BarMeterAnimations & followDualSignalFromCenter(const uint16_t *sig1Ptr, uint16_t updateIntv=100, const uint16_t *sig2Ptr=nullptr, uint16_t minMap=0, uint16_t maxMap=1023, uint8_t smoothingFactor=30, uint16_t samplingIntv=5)
Smoothly fills the lower half and upper half of the bar meter independently from center,...
SBK_BarMeterAnimations & explodingBlocks(uint16_t intv=50, uint8_t blockLength=2, uint8_t blockSpacing=1, uint8_t numBlocks=0)
Emit mirrored blocks from center outward.
bool animPendingLoop()
Detect whether animation has completed and is about to loop.
void _captureQueueEntry(QueueEntry &entry, bool resetRuntimeState, uint32_t duration)
static void _correctSwapOrEqualMinMax(uint16_t &minVal, uint16_t &maxVal)
bool isBlockEmissionEnabled() const
Query if block emission is enabled.
SBK_BarMeterAnimations & noLoop()
Disable auto-looping.
SBK_BarMeterAnimations & emptyDownIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr=nullptr)
Start animation that empties the bar from top to bottom at fixed intervals with live range.
SBK_BarMeterAnimations & stop()
Terminate the animation, loop, and queue, then clear mapped pixels.
int8_t _calculateSwitchedEmitTickCounter(uint8_t range)
SBK_BarMeterAnimations & upUnstackingBlocks(uint16_t intv=50, uint8_t blockLength=1, uint8_t blockSpacing=0)
Launch blocks upward and unstack from top.
SBK_BarMeterAnimations & wait(uint16_t duration)
Wait without blocking or changing pixel states; waits never loop.
uint8_t _corrPixelToDir(uint8_t pixel)
SBK_BarMeterAnimations & bounceFillFromCenterIntv(uint16_t fillIntv=25, uint16_t emptyIntv=25, uint8_t maxPercent=100, uint8_t minPercent=0)
Bounce fill from center outward with fixed range.
SBK_BarMeterAnimations & bounceFillFromCenterIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr=nullptr)
Bounce fill from center outward with live percent range tracking.
const uint8_t * _percentMinPtr() const
bool update(uint32_t syncTime=millis())
Advance the animation logic based on the current millis().
SBK_BarMeterAnimations & collidingBlocks(uint16_t intv=50, uint8_t blockLength=2, uint8_t blockSpacing=1, uint8_t numBlocks=0)
Emit mirrored blocks from edge to center.
QueueEntry _queue[QUEUE_CAPACITY]
SBK_BarMeterAnimations & noLoopQueue()
Disable repetition; finish the current queue pass before stopping.
SBK_BarMeterAnimations & emptyUpDur(uint16_t duration, uint8_t maxPercent=100, uint8_t minPercent=0)
Start animation that empties the bar from bottom to top over a duration.
SBK_BarMeterAnimations & resetLogic()
Reset logic to initial (default) value.
bool isLoopEnabled() const
Query if animation loop is enabled.
void setSegsNum(uint8_t n)
Set the total number of segments the animation logic should handle.
SBK_BarMeterAnimations & startQueue()
Start queued playback using the current queue-loop setting.
static void _normalizePercentRange(uint8_t &minP, uint8_t &maxP)
SBK_BarMeterAnimations & setAllOn()
Set all pixels ON.
SBK_BarMeterAnimations & stopBlockEmissionAfter(uint32_t duration)
Stop new block emission after an animation has run for a duration.
bool(SBK_BarMeterAnimations::*)() AnimUpdateFn
bool isRunning() const
Query if animation is currently active.
SBK_BarMeterAnimations & bounceFillDownIntv(uint16_t fillIntv=10, uint16_t emptyIntv=10, uint8_t maxPercent=100, uint8_t minPercent=0)
Start downwardfill/empty (bounce) with fixed percent range.
SBK_BarMeterAnimations & invertLogic()
Reverse logic compared to initial state Logic refer to behavior, example : fill/empty,...
SBK_BarMeterAnimations & scrollingDownBlocks(uint16_t intv=50, uint8_t blockLength=2, uint8_t blockSpacing=1, uint8_t numBlocks=0)
Scroll blocks downward.
SBK_BarMeterAnimations & emptyUpIntv(uint16_t updateIntv, uint8_t maxPercent=100, uint8_t minPercent=0)
Start animation that empties the bar from bottom to top at fixed intervals.
SBK_BarMeterAnimations & bounceFillDownIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr=nullptr)
Start downward fill/empty (bounce) with live percent range tracking.
SBK_BarMeterAnimations & forTime(uint32_t duration)
Limit a direct or queued animation to a maximum run time.
SBK_BarMeterAnimations & bounceFillFromCenterDur(uint16_t duration, uint8_t maxPercent=100, uint8_t minPercent=0, uint16_t fillIntv=0)
Bounce fill from center outward with fixed range.
SBK_BarMeterAnimations & skipCurrent()
Abandon the active animation and immediately start the next queue entry.
SBK_BarMeterAnimations & fillDownIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr=nullptr)
Start animation that fills the bar downwards at a fixed interval with live percent tracking.
static uint8_t _getMappedSignal(uint16_t sig, uint16_t minM, uint16_t maxM, uint8_t minR, uint8_t maxR)
SBK_BarMeterAnimations & resumeBlockEmission()
Resume emitting new blocks during compatible block animations.
SBK_BarMeterAnimations & clearQueue()
Discard the queue and disable queue looping without stopping the current animation.
static auto _maxValue(A a, B b) -> decltype(true ? A() :B())
SBK_BarMeterAnimations & randomFill(uint16_t interval=30)
Randomly light up pixels until the bar is full.
uint8_t queuedAnimations() const
Return the number of animations waiting behind the active one.
SBK_BarMeterAnimations & bounceFillUpIntv(uint16_t fillIntv=10, uint16_t emptyIntv=10, uint8_t maxPercent=100, uint8_t minPercent=0)
Start animation that fills/empties (bounces) bar upward with fixed range.
SBK_BarMeterAnimations & fillUpDur(uint16_t duration, uint8_t maxPercent=100, uint8_t minPercent=0)
Start animation that fills the bar upwards over a specified duration.
SBK_BarMeterAnimations & bounceFillFromEdgesIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr=nullptr)
Bounce fill from both edges inward with live percent range tracking.
static constexpr uint8_t NO_QUEUE_INDEX
SBK_BarMeterAnimations & scrollingUpBlocks(uint16_t intv=50, uint8_t blockLength=2, uint8_t blockSpacing=1, uint8_t numBlocks=0)
Scroll blocks upward.
SBK_BarMeterAnimations & resume(uint32_t syncTime=millis())
Resume with the same playback time remaining as when paused.
SBK_BarMeterAnimations & enqueueFor(uint32_t duration)
Queue the configured animation for a fixed amount of time.
SBK_BarMeterAnimations & followDualSignalFromEdges(const uint16_t *sig1Ptr, uint16_t updateIntv=100, const uint16_t *sig2Ptr=nullptr, uint16_t minMap=0, uint16_t maxMap=1023, uint8_t smoothingFactor=30, uint16_t samplingIntv=5)
Smoothly fills the lower half and upper half of the bar meter independently from center,...
bool isDirectionReversed()
Returns true if direction is reversed compared to the original.
SBK_BarMeterAnimations & bounceFillFromEdgesIntv(uint16_t fillIntv=25, uint16_t emptyIntv=25, uint8_t maxPercent=100, uint8_t minPercent=0)
Bounce fill from both edges inward with fix range.
bool isPaused() const
Query if animation is paused.
bool animInitLogicIsInverted
bool resetRuntimeState
bool 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