SBK_BarDrive Library 2.0.4
Modular animation and control library for bar meter LEDs
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
47template <typename BarMeterT>
49{
50public:
55 explicit SBK_BarMeterAnimations(BarMeterT &barMeter) : _barMeter(barMeter) {}
56
61 {
62 if (_blocks)
63 {
64 delete[] _blocks;
65 _blocks = nullptr;
66 }
67 }
68
74 void setSegsNum(uint8_t n)
75 {
76 _segsNum = n;
77 _maxTracker = n - 1;
78 }
79
85 bool update(uint32_t syncTime = millis())
86 {
87 _currentTime = syncTime;
88
89 if (!_isRunning || _isPaused || !_currentFunc)
90 return false;
91
92 if ((this->*_currentFunc)())
93 {
94 if (_loop)
95 {
96 if (_skipPending)
97 _isLoopingNow = false;
98 else
99 {
100 _isLoopingNow = true; // mark it so user can react
101 _init = true;
102 }
103 }
104 else
105 {
106 _isLoopingNow = false;
107 _isRunning = false;
108 _currentFunc = nullptr;
109 }
110 }
111 return _isRunning;
112 }
113
116 {
117 _init = true;
118 return *this;
119 }
120
123 {
124 _isPaused = true;
125 return *this;
126 }
127
130 {
131 _isPaused = false;
132 return *this;
133 }
134
137 {
138 _isPaused = false;
139 _isRunning = false;
140 _skipPending = false;
141 _currentFunc = nullptr;
142 _animLogicSet = false;
143 return *this;
144 }
145
148 {
149 _loop = true;
150 return *this;
151 }
152
155 {
156 _loop = false;
157 return *this;
158 }
159
161 SBK_BarMeterAnimations &setDir(bool newDirection)
162 {
163 _animRenderDirIsReversed = newDirection;
164 _animDirSet = true;
165 return *this;
166 }
167
170 {
171 _animRenderDirIsReversed = !_animRenderDirIsReversed;
172 _animDirSet = true;
173 return *this;
174 }
175
178 {
179 _animRenderDirIsReversed = !_AnimInitDirIsReversed;
180 _animDirSet = true;
181 return *this;
182 }
183
186 {
187 _animRenderDirIsReversed = _AnimInitDirIsReversed;
188 _animDirSet = false;
189 return *this;
190 }
191
197 {
198 if (_isNonInvertingLogicAnim)
199 return *this;
200 _animRenderLogicIsInverted = newLogic;
201 _animLogicSet = true;
202 return *this;
203 }
204
211 {
212 if (_isNonInvertingLogicAnim)
213 return *this;
214 _animRenderLogicIsInverted = !_animRenderLogicIsInverted;
215 _animLogicSet = true;
216 return *this;
217 }
218
225 {
226 if (_isNonInvertingLogicAnim)
227 return *this;
228 _animRenderLogicIsInverted = !_AnimInitLogicIsInverted;
229 _animLogicSet = true;
230 return *this;
231 }
232
239 {
240 if (_isNonInvertingLogicAnim)
241 return *this;
242 _animRenderLogicIsInverted = _AnimInitLogicIsInverted;
243 _animLogicSet = false;
244 return *this;
245 }
246
249 {
250 _emittingBlocksEnabled = false;
251 return *this;
252 }
253
256 {
257 _emittingBlocksEnabled = true;
258 return *this;
259 }
260
262 bool isRunning() const { return _isRunning; }
263
265 bool isPaused() const { return _isPaused; }
266
268 bool isLoopEnabled() const { return _loop; }
269
272 {
273 if (_skipPending == true)
274 _skipPending = false;
275 else
276 {
277 if (_isLoopingNow)
278 {
279 _isLoopingNow = false; // auto-clear after read
280 return true;
281 }
282 }
283 return false;
284 }
285
288 {
289 if (_AnimInitLogicIsInverted != _animRenderLogicIsInverted)
290 return true;
291 else
292 return false;
293 }
294
296 bool isNonInvertingLogicAnim() { return _isNonInvertingLogicAnim; }
297
300 {
301 if (_AnimInitDirIsReversed != _animRenderDirIsReversed)
302 return true;
303 else
304 return false;
305 }
306
308 bool isBlockEmissionEnabled() const { return _emittingBlocksEnabled; }
309
310 // Animation starters
315 SBK_BarMeterAnimations &setAll(bool onff) { return onff ? setAllOn() : setAllOff(); }
316
321 {
322 _currentFunc = &SBK_BarMeterAnimations::_setAllOn;
323 _isRunning = true;
324 _init = true;
325 return *this;
326 }
327
332 {
333 _currentFunc = &SBK_BarMeterAnimations::_setAllOff;
334 _isRunning = true;
335 _init = true;
336 return *this;
337 }
338
346 SBK_BarMeterAnimations &fillUpDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0)
347 {
348 _isNonInvertingLogicAnim = false;
349 _animRenderDirIsReversed = false;
350 _AnimInitLogicIsInverted = false;
351 _usePtr = false;
352 _sigPtr2 = nullptr;
353 _sigPtr1 = nullptr;
354 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
355 _isRunning = true;
356 uint8_t steps = max(1, _maxTracker - _minTracker + 1);
357 _updateIntv1 = max(5, duration / steps);
358 _currentFunc = &SBK_BarMeterAnimations::_fillOrEmpty;
359 return *this;
360 }
361
369 SBK_BarMeterAnimations &fillUpIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
370 {
371 _isNonInvertingLogicAnim = false;
372 _animRenderDirIsReversed = false;
373 _AnimInitLogicIsInverted = false;
374 _usePtr = true;
375 _sigPtr2 = maxPercentPtr;
376 _sigPtr1 = minPercentPtr;
377 _mapMinMaxTrackerFromPtr(_sigPtr1, _sigPtr2, 0, _segsNum - 1);
378 _isRunning = true;
379 _updateIntv1 = max(5, updateIntv);
380 _currentFunc = &SBK_BarMeterAnimations::_fillOrEmpty;
381 return *this;
382 }
383
391 SBK_BarMeterAnimations &fillUpIntv(uint16_t updateIntv, uint8_t maxPercent = 100, uint8_t minPercent = 0)
392 {
393 _isNonInvertingLogicAnim = false;
394 _animRenderDirIsReversed = false;
395 _AnimInitLogicIsInverted = false;
396 _usePtr = false;
397 _sigPtr2 = nullptr;
398 _sigPtr1 = nullptr;
399 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
400 _isRunning = true;
401 _updateIntv1 = max(5, updateIntv);
402 _currentFunc = &SBK_BarMeterAnimations::_fillOrEmpty;
403 return *this;
404 }
405
413 SBK_BarMeterAnimations &fillDownDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0)
414 {
415 _isNonInvertingLogicAnim = false;
416 _animRenderDirIsReversed = true;
417 _AnimInitLogicIsInverted = false;
418 _usePtr = false;
419 _sigPtr2 = nullptr;
420 _sigPtr1 = nullptr;
421 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
422 _isRunning = true;
423 uint8_t steps = max(1, _maxTracker - _minTracker + 1);
424 _updateIntv1 = max(5, duration / steps);
425 _currentFunc = &SBK_BarMeterAnimations::_fillOrEmpty;
426 return *this;
427 }
428
436 SBK_BarMeterAnimations &fillDownIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
437 {
438 _isNonInvertingLogicAnim = false;
439 _animRenderDirIsReversed = true;
440 _AnimInitLogicIsInverted = false;
441 _usePtr = true;
442 _sigPtr2 = maxPercentPtr;
443 _sigPtr1 = minPercentPtr;
444 _mapMinMaxTrackerFromPtr(_sigPtr1, _sigPtr2, 0, _segsNum - 1);
445 _isRunning = true;
446 uint8_t steps = max(1, _maxTracker - _minTracker + 1);
447 _updateIntv1 = max(5, updateIntv);
448 _currentFunc = &SBK_BarMeterAnimations::_fillOrEmpty;
449 return *this;
450 }
451
459 SBK_BarMeterAnimations &fillDownIntv(uint16_t updateIntv, uint8_t maxPercent = 100, uint8_t minPercent = 0)
460 {
461 _isNonInvertingLogicAnim = false;
462 _animRenderDirIsReversed = true;
463 _AnimInitLogicIsInverted = false;
464 _usePtr = false;
465 _sigPtr2 = nullptr;
466 _sigPtr1 = nullptr;
467 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
468 _isRunning = true;
469 _updateIntv1 = max(5, updateIntv);
470 _currentFunc = &SBK_BarMeterAnimations::_fillOrEmpty;
471 return *this;
472 }
473
481 SBK_BarMeterAnimations &emptyDownDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0)
482 {
483 _isNonInvertingLogicAnim = false;
484 _animRenderDirIsReversed = false;
485 _AnimInitLogicIsInverted = true;
486 _usePtr = false;
487 _sigPtr2 = nullptr;
488 _sigPtr1 = nullptr;
489 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
490 _isRunning = true;
491 uint8_t steps = max(1, _maxTracker - _minTracker + 1);
492 return emptyDownIntv(duration / steps, maxPercent, minPercent);
493 }
494
502 SBK_BarMeterAnimations &emptyDownIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
503 {
504 _isNonInvertingLogicAnim = false;
505 _animRenderDirIsReversed = false;
506 _AnimInitLogicIsInverted = true;
507 _usePtr = true;
508 _sigPtr2 = maxPercentPtr;
509 _sigPtr1 = minPercentPtr;
510 _mapMinMaxTrackerFromPtr(_sigPtr1, _sigPtr2, 0, _segsNum - 1);
511 _isRunning = true;
512 _updateIntv1 = max(5, updateIntv);
513 _currentFunc = &SBK_BarMeterAnimations::_fillOrEmpty;
514 return *this;
515 }
516
524 SBK_BarMeterAnimations &emptyDownIntv(uint16_t updateIntv, uint8_t maxPercent = 100, uint8_t minPercent = 0)
525 {
526 _isNonInvertingLogicAnim = false;
527 _animRenderDirIsReversed = false;
528 _AnimInitLogicIsInverted = true;
529 _usePtr = false;
530 _sigPtr2 = nullptr;
531 _sigPtr1 = nullptr;
532 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
533 _isRunning = true;
534 _updateIntv1 = max(5, updateIntv);
535 _currentFunc = &SBK_BarMeterAnimations::_fillOrEmpty;
536 return *this;
537 }
538
546 SBK_BarMeterAnimations &emptyUpDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0)
547 {
548 _isNonInvertingLogicAnim = false;
549 _animRenderDirIsReversed = true;
550 _AnimInitLogicIsInverted = true;
551 _usePtr = false;
552 _sigPtr2 = nullptr;
553 _sigPtr1 = nullptr;
554 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
555 _isRunning = true;
556 uint8_t steps = max(1, _maxTracker - _minTracker + 1);
557 _updateIntv1 = max(5, duration / steps);
558 _currentFunc = &SBK_BarMeterAnimations::_fillOrEmpty;
559 return *this;
560 }
561
569 SBK_BarMeterAnimations &emptyUpIntv(uint16_t updateIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
570 {
571 _isNonInvertingLogicAnim = false;
572 _animRenderDirIsReversed = true;
573 _AnimInitLogicIsInverted = true;
574 _usePtr = true;
575 _sigPtr2 = maxPercentPtr;
576 _sigPtr1 = minPercentPtr;
577 _mapMinMaxTrackerFromPtr(_sigPtr1, _sigPtr2, 0, _segsNum - 1);
578 _isRunning = true;
579 _updateIntv1 = max(5, updateIntv);
580 _currentFunc = &SBK_BarMeterAnimations::_fillOrEmpty;
581 return *this;
582 }
583
591 SBK_BarMeterAnimations &emptyUpIntv(uint16_t updateIntv, uint8_t maxPercent = 100, uint8_t minPercent = 0)
592 {
593 _isNonInvertingLogicAnim = false;
594 _animRenderDirIsReversed = true;
595 _AnimInitLogicIsInverted = true;
596 _usePtr = false;
597 _sigPtr2 = nullptr;
598 _sigPtr1 = nullptr;
599 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
600 _isRunning = true;
601 _updateIntv1 = max(5, updateIntv);
602 _currentFunc = &SBK_BarMeterAnimations::_fillOrEmpty;
603 return *this;
604 }
605
614 SBK_BarMeterAnimations &bounceFillUpDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0, uint16_t fillIntv = 0)
615 {
616 _isNonInvertingLogicAnim = true;
617 _animRenderDirIsReversed = false;
618 _usePtr = false;
619 _sigPtr2 = nullptr;
620 _sigPtr1 = nullptr;
621 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
622 uint8_t steps = max(1, _maxTracker - _minTracker + 1);
623 uint16_t emptyIntv = fillIntv;
624 if (fillIntv == 0)
625 {
626 fillIntv = round(max(5, duration) / (2 * steps));
627 emptyIntv = fillIntv;
628 }
629 else
630 {
631 emptyIntv = round(max(5, duration - fillIntv * steps) / (steps));
632 }
633 _updateIntv1 = fillIntv; // Starting interval
634 _updateIntv2 = _updateIntv1; // Fill intv
635 _updateIntv3 = emptyIntv; // Empty intv
636 _sequenceState = 0;
637 _isRunning = true;
638 _currentFunc = &SBK_BarMeterAnimations::_bounceFill;
639 return *this;
640 }
641
650 SBK_BarMeterAnimations &bounceFillUpIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
651 {
652 _isNonInvertingLogicAnim = true;
653 _animRenderDirIsReversed = false;
654 _sigPtr2 = maxPercentPtr;
655 _sigPtr1 = minPercentPtr;
656 _usePtr = true;
657 _mapMinMaxTrackerFromPtr(_sigPtr1, _sigPtr2, 0, _segsNum - 1);
658 _updateIntv1 = max(5, fillIntv); // Starting interval
659 _updateIntv2 = _updateIntv1; // Fill intv
660 _updateIntv3 = max(5, emptyIntv); // Empty intv
661 _sequenceState = 0;
662 _isRunning = true;
663 _currentFunc = &SBK_BarMeterAnimations::_bounceFill;
664 return *this;
665 }
666
675 SBK_BarMeterAnimations &bounceFillUpIntv(uint16_t fillIntv = 10, uint16_t emptyIntv = 10, uint8_t maxPercent = 100, uint8_t minPercent = 0)
676 {
677 _isNonInvertingLogicAnim = true;
678 _animRenderDirIsReversed = false;
679 _AnimInitLogicIsInverted = false;
680 _usePtr = false;
681 _sigPtr2 = nullptr;
682 _sigPtr1 = nullptr;
683 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
684 _updateIntv1 = max(5, fillIntv); // Starting interval
685 _updateIntv2 = _updateIntv1; // Fill intv
686 _updateIntv3 = max(5, emptyIntv); // Empty intv
687 _sequenceState = 0;
688 _isRunning = true;
689 _currentFunc = &SBK_BarMeterAnimations::_bounceFill;
690 return *this;
691 }
692
701 SBK_BarMeterAnimations &bounceFillDownDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0, uint16_t fillIntv = 0)
702 {
703 _isNonInvertingLogicAnim = true;
704 _animRenderDirIsReversed = true;
705 _AnimInitLogicIsInverted = false;
706 _usePtr = false;
707 _sigPtr2 = nullptr;
708 _sigPtr1 = nullptr;
709 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
710 uint8_t steps = max(1, _maxTracker - _minTracker + 1);
711 uint16_t emptyIntv = fillIntv;
712 if (fillIntv == 0)
713 {
714 fillIntv = round(max(5, duration) / (2 * steps));
715 emptyIntv = fillIntv;
716 }
717 else
718 {
719 emptyIntv = round(max(5, duration - fillIntv * steps) / (steps));
720 }
721 _updateIntv1 = fillIntv; // Starting interval
722 _updateIntv2 = _updateIntv1; // Fill intv
723 _updateIntv3 = emptyIntv; // Empty intv
724 _sequenceState = 0;
725 _isRunning = true;
726 _currentFunc = &SBK_BarMeterAnimations::_bounceFill;
727 return *this;
728 }
729
738 SBK_BarMeterAnimations &bounceFillDownIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
739 {
740 _isNonInvertingLogicAnim = true;
741 _animRenderDirIsReversed = true;
742 _AnimInitLogicIsInverted = false;
743 _sigPtr2 = maxPercentPtr;
744 _sigPtr1 = minPercentPtr;
745 _usePtr = true;
746 _mapMinMaxTrackerFromPtr(_sigPtr1, _sigPtr2, 0, _segsNum - 1);
747 _updateIntv1 = max(5, fillIntv); // Starting interval
748 _updateIntv2 = _updateIntv1; // Fill intv
749 _updateIntv3 = max(5, emptyIntv); // Empty intv
750 _sequenceState = 0;
751 _isRunning = true;
752 _currentFunc = &SBK_BarMeterAnimations::_bounceFill;
753 return *this;
754 }
755
764 SBK_BarMeterAnimations &bounceFillDownIntv(uint16_t fillIntv = 10, uint16_t emptyIntv = 10, uint8_t maxPercent = 100, uint8_t minPercent = 0)
765 {
766 _isNonInvertingLogicAnim = true;
767 _animRenderDirIsReversed = true;
768 _AnimInitLogicIsInverted = false;
769 _usePtr = false;
770 _sigPtr2 = nullptr;
771 _sigPtr1 = nullptr;
772 _mapMinMaxTracker(minPercent, maxPercent, 0, _segsNum - 1);
773 _updateIntv1 = max(5, fillIntv); // Starting interval
774 _updateIntv2 = _updateIntv1; // Fill intv
775 _updateIntv3 = max(5, emptyIntv); // Empty intv
776 _sequenceState = 0;
777 _isRunning = true;
778 _currentFunc = &SBK_BarMeterAnimations::_bounceFill;
779 return *this;
780 }
781
790 SBK_BarMeterAnimations &bounceFillFromCenterDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0, uint16_t fillIntv = 0)
791 {
792 _isNonInvertingLogicAnim = true;
793 _AnimInitLogicIsInverted = false;
794 _animRenderDirIsReversed = false;
795 _mirrorHalfRangeDir = false;
796 _usePtr = false;
797 _sigPtr2 = nullptr;
798 _sigPtr1 = nullptr;
799 _mapMinMaxTracker(minPercent, maxPercent, (_segsNum / 2) - 1, 0);
800 uint8_t steps = max(1, _maxTracker - _minTracker + 1);
801 uint16_t emptyIntv = fillIntv;
802 if (fillIntv == 0)
803 {
804 fillIntv = round(max(5, duration) / (2 * steps));
805 emptyIntv = fillIntv;
806 }
807 else
808 {
809 emptyIntv = round(max(5, duration - fillIntv * steps) / (steps));
810 }
811 _updateIntv1 = fillIntv; // Starting interval
812 _updateIntv2 = _updateIntv1; // Fill intv
813 _updateIntv3 = emptyIntv; // Empty intv
814 _sequenceState = 0;
815 _isRunning = true;
816 _currentFunc = &SBK_BarMeterAnimations::_bounceFillHalfRangeMirrorCenter;
817 return *this;
818 }
819
828 SBK_BarMeterAnimations &bounceFillFromCenterIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
829 {
830 _isNonInvertingLogicAnim = true;
831 _AnimInitLogicIsInverted = false;
832 _animRenderDirIsReversed = false;
833 _mirrorHalfRangeDir = false;
834 _sigPtr2 = maxPercentPtr;
835 _sigPtr1 = minPercentPtr;
836 _usePtr = true;
837 _mapMinMaxTrackerFromPtr(minPercentPtr, maxPercentPtr, (_segsNum / 2) - 1, 0);
838 _updateIntv1 = fillIntv; // Starting interval
839 _updateIntv2 = _updateIntv1; // Fill intv
840 _updateIntv3 = emptyIntv; // Empty intv
841 _sequenceState = 0;
842 _isRunning = true;
843 _currentFunc = &SBK_BarMeterAnimations::_bounceFillHalfRangeMirrorCenter;
844 return *this;
845 }
846
855 SBK_BarMeterAnimations &bounceFillFromCenterIntv(uint16_t fillIntv = 25, uint16_t emptyIntv = 25, uint8_t maxPercent = 100, uint8_t minPercent = 0)
856 {
857 _isNonInvertingLogicAnim = true;
858 _AnimInitLogicIsInverted = false;
859 _animRenderDirIsReversed = false;
860 _mirrorHalfRangeDir = false;
861 _usePtr = false;
862 _sigPtr2 = nullptr;
863 _sigPtr1 = nullptr;
864 _mapMinMaxTracker(minPercent, maxPercent, (_segsNum / 2) - 1, 0);
865 _updateIntv1 = fillIntv; // Starting interval
866 _updateIntv2 = _updateIntv1; // Fill intv
867 _updateIntv3 = emptyIntv; // Empty intv
868 _sequenceState = 0;
869 _isRunning = true;
870 _currentFunc = &SBK_BarMeterAnimations::_bounceFillHalfRangeMirrorCenter;
871 return *this;
872 }
873
882 SBK_BarMeterAnimations &bounceFillFromEdgesDur(uint16_t duration, uint8_t maxPercent = 100, uint8_t minPercent = 0, uint16_t fillIntv = 0)
883 {
884 _isNonInvertingLogicAnim = true;
885 _AnimInitLogicIsInverted = false;
886 _animRenderDirIsReversed = false;
887 _mirrorHalfRangeDir = true;
888 _usePtr = false;
889 _sigPtr2 = nullptr;
890 _sigPtr1 = nullptr;
891 _mapMinMaxTracker(minPercent, maxPercent, (_segsNum / 2) - 1, 0);
892 uint8_t steps = max(1, _maxTracker - _minTracker + 1);
893 uint16_t emptyIntv = fillIntv;
894 if (fillIntv == 0)
895 {
896 fillIntv = round(max(5, duration) / (2 * steps));
897 emptyIntv = fillIntv;
898 }
899 else
900 {
901 emptyIntv = round(max(5, duration - fillIntv * steps) / (steps));
902 }
903 _updateIntv1 = fillIntv; // Starting interval
904 _updateIntv2 = _updateIntv1; // Fill intv
905 _updateIntv3 = emptyIntv; // Empty intv
906 _sequenceState = 0;
907 _isRunning = true;
908 _currentFunc = &SBK_BarMeterAnimations::_bounceFillHalfRangeMirrorCenter;
909 return *this;
910 }
911
920 SBK_BarMeterAnimations &bounceFillFromEdgesIntv(uint16_t fillIntv, uint16_t emptyIntv, const uint8_t *maxPercentPtr, const uint8_t *minPercentPtr = nullptr)
921 {
922 _isNonInvertingLogicAnim = true;
923 _AnimInitLogicIsInverted = false;
924 _animRenderDirIsReversed = false;
925 _mirrorHalfRangeDir = true;
926 _sigPtr2 = maxPercentPtr;
927 _sigPtr1 = minPercentPtr;
928 _usePtr = true;
929 _mapMinMaxTrackerFromPtr(minPercentPtr, maxPercentPtr, (_segsNum / 2) - 1, 0);
930 _updateIntv1 = fillIntv; // Starting interval
931 _updateIntv2 = _updateIntv1; // Fill intv
932 _updateIntv3 = emptyIntv; // Empty intv
933 _sequenceState = 0;
934 _isRunning = true;
935 _currentFunc = &SBK_BarMeterAnimations::_bounceFillHalfRangeMirrorCenter;
936 return *this;
937 }
938
947 SBK_BarMeterAnimations &bounceFillFromEdgesIntv(uint16_t fillIntv = 25, uint16_t emptyIntv = 25, uint8_t maxPercent = 100, uint8_t minPercent = 0)
948 {
949 _isNonInvertingLogicAnim = true;
950 _AnimInitLogicIsInverted = false;
951 _animRenderDirIsReversed = false;
952 _mirrorHalfRangeDir = true;
953 _usePtr = false;
954 _sigPtr2 = nullptr;
955 _sigPtr1 = nullptr;
956 _mapMinMaxTracker(minPercent, maxPercent, (_segsNum / 2) - 1, 0);
957 _updateIntv1 = fillIntv; // Starting interval
958 _updateIntv2 = _updateIntv1; // Fill intv
959 _updateIntv3 = emptyIntv; // Empty intv
960 _sequenceState = 0;
961 _isRunning = true;
962 _currentFunc = &SBK_BarMeterAnimations::_bounceFillHalfRangeMirrorCenter;
963 return *this;
964 }
965
971 SBK_BarMeterAnimations &beatPulse(uint8_t *bpmPtr) // bouncing from bottom (maybe like a volume meter with the music)
972 {
973 _sigPtr1 = bpmPtr;
974 _param2 = min(35 * (_segsNum - 1) / 100, 255); // MIN_BASE_LEVEL
975 _param2 = min(67 * (_segsNum - 1) / 100, 255); // MIN_PEAK_LEVEL
976 _param4 = 150; // PEAK_HOLD_TIME
977 _usePtr = true;
978 _isNonInvertingLogicAnim = true;
979 _AnimInitLogicIsInverted = false;
980 _animRenderDirIsReversed = false;
981
982 _isRunning = true;
983 _currentFunc = &SBK_BarMeterAnimations::_beatPulse;
984 _init - true;
985 return *this;
986 }
987
993 SBK_BarMeterAnimations &beatPulse(uint8_t bpm = 116) // bouncing from bottom (maybe like a volume meter with the music)
994 {
995 _param1 = max(1, bpm);
996 _param2 = min(35 * (_segsNum - 1) / 100, 255); // MIN_BASE_LEVEL
997 _param3 = min(67 * (_segsNum - 1) / 100, 255); // MIN_PEAK_LEVEL
998 _param4 = 150; // PEAK_HOLD_TIME
999 _sigPtr1 = nullptr;
1000 _usePtr = false;
1001 _isNonInvertingLogicAnim = true;
1002 _AnimInitLogicIsInverted = false;
1003 _animRenderDirIsReversed = false;
1004
1005 _isRunning = true;
1006 _currentFunc = &SBK_BarMeterAnimations::_beatPulse;
1007 _init = true;
1008 return *this;
1009 }
1010
1019 SBK_BarMeterAnimations &explodingBlocks(uint16_t intv = 50, uint8_t blockLength = 2, uint8_t blockSpacing = 1, uint8_t numBlocks = 0)
1020 {
1021 _updateIntv1 = max(5, intv);
1022 _param1 = blockLength;
1023 _param2 = blockSpacing;
1024 _param3 = numBlocks;
1025 _param4 = ((_segsNum / 2) / (blockLength + blockSpacing)) + 2;
1026 _param4 = constrain(_param4, 2, 32);
1027
1028 _isNonInvertingLogicAnim = false;
1029 _animRenderDirIsReversed = false;
1030 _AnimInitLogicIsInverted = true;
1031 _isRunning = true;
1032 _currentFunc = &SBK_BarMeterAnimations::_mirrorBlocks;
1033 return *this;
1034 }
1035
1044 SBK_BarMeterAnimations &collidingBlocks(uint16_t intv = 50, uint8_t blockLength = 2, uint8_t blockSpacing = 1, uint8_t numBlocks = 0)
1045 {
1046
1047 _updateIntv1 = max(5, intv);
1048 _param1 = blockLength;
1049 _param2 = blockSpacing;
1050 _param3 = numBlocks;
1051 _param4 = ((_segsNum / 2) / (blockLength + blockSpacing)) + 2;
1052 _param4 = constrain(_param4, 2, 32);
1053
1054 _isNonInvertingLogicAnim = false;
1055 _animRenderDirIsReversed = false;
1056 _AnimInitLogicIsInverted = false;
1057 _isRunning = true;
1058 _currentFunc = &SBK_BarMeterAnimations::_mirrorBlocks;
1059 return *this;
1060 }
1061
1070 SBK_BarMeterAnimations &scrollingUpBlocks(uint16_t intv = 50, uint8_t blockLength = 2, uint8_t blockSpacing = 1, uint8_t numBlocks = 0)
1071 {
1072 _updateIntv1 = max(5, intv);
1073 _param1 = blockLength;
1074 _param2 = blockSpacing;
1075 _param3 = numBlocks;
1076 _param4 = (_segsNum / (blockLength + blockSpacing)) + 2;
1077 _param4 = constrain(_param4, 2, 64);
1078
1079 _isNonInvertingLogicAnim = false;
1080 _animRenderDirIsReversed = false;
1081 _AnimInitLogicIsInverted = false;
1082 _isRunning = true;
1083 _currentFunc = &SBK_BarMeterAnimations::_scrollingBlocks;
1084 return *this;
1085 }
1086
1095 SBK_BarMeterAnimations &scrollingDownBlocks(uint16_t intv = 50, uint8_t blockLength = 2, uint8_t blockSpacing = 1, uint8_t numBlocks = 0)
1096 {
1097 _updateIntv1 = max(5, intv);
1098 _param1 = blockLength;
1099 _param2 = blockSpacing;
1100 _param3 = numBlocks;
1101 _param4 = (_segsNum / (blockLength + blockSpacing)) + 2;
1102 _param4 = constrain(_param4, 2, 64);
1103
1104 _isNonInvertingLogicAnim = false;
1105 _animRenderDirIsReversed = false;
1106 _AnimInitLogicIsInverted = true;
1107 _isRunning = true;
1108 _currentFunc = &SBK_BarMeterAnimations::_scrollingBlocks;
1109 return *this;
1110 }
1111
1119 SBK_BarMeterAnimations &downStackingBlocks(uint16_t intv = 50, uint8_t blockLength = 1, uint8_t blockSpacing = 0)
1120 {
1121 _isNonInvertingLogicAnim = false;
1122 _animRenderDirIsReversed = false;
1123 _AnimInitLogicIsInverted = false;
1124 _updateIntv1 = max(5, intv);
1125 _param1 = blockLength;
1126 _param2 = blockSpacing;
1127 _param3 = 0; // requested number of blocks
1128 _param4 = 1; // aka maximum blocks number
1129 if (_init)
1130 _emittingBlocksEnabled = true;
1131 _isRunning = true;
1132 _currentFunc = &SBK_BarMeterAnimations::_stackingBlocks;
1133 return *this;
1134 }
1135
1143 SBK_BarMeterAnimations &upUnstackingBlocks(uint16_t intv = 50, uint8_t blockLength = 1, uint8_t blockSpacing = 0)
1144 {
1145 _isNonInvertingLogicAnim = false;
1146 _animRenderDirIsReversed = false;
1147 _AnimInitLogicIsInverted = true;
1148 _updateIntv1 = max(5, intv);
1149 _param1 = blockLength;
1150 _param2 = blockSpacing;
1151 _param3 = 0; // aka requested number of blocks
1152 _param4 = 1; // aka maximum blocks number
1153 if (_init)
1154 _emittingBlocksEnabled = true;
1155 _isRunning = true;
1156 _currentFunc = &SBK_BarMeterAnimations::_stackingBlocks;
1157 return *this;
1158 }
1159
1167 SBK_BarMeterAnimations &upStackingBlocks(uint16_t intv = 50, uint8_t blockLength = 1, uint8_t blockSpacing = 0)
1168 {
1169 _isNonInvertingLogicAnim = false;
1170 _animRenderDirIsReversed = true;
1171 _AnimInitLogicIsInverted = false;
1172 _updateIntv1 = max(5, intv);
1173 _param1 = blockLength;
1174 _param2 = blockSpacing;
1175 _param3 = 0; // aka requested number of blocks
1176 _param4 = 1; // aka maximum blocks number
1177 if (_init)
1178 _emittingBlocksEnabled = true;
1179 _isRunning = true;
1180 _currentFunc = &SBK_BarMeterAnimations::_stackingBlocks;
1181 return *this;
1182 }
1183
1191 SBK_BarMeterAnimations &downUnstackingBlocks(uint16_t intv = 50, uint8_t blockLength = 1, uint8_t blockSpacing = 0)
1192 {
1193 _isNonInvertingLogicAnim = false;
1194 _animRenderDirIsReversed = true;
1195 _AnimInitLogicIsInverted = true;
1196 _updateIntv1 = max(5, intv);
1197 _param1 = blockLength;
1198 _param2 = blockSpacing;
1199 _param3 = 0; // aka requested number of blocks
1200 _param4 = 1; // aka maximum blocks number
1201 if (_init)
1202 _emittingBlocksEnabled = true;
1203 _isRunning = true;
1204 _currentFunc = &SBK_BarMeterAnimations::_stackingBlocks;
1205 return *this;
1206 }
1207
1218 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)
1219 {
1220
1221 _sigPtr1 = sigPtr;
1222 _updateIntv1 = max(10, updateIntv);
1223 _minMap = max(0, minMap);
1224 _maxMap = max(0, maxMap);
1225 _correctSwapOrEqualMinMax(_minMap, _maxMap);
1226 _param1 = constrain(smoothingFactor, 0, 100);
1227 _updateIntv2 = samplingIntv;
1228
1229 _isNonInvertingLogicAnim = true;
1230 _animRenderDirIsReversed = false;
1231 _AnimInitLogicIsInverted = false;
1232 _isRunning = true;
1233 _init = true;
1234 _currentFunc = &SBK_BarMeterAnimations::_followSignalSmooth;
1235 return *this;
1236 }
1237
1248 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)
1249 {
1250 _sigPtr1 = sigPtr;
1251 _updateIntv1 = max(10, updateIntv);
1252 _minMap = max(0, minMap);
1253 _maxMap = max(0, maxMap);
1254 _correctSwapOrEqualMinMax(_minMap, _maxMap);
1255 _param1 = constrain(smoothingFactor, 0, 100);
1256 _updateIntv2 = samplingIntv;
1257
1258 _isNonInvertingLogicAnim = true;
1259 _animRenderDirIsReversed = false;
1260 _AnimInitLogicIsInverted = false;
1261 _isRunning = true;
1262 _init = true;
1263 _currentFunc = &SBK_BarMeterAnimations::_followSignalWithPointer;
1264 return *this;
1265 }
1266
1279 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)
1280 {
1281 _sigPtr1 = sig1Ptr;
1282 _updateIntv1 = max(10, updateIntv);
1283 _sigPtr2 = sig2Ptr ? sig2Ptr : sig1Ptr;
1284 _minMap = max(0, minMap);
1285 _maxMap = max(0, maxMap);
1286 _correctSwapOrEqualMinMax(_minMap, _maxMap);
1287 _param1 = constrain(smoothingFactor, 0, 100);
1288 _updateIntv2 = samplingIntv;
1289
1290 _isNonInvertingLogicAnim = false;
1291 _animRenderDirIsReversed = false;
1292 _AnimInitLogicIsInverted = false;
1293 //_mirrorHalfRangeDir = false;
1294 _isRunning = true;
1295 _init = true;
1296 _currentFunc = &SBK_BarMeterAnimations::_followDualSignalCenterMirror;
1297 return *this;
1298 }
1299
1315 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)
1316 {
1317 _sigPtr1 = sig1Ptr;
1318 _updateIntv1 = max(10, updateIntv);
1319 _sigPtr2 = sig2Ptr ? sig2Ptr : sig1Ptr;
1320 _minMap = max(0, minMap);
1321 _maxMap = max(0, maxMap);
1322 _correctSwapOrEqualMinMax(_minMap, _maxMap);
1323 _param1 = constrain(smoothingFactor, 0, 100);
1324 _updateIntv2 = samplingIntv;
1325
1326 _isNonInvertingLogicAnim = false;
1327 _animRenderDirIsReversed = true;
1328 _AnimInitLogicIsInverted = false;
1329 //_mirrorHalfRangeDir = true;
1330 _isRunning = true;
1331 _init = true;
1332 _currentFunc = &SBK_BarMeterAnimations::_followDualSignalCenterMirror;
1333 return *this;
1334 }
1335
1351 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)
1352 {
1353
1354 _sigPtr1 = sigPtr;
1355 _updateIntv3 = max(20, peakHoldTime);
1356 _updateIntv1 = max(10, updateIntv);
1357 _minMap = max(0, minMap);
1358 _maxMap = max(0, maxMap);
1359 _correctSwapOrEqualMinMax(_minMap, _maxMap);
1360 _param1 = constrain(smoothingFactor, 0, 100);
1361 _updateIntv2 = samplingIntv;
1362
1363 _isNonInvertingLogicAnim = true;
1364 _animRenderDirIsReversed = false;
1365 _AnimInitLogicIsInverted = false;
1366 _isRunning = true;
1367 _init = true;
1368 _currentFunc = &SBK_BarMeterAnimations::_followSignalFloatingPeak;
1369 return *this;
1370 }
1371
1377 SBK_BarMeterAnimations &randomFill(uint16_t interval = 30)
1378 {
1379 _updateIntv1 = interval;
1380
1381 _isNonInvertingLogicAnim = false;
1382 _animRenderDirIsReversed = false;
1383 _AnimInitLogicIsInverted = false;
1384 _isRunning = true;
1385 _currentFunc = &SBK_BarMeterAnimations::_randomPixelUpdater;
1386 _init = true;
1387 return *this;
1388 }
1389
1395 SBK_BarMeterAnimations &randomEmpty(uint16_t interval = 30)
1396 {
1397 _updateIntv1 = interval;
1398
1399 _isNonInvertingLogicAnim = false;
1400 _animRenderDirIsReversed = false;
1401 _AnimInitLogicIsInverted = true;
1402 _isRunning = true;
1403 _currentFunc = &SBK_BarMeterAnimations::_randomPixelUpdater;
1404 _init = true;
1405 return *this;
1406 }
1407
1408protected:
1409 BarMeterT &_barMeter;
1410 uint8_t _segsNum = 0;
1411
1412 // Active animation update function
1413 using AnimUpdateFn = bool (SBK_BarMeterAnimations::*)();
1414 AnimUpdateFn _currentFunc = nullptr;
1415
1416 // Control flags
1417 bool _init = true; // true = init function
1418 bool _isRunning = false;
1419 bool _isPaused = false;
1420 bool _loop = false;
1421 bool _isLoopingNow = false;
1422 bool _AnimInitLogicIsInverted = false;
1423 bool _animRenderLogicIsInverted = false;
1424 bool _prevAnimRenderLogic = false;
1425 bool _isNonInvertingLogicAnim = false;
1426 bool _mirrorHalfRangeDir = false;
1427 bool _animLogicSet = false;
1428 bool _skipPending = false;
1429 bool _animRenderDirIsReversed = false;
1430 bool _AnimInitDirIsReversed = false;
1431 bool _animDirSet = false;
1432 bool _usePtr = false;
1433 bool _emittingBlocksEnabled = true;
1434
1435 // Time trackings
1436 uint32_t _currentTime = 0;
1437 uint32_t _lastUpdate1 = 0, _lastUpdate2 = 0, _lastUpdate3 = 0;
1438
1439 // Animations update interval trackers
1440 uint16_t _updateIntv1 = 10, _updateIntv2 = 10, _updateIntv3 = 10;
1441
1442 // Animations trackers
1443 uint8_t _sequenceState = 0;
1444 int8_t _ledTracker1 = 0, _ledTracker2 = 0, _ledTracker3 = 0;
1445 int8_t _minTracker = 0, _maxTracker = 1;
1446 uint8_t _param1 = 0, _param2 = 0, _param3 = 0, _param4 = 0, _param5 = 0;
1447 uint16_t _smoothedValue1 = 0, _smoothedValue2 = 0;
1448 uint16_t _minMap = 0, _maxMap = 1023;
1449 uint8_t _counter1 = 0, _counter2 = 0;
1450 // Live signals trackers
1451 const uint16_t *_sigPtr1 = nullptr, *_sigPtr2 = nullptr;
1452 // Blocks related helpers
1453 struct Block
1454 {
1455 Block() : position(-1), active(false) {}
1456 int8_t position;
1457 bool active;
1458 };
1459 Block *_blocks = nullptr;
1460
1461 // Animation helpers
1462 static inline void _normalizePercentRange(uint8_t &minP, uint8_t &maxP)
1463 {
1464 minP = constrain(minP, 0, 100);
1465 maxP = constrain(maxP, 0, 100);
1466 if (minP > maxP)
1467 {
1468 uint8_t t = maxP;
1469 maxP = minP;
1470 minP = t;
1471 }
1472 if (minP == maxP)
1473 {
1474 if (maxP < 100)
1475 maxP = minP + 1;
1476 else
1477 minP--;
1478 }
1479 }
1480 inline void _mapMinMaxTrackerFromPtr(uint16_t *minPercPtr, uint16_t *maxPercPtr, int8_t minR, uint8_t maxR)
1481 {
1482 if (!_usePtr)
1483 return;
1484 uint8_t minP = minPercPtr ? *minPercPtr : 0;
1485 uint8_t maxP = maxPercPtr ? *maxPercPtr : 100;
1486 _normalizePercentRange(minP, maxP);
1487 _minTracker = map(minP, 0, 100, minR, maxR);
1488 _maxTracker = map(maxP, 0, 100, minR, maxR);
1489 }
1490 inline void _mapMinMaxTracker(uint8_t minP, uint8_t maxP, uint8_t minR, uint8_t maxR)
1491 {
1492 _normalizePercentRange(minP, maxP);
1493 _minTracker = map(minP, 0, 100, minR, maxR);
1494 _maxTracker = map(maxP, 0, 100, minR, maxR);
1495 }
1496 inline uint8_t _corrPixelToDir(uint8_t pixel)
1497 {
1498 return _animRenderDirIsReversed ? (_segsNum - 1) - pixel : pixel;
1499 }
1500 inline uint8_t _corrPixelToDirForHalfRange(uint8_t pixel)
1501 {
1502 if (!_mirrorHalfRangeDir)
1503 return pixel;
1504
1505 const uint8_t half = _segsNum / 2;
1506 return abs((half - 1) - pixel);
1507 }
1508 static inline void _correctSwapOrEqualMinMax(uint16_t &minVal, uint16_t &maxVal)
1509 {
1510 if (minVal > maxVal)
1511 {
1512 uint16_t temp = maxVal;
1513 maxVal = minVal;
1514 minVal = temp;
1515 }
1516 if (minVal == maxVal) // avoid division by zero in map()
1517 if (maxVal < 65, 535)
1518 maxVal = minVal + 1;
1519 else
1520 minVal--;
1521 }
1522 static inline uint8_t _getMappedSignal(uint16_t sig, uint16_t minM, uint16_t maxM, uint8_t minR, uint8_t maxR)
1523 {
1524 uint8_t mapped = map(sig, minM, maxM, minR, maxR);
1525 return constrain(mapped, minR, maxR); // avoid edge overshoot
1526 }
1527
1528 // Animation update functions
1529
1530 bool _setAllOn()
1531 {
1532 _init = false;
1533 for (uint8_t i = 0; i < _segsNum; i++)
1534 _barMeter.setPixel(i, true);
1535 return true;
1536 }
1537 bool _setAllOff()
1538 {
1539 _init = false;
1540 _barMeter.clear();
1541 return true;
1542 }
1543
1544 bool _fillOrEmpty()
1545 {
1546 _mapMinMaxTrackerFromPtr(_sigPtr1, _sigPtr2, 0, _segsNum - 1);
1547
1548 if (_init)
1549 {
1550 _init = false;
1551
1552 if (!_animLogicSet)
1553 _prevAnimRenderLogic = _animRenderLogicIsInverted = _AnimInitLogicIsInverted;
1554
1555 // Pre-fill for emptyDown mode
1556 if (_animRenderLogicIsInverted)
1557 {
1558 _ledTracker1 = _maxTracker;
1559 for (uint8_t i = 0; i < _segsNum; ++i)
1560 {
1561 if (i <= _maxTracker)
1562 _barMeter.setPixel(_corrPixelToDir(i), true);
1563 else
1564 _barMeter.setPixel(_corrPixelToDir(i), false);
1565 }
1566 }
1567 else // Pre-fill fillUp, light up to _minTracker inclusively
1568 {
1569 _ledTracker1 = _minTracker;
1570 for (uint8_t i = 0; i < _segsNum; ++i)
1571 {
1572 if (i <= _minTracker)
1573 _barMeter.setPixel(_corrPixelToDir(i), true);
1574 else
1575 _barMeter.setPixel(_corrPixelToDir(i), false);
1576 }
1577 }
1578 return false;
1579 }
1580
1581 if (_animRenderLogicIsInverted != _prevAnimRenderLogic)
1582 {
1583 _prevAnimRenderLogic = _animRenderLogicIsInverted;
1584
1585 // Clamp if inverted logic and tracker is beyond maxTracker
1586 if (_animRenderLogicIsInverted && _ledTracker1 > _maxTracker)
1587 _ledTracker1 = _maxTracker;
1588
1589 // Clamp in normal logic mode
1590 if (!_animRenderLogicIsInverted && _ledTracker1 < _minTracker)
1591 _ledTracker1 = _minTracker;
1592 }
1593
1594 if (_currentTime - _lastUpdate1 >= _updateIntv1)
1595 {
1596 _lastUpdate1 = _currentTime;
1597
1598 if (_animRenderLogicIsInverted)
1599 {
1600 if (_ledTracker1 >= _minTracker && _ledTracker1 >= _minTracker)
1601 {
1602 _barMeter.setPixel(_corrPixelToDir(_ledTracker1), false);
1603 _ledTracker1--;
1604 }
1605 else
1606 return true; // now done!
1607 }
1608 else
1609 {
1610 if (_ledTracker1 <= _maxTracker && _ledTracker1 < _segsNum)
1611 {
1612 _barMeter.setPixel(_corrPixelToDir(_ledTracker1), true);
1613 _ledTracker1++;
1614 }
1615 else
1616 return true; // done lighting up all
1617 }
1618 }
1619 return false;
1620 }
1621
1622 bool _bounceFill()
1623 {
1624 switch (_sequenceState)
1625 {
1626 case 0: // Fill Up
1627
1628 _skipPending = false;
1629 if (_fillOrEmpty())
1630 {
1631 _animRenderLogicIsInverted = !_AnimInitLogicIsInverted;
1632 _sequenceState = 1;
1633 _updateIntv1 = _updateIntv3; // Empty update interval
1634 }
1635 return false;
1636
1637 case 1: // Fill Down
1638
1639 _skipPending = true;
1640 if (_fillOrEmpty())
1641 {
1642 _animRenderLogicIsInverted = _AnimInitLogicIsInverted;
1643 _sequenceState = 0;
1644 _updateIntv1 = _updateIntv2; // Fill up update interval
1645 return true;
1646 }
1647 return false;
1648 }
1649
1650 return true; // Shouldn’t reach here
1651 }
1652
1653 bool _fillFromOrEmptyToCenter()
1654 {
1655 const uint8_t center = _segsNum / 2;
1656 _mapMinMaxTrackerFromPtr(_sigPtr1, _sigPtr2, center - 1, 0);
1657
1658 if (_init)
1659 {
1660 _init = false;
1661
1662 if (!_animLogicSet)
1663 _prevAnimRenderLogic = _animRenderLogicIsInverted = _AnimInitLogicIsInverted;
1664
1665 if (_animRenderLogicIsInverted)
1666 {
1667 _ledTracker1 = _maxTracker;
1668
1669 // Start fully lit
1670 for (uint8_t i = 0; i < center; ++i)
1671 {
1672 if (i >= _maxTracker)
1673 {
1674 _barMeter.setPixel(_corrPixelToDirForHalfRange(i), true);
1675 _barMeter.setPixel((_segsNum - 1) - _corrPixelToDirForHalfRange(i), true);
1676 }
1677 else
1678 {
1679 _barMeter.setPixel(_corrPixelToDirForHalfRange(i), false);
1680 _barMeter.setPixel((_segsNum - 1) - _corrPixelToDirForHalfRange(i), false);
1681 }
1682 }
1683 }
1684 else
1685 {
1686
1687 _ledTracker1 = _minTracker;
1688
1689 // Start with minimum fill
1690 for (uint8_t i = 0; i < center; ++i)
1691 {
1692 if (i > _minTracker)
1693 {
1694 _barMeter.setPixel(_corrPixelToDirForHalfRange(i), true);
1695 _barMeter.setPixel((_segsNum - 1) - _corrPixelToDirForHalfRange(i), true);
1696 }
1697 else
1698 {
1699 _barMeter.setPixel(_corrPixelToDirForHalfRange(i), false);
1700 _barMeter.setPixel((_segsNum - 1) - _corrPixelToDirForHalfRange(i), false);
1701 }
1702 }
1703 }
1704 return false;
1705 }
1706
1707 if (_currentTime - _lastUpdate1 >= _updateIntv1)
1708 {
1709 _lastUpdate1 = _currentTime;
1710 if (!_animRenderLogicIsInverted)
1711 {
1712 if (_ledTracker1 >= _maxTracker && _ledTracker1 >= 0)
1713 {
1714 _barMeter.setPixel(_corrPixelToDirForHalfRange(_ledTracker1), true);
1715 _barMeter.setPixel((_segsNum - 1) - _corrPixelToDirForHalfRange(_ledTracker1), true);
1716 _ledTracker1--;
1717 }
1718 else
1719 return true; // finished filling
1720 }
1721 else
1722 {
1723 if (_ledTracker1 <= _minTracker && _ledTracker1 < center)
1724 {
1725 _barMeter.setPixel(_corrPixelToDirForHalfRange(_ledTracker1), false);
1726 _barMeter.setPixel((_segsNum - 1) - _corrPixelToDirForHalfRange(_ledTracker1), false);
1727 _ledTracker1++;
1728 }
1729 else
1730 return true; // finished clearing
1731 }
1732 }
1733
1734 return false;
1735 }
1736
1737 bool _bounceFillHalfRangeMirrorCenter()
1738 {
1739 switch (_sequenceState)
1740 {
1741 case 0: // Fill from center
1742
1743 _skipPending = true;
1744 if (_fillFromOrEmptyToCenter())
1745 {
1746 _animRenderLogicIsInverted = !_AnimInitLogicIsInverted;
1747 _updateIntv1 = _updateIntv3; // Empty update interval
1748 _sequenceState = 1;
1749 }
1750 return false;
1751
1752 case 1: // Empty to center
1753
1754 _skipPending = false;
1755 if (_fillFromOrEmptyToCenter())
1756 {
1757 _animRenderLogicIsInverted = _AnimInitLogicIsInverted;
1758 _updateIntv1 = _updateIntv2; // Empty update interval
1759 _sequenceState = 0;
1760 return true; // one full bounce cycle completed
1761 }
1762 return false;
1763 }
1764
1765 return true; // should not happen
1766 }
1767
1768#define bpm _param1
1769#define MIN_BASE_LEVEL _param2
1770#define MIN_PEAK_LEVEL _param3
1771#define PEAK_HOLD_TIME _param4
1772#define bpmPtr _sigPtr1
1773#define currentLevel _ledTracker1
1774#define peakLevel _ledTracker2
1775#define beat _updateIntv1
1776#define lastBeat _lastUpdate1
1777#define lastRandomUpdate _lastUpdate2
1778#define prevPeakUpdate _lastUpdate3
1779 bool _beatPulse() // bouncing from bottom (maybe like a volume meter with the music)
1780 {
1781 static int8_t randomOffset;
1782 static bool isPeak = false;
1783
1784 if (_init)
1785 {
1786 _init = false;
1787
1788 if (!_animLogicSet)
1789 _prevAnimRenderLogic = _animRenderLogicIsInverted = _AnimInitLogicIsInverted;
1790
1791 _setAllOff();
1792 currentLevel = MIN_BASE_LEVEL;
1793 peakLevel = MIN_PEAK_LEVEL;
1794 }
1795
1796 // Get bpm from pointer if needed
1797 if (_usePtr)
1798 {
1799 bpm = bpmPtr ? max(1, *bpmPtr) : 116;
1800 }
1801 beat = 60000 / bpm;
1802
1803 // Update at bpm pulse
1804 if (_currentTime - lastBeat >= beat)
1805 {
1806 lastBeat = _currentTime;
1807 isPeak = !isPeak; // Toggle between peak and base level
1808 }
1809
1810 // Smooth transition between base and peak levels
1811 if (isPeak && currentLevel <= MIN_PEAK_LEVEL)
1812 {
1813 currentLevel += random(3, 5); // Gradual increase
1814 }
1815 else if (!isPeak && currentLevel >= MIN_BASE_LEVEL)
1816 {
1817 currentLevel -= random(0, 4); // Gradual decrease
1818 }
1819
1820 // Randomized additional LEDs on top at a random interval
1821 if (_currentTime - lastRandomUpdate >= uint16_t(random(50, 300)))
1822 {
1823 lastRandomUpdate = _currentTime;
1824 randomOffset = random(-4, 4); // 0 to 5 extra LEDs
1825 }
1826
1827 uint8_t finalLevel = currentLevel + randomOffset;
1828 finalLevel = constrain(finalLevel, 0, _segsNum);
1829 if (finalLevel > _segsNum)
1830 finalLevel = _segsNum;
1831 if (finalLevel < 0)
1832 finalLevel = 0;
1833
1834 // Update peak level
1835 if (finalLevel > peakLevel)
1836 {
1837 peakLevel = min(finalLevel, _segsNum - 1);
1838 prevPeakUpdate = _currentTime; // Reset decay timer
1839 }
1840 else if (_currentTime - prevPeakUpdate >= PEAK_HOLD_TIME && peakLevel > finalLevel)
1841 {
1842 peakLevel--; // Slowly drop peak
1843 prevPeakUpdate = _currentTime; // Reset decay timer
1844 }
1845
1846 // Update LED states
1847 for (uint8_t i = 0; i < _segsNum; i++)
1848 {
1849 _barMeter.setPixel(_corrPixelToDir(i), (i < finalLevel));
1850 }
1851
1852 // Ensure peak LED stays on
1853 if (peakLevel < _segsNum)
1854 {
1855 _barMeter.setPixel(_corrPixelToDir(peakLevel), true);
1856 }
1857 return false; // Continuous pulse animation, never auto-terminates
1858 }
1859#undef bpm
1860#undef MIN_BASE_LEVEL
1861#undef MIN_PEAK_LEVEL
1862#undef PEAK_HOLD_TIME
1863#undef bpmPtr
1864#undef currentLevel
1865#undef peakLevel
1866#undef beat
1867#undef lastBeat
1868#undef lastRandomUpdate
1869#undef prevPeakUpdate
1870
1871#define blockLength _param1
1872#define blockSpacing _param2
1873#define requestedNumBlocks _param3
1874#define maxBlocks _param4
1875#define emitIndex _param5
1876#define emittedBlocksCount _counter1
1877#define emitCooldown _counter2
1878 void _emitBlock(int8_t pos)
1879 {
1880 if (!_blocks || maxBlocks < 1)
1881 return;
1882
1883 if (emitCooldown > 0)
1884 {
1885 emitCooldown--;
1886 return;
1887 }
1888
1889 if (requestedNumBlocks > 0 && emittedBlocksCount >= requestedNumBlocks)
1890 return;
1891
1892 for (uint8_t i = 0; i < maxBlocks; ++i)
1893 {
1894 uint8_t idx = (emitIndex + i) % maxBlocks;
1895 Block &b = _blocks[idx];
1896
1897 if (!b.active)
1898 {
1899 b.position = pos;
1900 b.active = true;
1901 emittedBlocksCount++;
1902
1903 emitCooldown = blockLength + blockSpacing - 1;
1904 emitIndex = (emitIndex + 1) % maxBlocks;
1905 return;
1906 }
1907 }
1908 }
1909
1910 static inline int8_t _calculateSwtichPostion(int8_t pos, uint8_t bockL, uint8_t range)
1911 {
1912 // Compute switched block head position
1913 return (range - 1) - pos + (bockL - 1);
1914 }
1915
1916 int8_t _calculateSwitchedEmitTickCounter(uint8_t range)
1917 {
1918 const int8_t emitInterval = blockLength + blockSpacing;
1919
1920 int8_t closestSwp = -127; // Track the nearest visible block from new side
1921
1922 for (uint8_t i = 0; i < maxBlocks; ++i)
1923 {
1924 Block &b = _blocks[i];
1925
1926 if (!b.active)
1927 continue;
1928
1929 int8_t p = b.position; // head position before switching
1930
1931 // Step 1: compute switched block head position
1932 int8_t swp = _calculateSwtichPostion(p, blockLength, range);
1933
1934 b.position = swp;
1935
1936 // Step 2: if out of visible range, deactivate block
1937 if (swp < 0)
1938 {
1939 b.active = false;
1940 continue;
1941 }
1942
1943 // // Step 3: Keep the first (closest to emit side) visible block
1944 if (closestSwp == -127 || swp < closestSwp)
1945 closestSwp = swp;
1946 }
1947
1948 if (closestSwp >= 0)
1949 return (emitInterval - 1) - closestSwp;
1950 else
1951 return 0; // fallback if no visible block found
1952 }
1953
1954 bool _mirrorBlocks()
1955 {
1956 const uint8_t center = _segsNum / 2;
1957
1958 if (_init)
1959 {
1960 _init = false;
1961 _emittingBlocksEnabled = true;
1962
1963 emittedBlocksCount = 0;
1964 emitCooldown = 0;
1965
1966 if (!_animLogicSet)
1967 _prevAnimRenderLogic = _animRenderLogicIsInverted = _AnimInitLogicIsInverted;
1968
1969 if (_blocks)
1970 {
1971 delete[] _blocks;
1972 _blocks = nullptr;
1973 }
1974 _blocks = new Block[maxBlocks]{};
1975 return false;
1976 }
1977
1978 if (_prevAnimRenderLogic != _animRenderLogicIsInverted)
1979 {
1980 emitCooldown = _calculateSwitchedEmitTickCounter(_segsNum / 2);
1981 emittedBlocksCount = requestedNumBlocks ;
1982 _prevAnimRenderLogic = _animRenderLogicIsInverted;
1983 }
1984
1985 if (_currentTime - _lastUpdate1 >= _updateIntv1)
1986 {
1987 _lastUpdate1 = _currentTime;
1988 // _frameCounter++;
1989 _barMeter.clear();
1990
1991 if ((requestedNumBlocks == 0 || emittedBlocksCount < requestedNumBlocks) && _emittingBlocksEnabled)
1992 _emitBlock(-1); // Because the new block will move to 0 postion in first iteration
1993
1994 for (uint8_t i = 0; i < maxBlocks; ++i)
1995 {
1996 Block &b = _blocks[i];
1997 if (!b.active)
1998 continue;
1999
2000 b.position++;
2001
2002 // Only render to lower half (0 to _centerTracker - 1)
2003 uint16_t clamped = max(0, (int16_t)b.position + 1);
2004 uint8_t pixelsVisible = min(blockLength, (uint8_t)clamped);
2005 for (uint8_t j = 0; j < pixelsVisible; ++j)
2006 {
2007 int8_t pos = _animRenderLogicIsInverted ? center - 1 - b.position : b.position;
2008 int8_t tailOffset = _animRenderLogicIsInverted ? pos + j : pos - j;
2009
2010 if (tailOffset < 0)
2011 continue;
2012
2013 int8_t idx = tailOffset;
2014 if (idx >= center)
2015 continue;
2016
2017 int8_t mirrorIdx = _segsNum - 1 - idx;
2018
2019 if (idx >= 0 && idx < _segsNum)
2020 {
2021 _barMeter.setPixel(idx, true);
2022 }
2023 if (mirrorIdx != idx && mirrorIdx >= 0 && mirrorIdx < _segsNum)
2024 {
2025 _barMeter.setPixel(mirrorIdx, true);
2026 }
2027 }
2028
2029 // Deactivate block if head has passed the visual range
2030 if (b.position >= center - 1 + blockLength)
2031 b.active = false;
2032 }
2033 }
2034 // animation as ended if emmit is disable and all blocks are cleared
2035 if ((requestedNumBlocks > 0 && emittedBlocksCount >= requestedNumBlocks) || !_emittingBlocksEnabled)
2036 {
2037 bool anyActive = false;
2038 for (uint8_t i = 0; i < maxBlocks; ++i)
2039 {
2040 if (_blocks[i].active)
2041 {
2042 anyActive = true;
2043 break;
2044 }
2045 }
2046 if (!anyActive)
2047 return true; // animation complete
2048 }
2049
2050 return false;
2051 }
2052
2053 bool _scrollingBlocks()
2054 {
2055 if (_init)
2056 {
2057 _init = false;
2058 _emittingBlocksEnabled = true;
2059
2060 emittedBlocksCount = 0;
2061 emitCooldown = 0;
2062
2063 if (!_animLogicSet)
2064 _prevAnimRenderLogic = _animRenderLogicIsInverted = _AnimInitLogicIsInverted;
2065
2066 if (_blocks)
2067 {
2068 delete[] _blocks;
2069 _blocks = nullptr;
2070 }
2071 _blocks = new Block[maxBlocks]{};
2072 return false;
2073 }
2074
2075 if (_prevAnimRenderLogic != _animRenderLogicIsInverted)
2076 {
2077 emitCooldown = _calculateSwitchedEmitTickCounter(_segsNum);
2078 emittedBlocksCount = requestedNumBlocks ;
2079 _prevAnimRenderLogic = _animRenderLogicIsInverted;
2080
2081 }
2082
2083 if (_currentTime - _lastUpdate1 >= _updateIntv1)
2084 {
2085 _lastUpdate1 = _currentTime;
2086 _setAllOff();
2087
2088 if ((requestedNumBlocks == 0 || emittedBlocksCount < requestedNumBlocks) && _emittingBlocksEnabled)
2089 _emitBlock(-1); // Start fully off-screen left
2090
2091 for (uint8_t i = 0; i < maxBlocks; ++i)
2092 {
2093 Block &b = _blocks[i];
2094 if (!b.active)
2095 continue;
2096
2097 b.position++;
2098
2099 for (uint8_t j = 0; j < blockLength; ++j)
2100 {
2101 int8_t pos = _animRenderLogicIsInverted ? (_segsNum - 1) - b.position : b.position;
2102 int8_t tailOffset = _animRenderLogicIsInverted ? pos + j : pos - j;
2103
2104 if (tailOffset < 0)
2105 continue;
2106
2107 int8_t idx = tailOffset;
2108 if (idx >= _segsNum)
2109 continue;
2110
2111 if (idx >= 0 && idx < _segsNum)
2112 _barMeter.setPixel(_corrPixelToDir(idx), true);
2113 }
2114
2115 // Deactivate block if head has passed the visual range
2116 if (b.position >= (_segsNum - 1) + blockLength)
2117 b.active = false;
2118 }
2119 }
2120
2121 if ((requestedNumBlocks > 0 && emittedBlocksCount >= requestedNumBlocks) || !_emittingBlocksEnabled)
2122 {
2123 for (uint8_t i = 0; i < maxBlocks; ++i)
2124 {
2125 if (_blocks[i].active)
2126 return false;
2127 }
2128 return true;
2129 }
2130
2131 return false;
2132 }
2133
2134#define stackLevel _ledTracker1
2135 bool _stackingBlocks()
2136 {
2137 const uint8_t blockInterval = blockLength + blockSpacing;
2138 bool hasActive = false; // active block tracker
2139
2140 if (_init)
2141 {
2142 _init = false;
2143
2144 if (!_animLogicSet)
2145 _prevAnimRenderLogic = _animRenderLogicIsInverted = _AnimInitLogicIsInverted;
2146
2147 maxBlocks = 1;
2148 if (_blocks)
2149 {
2150 delete[] _blocks;
2151 _blocks = nullptr;
2152 }
2153 _blocks = new Block[maxBlocks]{};
2154
2155 emitIndex = 0;
2156 emitCooldown = 0;
2157
2158 stackLevel = 0;
2159 if (!_animRenderLogicIsInverted)
2160 {
2161 _barMeter.clear();
2162 // Falling blocks
2163 // stackLevel = 0;
2164 }
2165 else
2166 {
2167 // Flying upward - fill pattern first
2168 while (stackLevel < _segsNum)
2169 stackLevel += blockInterval;
2170 for (uint8_t i = 0; i < stackLevel; i++)
2171 {
2172 if ((i % blockInterval) < blockLength)
2173 _barMeter.setPixel(_corrPixelToDir(i), true);
2174 else
2175 _barMeter.setPixel(_corrPixelToDir(i), false);
2176 }
2177 }
2178
2179 return false;
2180 }
2181
2182 if (_animRenderLogicIsInverted != _prevAnimRenderLogic)
2183 {
2184 _prevAnimRenderLogic = _animRenderLogicIsInverted;
2185 if (_animRenderLogicIsInverted)
2186 stackLevel += blockInterval;
2187 else
2188 stackLevel -= blockInterval;
2189 }
2190
2191 if (_currentTime - _lastUpdate1 >= _updateIntv1)
2192 {
2193 _lastUpdate1 = _currentTime;
2194
2195 // Only clear pixels of active blocks, not the whole bar
2196 for (uint8_t i = 0; i < maxBlocks; ++i)
2197 {
2198 Block &b = _blocks[i];
2199 if (!b.active)
2200 continue;
2201
2202 for (uint8_t j = 0; j < blockLength; ++j)
2203 {
2204 int8_t seg = b.position + j;
2205 if (seg >= 0 && seg < _segsNum)
2206 _barMeter.setPixel(_corrPixelToDir(seg), false);
2207 }
2208 }
2209
2210 // Emit one block if none active
2211 for (uint8_t i = 0; i < maxBlocks; ++i)
2212 {
2213 if (_blocks[i].active)
2214 {
2215 hasActive = true;
2216 break;
2217 }
2218 }
2219 if (!hasActive)
2220 {
2221 emitCooldown = 0; // emit blocks on demand
2222 if (!_animRenderLogicIsInverted && stackLevel <= _segsNum)
2223 _emitBlock(_segsNum); // fall from top
2224 else if (_animRenderLogicIsInverted && stackLevel >= 0)
2225 _emitBlock(stackLevel - blockInterval);
2226 }
2227
2228 // Update and draw all active blocks
2229 for (uint8_t i = 0; i < maxBlocks; ++i)
2230 {
2231 Block &b = _blocks[i];
2232 if (!b.active)
2233 continue;
2234
2235 // Clear trailing pixel of previous frame
2236 int8_t clearPos = b.position;
2237 if (clearPos >= 0 && clearPos < _segsNum)
2238 _barMeter.setPixel(_corrPixelToDir(clearPos), false);
2239
2240 // Move block
2241 if (!_animRenderLogicIsInverted)
2242 b.position--;
2243 else
2244 b.position++;
2245
2246 for (uint8_t j = 0; j < blockLength; ++j)
2247 {
2248 int8_t seg = b.position + j;
2249 if (seg >= 0 && seg < _segsNum)
2250 _barMeter.setPixel(_corrPixelToDir(seg), true);
2251 }
2252
2253 if (!_animRenderLogicIsInverted)
2254 {
2255 if (b.position <= stackLevel)
2256 {
2257 stackLevel += blockInterval;
2258 b.active = false;
2259 }
2260 }
2261 else
2262 {
2263 if (b.position >= _segsNum)
2264 {
2265 stackLevel -= blockInterval;
2266 b.active = false;
2267 }
2268 }
2269 }
2270
2271 // Draw base for stacking
2272 if (stackLevel == 0)
2273 {
2274 _barMeter.setPixel(_corrPixelToDir(0), false);
2275 }
2276 if (!_animRenderLogicIsInverted)
2277 {
2278 for (int8_t i = 0; i < stackLevel - blockInterval; ++i)
2279 {
2280 if ((i % blockInterval) < blockLength)
2281 _barMeter.setPixel(_corrPixelToDir(i), true);
2282 else
2283 _barMeter.setPixel(_corrPixelToDir(i), false);
2284 }
2285 }
2286 else
2287 {
2288 for (uint8_t i = 0; i < stackLevel - blockInterval; i++)
2289 {
2290 if ((i % blockInterval) < blockLength)
2291 _barMeter.setPixel(_corrPixelToDir(i), true);
2292 else
2293 _barMeter.setPixel(_corrPixelToDir(i), false);
2294 }
2295 }
2296 if (!_animRenderLogicIsInverted)
2297 {
2298 if (stackLevel >= _segsNum - 1 && !hasActive)
2299 return true;
2300 }
2301 else
2302 {
2303 if (stackLevel <= 0 && !hasActive)
2304 return true;
2305 }
2306 }
2307 return false;
2308 }
2309#undef stackLevel
2310#undef blockLength
2311#undef blockSpacing
2312#undef requestedNumBlocks
2313#undef maxBlocks
2314#undef emitIndex
2315#undef emittedBlocksCount
2316#undef emitCooldown
2317
2318#define smoothingFactor _param1
2319 bool _followSignalSmooth()
2320 {
2321 if (!_sigPtr1)
2322 {
2323 _setAllOff();
2324 return true;
2325 }
2326
2327 if (_init)
2328 {
2329 _init = false;
2330
2331 if (!_animLogicSet)
2332 _prevAnimRenderLogic = _animRenderLogicIsInverted = _AnimInitLogicIsInverted;
2333
2334 _smoothedValue1 = *_sigPtr1;
2335 _lastUpdate1 = _currentTime;
2336 _setAllOff();
2337 return false;
2338 }
2339
2340 // Signal sampling
2341 if (_currentTime - _lastUpdate2 >= _updateIntv2)
2342 {
2343 uint16_t raw = *_sigPtr1;
2344 _smoothedValue1 = (raw * smoothingFactor + _smoothedValue1 * (100 - smoothingFactor)) / 100;
2345 _lastUpdate2 = _currentTime;
2346 }
2347
2348 if (_currentTime - _lastUpdate1 >= _updateIntv1)
2349 {
2350 _lastUpdate1 = _currentTime;
2351
2352 uint8_t level = _getMappedSignal(_smoothedValue1, _minMap, _maxMap, 0, _segsNum);
2353
2354 for (uint8_t i = 0; i < _segsNum; i++)
2355 {
2356 _barMeter.setPixel(_corrPixelToDir(i), i < level);
2357 }
2358 }
2359 return false; // continuous animation
2360 }
2361 bool _followSignalWithPointer()
2362 {
2363 if (!_sigPtr1)
2364 {
2365 _setAllOff();
2366 return true;
2367 }
2368
2369 if (_init)
2370 {
2371 _init = false;
2372
2373 if (!_animLogicSet)
2374 _prevAnimRenderLogic = _animRenderLogicIsInverted = _AnimInitLogicIsInverted;
2375
2376 _smoothedValue1 = *_sigPtr1;
2377 _lastUpdate1 = _currentTime;
2378 _setAllOff();
2379 return false;
2380 }
2381
2382 // Signal sampling
2383 if (_currentTime - _lastUpdate2 >= _updateIntv2)
2384 {
2385 uint16_t raw = *_sigPtr1;
2386 _smoothedValue1 = (raw * smoothingFactor + _smoothedValue1 * (100 - smoothingFactor)) / 100;
2387 _lastUpdate2 = _currentTime;
2388 }
2389
2390 if (_currentTime - _lastUpdate1 >= _updateIntv1)
2391 {
2392 _lastUpdate1 = _currentTime;
2393
2394 uint8_t avg = _getMappedSignal(_smoothedValue1, _minMap, _maxMap, 0, _segsNum);
2395 uint8_t pointer = _getMappedSignal(*_sigPtr1, _minMap, _maxMap, 0, _segsNum);
2396
2397 // First, fill up to moving average
2398 for (uint8_t i = 0; i < _segsNum; i++)
2399 {
2400 _barMeter.setPixel(_corrPixelToDir(i), i < avg);
2401 }
2402
2403 // Then selectively clear area around pointer
2404 if (pointer < avg && pointer > 0)
2405 _barMeter.setPixel(_corrPixelToDir(pointer - 1), false); // clear just below if pointer is inside the fill
2406
2407 if (pointer < avg - 2)
2408 _barMeter.setPixel(_corrPixelToDir(pointer + 1), false); // clear just above
2409
2410 // Finally, show the pointer
2411 _barMeter.setPixel(_corrPixelToDir(pointer), true);
2412 }
2413 return false; // continuous animation
2414 }
2415 bool _followDualSignalCenterMirror()
2416 {
2417 if (!_sigPtr1 && !_sigPtr2)
2418 {
2419 _setAllOff();
2420 return true;
2421 }
2422
2423 if (_init)
2424 {
2425 _init = false;
2426
2427 if (!_animLogicSet)
2428 _mirrorHalfRangeDir = _prevAnimRenderLogic = _animRenderLogicIsInverted = _AnimInitLogicIsInverted;
2429
2430 _smoothedValue1 = *_sigPtr1;
2431 if (_sigPtr2)
2432 _smoothedValue2 = *_sigPtr2;
2433 _lastUpdate1 = _currentTime;
2434 _setAllOff();
2435 return false;
2436 }
2437
2438 if (_animRenderLogicIsInverted != _prevAnimRenderLogic)
2439 _prevAnimRenderLogic = _animRenderLogicIsInverted;
2440
2441 // Signals sampling
2442 if (_currentTime - _lastUpdate2 >= _updateIntv2)
2443 {
2444 _lastUpdate2 = _currentTime;
2445
2446 uint16_t raw1 = *_sigPtr1;
2447 _smoothedValue1 = (uint16_t)(((uint32_t)smoothingFactor * raw1 + (uint32_t)(100 - smoothingFactor) * _smoothedValue1) / 100);
2448
2449 if (_sigPtr2)
2450 {
2451 uint16_t raw2 = *_sigPtr2;
2452 _smoothedValue2 = (uint16_t)(((uint32_t)smoothingFactor * raw2 + (uint32_t)(100 - smoothingFactor) * _smoothedValue2) / 100);
2453 }
2454 }
2455
2456 if (_currentTime - _lastUpdate1 >= _updateIntv1)
2457 {
2458 _lastUpdate1 = _currentTime;
2459
2460 uint16_t raw1 = *_sigPtr1;
2461 _smoothedValue1 = (uint16_t)(((uint32_t)smoothingFactor * raw1 + (uint32_t)(100 - smoothingFactor) * _smoothedValue1) / 100);
2462 uint8_t level1 = _getMappedSignal(_smoothedValue1, _minMap, _maxMap, 0, _segsNum / 2);
2463
2464 uint8_t level2 = level1;
2465 if (_sigPtr2)
2466 level2 = _getMappedSignal(_smoothedValue2, _minMap, _maxMap, 0, _segsNum / 2);
2467
2468 for (uint8_t i = 0; i < _segsNum; i++)
2469 {
2470 //_barMeter.setPixel(_corrPixelToDirForHalfRange(i), i >= (_segsNum / 2 - 1) - level1 && i <= ((_segsNum / 2) + level2));
2471 if (_animRenderLogicIsInverted)
2472 _barMeter.setPixel(i, i < (_segsNum / 2 - 1) - level1 || i > ((_segsNum / 2) + level2));
2473 else
2474 _barMeter.setPixel(i, i >= (_segsNum / 2 - 1) - level1 && i <= ((_segsNum / 2) + level2));
2475 }
2476 }
2477 return false; // continuous animation
2478 }
2479
2480#define currentLevel _ledTracker1
2481#define peakLevel _ledTracker3
2482#define baseUpdate _updateIntv1
2483#define lastBaseUpdate _lastUpdate1
2484#define peakHoldTime _updateIntv3
2485#define lastPeakUpdate _lastUpdate3
2486 bool _followSignalFloatingPeak() // bouncing from bottom (maybe like a volume meter with the music)
2487 {
2488
2489 if (!_sigPtr1)
2490 {
2491 _setAllOff();
2492 return true;
2493 }
2494
2495 if (_init)
2496 {
2497 _init = false;
2498
2499 if (!_animLogicSet)
2500 _prevAnimRenderLogic = _animRenderLogicIsInverted = _AnimInitLogicIsInverted;
2501
2502 _setAllOff();
2503 _smoothedValue1 = *_sigPtr1;
2504 lastBaseUpdate = _currentTime;
2505 lastPeakUpdate = _currentTime;
2506 currentLevel = 0;
2507 peakLevel = 0;
2508 return false;
2509 }
2510
2511 // Signal smoothing update
2512 if (_currentTime - _lastUpdate2 >= _updateIntv2)
2513 {
2514 _lastUpdate2 = _currentTime;
2515
2516 uint16_t raw = *_sigPtr1;
2517 _smoothedValue1 = (raw * smoothingFactor + _smoothedValue1 * (100 - smoothingFactor)) / 100;
2518 currentLevel = _getMappedSignal(_smoothedValue1, _minMap, _maxMap, 0, _segsNum);
2519 }
2520
2521 // Pixel + peak update
2522 if (_currentTime - _lastUpdate1 >= _updateIntv1)
2523 {
2524 _lastUpdate1 = _currentTime;
2525
2526 // Peak logic
2527 if (currentLevel > peakLevel)
2528 {
2529 peakLevel = min(currentLevel, _segsNum - 1);
2530 lastPeakUpdate = _currentTime; // Reset decay timer
2531 }
2532 else if (_currentTime - lastPeakUpdate >= peakHoldTime && peakLevel > currentLevel)
2533 {
2534 peakLevel--; // Slowly drop peak
2535 lastPeakUpdate = _currentTime; // Reset decay timer
2536 }
2537
2538 // Draw bar
2539 for (uint8_t i = 0; i < _segsNum; i++)
2540 {
2541 _barMeter.setPixel(_corrPixelToDir(i), (i <= currentLevel));
2542 }
2543
2544 // Draw peak
2545 if (peakLevel < _segsNum)
2546 {
2547 _barMeter.setPixel(_corrPixelToDir(peakLevel), true);
2548 }
2549 }
2550 return false; // Continuous pulse animation, never auto-terminates
2551 }
2552#undef currentLevel
2553#undef peakLevel
2554#undef baseUpdate
2555#undef lastBaseUpdate
2556#undef peakHoldTime
2557#undef lastPeakUpdate
2558
2559#undef smoothingFactor
2560
2561#define cursor _ledTracker1
2562 bool _randomPixelUpdater()
2563 {
2564
2565 static uint8_t *pixelOrder = nullptr;
2566
2567 if (_init)
2568 {
2569 _init = false;
2570
2571 if (!_animLogicSet)
2572 _prevAnimRenderLogic = _animRenderLogicIsInverted = _AnimInitLogicIsInverted;
2573
2574 if (_animRenderLogicIsInverted)
2575 _setAllOn();
2576 else
2577 _setAllOff();
2578
2579 if (pixelOrder)
2580 {
2581 delete[] pixelOrder;
2582 pixelOrder = nullptr;
2583 }
2584
2585 pixelOrder = new uint8_t[_segsNum];
2586 for (uint8_t i = 0; i < _segsNum; ++i)
2587 pixelOrder[i] = i;
2588
2589 for (uint8_t i = _segsNum - 1; i > 0; --i)
2590 {
2591 uint8_t j = random(0, i + 1);
2592 uint8_t tmp = pixelOrder[i];
2593 pixelOrder[i] = pixelOrder[j];
2594 pixelOrder[j] = tmp;
2595 }
2596
2597 cursor = 0;
2598 _lastUpdate1 = _currentTime;
2599
2600 return false;
2601 }
2602
2603 if (_currentTime - _lastUpdate1 >= _updateIntv1)
2604 {
2605 _lastUpdate1 = _currentTime;
2606
2607 uint8_t retries = 0;
2608 while (cursor < _segsNum && retries++ < _segsNum - 1)
2609 {
2610 uint8_t seg = pixelOrder[cursor];
2611 bool currentState = _barMeter.getPixelState(seg);
2612 bool shouldChange = (!_AnimInitLogicIsInverted && !currentState) || (_AnimInitLogicIsInverted && currentState);
2613
2614 if (shouldChange)
2615 {
2616 _barMeter.setPixel(seg, !_AnimInitLogicIsInverted);
2617 cursor++;
2618 break; // Change only one pixel per interval
2619 }
2620 else
2621 {
2622 cursor++; // Move on to next even if it doesn't need change
2623 }
2624 }
2625 }
2626 if (cursor >= _segsNum)
2627 {
2628 delete[] pixelOrder;
2629 pixelOrder = nullptr;
2630 return true;
2631 }
2632 return false;
2633 }
2634#undef cursor
2635};
Templated animation controller for SBK_BarMeter<T>.
Definition SBK_BarMeterAnimations.h:49
bool isNonInvertingLogicAnim()
Returns true if the current animation hasen't a logic invertion aka logic can't be inverted.
Definition SBK_BarMeterAnimations.h:296
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.
Definition SBK_BarMeterAnimations.h:369
SBK_BarMeterAnimations & beatPulse(uint8_t bpm=116)
Start a beat pulse animation synchronized to a constant BPM.
Definition SBK_BarMeterAnimations.h:993
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.
Definition SBK_BarMeterAnimations.h:650
SBK_BarMeterAnimations & resume()
Resume animation after pause.
Definition SBK_BarMeterAnimations.h:129
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.
Definition SBK_BarMeterAnimations.h:413
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.
Definition SBK_BarMeterAnimations.h:391
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.
Definition SBK_BarMeterAnimations.h:569
SBK_BarMeterAnimations & loop()
Set animation to automatically loop when complete.
Definition SBK_BarMeterAnimations.h:147
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.
Definition SBK_BarMeterAnimations.h:882
SBK_BarMeterAnimations & setDir(bool newDirection)
Explicitly set the animation rendering direction.
Definition SBK_BarMeterAnimations.h:161
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.
Definition SBK_BarMeterAnimations.h:614
SBK_BarMeterAnimations & beatPulse(uint8_t *bpmPtr)
Start a beat pulse animation synchronized to a live BPM value.
Definition SBK_BarMeterAnimations.h:971
SBK_BarMeterAnimations(BarMeterT &barMeter)
Construct an animation controller linked to a bar meter.
Definition SBK_BarMeterAnimations.h:55
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.
Definition SBK_BarMeterAnimations.h:459
SBK_BarMeterAnimations & toggleLogic()
Toggle rendering logic (inverted vs normal), example fill logic become empty logic....
Definition SBK_BarMeterAnimations.h:210
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.
Definition SBK_BarMeterAnimations.h:1248
SBK_BarMeterAnimations & toggleDir()
Toggle the current animation rendering direction.
Definition SBK_BarMeterAnimations.h:169
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.
Definition SBK_BarMeterAnimations.h:524
SBK_BarMeterAnimations & reverseDir()
Reverse the rendering direction compared to the original one.
Definition SBK_BarMeterAnimations.h:177
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.
Definition SBK_BarMeterAnimations.h:701
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.
Definition SBK_BarMeterAnimations.h:1218
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.
Definition SBK_BarMeterAnimations.h:1351
SBK_BarMeterAnimations & setAll(bool onff)
Set all pixels to the specified state.
Definition SBK_BarMeterAnimations.h:315
SBK_BarMeterAnimations & downStackingBlocks(uint16_t intv=50, uint8_t blockLength=1, uint8_t blockSpacing=0)
Drop blocks from top and stack from bottom up.
Definition SBK_BarMeterAnimations.h:1119
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.
Definition SBK_BarMeterAnimations.h:481
SBK_BarMeterAnimations & setLogic(bool newLogic)
Set rendering logic (true = inverted logic, false = normal logic), example fill logic become empty lo...
Definition SBK_BarMeterAnimations.h:196
SBK_BarMeterAnimations & downUnstackingBlocks(uint16_t intv=50, uint8_t blockLength=1, uint8_t blockSpacing=0)
Drop blocks from top and unstack from bottom up.
Definition SBK_BarMeterAnimations.h:1191
SBK_BarMeterAnimations & resetDir()
Reset direction to the original setting.
Definition SBK_BarMeterAnimations.h:185
SBK_BarMeterAnimations & setAllOff()
Set all pixels OFF.
Definition SBK_BarMeterAnimations.h:331
SBK_BarMeterAnimations & animInit()
Mark animation as ready to reinitialize in next update cycle.
Definition SBK_BarMeterAnimations.h:115
SBK_BarMeterAnimations & randomEmpty(uint16_t interval=30)
Randomly turn off pixels until the bar is empty.
Definition SBK_BarMeterAnimations.h:1395
SBK_BarMeterAnimations & stopBlockEmission()
Stop emitting new blocks during compatible block animations.
Definition SBK_BarMeterAnimations.h:248
SBK_BarMeterAnimations & upStackingBlocks(uint16_t intv=50, uint8_t blockLength=1, uint8_t blockSpacing=0)
Launch blocks from bottom and stack at the top.
Definition SBK_BarMeterAnimations.h:1167
bool isLogicInverted()
Returns true if current logic is different from original logic.
Definition SBK_BarMeterAnimations.h:287
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,...
Definition SBK_BarMeterAnimations.h:1279
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.
Definition SBK_BarMeterAnimations.h:1019
bool animPendingLoop()
Detect whether animation has completed and is about to loop.
Definition SBK_BarMeterAnimations.h:271
bool isBlockEmissionEnabled() const
Query if block emission is enabled.
Definition SBK_BarMeterAnimations.h:308
SBK_BarMeterAnimations & noLoop()
Disable auto-looping. Animation will stop when complete.
Definition SBK_BarMeterAnimations.h:154
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.
Definition SBK_BarMeterAnimations.h:502
SBK_BarMeterAnimations & stop()
Stop animation and reset internal pointers.
Definition SBK_BarMeterAnimations.h:136
SBK_BarMeterAnimations & pause()
Pause animation progression.
Definition SBK_BarMeterAnimations.h:122
SBK_BarMeterAnimations & upUnstackingBlocks(uint16_t intv=50, uint8_t blockLength=1, uint8_t blockSpacing=0)
Launch blocks upward and unstack from top.
Definition SBK_BarMeterAnimations.h:1143
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.
Definition SBK_BarMeterAnimations.h:855
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.
Definition SBK_BarMeterAnimations.h:828
bool update(uint32_t syncTime=millis())
Advance the animation logic based on the current millis().
Definition SBK_BarMeterAnimations.h:85
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.
Definition SBK_BarMeterAnimations.h:1044
~SBK_BarMeterAnimations()
Destructor. Frees block memory if allocated.
Definition SBK_BarMeterAnimations.h:60
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.
Definition SBK_BarMeterAnimations.h:546
SBK_BarMeterAnimations & resetLogic()
Reset logic to initial (default) value. Logic refer to behavior, example : fill/empty,...
Definition SBK_BarMeterAnimations.h:238
bool isLoopEnabled() const
Query if animation loop is enabled.
Definition SBK_BarMeterAnimations.h:268
void setSegsNum(uint8_t n)
Set the total number of segments the animation logic should handle. Typically called automatically fr...
Definition SBK_BarMeterAnimations.h:74
SBK_BarMeterAnimations & setAllOn()
Set all pixels ON.
Definition SBK_BarMeterAnimations.h:320
bool isRunning() const
Query if animation is currently active.
Definition SBK_BarMeterAnimations.h:262
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.
Definition SBK_BarMeterAnimations.h:764
SBK_BarMeterAnimations & invertLogic()
Reverse logic compared to initial state Logic refer to behavior, example : fill/empty,...
Definition SBK_BarMeterAnimations.h:224
SBK_BarMeterAnimations & scrollingDownBlocks(uint16_t intv=50, uint8_t blockLength=2, uint8_t blockSpacing=1, uint8_t numBlocks=0)
Scroll blocks downward.
Definition SBK_BarMeterAnimations.h:1095
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.
Definition SBK_BarMeterAnimations.h:591
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.
Definition SBK_BarMeterAnimations.h:738
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.
Definition SBK_BarMeterAnimations.h:790
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.
Definition SBK_BarMeterAnimations.h:436
SBK_BarMeterAnimations & resumeBlockEmission()
Resume emitting new blocks during compatible block animations.
Definition SBK_BarMeterAnimations.h:255
SBK_BarMeterAnimations & randomFill(uint16_t interval=30)
Randomly light up pixels until the bar is full.
Definition SBK_BarMeterAnimations.h:1377
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.
Definition SBK_BarMeterAnimations.h:675
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.
Definition SBK_BarMeterAnimations.h:346
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..
Definition SBK_BarMeterAnimations.h:920
SBK_BarMeterAnimations & scrollingUpBlocks(uint16_t intv=50, uint8_t blockLength=2, uint8_t blockSpacing=1, uint8_t numBlocks=0)
Scroll blocks upward.
Definition SBK_BarMeterAnimations.h:1070
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,...
Definition SBK_BarMeterAnimations.h:1315
bool isDirectionReversed()
Returns true if direction is reversed compared to the original.
Definition SBK_BarMeterAnimations.h:299
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.
Definition SBK_BarMeterAnimations.h:947
bool isPaused() const
Query if animation is paused.
Definition SBK_BarMeterAnimations.h:265
Definition SBK_BarMeterAnimations.h:1454