diff --git a/Helios/HeliosConfig.h b/Helios/HeliosConfig.h index 2f7bdf2b..1880220d 100644 --- a/Helios/HeliosConfig.h +++ b/Helios/HeliosConfig.h @@ -162,6 +162,11 @@ // forbidden constant: // #define HELIOS_ARDUINO 1 - +// Debug Pattern Logic +// +// Turn this on to print debug labels on the pattern states, this is useful if you +// are debugging a pattern strip from the command line and want to see what state +// the pattern is in each tick of the pattern +#define DEBUG_BASIC_PATTERN 1 #endif diff --git a/Helios/Pattern.cpp b/Helios/Pattern.cpp index 7dfa53b9..a8e90ef9 100644 --- a/Helios/Pattern.cpp +++ b/Helios/Pattern.cpp @@ -9,50 +9,162 @@ #include // for memcpy -// uncomment me to print debug labels on the pattern states, this is useful if you -// are debugging a pattern strip from the command line and want to see what state -// the pattern is in each tick of the pattern -//#define DEBUG_BASIC_PATTERN - -#ifdef DEBUG_BASIC_PATTERN -#include "../../Time/TimeControl.h" -#include -// print out the current state of the pattern -#define PRINT_STATE(state) printState(state) -static void printState(PatternState state) -{ - static uint64_t lastPrint = 0; - if (lastPrint == Time::getCurtime()) return; - switch (m_state) { - case STATE_ON: printf("on "); break; - case STATE_OFF: printf("off "); break; - case STATE_IN_GAP: printf("gap1"); break; - case STATE_IN_DASH: printf("dash"); break; - case STATE_IN_GAP2: printf("gap2"); break; - default: return; - } - lastPrint = Time::getCurtime(); -} -#else -#define PRINT_STATE(state) // do nothing -#endif +// Pattern Notation +// +// In order to describe how the inner workings of a pattern operate, a formal +// 'Pattern Notation' can be conceptualized. This notation is based off the +// well known ABNF notation used for RFCs to breakdown complex structures, but +// incorporates some extra aspects to convey the necessary details. +// +// To start, the ABNF notation for a Pattern would go something like this: +// +// A 'pattern' is a repetition of 'groups' possibly with 'separators' inbetween +// The pattern will start with the separator if one is present +// +// pattern = [separator] group * ( [separator] group ) +// +// A 'group' is N 'blinks' where N is the 'group size parameter' to the pattern +// If 'group size' is zero then the size of the colorset is used as 'groupsize' +// +// group = groupsize * blink +// +// A 'blink' is simply an 'on' followed by an 'off' +// +// blink = [on] [off] +// +// A 'separator' is like a 'blink', but with three operations: gap, dash, gap +// +// separator = [gap] [dash] [gap] +// +// And finally the various basic words map to the parameters of the pattern +// +// on = LED blinks ON for on_dur +// off = LED turns OFF for off_dur +// dash = LED turns on for dash_dur +// gap = LED turns off for gap_dur +// groupsize = number of 'blinks' per cycle +// +// ---------------------------------------------------------------------------- +// +// In order to establish a 'Pattern Notation' from this base ABNF notation the +// colorset must be taken into account. +// +// The trick with the colorset is each 'on' or 'dash' will advance the colorset +// to the next color, wrapping around to the first color as it reaches the end. +// +// Consider the 'dash' to be the same as the 'on' and 'gap' is just 'off'. +// +// Then add the colorset, for the sake of pattern notation there is only eight +// possible colors that can be used (first letter is unique): +// +// R - RED +// G - GREEN +// B - BLUE +// O - ORANGE +// Y - YELLOW +// P - PURPLE +// C - CYAN +// W - WHITE +// +// Legend for 'Pattern Notation': +// +// [C] = ON where C is the color, number of C's is the duration +// +// ex: [RRRR] = RED for four ticks +// [GG] = GREEN for two ticks +// +// [ ] = OFF where number of spaces is the duration +// +// ex: [ ] = OFF for one tick +// [ ] = OFF for three ticks +// +// Examples using Pattern Notation: +// +// ---------------------------------------------------------------------------- +// The group size in this example defaults to the number of colors (3), +// +// Parameters: (ON:1 OFF:2 GAP:0 DASH:0 GROUP:0) +// Colorset: (RED, GREEN, BLUE) +// +// ┌────── Group 1 ──────┐┌────── Group 2 ──────┐┌────── Group 3 ──────┐... +// [R][ ][G][ ][B][ ] [R][ ][G][ ][B][ ] [R][ ][G][ ][B][ ] +// +// ---------------------------------------------------------------------------- +// This would appear as a solid ribbon or tracer with no gaps whatsoever. +// +// Parameters: (ON:3 OFF:0 GAP:0 DASH:0 GROUP:0) +// Colorset: (RED, GREEN, BLUE) +// +// ┌─── Group 1 ───┐┌─── Group 2 ───┐┌─── Group 3 ───┐┌─── Group 4 ───┐... +// [RRR][GGG][BBB] [RRR][GGG][BBB] [RRR][GGG][BBB] [RRR][GGG][BBB] +// +// ---------------------------------------------------------------------------- +// This would be like a 'crush' pattern where all the blinks are tightly packed +// together with large gaps between each group. +// +// Parameters: (ON:1 OFF:1 GAP:11 DASH:0 GROUP:0) +// Colorset: (RED, GREEN, BLUE) +// +// ┌──── Group 1 ─────┐┌─ Separator ─┐┌──── Group 1 ─────┐┌─ Separator ─┐... +// [R][ ][G][ ][B][ ] [ ] [R][ ][G][ ][B][ ] [ ] +// +// ---------------------------------------------------------------------------- +// If a DASH is present, then the pattern starts on the dash in the separator. +// Since the group size is 0 it will default to the size of the colorset, +// however since there is a DASH present the group size is decremented. +// +// By decrementing the group size when a dash is present it ensures the dash +// always lines up with the first color in the colorset. +// +// Parameters: (ON:4 OFF:2 GAP:3 DASH:6 GROUP:0) +// Colorset: (RED, GREEN, BLUE) +// +// ┌─ Separator ─┐┌────── Group 1 ───────┐┌──── Separator ───┐┌────── Group 2 ───────┐... +// [RRRRRR][ ] [GGGG][ ][BBBB][ ] [ ][RRRRRR][ ] [GGGG][ ][BBBB][ ] +// +// ---------------------------------------------------------------------------- +// Up till now all of the examples have been very linear, however if the group +// size doesn't match the number of colors things start to misalign +// +// Almost each group in this example blinks a different pairing of colors +// because the explicit group size of 2 is at odds with the number of colors. +// +// Parameters: (ON:3 OFF:2 GAP:5 DASH:0 GROUP:2) +// Colorset: (RED, GREEN, BLUE) +// +// ┌─ Group 1 ──┐┌─ Sep ─┐┌─ Group 2 ──┐┌─ Sep ─┐┌─ Group 3 ──┐┌─ Sep ─┐┌─ Group 4 ──┐... +// [R][ ][G][ ] [ ] [B][ ][R][ ] [ ] [G][ ][B][ ] [ ] [R][ ][G][ ] +// +// ---------------------------------------------------------------------------- +// When the dash is thrown into the mix the group size will decrement to +// acocunt for it. If a group size doesn't match the default group size then +// colors do not align perfectly again +// +// Parameters: (ON:4 OFF:2 GAP:3 DASH:6 GROUP:3) +// Colorset: (RED, GREEN, BLUE) +// +// ┌─ Separator ─┐┌──────────── Group 1 ────────────┐┌──── Separator ───┐┌──────────── Group 2 ────────────┐... +// [RRRRRR][ ] [GGGG][ ][BBBB][ ][RRRR][ ] [ ][GGGGGG][ ] [BBBB][ ][RRRR][ ][GGGG][ ] +// Pattern::Pattern(uint8_t onDur, uint8_t offDur, uint8_t gap, - uint8_t dash, uint8_t group, uint8_t blend) : - m_args(onDur, offDur, gap, dash, group, blend), + uint8_t dash, uint8_t group, uint8_t blend, uint8_t fade) : + m_args(onDur, offDur, gap, dash, group, blend, fade), m_patternFlags(0), m_colorset(), m_groupCounter(0), m_state(STATE_BLINK_ON), m_blinkTimer(), m_cur(), - m_next() + m_next(), + m_fadeValue(0), + m_lastFadeTick(0) { } Pattern::Pattern(const PatternArgs &args) : Pattern(args.on_dur, args.off_dur, args.gap_dur, - args.dash_dur, args.group_size, args.blend_speed) + args.dash_dur, args.group_size, args.blend_speed, args.fade_range) { } @@ -81,6 +193,40 @@ void Pattern::init() // convert current/next colors to HSV but only if we are doing a blend m_cur = m_colorset.getNext(); m_next = m_colorset.getNext(); + } else if (m_args.fade_range) { + // if there is a fade dur and no blend need to iterate colorset + m_colorset.getNext(); + } + + // Initialize the fluctuating fade value + m_fadeValue = 1; + m_lastFadeTick = 0; + m_curStep = 0; +} + +#include + +void Pattern::tickFade() +{ + // TODO: adjust this to make fade_range multiplied by some constant? + //uint32_t duration = m_args.fade_range; + + // count the number of steps (times this logic has run) + // NOTE: This function shouldn't run if fade_range is 0 + m_curStep++; + + uint32_t half_range = (m_args.fade_range) / 2; + uint32_t mod = m_curStep % m_args.fade_range; + + // Triangle wave: up from 0 to range, then down to 0 + m_fadeValue = (mod < half_range) ? mod : ((uint32_t)m_args.fade_range - mod); + + //printf("m_fadeValue: %d\n", m_fadeValue); + + // iterate color when at lowest point + if (m_fadeValue == 0) { + m_colorset.getNext(); + m_fadeValue++; } } @@ -90,55 +236,90 @@ void Pattern::play() // instead of using a loop or recursion I have just used a simple goto replay: - // its kinda evolving as i go + // this switch will run the 'entry' handlers for each state if it is valid to + // do so, otherwise, for example if on_dur is zero then it would naturally + // fall through to the 'off' state and perform the logic for that switch (m_state) { case STATE_DISABLED: + // only happens if the on and dash are zero, or there's no colors + // just do nothing and return return; case STATE_BLINK_ON: + // the first possible state of the pattern is the 'on' blink, this only + // happens if there is a non-zero on_dur in the arguments if (m_args.on_dur > 0) { + // if there is an 'on' duration then run the onBlinkOn callback onBlinkOn(); + // decrement the blink/group counter --m_groupCounter; - nextState(m_args.on_dur); + // then iterate the state forward to STATE_ON for on_dur ticks + nextState(isFade() ? (m_fadeValue) : m_args.on_dur); + // done with this tick return; } + // otherwise no on_dur so switch state to off m_state = STATE_BLINK_OFF; + // fallthrough case STATE_BLINK_OFF: - // the whole 'should blink off' situation is tricky because we might need - // to go back to blinking on if our colorset isn't at the end yet + // first condition is if the pattern is not at the end of the 'group' yet + // If this is the end of the grouping then we should fallthrough and + // begin the gap. If there is still blinks left in the group, or there is + // no gap and dash then it's possible the 'blink off' state should run if (m_groupCounter > 0 || (!m_args.gap_dur && !m_args.dash_dur)) { + // the primary check to determine if there should be an 'off' state is + // the off_dur in the arguments, zero means no 'off' state if (m_args.off_dur > 0) { + // otherwise if there is an off_dur then call the onBlinkOff callback onBlinkOff(); - nextState(m_args.off_dur); + // then iterate the state into STATE_OFF for the next off_dur ticks + nextState(isFade() ? (m_args.fade_range - m_fadeValue) : m_args.off_dur); + // done with this tick return; } + // if we reached here then that means there was 0 off duration but there is + // either still some blinks left in the group, or no gap and dash. Either of + // those cases means iterating the state backwards to STATE_BLINK_ON if (m_groupCounter > 0 && m_args.on_dur > 0) { m_state = STATE_BLINK_ON; + // fake-fallthrough via goto goto replay; } } + // switch state to gap m_state = STATE_BEGIN_GAP; + // fallthrough case STATE_BEGIN_GAP: + // When the first 'gap' begins it means the 'group' has ended, therefore + // first gap is to reset the group counter so + // that the next time it reaches the 'on' state the group counter will be + // reset m_groupCounter = m_args.group_size ? m_args.group_size : (m_colorset.numColors() - (m_args.dash_dur != 0)); if (m_args.gap_dur > 0) { beginGap(); nextState(m_args.gap_dur); return; } + // switch state to dash m_state = STATE_BEGIN_DASH; + // fallthrough case STATE_BEGIN_DASH: if (m_args.dash_dur > 0) { beginDash(); nextState(m_args.dash_dur); return; } + // switch state back to gap m_state = STATE_BEGIN_GAP2; + // fallthrough case STATE_BEGIN_GAP2: if (m_args.dash_dur > 0 && m_args.gap_dur > 0) { beginGap(); nextState(m_args.gap_dur); return; } + // switch state to on m_state = STATE_BLINK_ON; + // fake-fallthrough via goto goto replay; default: break; @@ -183,6 +364,15 @@ void Pattern::onBlinkOn() blendBlinkOn(); return; } + + // Check if this is a fading duration pattern + if (isFade()) { + //tickFade(); + // Just use the current color without advancing to the next one yet + Led::set(m_colorset.cur()); + return; + } + Led::set(m_colorset.getNext()); } @@ -279,3 +469,31 @@ void Pattern::interpolate(uint8_t ¤t, const uint8_t next) current -= step; } } + +// ================================== +// Debug Code +#if DEBUG_BASIC_PATTERN == 1 +#include +void Pattern::printState(PatternState state) +{ + static uint32_t lastPrint = UINT32_MAX; + if (lastPrint == Time::getCurtime()) { + return; + } + switch (m_state) { + case STATE_DISABLED: printf("DIS "); break; + case STATE_BLINK_ON: printf("ON "); break; + case STATE_ON: printf("on "); break; + case STATE_BLINK_OFF: printf("OFF "); break; + case STATE_OFF: printf("off "); break; + case STATE_BEGIN_GAP: printf("GAP1"); break; + case STATE_IN_GAP: printf("gap1"); break; + case STATE_BEGIN_DASH: printf("DASH"); break; + case STATE_IN_DASH: printf("dash"); break; + case STATE_BEGIN_GAP2: printf("GAP2"); break; + case STATE_IN_GAP2: printf("gap2"); break; + default: printf("(%02u)", m_state); break; + } + lastPrint = Time::getCurtime(); +} +#endif diff --git a/Helios/Pattern.h b/Helios/Pattern.h index fb78176f..43a66042 100644 --- a/Helios/Pattern.h +++ b/Helios/Pattern.h @@ -8,14 +8,15 @@ // for specifying things like default args struct PatternArgs { - PatternArgs(uint8_t on = 0, uint8_t off = 0, uint8_t gap = 0, uint8_t dash = 0, uint8_t group = 0, uint8_t blend = 0) : - on_dur(on), off_dur(off), gap_dur(gap), dash_dur(dash), group_size(group), blend_speed(blend) {} + PatternArgs(uint8_t on = 0, uint8_t off = 0, uint8_t gap = 0, uint8_t dash = 0, uint8_t group = 0, uint8_t blend = 0, uint8_t fade = 0) : + on_dur(on), off_dur(off), gap_dur(gap), dash_dur(dash), group_size(group), blend_speed(blend), fade_range(fade) {} uint8_t on_dur; uint8_t off_dur; uint8_t gap_dur; uint8_t dash_dur; uint8_t group_size; uint8_t blend_speed; + uint8_t fade_range; }; class Pattern @@ -23,7 +24,7 @@ class Pattern public: // try to not set on duration to 0 Pattern(uint8_t onDur = 1, uint8_t offDur = 0, uint8_t gap = 0, - uint8_t dash = 0, uint8_t group = 0, uint8_t blend = 0); + uint8_t dash = 0, uint8_t group = 0, uint8_t blend = 0, uint8_t fade = 0); Pattern(const PatternArgs &args); ~Pattern(); @@ -62,6 +63,9 @@ class Pattern // whether blend speed is non 0 bool isBlend() const { return m_args.blend_speed > 0; } + // whether fade speed is non 0 + bool isFade() const { return m_args.fade_range > 0; } + protected: // ================================== // Pattern Parameters @@ -129,6 +133,32 @@ class Pattern // apis for blend void blendBlinkOn(); void interpolate(uint8_t ¤t, const uint8_t next); + + // ================================== + // Fade Members + + // shifting value to represent current fade + uint8_t m_fadeValue; + + uint32_t m_lastFadeTick; + uint32_t m_curStep; + + // apis for fade + void tickFade(); + + // ================================== + // Debug Code + + // if debug basic pattern is enabled +#if DEBUG_BASIC_PATTERN == 1 + // define a function for printing out state info + void printState(PatternState state); + // and define a macro that wraps it + #define PRINT_STATE(state) printState(state) +#else + // otherwise if debug is off the macro does nothing + #define PRINT_STATE(state) +#endif }; #endif diff --git a/Helios/Patterns.cpp b/Helios/Patterns.cpp index 2bb08921..8f851e16 100644 --- a/Helios/Patterns.cpp +++ b/Helios/Patterns.cpp @@ -32,9 +32,10 @@ void Patterns::make_default(uint8_t index, Pattern &pat) PatternArgs args; switch (index) { case 0: // Rainbow Flow - args.on_dur = 3; - args.off_dur = 23; - args.blend_speed = 10; + args.on_dur = 1; + args.off_dur = 3; + //args.blend_speed = 0; + args.fade_range = 25; break; case 1: // Ghostly args.on_dur = 1; @@ -117,6 +118,12 @@ void Patterns::make_pattern(PatternID id, Pattern &pat) args.dash_dur = 24; break; + case PATTERN_FADE: + args.on_dur = 1; + args.off_dur = 15; + args.fade_range = 255; + break; + } pat.setArgs(args); diff --git a/Helios/Patterns.h b/Helios/Patterns.h index 5c6e475b..eeb38339 100644 --- a/Helios/Patterns.h +++ b/Helios/Patterns.h @@ -31,6 +31,8 @@ enum PatternID : int8_t { // Dash PATTERN_DASH_DOPS, PATTERN_DASH_DOT, + // Fade + PATTERN_FADE, // Meta pattern constants INTERNAL_PATTERNS_END, PATTERN_LAST = (INTERNAL_PATTERNS_END - 1), diff --git a/HeliosCLI/Makefile b/HeliosCLI/Makefile index e4d245c4..3865149b 100644 --- a/HeliosCLI/Makefile +++ b/HeliosCLI/Makefile @@ -12,7 +12,7 @@ MAKE=make RM=rm -rf RANLIB=ranlib -CFLAGS=-O2 -g -Wall -std=c++11 +CFLAGS=-O0 -g -Wall -std=c++11 # compiler defines DEFINES=\ @@ -110,7 +110,7 @@ clean: @$(RM) $(DFILES) $(OBJS) $(TARGETS) $(TESTS) compute_version: - $(eval LATEST_TAG ?= $(shell git fetch --depth=1 origin +refs/tags/*:refs/tags/* &> /dev/null && git tag --list | sort -V | tail -n1)) + $(eval LATEST_TAG ?= $(shell git tag --list | sort -V | tail -n1)) $(eval HELIOS_VERSION_MAJOR ?= $(shell echo $(LATEST_TAG) | cut -d. -f1)) $(eval HELIOS_VERSION_MINOR ?= $(shell echo $(LATEST_TAG) | cut -d. -f2)) $(eval LAST_HELIOS_BUILD_NUMBER ?= $(shell echo $(LATEST_TAG) | cut -d. -f3)) diff --git a/HeliosEmbedded/Makefile b/HeliosEmbedded/Makefile index 4139dbb1..82abadfe 100644 --- a/HeliosEmbedded/Makefile +++ b/HeliosEmbedded/Makefile @@ -274,10 +274,10 @@ upload_eeprom_fingers: eeprom_fingers.eep ################### upload_hex: - $(AVRDUDE) $(AVRDUDE_FLAGS) -U flash:w:helios_firmware.hex:i + $(AVRDUDE) $(AVRDUDE_FLAGS) -U flash:w:aeos_firmware.hex:i -extract_hex: helios_firmware.hex - $(AVRDUDE) $(AVRDUDE_FLAGS) -U flash:r:helios_firmware.hex:i +extract_hex: aeos_firmware.hex + $(AVRDUDE) $(AVRDUDE_FLAGS) -U flash:r:aeos_firmware.hex:i ##################### ####### CLEAN ####### diff --git a/HeliosLib/HeliosLib.cpp b/HeliosLib/HeliosLib.cpp index ad406a1f..65be027c 100644 --- a/HeliosLib/HeliosLib.cpp +++ b/HeliosLib/HeliosLib.cpp @@ -126,7 +126,8 @@ EMSCRIPTEN_BINDINGS(Vortex) { .property("gap_dur", &PatternArgs::gap_dur) .property("dash_dur", &PatternArgs::dash_dur) .property("group_size", &PatternArgs::group_size) - .property("blend_speed", &PatternArgs::blend_speed); + .property("blend_speed", &PatternArgs::blend_speed) + .property("fade_range", &PatternArgs::fade_range); // pattern class class_("Pattern") diff --git a/README.md b/README.md index 0098db42..d66dc761 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,10 @@ With it's core derived from [Vortex Engine](https://github.com/StoneOrbits/Vorte - Extensive list of available flashing patterns - Standard patterns (strobe, blink, glow, etc.) - Blending patterns for smooth color transitions + - Morphing patterns: + - Color morphing (blends between colors) + - Fading patterns + - Duration fading (dynamically shifts between on/off times while maintaining a constant total period) - Customizable colorsets with up to 6 slots and over 260 colors - Ability to shift modes - Colorset/Pattern randomizer feature