From 7d4be6cfce2b9383640ffb2260b2f388223d1c84 Mon Sep 17 00:00:00 2001 From: Unreal-Dan Date: Fri, 10 Jul 2026 00:41:01 -0700 Subject: [PATCH 01/10] c rewrite --- Makefile | 26 +- VortexEngine/Makefile | 56 +- VortexEngine/Makefile.embedded | 45 + VortexEngine/VortexLib/Makefile | 19 +- VortexEngine/appmain.c | 7 + VortexEngine/src/Buttons/Button.c | 205 +++ VortexEngine/src/Buttons/Button.h | 129 +- VortexEngine/src/Buttons/Buttons.c | 39 + VortexEngine/src/Buttons/Buttons.h | 36 +- VortexEngine/src/Colors/ColorConstants.h | 219 ++- VortexEngine/src/Colors/ColorTypes.c | 553 +++++++ VortexEngine/src/Colors/ColorTypes.h | 128 +- VortexEngine/src/Colors/Colorset.c | 471 ++++++ VortexEngine/src/Colors/Colorset.h | 226 +-- VortexEngine/src/Leds/LedStash.c | 31 + VortexEngine/src/Leds/LedStash.h | 26 +- VortexEngine/src/Leds/LedTypes.h | 166 +- VortexEngine/src/Leds/Leds.c | 489 ++++++ VortexEngine/src/Leds/Leds.h | 180 +- VortexEngine/src/Log/Log.c | 52 + VortexEngine/src/Log/Log.h | 6 - VortexEngine/src/Memory/Memory.c | 111 ++ VortexEngine/src/Memory/Memory.h | 24 +- VortexEngine/src/Menus/Menu.c | 153 ++ VortexEngine/src/Menus/Menu.cpp | 160 -- VortexEngine/src/Menus/Menu.h | 98 +- VortexEngine/src/Menus/MenuList/ColorSelect.c | 277 ++++ .../src/Menus/MenuList/ColorSelect.cpp | 274 --- VortexEngine/src/Menus/MenuList/ColorSelect.h | 89 +- .../src/Menus/MenuList/EditorConnection.c | 544 ++++++ .../src/Menus/MenuList/EditorConnection.cpp | 602 ------- .../src/Menus/MenuList/EditorConnection.h | 172 +- .../src/Menus/MenuList/FactoryReset.c | 146 ++ .../src/Menus/MenuList/FactoryReset.cpp | 129 -- .../src/Menus/MenuList/FactoryReset.h | 24 +- .../src/Menus/MenuList/GlobalBrightness.c | 204 +++ .../src/Menus/MenuList/GlobalBrightness.cpp | 212 --- .../src/Menus/MenuList/GlobalBrightness.h | 74 +- VortexEngine/src/Menus/MenuList/ModeSharing.c | 181 ++ .../src/Menus/MenuList/ModeSharing.cpp | 184 --- VortexEngine/src/Menus/MenuList/ModeSharing.h | 60 +- .../src/Menus/MenuList/PatternSelect.c | 96 ++ .../src/Menus/MenuList/PatternSelect.cpp | 80 - .../src/Menus/MenuList/PatternSelect.h | 39 +- VortexEngine/src/Menus/MenuList/Randomizer.c | 361 ++++ .../src/Menus/MenuList/Randomizer.cpp | 390 ----- VortexEngine/src/Menus/MenuList/Randomizer.h | 102 +- VortexEngine/src/Menus/Menus.c | 249 +++ VortexEngine/src/Menus/Menus.cpp | 296 ---- VortexEngine/src/Menus/Menus.h | 80 +- VortexEngine/src/Modes/DefaultModes.c | 55 + VortexEngine/src/Modes/DefaultModes.h | 6 +- VortexEngine/src/Modes/Mode.c | 1035 ++++++++++++ VortexEngine/src/Modes/Mode.h | 195 +-- VortexEngine/src/Modes/Modes.c | 1044 ++++++++++++ VortexEngine/src/Modes/Modes.h | 334 ++-- .../src/Patterns/Multi/BackStrobePattern.c | 15 + .../src/Patterns/Multi/BouncePattern.c | 15 + .../src/Patterns/Multi/CompoundPattern.c | 14 + .../src/Patterns/Multi/CrossDopsPattern.c | 15 + .../src/Patterns/Multi/DoubleStrobePattern.c | 15 + .../src/Patterns/Multi/DripMorphPattern.c | 15 + VortexEngine/src/Patterns/Multi/DripPattern.c | 15 + VortexEngine/src/Patterns/Multi/FillPattern.c | 15 + .../src/Patterns/Multi/HueShiftPattern.c | 15 + .../src/Patterns/Multi/LighthousePattern.c | 15 + .../src/Patterns/Multi/MeteorPattern.c | 15 + .../src/Patterns/Multi/MultiLedPattern.c | 32 + .../src/Patterns/Multi/MultiLedPattern.h | 23 +- .../src/Patterns/Multi/PulsishPattern.c | 15 + .../Patterns/Multi/Sequencer/ChaserPattern.c | 15 + .../src/Patterns/Multi/Sequencer/Sequence.c | 79 + .../Multi/Sequencer/SequencedPattern.c | 14 + .../src/Patterns/Multi/SnowballPattern.c | 15 + .../src/Patterns/Multi/SparkleTracePattern.c | 15 + .../src/Patterns/Multi/TheaterChasePattern.c | 15 + .../src/Patterns/Multi/VortexPattern.c | 15 + .../src/Patterns/Multi/VortexWipePattern.c | 15 + VortexEngine/src/Patterns/Multi/WarpPattern.c | 15 + .../src/Patterns/Multi/WarpWormPattern.c | 15 + .../src/Patterns/Multi/ZigzagPattern.c | 15 + VortexEngine/src/Patterns/Pattern.c | 237 +++ VortexEngine/src/Patterns/Pattern.h | 168 +- VortexEngine/src/Patterns/PatternArgs.c | 169 ++ VortexEngine/src/Patterns/PatternArgs.h | 88 +- VortexEngine/src/Patterns/PatternBuilder.c | 394 +++++ VortexEngine/src/Patterns/PatternBuilder.h | 51 +- VortexEngine/src/Patterns/Patterns.h | 67 +- .../src/Patterns/Single/BasicPattern.c | 158 ++ .../src/Patterns/Single/BasicPattern.h | 94 +- .../src/Patterns/Single/BlendPattern.c | 78 + .../src/Patterns/Single/BlendPattern.h | 37 +- .../src/Patterns/Single/SingleLedPattern.c | 6 + .../src/Patterns/Single/SingleLedPattern.h | 17 +- .../src/Patterns/Single/SolidPattern.c | 33 + .../src/Patterns/Single/SolidPattern.h | 17 +- VortexEngine/src/Random/Random.c | 28 + VortexEngine/src/Random/Random.h | 26 +- VortexEngine/src/Serial/BitStream.c | 119 ++ VortexEngine/src/Serial/BitStream.h | 74 +- VortexEngine/src/Serial/ByteStream.c | 556 +++++++ VortexEngine/src/Serial/ByteStream.h | 265 +-- VortexEngine/src/Serial/Compression.c | 1468 +++++++++++++++++ VortexEngine/src/Serial/Serial.c | 140 ++ VortexEngine/src/Serial/Serial.h | 47 +- VortexEngine/src/Storage/Storage.c | 263 +++ VortexEngine/src/Storage/Storage.h | 43 +- VortexEngine/src/Time/TimeControl.c | 332 ++++ VortexEngine/src/Time/TimeControl.h | 142 +- VortexEngine/src/Time/Timer.c | 227 +++ VortexEngine/src/Time/Timer.h | 91 +- VortexEngine/src/VortexEngine.c | 464 ++++++ VortexEngine/src/VortexEngine.h | 84 +- VortexEngine/src/Wireless/IRReceiver.c | 173 ++ VortexEngine/src/Wireless/IRReceiver.h | 89 +- VortexEngine/src/Wireless/IRSender.c | 179 ++ VortexEngine/src/Wireless/IRSender.h | 76 +- VortexEngine/src/Wireless/VLReceiver.c | 276 ++++ VortexEngine/src/Wireless/VLReceiver.h | 141 +- VortexEngine/src/Wireless/VLSender.c | 133 ++ VortexEngine/src/Wireless/VLSender.h | 63 +- avrsize.sh | 0 122 files changed, 13616 insertions(+), 5148 deletions(-) create mode 100644 VortexEngine/Makefile.embedded create mode 100644 VortexEngine/appmain.c create mode 100644 VortexEngine/src/Buttons/Button.c create mode 100644 VortexEngine/src/Buttons/Buttons.c create mode 100644 VortexEngine/src/Colors/ColorTypes.c create mode 100644 VortexEngine/src/Colors/Colorset.c create mode 100644 VortexEngine/src/Leds/LedStash.c create mode 100644 VortexEngine/src/Leds/Leds.c create mode 100644 VortexEngine/src/Log/Log.c create mode 100644 VortexEngine/src/Memory/Memory.c create mode 100644 VortexEngine/src/Menus/Menu.c delete mode 100644 VortexEngine/src/Menus/Menu.cpp create mode 100644 VortexEngine/src/Menus/MenuList/ColorSelect.c delete mode 100644 VortexEngine/src/Menus/MenuList/ColorSelect.cpp create mode 100644 VortexEngine/src/Menus/MenuList/EditorConnection.c delete mode 100644 VortexEngine/src/Menus/MenuList/EditorConnection.cpp create mode 100644 VortexEngine/src/Menus/MenuList/FactoryReset.c delete mode 100644 VortexEngine/src/Menus/MenuList/FactoryReset.cpp create mode 100644 VortexEngine/src/Menus/MenuList/GlobalBrightness.c delete mode 100644 VortexEngine/src/Menus/MenuList/GlobalBrightness.cpp create mode 100644 VortexEngine/src/Menus/MenuList/ModeSharing.c delete mode 100644 VortexEngine/src/Menus/MenuList/ModeSharing.cpp create mode 100644 VortexEngine/src/Menus/MenuList/PatternSelect.c delete mode 100644 VortexEngine/src/Menus/MenuList/PatternSelect.cpp create mode 100644 VortexEngine/src/Menus/MenuList/Randomizer.c delete mode 100644 VortexEngine/src/Menus/MenuList/Randomizer.cpp create mode 100644 VortexEngine/src/Menus/Menus.c delete mode 100644 VortexEngine/src/Menus/Menus.cpp create mode 100644 VortexEngine/src/Modes/DefaultModes.c create mode 100644 VortexEngine/src/Modes/Mode.c create mode 100644 VortexEngine/src/Modes/Modes.c create mode 100644 VortexEngine/src/Patterns/Multi/BackStrobePattern.c create mode 100644 VortexEngine/src/Patterns/Multi/BouncePattern.c create mode 100644 VortexEngine/src/Patterns/Multi/CompoundPattern.c create mode 100644 VortexEngine/src/Patterns/Multi/CrossDopsPattern.c create mode 100644 VortexEngine/src/Patterns/Multi/DoubleStrobePattern.c create mode 100644 VortexEngine/src/Patterns/Multi/DripMorphPattern.c create mode 100644 VortexEngine/src/Patterns/Multi/DripPattern.c create mode 100644 VortexEngine/src/Patterns/Multi/FillPattern.c create mode 100644 VortexEngine/src/Patterns/Multi/HueShiftPattern.c create mode 100644 VortexEngine/src/Patterns/Multi/LighthousePattern.c create mode 100644 VortexEngine/src/Patterns/Multi/MeteorPattern.c create mode 100644 VortexEngine/src/Patterns/Multi/MultiLedPattern.c create mode 100644 VortexEngine/src/Patterns/Multi/PulsishPattern.c create mode 100644 VortexEngine/src/Patterns/Multi/Sequencer/ChaserPattern.c create mode 100644 VortexEngine/src/Patterns/Multi/Sequencer/Sequence.c create mode 100644 VortexEngine/src/Patterns/Multi/Sequencer/SequencedPattern.c create mode 100644 VortexEngine/src/Patterns/Multi/SnowballPattern.c create mode 100644 VortexEngine/src/Patterns/Multi/SparkleTracePattern.c create mode 100644 VortexEngine/src/Patterns/Multi/TheaterChasePattern.c create mode 100644 VortexEngine/src/Patterns/Multi/VortexPattern.c create mode 100644 VortexEngine/src/Patterns/Multi/VortexWipePattern.c create mode 100644 VortexEngine/src/Patterns/Multi/WarpPattern.c create mode 100644 VortexEngine/src/Patterns/Multi/WarpWormPattern.c create mode 100644 VortexEngine/src/Patterns/Multi/ZigzagPattern.c create mode 100644 VortexEngine/src/Patterns/Pattern.c create mode 100644 VortexEngine/src/Patterns/PatternArgs.c create mode 100644 VortexEngine/src/Patterns/PatternBuilder.c create mode 100644 VortexEngine/src/Patterns/Single/BasicPattern.c create mode 100644 VortexEngine/src/Patterns/Single/BlendPattern.c create mode 100644 VortexEngine/src/Patterns/Single/SingleLedPattern.c create mode 100644 VortexEngine/src/Patterns/Single/SolidPattern.c create mode 100644 VortexEngine/src/Random/Random.c create mode 100644 VortexEngine/src/Serial/BitStream.c create mode 100644 VortexEngine/src/Serial/ByteStream.c create mode 100644 VortexEngine/src/Serial/Compression.c create mode 100644 VortexEngine/src/Serial/Serial.c create mode 100644 VortexEngine/src/Storage/Storage.c create mode 100644 VortexEngine/src/Time/TimeControl.c create mode 100644 VortexEngine/src/Time/Timer.c create mode 100644 VortexEngine/src/VortexEngine.c create mode 100644 VortexEngine/src/Wireless/IRReceiver.c create mode 100644 VortexEngine/src/Wireless/IRSender.c create mode 100644 VortexEngine/src/Wireless/VLReceiver.c create mode 100644 VortexEngine/src/Wireless/VLSender.c mode change 100644 => 100755 avrsize.sh diff --git a/Makefile b/Makefile index 1c279a53ef..0a1f85ffb9 100644 --- a/Makefile +++ b/Makefile @@ -13,8 +13,8 @@ else # linux INCLUDE_DIR=~/atmel_setup/include/ endif -CC = ${BINDIR}/avr-g++ -LD = ${BINDIR}/avr-g++ +CC = ${BINDIR}/avr-gcc +LD = ${BINDIR}/avr-gcc OBJCOPY = ${BINDIR}/avr-objcopy -v AR = ${BINDIR}/avr-gcc-ar SIZE = ${BINDIR}/avr-size @@ -77,6 +77,8 @@ DEFINES=\ -DVORTEX_VERSION_NUMBER=$(VORTEX_VERSION_NUMBER) \ -D__AVR_ATtiny3217__ \ -DF_CPU=$(CPU_SPEED) \ + -DVORTEX_EMBEDDED \ + -DVORTEX_SLIM=1 \ CFLAGS = -g \ -Os \ @@ -84,15 +86,13 @@ CFLAGS = -g \ -Wall \ -flto \ -mrelax \ - -std=gnu++17 \ + -std=gnu11 \ -fshort-enums \ -fpack-struct \ - -fno-exceptions \ -fdata-sections \ -funsigned-char \ -ffunction-sections\ -funsigned-bitfields \ - -fno-threadsafe-statics \ -mmcu=$(AVRDUDE_CHIP) \ -B $(DEVICE_DIR) @@ -118,20 +118,20 @@ ifneq ($(INCLUDES),) CFLAGS+=$(INCLUDES) endif -# Source files +# Source files (exclude multi-led patterns - VORTEX_SLIM=1) ifeq ($(OS),Windows_NT) # Windows SRCS = \ - $(shell find ./VortexEngine/src/ -type f -name '\*.cpp') \ - ./VortexEngine/appmain.cpp + $(shell find ./VortexEngine/src/ -type f -name '\*.c' ! -path '*/Multi/*') \ + ./VortexEngine/appmain.c else # linux SRCS = \ - $(shell find ./VortexEngine/src/ -type f -name \*.cpp) \ - ./VortexEngine/appmain.cpp + $(shell find ./VortexEngine/src/ -type f -name \*.c ! -path '*/Multi/*') \ + ./VortexEngine/appmain.c endif -OBJS = $(SRCS:.cpp=.o) +OBJS = $(SRCS:.c=.o) -DFILES = $(SRCS:.cpp=.d) +DFILES = $(SRCS:.c=.d) # Target name TARGET = vortex @@ -154,7 +154,7 @@ $(TARGET).elf: $(OBJS) %.o: %.S $(CC) $(ASMFLAGS) -c $< -o $@ -%.o: %.cpp +%.o: %.c $(CC) $(CFLAGS) -c $< -o $@ upload: all diff --git a/VortexEngine/Makefile b/VortexEngine/Makefile index d829b8cb14..7eb92b0771 100644 --- a/VortexEngine/Makefile +++ b/VortexEngine/Makefile @@ -2,41 +2,71 @@ .SUFFIXES: # List all make targets which are not filenames -.PHONY: all tests clean wasm +.PHONY: all clean wasm vortex vortex.a lib MAKE=make RM=rm -rf RANLIB=ranlib -# target files -TARGETS=\ - vortex +# ============================================================ +# Embedded firmware target (pure C, default) +# ============================================================ +# Set these to match your target hardware +MCU=attiny3217 +F_CPU=20000000 -.PHONY: $(TARGETS) +CC=avr-gcc +OBJCOPY=avr-objcopy +SIZE=avr-size -# Default target for 'make' command -all: $(TARGETS) +CFLAGS=-mmcu=$(MCU) -DF_CPU=$(F_CPU) -DVORTEX_EMBEDDED -DVORTEX_SLIM=1 -Os -Wall -Wextra -std=gnu11 +CFLAGS += -ffunction-sections -fdata-sections +LDFLAGS=-mmcu=$(MCU) -Wl,-gc-sections +INCLUDES=-I src/ -# unit test target -tests: $(TESTS) +# All .c files in src/ tree, excluding multi-led patterns (VORTEX_SLIM) +SRC=$(shell find src/ -type f -name '*.c' \ + ! -path 'src/Patterns/Multi/*' \ + ! -path 'src/Serial/Compression.c') appmain.c +OBJS=$(SRC:.c=.o) + +TARGET=vortex + +all: $(TARGET).hex $(TARGET).elf + +$(TARGET).elf: $(OBJS) + $(CC) $(LDFLAGS) -o $@ $^ + $(SIZE) $@ + +$(TARGET).hex: $(TARGET).elf + $(OBJCOPY) -O ihex $< $@ + +%.o: %.c + $(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@ + +# ============================================================ +# Desktop targets (delegated to sub-makefiles) +# ============================================================ # force sub-build of wasm wasm: FORCE env WASM=1 $(MAKE) -vortex.a: +vortex.a: $(MAKE) -C VortexLib cp -f VortexLib/vortex.a . -vortex: +vortex: $(MAKE) -C VortexCLI cp -f VortexCLI/vortex . -# emptry rule forces stuff to re reun every time +lib: vortex.a + +# Empty rule forces stuff to re-run every time FORCE: # generic clean target clean: - @$(RM) $(TARGETS) $(TESTS) vortex.exe + @$(RM) $(TARGETS) $(TESTS) vortex.exe vortex.elf vortex.hex $(OBJS) $(MAKE) -C VortexLib clean $(MAKE) -C VortexCLI clean diff --git a/VortexEngine/Makefile.embedded b/VortexEngine/Makefile.embedded new file mode 100644 index 0000000000..8c330ad851 --- /dev/null +++ b/VortexEngine/Makefile.embedded @@ -0,0 +1,45 @@ +# clear out all default make targets +.SUFFIXES: + +# List all make targets which are not filenames +.PHONY: all clean + +RM=rm -rf + +# Embedded AVR toolchain +CC=avr-gcc +OBJCOPY=avr-objcopy +SIZE=avr-size +# Set this to match your target hardware (e.g. attiny3216, atmega4809, avr64dd28) +MCU=attiny3217 +F_CPU=20000000 + +CFLAGS=-mmcu=$(MCU) -DF_CPU=$(F_CPU) -DVORTEX_EMBEDDED -DVORTEX_SLIM=1 -Os -Wall -Wextra -std=gnu11 +CFLAGS += -ffunction-sections -fdata-sections +LDFLAGS=-mmcu=$(MCU) -Wl,-gc-sections + +# include paths +INCLUDES=-I src/ + +# source files - all .c files in src/ tree, excluding multi-led patterns (VORTEX_SLIM) +SRC=$(shell find src/ -type f -name '*.c' \ + ! -path 'src/Patterns/Multi/*' \ + ! -path 'src/Serial/Compression.c') appmain.c +OBJS=$(SRC:.c=.o) + +TARGET=vortex + +all: $(TARGET).hex $(TARGET).elf + +$(TARGET).elf: $(OBJS) + $(CC) $(LDFLAGS) -o $@ $^ + $(SIZE) $@ + +$(TARGET).hex: $(TARGET).elf + $(OBJCOPY) -O ihex $< $@ + +%.o: %.c + $(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@ + +clean: + @$(RM) $(OBJS) $(TARGET).elf $(TARGET).hex diff --git a/VortexEngine/VortexLib/Makefile b/VortexEngine/VortexLib/Makefile index 5d02c7f95e..317e08e356 100644 --- a/VortexEngine/VortexLib/Makefile +++ b/VortexEngine/VortexLib/Makefile @@ -9,7 +9,11 @@ ifdef WASM CC=em++ AR=emar cru else +ifdef USE_C +CC=gcc +else CC=g++ +endif AR=ar cru endif @@ -81,18 +85,18 @@ LIBS=\ # source files # local source files first, other sources after ifeq ($(OS),Windows_NT) - SRC = $(shell find ../src/ -type f -name \\*.cpp) VortexLib.cpp + SRC = $(shell find ../src/ -type f -name \\*.c -o -type f -name \\*.cpp) VortexLib.cpp else - SRC = $(shell find ../src/ -type f -name '*.cpp') VortexLib.cpp + SRC = $(shell find ../src/ -type f -name '*.c' -o -type f -name '*.cpp') VortexLib.cpp endif -# object files are source files with .c replaced with .o +# object files are source files with .c or .cpp replaced with .o OBJS=\ - $(SRC:.cpp=.o) \ + $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(SRC))) \ -# dependency files are source files with .c replaced with .d +# dependency files are source files with .c or .cpp replaced with .d DFILES=\ - $(SRC:.cpp=.d) \ + $(patsubst %.c,%.d,$(patsubst %.cpp,%.d,$(SRC))) \ # target dependencies # this includes any script generated c/h files, @@ -130,6 +134,9 @@ VortexLib.js: compute_version $(DEPS) $(CC) $(LDFLAGS) $(DEPS) -o $@ $(LLIBS) # catch-all make target to generate .o and .d files +%.o: %.c + $(CC) $(CFLAGS) -MMD -c $< -o $@ + %.o: %.cpp $(CC) $(CFLAGS) -MMD -c $< -o $@ diff --git a/VortexEngine/appmain.c b/VortexEngine/appmain.c new file mode 100644 index 0000000000..6fe4dfe7fb --- /dev/null +++ b/VortexEngine/appmain.c @@ -0,0 +1,7 @@ +#include "src/VortexEngine.h" + +int main(void) +{ + VortexEngine_init(); + return 0; +} diff --git a/VortexEngine/src/Buttons/Button.c b/VortexEngine/src/Buttons/Button.c new file mode 100644 index 0000000000..66c32e88b5 --- /dev/null +++ b/VortexEngine/src/Buttons/Button.c @@ -0,0 +1,205 @@ +#include "Button.h" + +#include "../Time/TimeControl.h" +#include "../Time/Timings.h" +#include "../Log/Log.h" + +#ifdef VORTEX_LIB +#include "VortexLib.h" +#endif + +#ifdef VORTEX_EMBEDDED +#include "../VortexEngine.h" +#include +#include + +#define PIN_NUM 2 +#define PORT_LETTER C + +#define CONCATENATE_DETAIL(x, y) x##y +#define CONCATENATE(x, y) CONCATENATE_DETAIL(x, y) +#define CONCATENATE_DETAIL_3(x, y, z) x##y##z +#define CONCATENATE_3(x, y, z) CONCATENATE_DETAIL_3(x, y, z) + +#define BUTTON_PORT CONCATENATE(PORT, PORT_LETTER) +#define BUTTON_VPORT CONCATENATE(VPORT, PORT_LETTER) +#define BUTTON_PIN CONCATENATE_3(PIN, PIN_NUM, _bm) +#define PIN_CTRL CONCATENATE_3(PIN, PIN_NUM, CTRL) +#define PORT_VECT CONCATENATE_3(PORT, PORT_LETTER, _PORT_vect) + +ISR(PORT_VECT) +{ + BUTTON_PORT.INTFLAGS = BUTTON_PIN; + BUTTON_PORT.PIN_CTRL &= ~PORT_ISC_gm; + VortexEngine_wakeup(true); +} + +void Button_enableWake(Button *self) +{ + BUTTON_PORT.PIN_CTRL = PORT_PULLUPEN_bm | PORT_ISC_FALLING_gc; +} +#endif + +void Button_init(Button *self) +{ + self->pressTime = 0; + self->releaseTime = 0; + self->holdDuration = 0; + self->releaseDuration = 0; + self->consecutivePresses = 0; + self->releaseCount = 0; + self->buttonState = false; + self->newPress = false; + self->newRelease = false; + self->isPressed = false; + self->shortClick = false; + self->longClick = false; +} + +void Button_cleanup(Button *self) +{ +} + +bool Button_initPin(Button *self, uint8_t pin) +{ + self->pressTime = 0; + self->releaseTime = 0; + self->holdDuration = 0; + self->releaseDuration = 0; + self->consecutivePresses = 0; + self->newPress = false; + self->newRelease = false; + self->shortClick = false; + self->longClick = false; + self->buttonState = Button_check(self); + self->releaseCount = !Button_check(self); + self->isPressed = self->buttonState; +#ifdef VORTEX_EMBEDDED + BUTTON_PORT.PIN_CTRL = PORT_PULLUPEN_bm; +#endif + return true; +} + +bool Button_check(Button *self) +{ +#ifdef VORTEX_LIB + return (Vortex_vcallbacks()->checkPinHook(0) == 0); +#else + return ((BUTTON_VPORT.IN & BUTTON_PIN) == 0); +#endif +} + +void Button_update(Button *self) +{ + self->newPress = false; + self->newRelease = false; + + bool newButtonState = Button_check(self); + + if (newButtonState != self->buttonState) { + self->buttonState = newButtonState; + self->isPressed = self->buttonState; + + if (self->isPressed) { + self->pressTime = Time_getCurtime(); + self->newPress = true; + } else { + if (self->releaseCount > 0) { + self->releaseTime = Time_getCurtime(); + self->newRelease = true; + } + self->releaseCount++; + } + } + + if (self->isPressed) { + if (Time_getCurtime() >= self->pressTime) { + self->holdDuration = (uint32_t)(Time_getCurtime() - self->pressTime); + } + } else { + if (Time_getCurtime() >= self->releaseTime) { + self->releaseDuration = (uint32_t)(Time_getCurtime() - self->releaseTime); + if (self->releaseDuration > CONSECUTIVE_WINDOW_TICKS) { + self->consecutivePresses = 0; + } + } + } + + if (self->newRelease) { + self->consecutivePresses++; + } + + self->shortClick = (self->newRelease && (self->holdDuration <= SHORT_CLICK_THRESHOLD_TICKS)); + self->longClick = (self->newRelease && (self->holdDuration > SHORT_CLICK_THRESHOLD_TICKS)); + + if (self->shortClick) { + DEBUG_LOG("Short click"); + } + if (self->longClick) { + DEBUG_LOG("Long click"); + } +} + +bool Button_onPress(const Button *self) +{ + return self->newPress; +} + +bool Button_onRelease(const Button *self) +{ + return self->newRelease; +} + +bool Button_isPressed(const Button *self) +{ + return self->isPressed; +} + +bool Button_onShortClick(const Button *self) +{ + return self->shortClick; +} + +bool Button_onLongClick(const Button *self) +{ + return self->longClick; +} + +bool Button_onConsecutivePresses(Button *self, uint8_t numPresses) +{ + if (self->consecutivePresses >= numPresses) { + self->consecutivePresses = 0; + return true; + } + return false; +} + +uint32_t Button_pressTime(const Button *self) +{ + return self->pressTime; +} + +uint32_t Button_releaseTime(const Button *self) +{ + return self->releaseTime; +} + +uint32_t Button_holdDuration(const Button *self) +{ + return self->holdDuration; +} + +uint32_t Button_releaseDuration(const Button *self) +{ + return self->releaseDuration; +} + +uint8_t Button_consecutivePresses(const Button *self) +{ + return self->consecutivePresses; +} + +uint8_t Button_releaseCount(const Button *self) +{ + return self->releaseCount; +} diff --git a/VortexEngine/src/Buttons/Button.h b/VortexEngine/src/Buttons/Button.h index 8d0d98ab19..35e6deb56e 100644 --- a/VortexEngine/src/Buttons/Button.h +++ b/VortexEngine/src/Buttons/Button.h @@ -2,106 +2,45 @@ #define BUTTON_H #include - +#include #include "../VortexConfig.h" -// although there is only one button on the VortexFramework -// I am still opting for a non-static button class -class Button -{ -private: - // private unimplemented copy and assignment constructors to prevent copies - Button(Button const &); - void operator=(Button const &); - -public: - Button(); - ~Button(); - - // initialize a new button object with a pin number - bool init(uint8_t pin); - // directly poll the pin for whether it's pressed right now - bool check(); - // poll the button pin and update the state of the button object - void update(); - +typedef struct Button_s { + uint32_t pressTime; + uint32_t releaseTime; + uint32_t holdDuration; + uint32_t releaseDuration; + uint8_t consecutivePresses; + uint8_t releaseCount; + bool buttonState; + bool newPress; + bool newRelease; + bool isPressed; + bool shortClick; + bool longClick; +} Button; + +void Button_init(Button *self); +void Button_cleanup(Button *self); +bool Button_initPin(Button *self, uint8_t pin); +bool Button_check(Button *self); +void Button_update(Button *self); #ifdef VORTEX_EMBEDDED - // enable the button-wake trigger to wake the device on press - void enableWake(); -#endif - - // whether the button was pressed this tick - bool onPress() const { return m_newPress; } - // whether the button was released this tick - bool onRelease() const { return m_newRelease; } - // whether the button is currently pressed - bool isPressed() const { return m_isPressed; } - - // whether the button was shortclicked this tick - bool onShortClick() const { return m_shortClick; } - // whether the button was long clicked this tick - bool onLongClick() const { return m_longClick; } - // fired when a certain number of presses is reached, the consecutive press - // counter is automatically reset when that happens - bool onConsecutivePresses(uint8_t numPresses); - - // when the button was last pressed - uint32_t pressTime() const { return m_pressTime; } - // when the button was last released - uint32_t releaseTime() const { return m_releaseTime; } - - // how long the button is currently or was last held down (in ticks) - uint32_t holdDuration() const { return m_holdDuration; } - // how long the button is currently or was last released for (in ticks) - uint32_t releaseDuration() const { return m_releaseDuration; } - - // the number of consecutive presses - uint8_t consecutivePresses() const { return m_consecutivePresses; } - // the number of releases - uint8_t releaseCount() const { return m_releaseCount; } - -private: - // ======================================== - // state data that is populated each check - - // the timestamp of when the button was pressed - uint32_t m_pressTime; - // the timestamp of when the button was released - uint32_t m_releaseTime; - - // the last hold duration - uint32_t m_holdDuration; - // the last release duration - uint32_t m_releaseDuration; - - // the number of repeated presses (automatically detects rapid presses) - uint8_t m_consecutivePresses; - // the number of times released, will overflow at 255 - uint8_t m_releaseCount; - - // the active state of the button - bool m_buttonState; - - // whether pressed this tick - bool m_newPress; - // whether released this tick - bool m_newRelease; - // whether currently pressed - bool m_isPressed; - // whether a short click occurred - bool m_shortClick; - // whether a long click occurred - bool m_longClick; - -#ifdef VORTEX_LIB - // allow the VortexLib class to manipulate the members of this - // class so that it can inject button events to make apis like - // Vortex::shortClick() and Vortex::longClick() possible - friend class Vortex; +void Button_enableWake(Button *self); #endif -}; +bool Button_onPress(const Button *self); +bool Button_onRelease(const Button *self); +bool Button_isPressed(const Button *self); +bool Button_onShortClick(const Button *self); +bool Button_onLongClick(const Button *self); +bool Button_onConsecutivePresses(Button *self, uint8_t numPresses); +uint32_t Button_pressTime(const Button *self); +uint32_t Button_releaseTime(const Button *self); +uint32_t Button_holdDuration(const Button *self); +uint32_t Button_releaseDuration(const Button *self); +uint8_t Button_consecutivePresses(const Button *self); +uint8_t Button_releaseCount(const Button *self); -// See Button.cpp for info about this extern Button *g_pButton; #endif diff --git a/VortexEngine/src/Buttons/Buttons.c b/VortexEngine/src/Buttons/Buttons.c new file mode 100644 index 0000000000..9326c85c37 --- /dev/null +++ b/VortexEngine/src/Buttons/Buttons.c @@ -0,0 +1,39 @@ +#include "Buttons.h" + +#include "../Memory/Memory.h" + +#ifdef VORTEX_LIB +#include "VortexLib.h" +#else +#include "../Time/TimeControl.h" +#include "../Time/Timings.h" +#endif + +Button *g_pButton = NULL; + +bool Buttons_init(uint8_t pin) +{ + g_pButton = (Button *)vmalloc(sizeof(Button)); + if (!g_pButton) { + return false; + } + Button_init(g_pButton); + return Button_initPin(g_pButton, pin); +} + +void Buttons_cleanup(void) +{ + if (g_pButton) { + Button_cleanup(g_pButton); + vfree(g_pButton); + g_pButton = NULL; + } +} + +void Buttons_update(void) +{ + Button_update(g_pButton); +#ifdef VORTEX_LIB + Vortex_handleInputQueue(g_pButton, 1); +#endif +} diff --git a/VortexEngine/src/Buttons/Buttons.h b/VortexEngine/src/Buttons/Buttons.h index ad9ed95146..2bc248a97c 100644 --- a/VortexEngine/src/Buttons/Buttons.h +++ b/VortexEngine/src/Buttons/Buttons.h @@ -1,33 +1,19 @@ #ifndef BUTTONS_H #define BUTTONS_H +#include +#include #include "Button.h" +#include "../VortexConfig.h" -// the number of buttons, this isn't in VortexConfig.h because -// changing it won't really work without updating other things -// like which pins the buttons are attached to. So this is more -// of a hardcoded constant than a configuration setting -#define NUM_BUTTONS 1 - -class Buttons -{ - // private unimplemented constructor - Buttons(); - -public: - // initialize all buttons - static bool init(); - static void cleanup(); - - // poll the buttons - static void update(); - - static uint8_t numButtons() { return NUM_BUTTONS; } - -private: -}; +#ifdef VORTEX_EMBEDDED +#define REGISTER_BUTTON(pin) Buttons_init(pin) +#else +#define REGISTER_BUTTON(pin) Buttons_init() +#endif -// best way I think -extern Button *g_pButton; +bool Buttons_init(uint8_t pin); +void Buttons_cleanup(void); +void Buttons_update(void); #endif diff --git a/VortexEngine/src/Colors/ColorConstants.h b/VortexEngine/src/Colors/ColorConstants.h index a33f6c3a80..f379236c45 100644 --- a/VortexEngine/src/Colors/ColorConstants.h +++ b/VortexEngine/src/Colors/ColorConstants.h @@ -1,12 +1,6 @@ -#pragma once +#ifndef COLOR_CONSTANTS_H +#define COLOR_CONSTANTS_H -// ==================================================================================================== -// Color Constants -// -// This file defines constants for colors as we best see them for the Vortex Engine - -// Some Pre-defined hue values -// TODO: remove HSV_ underscore once FastLED is gone #define HSV_HUE_RED 0 #define HSV_HUE_ORANGE 32 #define HSV_HUE_YELLOW 64 @@ -16,125 +10,118 @@ #define HSV_HUE_PURPLE 192 #define HSV_HUE_PINK 224 -// if this bit is present it's an HSV constant #define HSV_BIT ((uint32_t)1 << 31) -// produce a DWORD HSV constant #define HSV(h, s, v) (HSV_BIT | ((uint32_t)h << 16) | ((uint32_t)s << 8) | (uint32_t)v) -// Pre defined hex HSV values -#define HSV_WHITE HSV_BIT | (uint32_t)0x00006E // 0 0 110 -#define HSV_BLUE HSV_BIT | (uint32_t)0xA0FF6E // 160 255 110 -#define HSV_YELLOW HSV_BIT | (uint32_t)0x3CFF6E // 60 255 110 -#define HSV_RED HSV_BIT | (uint32_t)0x00FF6E // 0 255 110 -#define HSV_GREEN HSV_BIT | (uint32_t)0x55FF6E // 85 255 110 -#define HSV_CYAN HSV_BIT | (uint32_t)0x78FF6E // 120 255 110 -#define HSV_PURPLE HSV_BIT | (uint32_t)0xD4FF6E // 212 255 110 -#define HSV_ORANGE HSV_BIT | (uint32_t)0x14FF6E // 20 255 110 -#define HSV_OFF HSV_BIT | (uint32_t)0x000000 // 0 0 0 +#define HSV_WHITE HSV_BIT | (uint32_t)0x00006E +#define HSV_BLUE HSV_BIT | (uint32_t)0xA0FF6E +#define HSV_YELLOW HSV_BIT | (uint32_t)0x3CFF6E +#define HSV_RED HSV_BIT | (uint32_t)0x00FF6E +#define HSV_GREEN HSV_BIT | (uint32_t)0x55FF6E +#define HSV_CYAN HSV_BIT | (uint32_t)0x78FF6E +#define HSV_PURPLE HSV_BIT | (uint32_t)0xD4FF6E +#define HSV_ORANGE HSV_BIT | (uint32_t)0x14FF6E +#define HSV_OFF HSV_BIT | (uint32_t)0x000000 -// Pure RGB colors (brightest) -#define RGB_WHITE (uint32_t)0xFFFFFF // 255 255 255 -#define RGB_BLUE (uint32_t)0x0000FF // 0 0 255 -#define RGB_YELLOW (uint32_t)0xFFFF00 // 255 255 0 -#define RGB_RED (uint32_t)0xFF0000 // 255 0 0 -#define RGB_GREEN (uint32_t)0x00FF00 // 0 255 0 -#define RGB_CYAN (uint32_t)0x00FFFF // 0 255 255 -#define RGB_PURPLE (uint32_t)0x9933FF // 153 51 255 -#define RGB_ORANGE (uint32_t)0xFF8300 // 255 131 0 -#define RGB_PINK (uint32_t)0xFF0099 // 255 0 153 -#define RGB_MAGENTA (uint32_t)0xFF00FF // 255 0 255 -#define RGB_OFF (uint32_t)0x000000 // 0 0 0 +#define RGB_WHITE (uint32_t)0xFFFFFF +#define RGB_BLUE (uint32_t)0x0000FF +#define RGB_YELLOW (uint32_t)0xFFFF00 +#define RGB_RED (uint32_t)0xFF0000 +#define RGB_GREEN (uint32_t)0x00FF00 +#define RGB_CYAN (uint32_t)0x00FFFF +#define RGB_PURPLE (uint32_t)0x9933FF +#define RGB_ORANGE (uint32_t)0xFF8300 +#define RGB_PINK (uint32_t)0xFF0099 +#define RGB_MAGENTA (uint32_t)0xFF00FF +#define RGB_OFF (uint32_t)0x000000 -// =========================================================== -// Steps of colors in increasing brightness from 0 to 9, -// Where O is the lowest brightness and 9 is the highest -// brightness before the pure colors above +#define RGB_WHITE0 (uint32_t)0x101010 +#define RGB_WHITE1 (uint32_t)0x1C1C1C +#define RGB_WHITE2 (uint32_t)0x383838 +#define RGB_WHITE3 (uint32_t)0x545454 +#define RGB_WHITE4 (uint32_t)0x707070 +#define RGB_WHITE5 (uint32_t)0x8C8C8C +#define RGB_WHITE6 (uint32_t)0xA8A8A8 +#define RGB_WHITE7 (uint32_t)0xC4C4C4 +#define RGB_WHITE8 (uint32_t)0xE0E0E0 +#define RGB_WHITE9 (uint32_t)0xFCFCFC -#define RGB_WHITE0 (uint32_t)0x101010 // 16 16 16 -#define RGB_WHITE1 (uint32_t)0x1C1C1C // 28 28 28 -#define RGB_WHITE2 (uint32_t)0x383838 // 56 56 56 -#define RGB_WHITE3 (uint32_t)0x545454 // 84 84 84 -#define RGB_WHITE4 (uint32_t)0x707070 // 112 112 112 -#define RGB_WHITE5 (uint32_t)0x8C8C8C // 140 140 140 -#define RGB_WHITE6 (uint32_t)0xA8A8A8 // 168 168 168 -#define RGB_WHITE7 (uint32_t)0xC4C4C4 // 196 196 196 -#define RGB_WHITE8 (uint32_t)0xE0E0E0 // 224 224 224 -#define RGB_WHITE9 (uint32_t)0xFCFCFC // 252 252 252 +#define RGB_BLUE0 (uint32_t)0x000010 +#define RGB_BLUE1 (uint32_t)0x00001C +#define RGB_BLUE2 (uint32_t)0x000038 +#define RGB_BLUE3 (uint32_t)0x000054 +#define RGB_BLUE4 (uint32_t)0x000070 +#define RGB_BLUE5 (uint32_t)0x00008C +#define RGB_BLUE6 (uint32_t)0x0000A8 +#define RGB_BLUE7 (uint32_t)0x0000C4 +#define RGB_BLUE8 (uint32_t)0x0000E0 +#define RGB_BLUE9 (uint32_t)0x0000FC -#define RGB_BLUE0 (uint32_t)0x000010 // 0 0 16 -#define RGB_BLUE1 (uint32_t)0x00001C // 0 0 28 -#define RGB_BLUE2 (uint32_t)0x000038 // 0 0 56 -#define RGB_BLUE3 (uint32_t)0x000054 // 0 0 84 -#define RGB_BLUE4 (uint32_t)0x000070 // 0 0 112 -#define RGB_BLUE5 (uint32_t)0x00008C // 0 0 140 -#define RGB_BLUE6 (uint32_t)0x0000A8 // 0 0 168 -#define RGB_BLUE7 (uint32_t)0x0000C4 // 0 0 196 -#define RGB_BLUE8 (uint32_t)0x0000E0 // 0 0 224 -#define RGB_BLUE9 (uint32_t)0x0000FC // 0 0 252 +#define RGB_YELLOW0 (uint32_t)0x101000 +#define RGB_YELLOW1 (uint32_t)0x1C1C00 +#define RGB_YELLOW2 (uint32_t)0x383800 +#define RGB_YELLOW3 (uint32_t)0x545400 +#define RGB_YELLOW4 (uint32_t)0x707000 +#define RGB_YELLOW5 (uint32_t)0x8C8C00 +#define RGB_YELLOW6 (uint32_t)0xA8A800 +#define RGB_YELLOW7 (uint32_t)0xC4C400 +#define RGB_YELLOW8 (uint32_t)0xE0E000 +#define RGB_YELLOW9 (uint32_t)0xFCFC00 -#define RGB_YELLOW0 (uint32_t)0x101000 // 16 16 0 -#define RGB_YELLOW1 (uint32_t)0x1C1C00 // 28 28 0 -#define RGB_YELLOW2 (uint32_t)0x383800 // 56 56 0 -#define RGB_YELLOW3 (uint32_t)0x545400 // 84 84 0 -#define RGB_YELLOW4 (uint32_t)0x707000 // 112 112 0 -#define RGB_YELLOW5 (uint32_t)0x8C8C00 // 140 140 0 -#define RGB_YELLOW6 (uint32_t)0xA8A800 // 168 168 0 -#define RGB_YELLOW7 (uint32_t)0xC4C400 // 196 196 0 -#define RGB_YELLOW8 (uint32_t)0xE0E000 // 224 224 0 -#define RGB_YELLOW9 (uint32_t)0xFCFC00 // 252 252 0 +#define RGB_RED0 (uint32_t)0x100000 +#define RGB_RED1 (uint32_t)0x1C0000 +#define RGB_RED2 (uint32_t)0x380000 +#define RGB_RED3 (uint32_t)0x540000 +#define RGB_RED4 (uint32_t)0x700000 +#define RGB_RED5 (uint32_t)0x8C0000 +#define RGB_RED6 (uint32_t)0xA80000 +#define RGB_RED7 (uint32_t)0xC40000 +#define RGB_RED8 (uint32_t)0xE00000 +#define RGB_RED9 (uint32_t)0xFC0000 -#define RGB_RED0 (uint32_t)0x100000 // 16 0 0 -#define RGB_RED1 (uint32_t)0x1C0000 // 28 0 0 -#define RGB_RED2 (uint32_t)0x380000 // 56 0 0 -#define RGB_RED3 (uint32_t)0x540000 // 84 0 0 -#define RGB_RED4 (uint32_t)0x700000 // 112 0 0 -#define RGB_RED5 (uint32_t)0x8C0000 // 140 0 0 -#define RGB_RED6 (uint32_t)0xA80000 // 168 0 0 -#define RGB_RED7 (uint32_t)0xC40000 // 196 0 0 -#define RGB_RED8 (uint32_t)0xE00000 // 224 0 0 -#define RGB_RED9 (uint32_t)0xFC0000 // 252 0 0 +#define RGB_GREEN0 (uint32_t)0x001000 +#define RGB_GREEN1 (uint32_t)0x001C00 +#define RGB_GREEN2 (uint32_t)0x003800 +#define RGB_GREEN3 (uint32_t)0x005400 +#define RGB_GREEN4 (uint32_t)0x007000 +#define RGB_GREEN5 (uint32_t)0x008C00 +#define RGB_GREEN6 (uint32_t)0x00A800 +#define RGB_GREEN7 (uint32_t)0x00C400 +#define RGB_GREEN8 (uint32_t)0x00E000 +#define RGB_GREEN9 (uint32_t)0x00FC00 -#define RGB_GREEN0 (uint32_t)0x001000 // 0 16 0 -#define RGB_GREEN1 (uint32_t)0x001C00 // 0 28 0 -#define RGB_GREEN2 (uint32_t)0x003800 // 0 56 0 -#define RGB_GREEN3 (uint32_t)0x005400 // 0 84 0 -#define RGB_GREEN4 (uint32_t)0x007000 // 0 112 0 -#define RGB_GREEN5 (uint32_t)0x008C00 // 0 140 0 -#define RGB_GREEN6 (uint32_t)0x00A800 // 0 168 0 -#define RGB_GREEN7 (uint32_t)0x00C400 // 0 196 0 -#define RGB_GREEN8 (uint32_t)0x00E000 // 0 224 0 -#define RGB_GREEN9 (uint32_t)0x00FC00 // 0 252 0 +#define RGB_CYAN0 (uint32_t)0x001010 +#define RGB_CYAN1 (uint32_t)0x001C1C +#define RGB_CYAN2 (uint32_t)0x003838 +#define RGB_CYAN3 (uint32_t)0x005454 +#define RGB_CYAN4 (uint32_t)0x007070 +#define RGB_CYAN5 (uint32_t)0x008C8C +#define RGB_CYAN6 (uint32_t)0x00A8A8 +#define RGB_CYAN7 (uint32_t)0x00C4C4 +#define RGB_CYAN8 (uint32_t)0x00E0E0 +#define RGB_CYAN9 (uint32_t)0x00FCFC -#define RGB_CYAN0 (uint32_t)0x001010 // 0 16 16 -#define RGB_CYAN1 (uint32_t)0x001C1C // 0 28 28 -#define RGB_CYAN2 (uint32_t)0x003838 // 0 56 56 -#define RGB_CYAN3 (uint32_t)0x005454 // 0 84 84 -#define RGB_CYAN4 (uint32_t)0x007070 // 0 112 112 -#define RGB_CYAN5 (uint32_t)0x008C8C // 0 140 140 -#define RGB_CYAN6 (uint32_t)0x00A8A8 // 0 168 168 -#define RGB_CYAN7 (uint32_t)0x00C4C4 // 0 196 196 -#define RGB_CYAN8 (uint32_t)0x00E0E0 // 0 224 224 -#define RGB_CYAN9 (uint32_t)0x00FCFC // 0 252 252 +#define RGB_MAGENTA0 (uint32_t)0x100010 +#define RGB_MAGENTA1 (uint32_t)0x1C001C +#define RGB_MAGENTA2 (uint32_t)0x380038 +#define RGB_MAGENTA3 (uint32_t)0x540054 +#define RGB_MAGENTA4 (uint32_t)0x700070 +#define RGB_MAGENTA5 (uint32_t)0x8C008C +#define RGB_MAGENTA6 (uint32_t)0xA800A8 +#define RGB_MAGENTA7 (uint32_t)0xC400C4 +#define RGB_MAGENTA8 (uint32_t)0xE000E0 +#define RGB_MAGENTA9 (uint32_t)0xFC00FC -#define RGB_MAGENTA0 (uint32_t)0x100010 // 16 0 16 -#define RGB_MAGENTA1 (uint32_t)0x1C001C // 28 0 28 -#define RGB_MAGENTA2 (uint32_t)0x380038 // 56 0 56 -#define RGB_MAGENTA3 (uint32_t)0x540054 // 84 0 84 -#define RGB_MAGENTA4 (uint32_t)0x700070 // 112 0 112 -#define RGB_MAGENTA5 (uint32_t)0x8C008C // 140 0 140 -#define RGB_MAGENTA6 (uint32_t)0xA800A8 // 168 0 168 -#define RGB_MAGENTA7 (uint32_t)0xC400C4 // 196 0 196 -#define RGB_MAGENTA8 (uint32_t)0xE000E0 // 224 0 224 -#define RGB_MAGENTA9 (uint32_t)0xFC00FC // 252 0 252 +#define RGB_ORANGE0 (uint32_t)0x100800 +#define RGB_ORANGE1 (uint32_t)0x1C0E00 +#define RGB_ORANGE2 (uint32_t)0x381C00 +#define RGB_ORANGE3 (uint32_t)0x542B00 +#define RGB_ORANGE4 (uint32_t)0x703900 +#define RGB_ORANGE5 (uint32_t)0x8C4800 +#define RGB_ORANGE6 (uint32_t)0xA85600 +#define RGB_ORANGE7 (uint32_t)0xC46500 +#define RGB_ORANGE8 (uint32_t)0xE07300 +#define RGB_ORANGE9 (uint32_t)0xFC8200 -#define RGB_ORANGE0 (uint32_t)0x100800 // 16 8 0 -#define RGB_ORANGE1 (uint32_t)0x1C0E00 // 28 14 0 -#define RGB_ORANGE2 (uint32_t)0x381C00 // 56 28 0 -#define RGB_ORANGE3 (uint32_t)0x542B00 // 84 43 0 -#define RGB_ORANGE4 (uint32_t)0x703900 // 112 57 0 -#define RGB_ORANGE5 (uint32_t)0x8C4800 // 140 72 0 -#define RGB_ORANGE6 (uint32_t)0xA85600 // 168 86 0 -#define RGB_ORANGE7 (uint32_t)0xC46500 // 196 101 0 -#define RGB_ORANGE8 (uint32_t)0xE07300 // 224 115 0 -#define RGB_ORANGE9 (uint32_t)0xFC8200 // 252 130 0 +#endif diff --git a/VortexEngine/src/Colors/ColorTypes.c b/VortexEngine/src/Colors/ColorTypes.c new file mode 100644 index 0000000000..e22d80745d --- /dev/null +++ b/VortexEngine/src/Colors/ColorTypes.c @@ -0,0 +1,553 @@ +#include "ColorTypes.h" + +#include "../Serial/ByteStream.h" + +hsv_to_rgb_algorithm g_hsv_rgb_alg = HSV_TO_RGB_GENERIC; + +void HSVColor_init(HSVColor *self) { + self->hue = 0; self->sat = 0; self->val = 0; +} + +void HSVColor_initHSV(HSVColor *self, uint8_t hue, uint8_t sat, uint8_t val) { + self->hue = hue; self->sat = sat; self->val = val; +} + +void HSVColor_initFromU32(HSVColor *self, uint32_t dwVal) { + HSVColor_init(self); + HSVColor_assignU32(self, &dwVal); +} + +void HSVColor_assignU32(HSVColor *self, const uint32_t *rhs) { + if (!(*rhs & HSV_BIT)) { + RGBColor tmp; + RGBColor_initFromU32(&tmp, *rhs); + HSVColor_assignRGB(self, &tmp); + return; + } + self->hue = ((*rhs >> 16) & 0xFF); + self->sat = ((*rhs >> 8) & 0xFF); + self->val = (*rhs & 0xFF); +} + +void HSVColor_copy(HSVColor *self, const HSVColor *rhs) { + self->hue = rhs->hue; + self->sat = rhs->sat; + self->val = rhs->val; +} + +void HSVColor_initFromRGB(HSVColor *self, const RGBColor *rhs) { + HSVColor_assignRGB(self, rhs); +} + +void HSVColor_assignRGB(HSVColor *self, const RGBColor *rhs) { +#if HSV_TO_RGB_ALGORITHM == 1 + HSVColor result = rgb_to_hsv_approx(rhs); + HSVColor_copy(self, &result); +#else + HSVColor result = rgb_to_hsv_generic(rhs); + HSVColor_copy(self, &result); +#endif +} + +bool HSVColor_equals(const HSVColor *self, const HSVColor *other) { + return (HSVColor_raw(other) == HSVColor_raw(self)); +} + +bool HSVColor_notEquals(const HSVColor *self, const HSVColor *other) { + return (HSVColor_raw(other) != HSVColor_raw(self)); +} + +bool HSVColor_empty(const HSVColor *self) { + return !self->hue && !self->sat && !self->val; +} + +void HSVColor_clear(HSVColor *self) { + self->hue = 0; self->sat = 0; self->val = 0; +} + +uint32_t HSVColor_raw(const HSVColor *self) { + return HSV_BIT | ((uint32_t)self->hue << 16) | ((uint32_t)self->sat << 8) | (uint32_t)self->val; +} + +void RGBColor_init(RGBColor *self) { + self->red = 0; self->green = 0; self->blue = 0; +} + +void RGBColor_initRGB(RGBColor *self, uint8_t red, uint8_t green, uint8_t blue) { + self->red = red; self->green = green; self->blue = blue; +} + +void RGBColor_initFromU32(RGBColor *self, uint32_t dwVal) { + RGBColor_init(self); + RGBColor_assignU32(self, &dwVal); +} + +void RGBColor_assignU32(RGBColor *self, const uint32_t *rhs) { + if ((*rhs & HSV_BIT) != 0) { + HSVColor hsv; + HSVColor_initFromU32(&hsv, *rhs); + RGBColor_assignHSV(self, &hsv); + return; + } + self->red = ((*rhs >> 16) & 0xFF); + self->green = ((*rhs >> 8) & 0xFF); + self->blue = (*rhs & 0xFF); +} + +void RGBColor_copy(RGBColor *self, const RGBColor *rhs) { + self->red = rhs->red; + self->green = rhs->green; + self->blue = rhs->blue; +} + +void RGBColor_initFromHSV(RGBColor *self, const HSVColor *rhs) { + RGBColor_assignHSV(self, rhs); +} + +void RGBColor_assignHSV(RGBColor *self, const HSVColor *rhs) { + switch (g_hsv_rgb_alg) { + case HSV_TO_RGB_RAINBOW: { + RGBColor result = hsv_to_rgb_rainbow(rhs); + RGBColor_copy(self, &result); + break; + } + case HSV_TO_RGB_RAW: { + RGBColor result = hsv_to_rgb_raw_C(rhs); + RGBColor_copy(self, &result); + break; + } + case HSV_TO_RGB_GENERIC: { + RGBColor result = hsv_to_rgb_generic(rhs); + RGBColor_copy(self, &result); + break; + } + } +} + +bool RGBColor_equals(const RGBColor *self, const RGBColor *other) { + return (RGBColor_raw(other) == RGBColor_raw(self)); +} + +bool RGBColor_notEquals(const RGBColor *self, const RGBColor *other) { + return (RGBColor_raw(other) != RGBColor_raw(self)); +} + +bool RGBColor_empty(const RGBColor *self) { + return !self->red && !self->green && !self->blue; +} + +void RGBColor_clear(RGBColor *self) { + self->red = 0; self->green = 0; self->blue = 0; +} + +void RGBColor_adjustBrightness(RGBColor *self, uint8_t fadeBy) { + self->red = (((int)self->red) * (int)(256 - fadeBy)) >> 8; + self->green = (((int)self->green) * (int)(256 - fadeBy)) >> 8; + self->blue = (((int)self->blue) * (int)(256 - fadeBy)) >> 8; +} + +uint32_t RGBColor_raw(const RGBColor *self) { + return ((uint32_t)self->red << 16) | ((uint32_t)self->green << 8) | (uint32_t)self->blue; +} + +bool RGBColor_serialize(const RGBColor *self, ByteStream *buffer) { + if (!ByteStream_serialize8(buffer, self->red)) return false; + if (!ByteStream_serialize8(buffer, self->green)) return false; + if (!ByteStream_serialize8(buffer, self->blue)) return false; + return true; +} + +bool RGBColor_unserialize(RGBColor *self, ByteStream *buffer) { + if (!ByteStream_unserialize8(buffer, &self->red)) return false; + if (!ByteStream_unserialize8(buffer, &self->green)) return false; + if (!ByteStream_unserialize8(buffer, &self->blue)) return false; + return true; +} + +#define SCALE8(i, scale) (((uint16_t)i * (uint16_t)(scale)) >> 8) +#define FIXFRAC8(N,D) (((N)*256)/(D)) + +RGBColor hsv_to_rgb_rainbow(const HSVColor *rhs) +{ + RGBColor col; + const uint8_t Y1 = 1; + const uint8_t Y2 = 0; + const uint8_t G2 = 0; + const uint8_t Gscale = 185; + + uint8_t hue = rhs->hue; + uint8_t sat = rhs->sat; + uint8_t val = rhs->val; + + uint8_t offset = hue & 0x1F; + + uint8_t offset8 = offset; + offset8 <<= 3; + + uint8_t third = SCALE8(offset8, (256 / 3)); + uint8_t r, g, b; + if (!(hue & 0x80)) { + if (!(hue & 0x40)) { + if (!(hue & 0x20)) { + r = 255 - third; + g = third; + b = 0; + } else { + if (Y1) { + r = 171; + g = 85 + third; + b = 0; + } + if (Y2) { + r = 170 + third; + uint8_t twothirds = SCALE8(offset8, ((256 * 2) / 3)); + g = 85 + twothirds; + b = 0; + } + } + } else { + if (!(hue & 0x20)) { + if (Y1) { + uint8_t twothirds = SCALE8(offset8, ((256 * 2) / 3)); + r = 171 - twothirds; + g = 170 + third; + b = 0; + } + if (Y2) { + r = 255 - offset8; + g = 255; + b = 0; + } + } else { + r = 0; + g = 255 - third; + b = third; + } + } + } else { + if (!(hue & 0x40)) { + if (!(hue & 0x20)) { + r = 0; + uint8_t twothirds = SCALE8(offset8, ((256 * 2) / 3)); + g = 171 - twothirds; + b = 85 + twothirds; + } else { + r = third; + g = 0; + b = 255 - third; + } + } else { + if (!(hue & 0x20)) { + r = 85 + third; + g = 0; + b = 171 - third; + } else { + r = 170 + third; + g = 0; + b = 85 - third; + } + } + } + + if (G2) g = g >> 1; + if (Gscale) g = SCALE8(g, Gscale); + + if (sat != 255) { + if (sat == 0) { + r = 255; b = 255; g = 255; + } else { + if (r) r = SCALE8(r, sat) + 1; + if (g) g = SCALE8(g, sat) + 1; + if (b) b = SCALE8(b, sat) + 1; + + uint8_t desat = 255 - sat; + desat = SCALE8(desat, desat); + + uint8_t brightness_floor = desat; + r += brightness_floor; + g += brightness_floor; + b += brightness_floor; + } + } + + if (val != 255) { + val = SCALE8(val, val); + if (val == 0) { + r = 0; g = 0; b = 0; + } else { + if (r) r = SCALE8(r, val) + 1; + if (g) g = SCALE8(g, val) + 1; + if (b) b = SCALE8(b, val) + 1; + } + } + + col.red = r; + col.green = g; + col.blue = b; + return col; +} + +RGBColor hsv_to_rgb_raw_C(const HSVColor *rhs) +{ + RGBColor col; + uint8_t value = rhs->val; + uint8_t saturation = rhs->sat; + + uint8_t invsat = 255 - saturation; + uint8_t brightness_floor = (value * invsat) / 256; + + uint8_t color_amplitude = value - brightness_floor; + + uint8_t section = rhs->hue / 0x40; + uint8_t offset = rhs->hue % 0x40; + + uint8_t rampup = offset; + uint8_t rampdown = (0x40 - 1) - offset; + + uint8_t rampup_amp_adj = (rampup * color_amplitude) / (256 / 4); + uint8_t rampdown_amp_adj = (rampdown * color_amplitude) / (256 / 4); + + uint8_t rampup_adj_with_floor = rampup_amp_adj + brightness_floor; + uint8_t rampdown_adj_with_floor = rampdown_amp_adj + brightness_floor; + + if (section) { + if (section == 1) { + col.red = brightness_floor; + col.green = rampdown_adj_with_floor; + col.blue = rampup_adj_with_floor; + } else { + col.red = rampup_adj_with_floor; + col.green = brightness_floor; + col.blue = rampdown_adj_with_floor; + } + } else { + col.red = rampdown_adj_with_floor; + col.green = rampup_adj_with_floor; + col.blue = brightness_floor; + } + return col; +} + +RGBColor hsv_to_rgb_generic(const HSVColor *rhs) +{ + unsigned char region, remainder, p, q, t; + RGBColor col; + + if (rhs->sat == 0) { + col.red = rhs->val; + col.green = rhs->val; + col.blue = rhs->val; + return col; + } + + region = rhs->hue / 43; + remainder = ((rhs->hue - (region * 43)) * 6); + + p = (uint8_t)(((uint16_t)(rhs->val) * (255 - rhs->sat)) >> 8); + q = (uint8_t)(((uint16_t)(rhs->val) * (255 - (((uint16_t)(rhs->sat) * remainder) >> 8))) >> 8); + t = (uint8_t)(((uint16_t)(rhs->val) * (255 - (((uint16_t)(rhs->sat) * (255 - remainder)) >> 8))) >> 8); + + switch (region) { + case 0: + col.red = rhs->val; col.green = t; col.blue = p; + break; + case 1: + col.red = q; col.green = rhs->val; col.blue = p; + break; + case 2: + col.red = p; col.green = rhs->val; col.blue = t; + break; + case 3: + col.red = p; col.green = q; col.blue = rhs->val; + break; + case 4: + col.red = t; col.green = p; col.blue = rhs->val; + break; + default: + col.red = rhs->val; col.green = p; col.blue = q; + break; + } + return col; +} + +static uint8_t sqrt16(uint16_t x) +{ + if (x <= 1) { + return (uint8_t)x; + } + uint8_t low = 1; + uint8_t mid = 0; + uint8_t hi = (x > 7904) ? 255 : ((x >> 5) + 8); + do { + mid = (low + hi) >> 1; + if ((uint16_t)(mid * mid) > x) { + hi = mid - 1; + } else { + if (mid == 255) { + return 255; + } + low = mid + 1; + } + } while (hi >= low); + return low - 1; +} + +static uint8_t qsub8(uint8_t i, uint8_t j) +{ + int t = i - j; + if (t < 0) { + return 0; + } + return t; +} + +enum +{ + HUE_RED = 0, + HUE_ORANGE = 32, + HUE_YELLOW = 64, + HUE_GREEN = 96, + HUE_AQUA = 128, + HUE_BLUE = 160, + HUE_PURPLE = 192, + HUE_PINK = 224 +}; + +HSVColor rgb_to_hsv_approx(const RGBColor *rhs) +{ + uint8_t r = rhs->red; + uint8_t g = rhs->green; + uint8_t b = rhs->blue; + uint8_t h, s, v; + + uint8_t desat = 255; + if (r < desat) desat = r; + if (g < desat) desat = g; + if (b < desat) desat = b; + + r -= desat; + g -= desat; + b -= desat; + + s = 255 - desat; + if (s != 255) { + s = 255 - sqrt16((255 - s) * 256); + } + + if ((r + g + b) == 0) { + HSVColor result; + HSVColor_initHSV(&result, 0, 0, 255 - s); + return result; + } + + if (s < 255) { + if (s == 0) s = 1; + uint32_t scaleup = 65535 / (s); + r = ((uint32_t)(r)*scaleup) / 256; + g = ((uint32_t)(g)*scaleup) / 256; + b = ((uint32_t)(b)*scaleup) / 256; + } + + uint16_t total = r + g + b; + + if (total < 255) { + if (total == 0) total = 1; + uint32_t scaleup = 65535 / (total); + r = ((uint32_t)(r)*scaleup) / 256; + g = ((uint32_t)(g)*scaleup) / 256; + b = ((uint32_t)(b)*scaleup) / 256; + } + + if (total > 255) { + v = 255; + } else { + if (((uint32_t)desat + (uint32_t)total) <= 255) { + v = desat + total; + } else { + v = 255; + } + if (v != 255) v = sqrt16(v * 256); + } + + uint8_t highest = r; + if (g > highest) highest = g; + if (b > highest) highest = b; + + if (highest == r) { + if (g == 0) { + h = (HUE_PURPLE + HUE_PINK) / 2; + h += SCALE8(qsub8(r, 128), FIXFRAC8(48, 128)); + } else if ((r - g) > g) { + h = HUE_RED; + h += SCALE8(g, FIXFRAC8(32, 85)); + } else { + h = HUE_ORANGE; + h += SCALE8(qsub8((g - 85) + (171 - r), 4), FIXFRAC8(32, 85)); + } + } else if (highest == g) { + if (b == 0) { + h = HUE_YELLOW; + uint8_t radj = SCALE8(qsub8(171, r), 47); + uint8_t gadj = SCALE8(qsub8(g, 171), 96); + uint8_t rgadj = radj + gadj; + uint8_t hueadv = rgadj / 2; + h += hueadv; + } else { + if ((g - b) > b) { + h = HUE_GREEN; + h += SCALE8(b, FIXFRAC8(32, 85)); + } else { + h = HUE_AQUA; + h += SCALE8(qsub8(b, 85), FIXFRAC8(8, 42)); + } + } + } else { + if (r == 0) { + h = HUE_AQUA + ((HUE_BLUE - HUE_AQUA) / 4); + h += SCALE8(qsub8(b, 128), FIXFRAC8(24, 128)); + } else if ((b - r) > r) { + h = HUE_BLUE; + h += SCALE8(r, FIXFRAC8(32, 85)); + } else { + h = HUE_PURPLE; + h += SCALE8(qsub8(r, 85), FIXFRAC8(32, 85)); + } + } + h += 1; + + HSVColor result; + HSVColor_initHSV(&result, h, s, v); + return result; +} + +HSVColor rgb_to_hsv_generic(const RGBColor *rhs) +{ + unsigned char rgbMin, rgbMax; + rgbMin = rhs->red < rhs->green ? (rhs->red < rhs->blue ? rhs->red : rhs->blue) : (rhs->green < rhs->blue ? rhs->green : rhs->blue); + rgbMax = rhs->red > rhs->green ? (rhs->red > rhs->blue ? rhs->red : rhs->blue) : (rhs->green > rhs->blue ? rhs->green : rhs->blue); + + HSVColor hsv; + HSVColor_init(&hsv); + + hsv.val = rgbMax; + if (hsv.val == 0) { + hsv.hue = 0; + hsv.sat = 0; + return hsv; + } + + hsv.sat = 255 * (long)(rgbMax - rgbMin) / hsv.val; + if (hsv.sat == 0) { + hsv.hue = 0; + return hsv; + } + + if (rgbMax == rhs->red) { + hsv.hue = 0 + 43 * (rhs->green - rhs->blue) / (rgbMax - rgbMin); + } else if (rgbMax == rhs->green) { + hsv.hue = 85 + 43 * (rhs->blue - rhs->red) / (rgbMax - rgbMin); + } else { + hsv.hue = 171 + 43 * (rhs->red - rhs->green) / (rgbMax - rgbMin); + } + return hsv; +} diff --git a/VortexEngine/src/Colors/ColorTypes.h b/VortexEngine/src/Colors/ColorTypes.h index c4e92b2936..5fa212ca9d 100644 --- a/VortexEngine/src/Colors/ColorTypes.h +++ b/VortexEngine/src/Colors/ColorTypes.h @@ -2,108 +2,76 @@ #define COLOR_H #include +#include #include "../VortexConfig.h" #include "ColorConstants.h" -enum hsv_to_rgb_algorithm : uint8_t -{ +typedef uint8_t hsv_to_rgb_algorithm; + +enum { HSV_TO_RGB_GENERIC, HSV_TO_RGB_RAW, HSV_TO_RGB_RAINBOW }; -// global hsv to rgb algorithm selector, switch this to control -// all hsv to rgb conversions extern hsv_to_rgb_algorithm g_hsv_rgb_alg; -class ByteStream; -class RGBColor; - -class HSVColor -{ -public: - HSVColor(); - HSVColor(uint8_t hue, uint8_t sat, uint8_t val); - - // assignment from uint32_t - HSVColor(uint32_t dwVal); - HSVColor &operator=(const uint32_t &rhs); - - // copy/assignment construction - HSVColor(const HSVColor &rhs); - HSVColor &operator=(const HSVColor &rhs); +typedef struct ByteStream ByteStream; - // construction/assignment from RGB - HSVColor(const RGBColor &rhs); - HSVColor &operator=(const RGBColor &rhs); +typedef struct RGBColor_t RGBColor; +typedef struct HSVColor_t HSVColor; - // equality operators - bool operator==(const HSVColor &other) const; - bool operator!=(const HSVColor &other) const; - - bool empty() const; - void clear(); - - uint32_t raw() const { return HSV_BIT | ((uint32_t)hue << 16) | ((uint32_t)sat << 8) | (uint32_t)val; } - - // public members +struct HSVColor_t { uint8_t hue; uint8_t sat; uint8_t val; }; -class RGBColor -{ -public: - RGBColor(); - RGBColor(uint8_t red, uint8_t green, uint8_t blue); - - // assignment from uint32_t - RGBColor(uint32_t dwVal); - RGBColor &operator=(const uint32_t &rhs); - - // copy/assignment construction - RGBColor(const RGBColor &rhs); - RGBColor &operator=(const RGBColor &rhs); - - // construction/assignment from HSV - RGBColor(const HSVColor &rhs); - RGBColor &operator=(const HSVColor &rhs); - - // equality operators - bool operator==(const RGBColor &other) const; - bool operator!=(const RGBColor &other) const; - - bool empty() const; - void clear(); - - RGBColor adjustBrightness(uint8_t fadeBy); - bool serialize(ByteStream &buffer) const; - bool unserialize(ByteStream &buffer); - - uint32_t raw() const { return ((uint32_t)red << 16) | ((uint32_t)green << 8) | (uint32_t)blue; } - - // public members +struct RGBColor_t { uint8_t red; uint8_t green; uint8_t blue; }; -// Stolen from FastLED hsv to rgb full rainbox where all colours -// are given equal weight, this makes for-example yellow larger -// best to use this function as it is the legacy choice -RGBColor hsv_to_rgb_rainbow(const HSVColor &rhs); -// stolen from FastLED raw hsv to rgb conversion, idk what is special -// about this one but I think the hue stops at 190 maybe? -RGBColor hsv_to_rgb_raw_C(const HSVColor &rhs); -// generic hsv to rgb conversion nothing special -RGBColor hsv_to_rgb_generic(const HSVColor &rhs); - -// Convert rgb to hsv with slower approximation that matches -// the fastled hsv to rgb 'rainbow' -HSVColor rgb_to_hsv_approx(const RGBColor &rhs); -// Convert rgb to hsv with generic fast method -HSVColor rgb_to_hsv_generic(const RGBColor &rhs); +void HSVColor_init(HSVColor *self); +void HSVColor_initHSV(HSVColor *self, uint8_t hue, uint8_t sat, uint8_t val); +void HSVColor_initFromU32(HSVColor *self, uint32_t dwVal); +void HSVColor_copy(HSVColor *self, const HSVColor *rhs); +void HSVColor_initFromRGB(HSVColor *self, const RGBColor *rhs); +void HSVColor_assignRGB(HSVColor *self, const RGBColor *rhs); +void HSVColor_assignU32(HSVColor *self, const uint32_t *rhs); +bool HSVColor_equals(const HSVColor *self, const HSVColor *other); +bool HSVColor_notEquals(const HSVColor *self, const HSVColor *other); +bool HSVColor_empty(const HSVColor *self); +void HSVColor_clear(HSVColor *self); +uint32_t HSVColor_raw(const HSVColor *self); + +void RGBColor_init(RGBColor *self); +void RGBColor_initRGB(RGBColor *self, uint8_t red, uint8_t green, uint8_t blue); +void RGBColor_initFromU32(RGBColor *self, uint32_t dwVal); +void RGBColor_copy(RGBColor *self, const RGBColor *rhs); +void RGBColor_initFromHSV(RGBColor *self, const HSVColor *rhs); +void RGBColor_assignU32(RGBColor *self, const uint32_t *rhs); +void RGBColor_assignHSV(RGBColor *self, const HSVColor *rhs); +bool RGBColor_equals(const RGBColor *self, const RGBColor *other); +bool RGBColor_notEquals(const RGBColor *self, const RGBColor *other); +bool RGBColor_empty(const RGBColor *self); +void RGBColor_clear(RGBColor *self); +void RGBColor_adjustBrightness(RGBColor *self, uint8_t fadeBy); +uint32_t RGBColor_raw(const RGBColor *self); + +bool RGBColor_serialize(const RGBColor *self, ByteStream *buffer); +bool RGBColor_unserialize(RGBColor *self, ByteStream *buffer); + +RGBColor hsv_to_rgb_rainbow(const HSVColor *rhs); +RGBColor hsv_to_rgb_raw_C(const HSVColor *rhs); +RGBColor hsv_to_rgb_generic(const HSVColor *rhs); +HSVColor rgb_to_hsv_approx(const RGBColor *rhs); +HSVColor rgb_to_hsv_generic(const RGBColor *rhs); + +// Convert uint32_t color value to RGBColor struct (C has no implicit uint32_t→RGBColor) +#define RGB_COLOR(v) ((RGBColor){ .red = (uint8_t)((v) >> 16), .green = (uint8_t)((v) >> 8), .blue = (uint8_t)(v) }) +#define HSV_COLOR(v) ((HSVColor){ .hue = (uint8_t)((v) >> 16), .sat = (uint8_t)((v) >> 8), .val = (uint8_t)(v) }) #endif diff --git a/VortexEngine/src/Colors/Colorset.c b/VortexEngine/src/Colors/Colorset.c new file mode 100644 index 0000000000..abb9528086 --- /dev/null +++ b/VortexEngine/src/Colors/Colorset.c @@ -0,0 +1,471 @@ +#include "Colorset.h" +#include "../Serial/ByteStream.h" +#include "../Random/Random.h" +#include "../Memory/Memory.h" +#include "../Log/Log.h" +#include + +#define INDEX_NONE UINT8_MAX + +void Colorset_init(Colorset *self) +{ + memset(self->palette, 0, sizeof(self->palette)); + self->curIndex = INDEX_NONE; + self->numColors = 0; +} + +void Colorset_initColors(Colorset *self, RGBColor c1, RGBColor c2, RGBColor c3, RGBColor c4, RGBColor c5, RGBColor c6, RGBColor c7, RGBColor c8) +{ + Colorset_init(self); + Colorset_initPalette(self, c1, c2, c3, c4, c5, c6, c7, c8); +} + +void Colorset_initFromU32s(Colorset *self, uint8_t numCols, const uint32_t *cols) +{ + Colorset_init(self); + if (numCols > MAX_COLOR_SLOTS) { + numCols = MAX_COLOR_SLOTS; + } + for (uint8_t i = 0; i < numCols; ++i) { + RGBColor col; + RGBColor_initFromU32(&col, cols[i]); + Colorset_addColor(self, col); + } +} + +void Colorset_copy(Colorset *self, const Colorset *other) +{ + Colorset_initPaletteInternal(self, other->numColors); + for (uint8_t i = 0; i < other->numColors; ++i) { + self->palette[i] = other->palette[i]; + } + Colorset_resetIndex(self); +} + +void Colorset_destroy(Colorset *self) +{ + Colorset_clear(self); +} + +bool Colorset_equals(const Colorset *self, const Colorset *other) +{ + return (self->numColors == other->numColors) && + (memcmp(self->palette, other->palette, self->numColors * sizeof(RGBColor)) == 0); +} + +bool Colorset_notEquals(const Colorset *self, const Colorset *other) +{ + return !Colorset_equals(self, other); +} + +bool Colorset_equalsPtr(const Colorset *self, const Colorset *set) +{ + if (!set) { + return false; + } + return Colorset_equals(self, set); +} + +void Colorset_initPalette(Colorset *self, RGBColor c1, RGBColor c2, RGBColor c3, RGBColor c4, RGBColor c5, RGBColor c6, RGBColor c7, RGBColor c8) +{ + Colorset_clear(self); + if (!RGBColor_empty(&c1)) Colorset_addColor(self, c1); + if (!RGBColor_empty(&c2)) Colorset_addColor(self, c2); + if (!RGBColor_empty(&c3)) Colorset_addColor(self, c3); + if (!RGBColor_empty(&c4)) Colorset_addColor(self, c4); + if (!RGBColor_empty(&c5)) Colorset_addColor(self, c5); + if (!RGBColor_empty(&c6)) Colorset_addColor(self, c6); + if (!RGBColor_empty(&c7)) Colorset_addColor(self, c7); + if (!RGBColor_empty(&c8)) Colorset_addColor(self, c8); +} + +void Colorset_clear(Colorset *self) +{ + memset(self->palette, 0, sizeof(self->palette)); + self->numColors = 0; + Colorset_resetIndex(self); +} + +bool Colorset_addColor(Colorset *self, RGBColor col) +{ + if (self->numColors >= MAX_COLOR_SLOTS) { + return false; + } + self->palette[self->numColors] = col; + self->numColors++; + return true; +} + +bool Colorset_addColorHSV(Colorset *self, uint8_t hue, uint8_t sat, uint8_t val) +{ + RGBColor col; + HSVColor hsv; + HSVColor_initHSV(&hsv, hue, sat, val); + RGBColor_assignHSV(&col, &hsv); + return Colorset_addColor(self, col); +} + +void Colorset_addColorWithValueStyle(Colorset *self, Random *ctx, uint8_t hue, uint8_t sat, ValueStyle valStyle, uint8_t numColors, uint8_t colorPos) +{ + if (numColors == 1) { + Colorset_addColorHSV(self, hue, sat, Random_next8(ctx, 16, 255)); + return; + } + switch (valStyle) { + default: + case VAL_STYLE_RANDOM: + Colorset_addColorHSV(self, hue, sat, 85 * Random_next8(ctx, 1, 4)); + break; + case VAL_STYLE_LOW_FIRST_COLOR: + if (self->numColors == 0) { + Colorset_addColorHSV(self, hue, sat, Random_next8(ctx, 0, 86)); + } else { + Colorset_addColorHSV(self, hue, sat, 85 * Random_next8(ctx, 1, 4)); + } + break; + case VAL_STYLE_HIGH_FIRST_COLOR: + if (self->numColors == 0) { + Colorset_addColorHSV(self, hue, sat, 255); + } else { + Colorset_addColorHSV(self, hue, sat, Random_next8(ctx, 0, 86)); + } + break; + case VAL_STYLE_ALTERNATING: + if (self->numColors % 2 == 0) { + Colorset_addColorHSV(self, hue, sat, 255); + } else { + Colorset_addColorHSV(self, hue, sat, 85); + } + break; + case VAL_STYLE_ASCENDING: + Colorset_addColorHSV(self, hue, sat, (colorPos + 1) * (255 / numColors)); + break; + case VAL_STYLE_DESCENDING: + Colorset_addColorHSV(self, hue, sat, 255 - (colorPos * (255 / numColors))); + break; + case VAL_STYLE_CONSTANT: + Colorset_addColorHSV(self, hue, sat, 255); + } +} + +void Colorset_removeColor(Colorset *self, uint8_t index) +{ + if (index >= self->numColors) { + return; + } + for (uint8_t i = index; i < (self->numColors - 1); ++i) { + self->palette[i] = self->palette[i + 1]; + } + self->numColors--; + RGBColor_clear(&self->palette[self->numColors]); +} + +void Colorset_randomize(Colorset *self, Random *ctx, uint8_t numColors) +{ + Colorset_clear(self); + if (!numColors) { + numColors = Random_next8(ctx, 2, 9); + } + ValueStyle valStyle = (ValueStyle)Random_next8(ctx, 0, VAL_STYLE_COUNT); + + for (uint8_t i = 0; i < numColors; ++i) { + uint8_t sat = Random_next8(ctx, 0, 255); + uint8_t hue = Random_next8(ctx, 0, 255); + Colorset_addColorWithValueStyle(self, ctx, hue, sat, valStyle, numColors, i); + } +} + +void Colorset_randomizeColors(Colorset *self, Random *ctx, uint8_t numColors, ColorMode mode) +{ + Colorset_clear(self); + if (!numColors) { + numColors = Random_next8(ctx, (mode == COLOR_MODE_MONOCHROMATIC) ? 2 : 1, 9); + } + uint8_t randomizedHue = Random_next8(ctx, 0, 255); + uint8_t colorGap = 0; + if (mode == COLOR_MODE_THEORY && numColors > 1) { + colorGap = Random_next8(ctx, 16, 256 / (numColors - 1)); + } + ValueStyle valStyle = (ValueStyle)Random_next8(ctx, 0, VAL_STYLE_COUNT); + uint8_t doubleStyle = 0; + if (numColors <= 7) { + doubleStyle = Random_next8(ctx, 0, 1); + } + if (numColors <= 4) { + doubleStyle = Random_next8(ctx, 0, 2); + } + for (uint8_t i = 0; i < numColors; i++) { + uint8_t hueToUse; + uint8_t valueToUse = 255; + if (mode == COLOR_MODE_THEORY) { + hueToUse = (randomizedHue + (i * colorGap)); + } else if (mode == COLOR_MODE_MONOCHROMATIC) { + hueToUse = randomizedHue; + valueToUse = 255 - (i * (256 / numColors)); + } else { + hueToUse = (randomizedHue + (256 / numColors) * i); + } + Colorset_addColorWithValueStyle(self, ctx, hueToUse, valueToUse, valStyle, numColors, i); + if (doubleStyle == 2 || (doubleStyle == 1 && !i)) { + Colorset_addColorWithValueStyle(self, ctx, hueToUse, valueToUse, valStyle, numColors, i); + } + } +} + +void Colorset_randomizeColors2(Colorset *self, Random *ctx, ColorMode2 mode) +{ + Colorset_clear(self); + uint8_t primaryHue = Random_next8(ctx, 0, 255); + if (mode == COLOR_MODE2_DOUBLE_SPLIT_COMPLIMENTARY) { + uint8_t splitGap = Random_next8(ctx, 1, 64); + ValueStyle valStyle = (ValueStyle)Random_next8(ctx, 0, VAL_STYLE_COUNT); + Colorset_addColorWithValueStyle(self, ctx, (primaryHue + splitGap + 128), 255, valStyle, 5, 0); + Colorset_addColorWithValueStyle(self, ctx, (primaryHue - splitGap), 255, valStyle, 5, 1); + Colorset_addColorWithValueStyle(self, ctx, primaryHue, 255, valStyle, 5, 2); + Colorset_addColorWithValueStyle(self, ctx, (primaryHue + splitGap), 255, valStyle, 5, 3); + Colorset_addColorWithValueStyle(self, ctx, (primaryHue - splitGap + 128), 255, valStyle, 5, 4); + } else if (mode == COLOR_MODE2_TETRADIC) { + uint8_t secondaryHue = Random_next8(ctx, 0, 255); + ValueStyle valStyle = (ValueStyle)Random_next8(ctx, 0, VAL_STYLE_COUNT); + Colorset_addColorWithValueStyle(self, ctx, primaryHue, 255, valStyle, 4, 0); + Colorset_addColorWithValueStyle(self, ctx, secondaryHue, 255, valStyle, 4, 1); + Colorset_addColorWithValueStyle(self, ctx, (primaryHue + 128), 255, valStyle, 4, 2); + Colorset_addColorWithValueStyle(self, ctx, (secondaryHue + 128), 255, valStyle, 4, 3); + } +} + +void Colorset_randomizeSolid(Colorset *self, Random *ctx) +{ + Colorset_randomizeColors(self, ctx, 1, COLOR_MODE_EVENLY_SPACED); +} + +void Colorset_randomizeComplimentary(Colorset *self, Random *ctx) +{ + Colorset_randomizeColors(self, ctx, 2, COLOR_MODE_EVENLY_SPACED); +} + +void Colorset_randomizeTriadic(Colorset *self, Random *ctx) +{ + Colorset_randomizeColors(self, ctx, 3, COLOR_MODE_EVENLY_SPACED); +} + +void Colorset_randomizeSquare(Colorset *self, Random *ctx) +{ + Colorset_randomizeColors(self, ctx, 4, COLOR_MODE_EVENLY_SPACED); +} + +void Colorset_randomizePentadic(Colorset *self, Random *ctx) +{ + Colorset_randomizeColors(self, ctx, 5, COLOR_MODE_EVENLY_SPACED); +} + +void Colorset_randomizeRainbow(Colorset *self, Random *ctx) +{ + Colorset_randomizeColors(self, ctx, 8, COLOR_MODE_EVENLY_SPACED); +} + +void Colorset_adjustBrightness(Colorset *self, uint8_t fadeby) +{ + for (uint8_t i = 0; i < self->numColors; ++i) { + RGBColor_adjustBrightness(&self->palette[i], fadeby); + } +} + +RGBColor Colorset_get(const Colorset *self, uint8_t index) +{ + RGBColor result; + if (index >= self->numColors) { + RGBColor_initRGB(&result, 0, 0, 0); + return result; + } + return self->palette[index]; +} + +void Colorset_set(Colorset *self, uint8_t index, RGBColor col) +{ + if (index >= self->numColors) { + if (!Colorset_addColor(self, col)) { + ERROR_LOGF("Failed to add new color at index %u", index); + } + return; + } + self->palette[index] = col; +} + +void Colorset_skip(Colorset *self, int32_t amount) +{ + if (!self->numColors) { + return; + } + if (self->curIndex == INDEX_NONE) { + self->curIndex = 0; + } + amount %= (int32_t)self->numColors; + self->curIndex = ((int32_t)self->curIndex + (int32_t)amount) % (int32_t)self->numColors; + if (self->curIndex > self->numColors) { + self->curIndex += self->numColors; + } +} + +RGBColor Colorset_cur(Colorset *self) +{ + RGBColor result; + if (self->curIndex >= self->numColors) { + RGBColor_initRGB(&result, 0, 0, 0); + return result; + } + if (self->curIndex == INDEX_NONE) { + return self->palette[0]; + } + return self->palette[self->curIndex]; +} + +void Colorset_setCurIndex(Colorset *self, uint8_t index) +{ + if (!self->numColors) { + return; + } + if (index > (self->numColors - 1)) { + return; + } + self->curIndex = index; +} + +void Colorset_resetIndex(Colorset *self) +{ + self->curIndex = INDEX_NONE; +} + +uint8_t Colorset_curIndex(const Colorset *self) +{ + return self->curIndex; +} + +RGBColor Colorset_getPrev(Colorset *self) +{ + RGBColor result; + if (!self->numColors) { + RGBColor_initFromU32(&result, 0); + return result; + } + if (self->curIndex == 0 || self->curIndex == INDEX_NONE) { + self->curIndex = Colorset_numColors(self) - 1; + } else { + self->curIndex--; + } + return self->palette[self->curIndex]; +} + +RGBColor Colorset_getNext(Colorset *self) +{ + RGBColor result; + if (!self->numColors) { + RGBColor_initFromU32(&result, 0); + return result; + } + self->curIndex++; + self->curIndex %= Colorset_numColors(self); + return self->palette[self->curIndex]; +} + +RGBColor Colorset_peek(const Colorset *self, int32_t offset) +{ + RGBColor result; + if (!self->numColors) { + RGBColor_initFromU32(&result, 0); + return result; + } + uint8_t nextIndex = 0; + if (offset >= 0) { + nextIndex = (self->curIndex + offset) % Colorset_numColors(self); + } else { + if (offset < -1 * (int32_t)(Colorset_numColors(self))) { + RGBColor_initFromU32(&result, 0); + return result; + } + nextIndex = ((self->curIndex + Colorset_numColors(self)) + (int)offset) % Colorset_numColors(self); + } + return self->palette[nextIndex]; +} + +RGBColor Colorset_peekNext(const Colorset *self) +{ + return Colorset_peek(self, 1); +} + +uint8_t Colorset_numColors(const Colorset *self) +{ + return self->numColors; +} + +bool Colorset_onStart(const Colorset *self) +{ + return (self->curIndex == 0); +} + +bool Colorset_onEnd(const Colorset *self) +{ + if (!self->numColors) { + return false; + } + return (self->curIndex == self->numColors - 1); +} + +bool Colorset_serialize(const Colorset *self, ByteStream *buffer) +{ + if (!ByteStream_serialize8(buffer, self->numColors)) { + return false; + } + for (uint8_t i = 0; i < self->numColors; ++i) { + if (!ByteStream_serialize8(buffer, self->palette[i].red)) { + return false; + } + } + for (uint8_t i = 0; i < self->numColors; ++i) { + if (!ByteStream_serialize8(buffer, self->palette[i].green)) { + return false; + } + } + for (uint8_t i = 0; i < self->numColors; ++i) { + if (!ByteStream_serialize8(buffer, self->palette[i].blue)) { + return false; + } + } + return true; +} + +bool Colorset_unserialize(Colorset *self, ByteStream *buffer) +{ + if (!ByteStream_unserialize8(buffer, &self->numColors)) { + return false; + } + if (self->numColors > MAX_COLOR_SLOTS) { + return false; + } + if (!Colorset_initPaletteInternal(self, self->numColors)) { + return false; + } + for (uint8_t i = 0; i < self->numColors; ++i) { + if (!ByteStream_unserialize8(buffer, &self->palette[i].red)) { + return false; + } + } + for (uint8_t i = 0; i < self->numColors; ++i) { + if (!ByteStream_unserialize8(buffer, &self->palette[i].green)) { + return false; + } + } + for (uint8_t i = 0; i < self->numColors; ++i) { + if (!ByteStream_unserialize8(buffer, &self->palette[i].blue)) { + return false; + } + } + return true; +} + +bool Colorset_initPaletteInternal(Colorset *self, uint8_t numColors) +{ + Colorset_clear(self); + self->numColors = numColors; + return true; +} diff --git a/VortexEngine/src/Colors/Colorset.h b/VortexEngine/src/Colors/Colorset.h index 2c34f32c0d..d210b6f0e1 100644 --- a/VortexEngine/src/Colors/Colorset.h +++ b/VortexEngine/src/Colors/Colorset.h @@ -2,161 +2,83 @@ #define COLORSET_H #include "ColorTypes.h" - #include "../VortexConfig.h" +#include + +typedef struct ByteStream ByteStream; +#include "../Random/Random.h" + +typedef uint8_t ValueStyle; +enum { + VAL_STYLE_RANDOM = 0, + VAL_STYLE_LOW_FIRST_COLOR, + VAL_STYLE_HIGH_FIRST_COLOR, + VAL_STYLE_ALTERNATING, + VAL_STYLE_ASCENDING, + VAL_STYLE_DESCENDING, + VAL_STYLE_CONSTANT, + VAL_STYLE_COUNT +}; -class ByteStream; -class Random; - -class Colorset -{ -public: - // empty colorset - Colorset(); - // constructor for 1-8 color slots - Colorset(RGBColor c1, RGBColor c2 = RGB_OFF, RGBColor c3 = RGB_OFF, - RGBColor c4 = RGB_OFF, RGBColor c5 = RGB_OFF, RGBColor c6 = RGB_OFF, - RGBColor c7 = RGB_OFF, RGBColor c8 = RGB_OFF); - Colorset(uint8_t numCols, const uint32_t *cols); - ~Colorset(); - - // move constructor - Colorset(Colorset &&other) noexcept; - - // copy and assignment operators - Colorset(const Colorset &other); - void operator=(const Colorset &other); - - // equality operators - bool operator==(const Colorset &other) const; - bool operator!=(const Colorset &other) const; - - // initialize the colorset - void init(RGBColor c1 = RGB_OFF, RGBColor c2 = RGB_OFF, RGBColor c3 = RGB_OFF, - RGBColor c4 = RGB_OFF, RGBColor c5 = RGB_OFF, RGBColor c6 = RGB_OFF, - RGBColor c7 = RGB_OFF, RGBColor c8 = RGB_OFF); - - // clear the colorset - void clear(); - - // pointer comparison - bool equals(const Colorset &set) const; - bool equals(const Colorset *set) const; - - // index operator to access color index - RGBColor operator[](int index) const; - - enum ValueStyle : uint8_t - { - // Random values - VAL_STYLE_RANDOM = 0, - // First color low value, the rest are random - VAL_STYLE_LOW_FIRST_COLOR, - // First color high value, the rest are low - VAL_STYLE_HIGH_FIRST_COLOR, - // Alternat between high and low value - VAL_STYLE_ALTERNATING, - // Ascending values from low to high - VAL_STYLE_ASCENDING, - // Descending values from high to low - VAL_STYLE_DESCENDING, - // Constant value - VAL_STYLE_CONSTANT, - // Total number of value styles - VAL_STYLE_COUNT - }; - - // add a single color - bool addColor(RGBColor col); - bool addColorHSV(uint8_t hue, uint8_t sat, uint8_t val); - void addColorWithValueStyle(Random &ctx, uint8_t hue, uint8_t sat, - ValueStyle valStyle, uint8_t numColors, uint8_t colorPos); - void removeColor(uint8_t index); - - // randomize a colorset with a specific number of colors with - // various different randomization techniques - void randomize(Random &ctx, uint8_t numColors = 0); - - // function to randomize the colors with various different modes of randomization - enum ColorMode { - THEORY, - MONOCHROMATIC, - EVENLY_SPACED - }; - void randomizeColors(Random &ctx, uint8_t numColors, ColorMode mode); - - // similar function but with some different modes - enum ColorMode2 { - DOUBLE_SPLIT_COMPLIMENTARY, - TETRADIC - }; - void randomizeColors2(Random &ctx, ColorMode2 mode); - - // wrappers for various spacings - void randomizeSolid(Random &ctx) { randomizeColors(ctx, 1, Colorset::ColorMode::EVENLY_SPACED); } - void randomizeComplimentary(Random &ctx) { randomizeColors(ctx, 2, Colorset::ColorMode::EVENLY_SPACED); } - void randomizeTriadic(Random &ctx) { randomizeColors(ctx, 3, Colorset::ColorMode::EVENLY_SPACED); } - void randomizeSquare(Random &ctx) { randomizeColors(ctx, 4, Colorset::ColorMode::EVENLY_SPACED); } - void randomizePentadic(Random &ctx) { randomizeColors(ctx, 5, Colorset::ColorMode::EVENLY_SPACED); } - void randomizeRainbow(Random &ctx) { randomizeColors(ctx, 8, Colorset::ColorMode::EVENLY_SPACED); } - - // fade all of the colors in the set - void adjustBrightness(uint8_t fadeby); - - // get a color from the colorset - RGBColor get(uint8_t index = 0) const; - - // set an rgb color in a slot, or add a new color if you specify - // a slot higher than the number of colors in the colorset - void set(uint8_t index, RGBColor col); - - // skip some amount of colors - void skip(int32_t amount = 1); - - // get current color in cycle - RGBColor cur(); - - // set the current index of the colorset - void setCurIndex(uint8_t index); - void resetIndex(); - - // the current index - uint8_t curIndex() const { return m_curIndex; } - - // get the prev color in cycle - RGBColor getPrev(); - - // get the next color in cycle - RGBColor getNext(); - - // peek at the color indexes from current but don't iterate - RGBColor peek(int32_t offset) const; - - // better wording for peek 1 ahead - RGBColor peekNext() const { return peek(1); } - - // the number of colors in the palette - uint8_t numColors() const { return m_numColors; } - - // whether the colorset is currently on the first color or last color - bool onStart() const; - bool onEnd() const; - - // serialize the colorset to save/load - bool serialize(ByteStream &buffer) const; - bool unserialize(ByteStream &buffer); - -private: - // pre-allocate the palette - bool initPalette(uint8_t numColors); +typedef uint8_t ColorMode; +enum { + COLOR_MODE_THEORY, + COLOR_MODE_MONOCHROMATIC, + COLOR_MODE_EVENLY_SPACED +}; - // palette of colors - RGBColor m_palette[MAX_COLOR_SLOTS]; - // the current index, starts at UINT8_MAX so that - // the very first call to getNext will iterate to 0 - uint8_t m_curIndex; - // the actual number of colors in the set - uint8_t m_numColors; +typedef uint8_t ColorMode2; +enum { + COLOR_MODE2_DOUBLE_SPLIT_COMPLIMENTARY, + COLOR_MODE2_TETRADIC }; +typedef struct Colorset_s { + RGBColor palette[MAX_COLOR_SLOTS]; + uint8_t curIndex; + uint8_t numColors; +} Colorset; + +void Colorset_init(Colorset *self); +void Colorset_initColors(Colorset *self, RGBColor c1, RGBColor c2, RGBColor c3, RGBColor c4, RGBColor c5, RGBColor c6, RGBColor c7, RGBColor c8); +void Colorset_initFromU32s(Colorset *self, uint8_t numCols, const uint32_t *cols); +void Colorset_copy(Colorset *self, const Colorset *other); +void Colorset_destroy(Colorset *self); +bool Colorset_equals(const Colorset *self, const Colorset *other); +bool Colorset_notEquals(const Colorset *self, const Colorset *other); +bool Colorset_equalsPtr(const Colorset *self, const Colorset *set); +void Colorset_initPalette(Colorset *self, RGBColor c1, RGBColor c2, RGBColor c3, RGBColor c4, RGBColor c5, RGBColor c6, RGBColor c7, RGBColor c8); +void Colorset_clear(Colorset *self); +bool Colorset_addColor(Colorset *self, RGBColor col); +bool Colorset_addColorHSV(Colorset *self, uint8_t hue, uint8_t sat, uint8_t val); +void Colorset_addColorWithValueStyle(Colorset *self, Random *ctx, uint8_t hue, uint8_t sat, ValueStyle valStyle, uint8_t numColors, uint8_t colorPos); +void Colorset_removeColor(Colorset *self, uint8_t index); +void Colorset_randomize(Colorset *self, Random *ctx, uint8_t numColors); +void Colorset_randomizeColors(Colorset *self, Random *ctx, uint8_t numColors, ColorMode mode); +void Colorset_randomizeColors2(Colorset *self, Random *ctx, ColorMode2 mode); +void Colorset_randomizeSolid(Colorset *self, Random *ctx); +void Colorset_randomizeComplimentary(Colorset *self, Random *ctx); +void Colorset_randomizeTriadic(Colorset *self, Random *ctx); +void Colorset_randomizeSquare(Colorset *self, Random *ctx); +void Colorset_randomizePentadic(Colorset *self, Random *ctx); +void Colorset_randomizeRainbow(Colorset *self, Random *ctx); +void Colorset_adjustBrightness(Colorset *self, uint8_t fadeby); +RGBColor Colorset_get(const Colorset *self, uint8_t index); +void Colorset_set(Colorset *self, uint8_t index, RGBColor col); +void Colorset_skip(Colorset *self, int32_t amount); +RGBColor Colorset_cur(Colorset *self); +void Colorset_setCurIndex(Colorset *self, uint8_t index); +void Colorset_resetIndex(Colorset *self); +uint8_t Colorset_curIndex(const Colorset *self); +RGBColor Colorset_getPrev(Colorset *self); +RGBColor Colorset_getNext(Colorset *self); +RGBColor Colorset_peek(const Colorset *self, int32_t offset); +RGBColor Colorset_peekNext(const Colorset *self); +uint8_t Colorset_numColors(const Colorset *self); +bool Colorset_onStart(const Colorset *self); +bool Colorset_onEnd(const Colorset *self); +bool Colorset_serialize(const Colorset *self, ByteStream *buffer); +bool Colorset_unserialize(Colorset *self, ByteStream *buffer); +bool Colorset_initPaletteInternal(Colorset *self, uint8_t numColors); + #endif diff --git a/VortexEngine/src/Leds/LedStash.c b/VortexEngine/src/Leds/LedStash.c new file mode 100644 index 0000000000..46f6e0da22 --- /dev/null +++ b/VortexEngine/src/Leds/LedStash.c @@ -0,0 +1,31 @@ +#include "LedStash.h" + +void LedStash_init(LedStash *self) +{ + (void)self; +} + +void LedStash_setIndex(LedStash *self, LedPos pos, RGBColor col) +{ + if (pos > LED_LAST) { + return; + } + self->ledColorsStash[pos] = col; +} + +void LedStash_clear(LedStash *self) +{ + for (LedPos pos = LED_FIRST; pos < LED_LAST; ++pos) { + RGBColor_clear(&self->ledColorsStash[pos]); + } +} + +RGBColor* LedStash_get(LedStash *self, int index) +{ + return &self->ledColorsStash[index]; +} + +void LedStash_cleanup(LedStash *self) +{ + (void)self; +} diff --git a/VortexEngine/src/Leds/LedStash.h b/VortexEngine/src/Leds/LedStash.h index 1089ca40c9..3ad38585f2 100644 --- a/VortexEngine/src/Leds/LedStash.h +++ b/VortexEngine/src/Leds/LedStash.h @@ -4,22 +4,14 @@ #include "../Colors/ColorTypes.h" #include "LedTypes.h" -class LedStash -{ - friend class Leds; -public: - LedStash(); - ~LedStash(); +typedef struct LedStash_s { + RGBColor ledColorsStash[LED_COUNT]; +} LedStash; - void setIndex(LedPos pos, RGBColor col); +void LedStash_init(LedStash *self); +void LedStash_setIndex(LedStash *self, LedPos pos, RGBColor col); +void LedStash_clear(LedStash *self); +RGBColor* LedStash_get(LedStash *self, int index); +void LedStash_cleanup(LedStash *self); - void clear(); - - // index operator to access color index - RGBColor &operator[](int index); - -private: - RGBColor m_ledColorsStash[LED_COUNT]; -}; - -#endif \ No newline at end of file +#endif diff --git a/VortexEngine/src/Leds/LedTypes.h b/VortexEngine/src/Leds/LedTypes.h index 953ffa4c99..8ae429dffc 100644 --- a/VortexEngine/src/Leds/LedTypes.h +++ b/VortexEngine/src/Leds/LedTypes.h @@ -3,77 +3,36 @@ #include #include +#include #include "../VortexConfig.h" -// Defined the LED positions, their order, and index -enum LedPos : uint8_t -{ - // this should always be first - LED_FIRST = 0, +typedef uint8_t LedPos; - // LED constants for each led +enum { + LED_FIRST = 0, LED_0 = LED_FIRST, LED_1, - - // the number of entries above LED_COUNT, - - // the last LED index LED_LAST = (LED_COUNT - 1), - - // target all leds (multi and single) - // When fetching this the same as LED_ANY - // When setting this will set all of the leds LED_ALL = LED_COUNT, - - // target the multi led slot - // - // When fetching this will return the multi led slot - // When setting this will set the multi led slot LED_MULTI = (LED_COUNT + 1), - - // target all single led slots - // - // When fetching this will return the first single led slot - // When setting this will set all single led slots LED_ALL_SINGLE = (LED_COUNT + 2), - - // Target the 'effective' led slot (any slot) - // - // When fetching this will: - // 1. return the multi led slot if it exists - // 2. otherwise the first single led slot - // - // When setting this will: - // 1. if setting single led pattern will set all - // 2. if setting multi led pattern will set multi LED_ANY = (LED_COUNT + 3), - - // other customs? - // LED_EVENS = (LED_COUNT + 2), - // LED_ODDS = (LED_COUNT + 3), }; -// some helpers for microlight code #define LED_TIP LED_0 #define LED_TOP LED_1 -enum Pair : uint8_t -{ - PAIR_FIRST = 0, +typedef uint8_t Pair; - // one pair for each pair of leds, adjust this to be 2x the LED_COUNT +enum { + PAIR_FIRST = 0, PAIR_0 = PAIR_FIRST, - PAIR_COUNT, PAIR_LAST = (PAIR_COUNT - 1), }; -// Compile-time check on the number of pairs and leds -static_assert(LED_COUNT == (PAIR_COUNT * 2), "Incorrect number of Pairs for Leds! Adjust the Led enum or Pair enum to match"); - -// backwards compat with bigger patterns #define LED_2 LED_0 #define LED_3 LED_1 #define LED_4 LED_0 @@ -88,36 +47,26 @@ static_assert(LED_COUNT == (PAIR_COUNT * 2), "Incorrect number of Pairs for Leds #define PAIR_3 PAIR_0 #define PAIR_4 PAIR_0 -// check if an led is even or odd #define isEven(pos) ((pos % 2) == 0) #define isOdd(pos) ((pos % 2) != 0) -// convert a pair to even or odd led position -#define pairEven(pair) (LedPos)((uint32_t)pair * 2) -#define pairOdd(pair) (LedPos)(((uint32_t)pair * 2) + 1) +#define pairEven(pair) ((LedPos)((uint32_t)pair * 2)) +#define pairOdd(pair) ((LedPos)(((uint32_t)pair * 2) + 1)) -// convert an led position to a pair -#define ledToPair(pos) (Pair)((uint32_t)pos / 2) +#define ledToPair(pos) ((Pair)((uint32_t)pos / 2)) -// LedMap is a bitmap of leds, used for expressing whether to turn certain leds on -// or off with a single integer typedef uint64_t LedMap; -// various macros for mapping leds to an LedMap -#define MAP_LED(led) (LedMap)((uint64_t)1 << led) +#define MAP_LED(led) ((LedMap)((uint64_t)1 << led)) #define MAP_PAIR_EVEN(pair) MAP_LED(pairEven(pair)) #define MAP_PAIR_ODD(pair) MAP_LED(pairOdd(pair)) #define MAP_PAIR(pair) (MAP_PAIR_EVEN(pair) | MAP_PAIR_ODD(pair)) -// check if a map is purely just 1 led or not #define MAP_IS_ONE_LED(map) (map && !(map & (map-1))) -// foreach led macro (only iterates singles) #define MAP_FOREACH_LED(map) for (LedPos pos = ledmapGetFirstLed(map); pos != LED_COUNT; pos = ledmapGetNextLed(map, pos)) -// convert a map to the first Led position in the map -inline LedPos ledmapGetFirstLed(LedMap map) -{ +static inline LedPos ledmapGetFirstLed(LedMap map) { if (map == MAP_LED(LED_MULTI)) { return LED_MULTI; } @@ -132,9 +81,7 @@ inline LedPos ledmapGetFirstLed(LedMap map) return LED_COUNT; } -// given an led map and a position, find the next position in the map -inline LedPos ledmapGetNextLed(LedMap map, LedPos pos) -{ +static inline LedPos ledmapGetNextLed(LedMap map, LedPos pos) { pos = (LedPos)(pos + 1); map >>= pos; while (map && pos < LED_COUNT) { @@ -147,98 +94,41 @@ inline LedPos ledmapGetNextLed(LedMap map, LedPos pos) return LED_COUNT; } -// bitmap of all pairs (basically LED_COUNT bits) #define MAP_LED_ALL ((2 << (LED_COUNT - 1)) - 1) - -// blank map #define MAP_LED_NONE 0 - #define MAP_INVERSE(map) ((~map) & MAP_LED_ALL) - -// macro for all evens and odds #define MAP_PAIR_EVENS (((1 << LED_COUNT) - 1) & 0x55555555) #define MAP_PAIR_ODDS (((1 << LED_COUNT) - 1) & 0xAAAAAAAA) - -// Some preset bitmaps for pair groupings #define MAP_PAIR_ODD_EVENS (MAP_PAIR_EVEN(PAIR_0) | MAP_PAIR_EVEN(PAIR_2) | MAP_PAIR_EVEN(PAIR_4)) #define MAP_PAIR_ODD_ODDS (MAP_PAIR_ODD(PAIR_0) | MAP_PAIR_ODD(PAIR_2) | MAP_PAIR_ODD(PAIR_4)) - #define MAP_PAIR_EVEN_EVENS (MAP_PAIR_EVEN(PAIR_3) | MAP_PAIR_EVEN(PAIR_1)) #define MAP_PAIR_EVEN_ODDS (MAP_PAIR_ODD(PAIR_3) | MAP_PAIR_ODD(PAIR_1)) -// set a single led -inline void ledmapSetLed(LedMap &map, LedPos pos) -{ - if (pos < LED_COUNT) map |= (1ull << pos); +static inline void ledmapSetLed(LedMap *map, LedPos pos) { + if (pos < LED_COUNT) *map |= (1ull << pos); } -// set a single pair -inline void ledmapSetPair(LedMap &map, Pair pair) -{ + +static inline void ledmapSetPair(LedMap *map, Pair pair) { ledmapSetLed(map, pairEven(pair)); ledmapSetLed(map, pairOdd(pair)); } -// check if an led is set in the map -inline bool ledmapCheckLed(LedMap map, LedPos pos) -{ +static inline bool ledmapCheckLed(LedMap map, LedPos pos) { return ((map & (1ull << pos)) != 0); } -// check if a pair is set in the map (both leds) -inline bool ledmapCheckPair(LedMap map, Pair pair) -{ + +static inline bool ledmapCheckPair(LedMap map, Pair pair) { return ledmapCheckLed(map, pairEven(pair)) && ledmapCheckLed(map, pairOdd(pair)); } -// LedPos operators -inline LedPos &operator++(LedPos &c) -{ - c = LedPos(((uint32_t)c) + 1); - return c; -} -inline LedPos operator++(LedPos &c, int) -{ - LedPos temp = c; - ++c; - return temp; -} -inline LedPos operator+(LedPos &c, int b) -{ - return (LedPos)((uint32_t)c + b); -} -inline LedPos &operator+=(LedPos &c, int b) -{ - c = LedPos(((uint32_t)c) + b); - return c; -} -inline LedPos operator-(LedPos &c, int b) -{ - return (LedPos)((uint32_t)c - b); -} -inline LedPos &operator-=(LedPos &c, int b) -{ - c = LedPos(((uint32_t)c) - b); - return c; -} +static inline LedPos ledPosIncr(LedPos c) { return (LedPos)((uint32_t)c + 1); } +static inline LedPos ledPosAdd(LedPos c, int b) { return (LedPos)((uint32_t)c + (uint32_t)b); } +static inline LedPos ledPosAddEq(LedPos *c, int b) { *c = (LedPos)((uint32_t)(*c) + (uint32_t)b); return *c; } +static inline LedPos ledPosSub(LedPos c, int b) { return (LedPos)((uint32_t)c - (uint32_t)b); } +static inline LedPos ledPosSubEq(LedPos *c, int b) { *c = (LedPos)((uint32_t)(*c) - (uint32_t)b); return *c; } -// pair operators -inline Pair &operator++(Pair &c) -{ - c = Pair(((uint32_t)c) + 1); - return c; -} -inline Pair operator++(Pair &c, int) -{ - Pair temp = c; - ++c; - return temp; -} -inline Pair operator+(Pair &c, int b) -{ - return (Pair)((uint32_t)c + b); -} -inline Pair operator-(Pair &c, int b) -{ - return (Pair)((uint32_t)c - b); -} +static inline Pair pairIncr(Pair c) { return (Pair)((uint32_t)c + 1); } +static inline Pair pairAdd(Pair c, int b) { return (Pair)((uint32_t)c + (uint32_t)b); } +static inline Pair pairSub(Pair c, int b) { return (Pair)((uint32_t)c - (uint32_t)b); } #endif diff --git a/VortexEngine/src/Leds/Leds.c b/VortexEngine/src/Leds/Leds.c new file mode 100644 index 0000000000..20a697d896 --- /dev/null +++ b/VortexEngine/src/Leds/Leds.c @@ -0,0 +1,489 @@ +#include + +#include "LedStash.h" +#include "Leds.h" + +#include "../Time/TimeControl.h" +#include "../Modes/Modes.h" + +#include "../VortexConfig.h" + +#ifdef VORTEX_LIB +#include "../../VortexLib/VortexLib.h" +#endif + +#ifdef VORTEX_EMBEDDED +#include +#include +#endif + +#define LED_DATA_PIN 7 + +uint8_t Leds_brightness = DEFAULT_BRIGHTNESS; +RGBColor Leds_ledColors[LED_COUNT] = { {0, 0, 0} }; +volatile uint8_t *Leds_port = NULL; +uint8_t Leds_pinMask = 0; + +bool Leds_init(void) +{ +#ifdef VORTEX_EMBEDDED + PORTB.DIRSET |= PIN4_bm; + Leds_port = &VPORTB.OUT; + Leds_pinMask = PIN4_bm; +#endif +#ifdef VORTEX_LIB + Vortex_vcallbacks()->ledsInit(Leds_ledColors, LED_COUNT); +#endif + return true; +} + +void Leds_cleanup(void) +{ + for (uint8_t i = 0; i < LED_COUNT; ++i) { + RGBColor_clear(&Leds_ledColors[i]); + } +} + +RGBColor* Leds_led(LedPos pos) +{ + if (pos > LED_LAST) { + pos = LED_LAST; + } + return &Leds_ledColors[pos]; +} + +void Leds_setIndex(LedPos target, RGBColor col) +{ + if (target >= LED_COUNT) { + Leds_setAll(col); + return; + } + *Leds_led(target) = col; +} + +void Leds_setRange(LedPos first, LedPos last, RGBColor col) +{ + for (LedPos pos = first; pos <= last; pos++) { + Leds_setIndex(pos, col); + } +} + +void Leds_setAll(RGBColor col) +{ + Leds_setRange(LED_FIRST, LED_LAST, col); +} + +void Leds_clearIndex(LedPos target) +{ + RGBColor off; + RGBColor_initFromU32(&off, 0); + Leds_setIndex(target, off); +} + +void Leds_clearRange(LedPos first, LedPos last) +{ + RGBColor off; + RGBColor_initFromU32(&off, 0); + Leds_setRange(first, last, off); +} + +void Leds_clearAll(void) +{ + RGBColor off; + RGBColor_initFromU32(&off, 0); + Leds_setAll(off); +} + +void Leds_setPair(Pair pair, RGBColor col) +{ + Leds_setRange(pairEven(pair), pairOdd(pair), col); +} + +void Leds_setPairs(Pair first, Pair last, RGBColor col) +{ + Leds_setRange(pairEven(first), pairOdd(last), col); +} + +void Leds_clearPair(Pair pair) +{ + RGBColor off; + RGBColor_initFromU32(&off, 0); + Leds_setPair(pair, off); +} + +void Leds_clearPairs(Pair first, Pair last) +{ + RGBColor off; + RGBColor_initFromU32(&off, 0); + Leds_setPairs(first, last, off); +} + +void Leds_setRangeEvens(Pair first, Pair last, RGBColor col) +{ + for (Pair pos = first; pos <= last; pos++) { + Leds_setIndex(pairEven(pos), col); + } +} + +void Leds_setAllEvens(RGBColor col) +{ + for (Pair pos = PAIR_FIRST; pos <= PAIR_LAST; pos++) { + Leds_setIndex(pairEven(pos), col); + } +} + +void Leds_setRangeOdds(Pair first, Pair last, RGBColor col) +{ + for (Pair pos = first; pos <= last; pos++) { + Leds_setIndex(pairOdd(pos), col); + } +} + +void Leds_setAllOdds(RGBColor col) +{ + for (Pair pos = PAIR_FIRST; pos <= PAIR_LAST; pos++) { + Leds_setIndex(pairOdd(pos), col); + } +} + +void Leds_clearRangeEvens(Pair first, Pair last) +{ + for (Pair pos = first; pos <= last; pos++) { + Leds_clearIndex(pairEven(pos)); + } +} + +void Leds_clearAllEvens(void) +{ + for (Pair pos = PAIR_FIRST; pos <= PAIR_LAST; pos++) { + Leds_clearIndex(pairEven(pos)); + } +} + +void Leds_clearRangeOdds(Pair first, Pair last) +{ + for (Pair pos = first; pos <= last; pos++) { + Leds_clearIndex(pairOdd(pos)); + } +} + +void Leds_clearAllOdds(void) +{ + for (Pair pos = PAIR_FIRST; pos <= PAIR_LAST; pos++) { + Leds_clearIndex(pairOdd(pos)); + } +} + +void Leds_setMap(LedMap map, RGBColor col) +{ + for (LedPos pos = LED_FIRST; pos <= LED_LAST; pos++) { + if (ledmapCheckLed(map, pos)) { + Leds_setIndex(pos, col); + } + } +} + +void Leds_clearMap(LedMap map) +{ + for (LedPos pos = LED_FIRST; pos <= LED_LAST; pos++) { + if (ledmapCheckLed(map, pos)) { + Leds_clearIndex(pos); + } + } +} + +void Leds_stashAll(LedStash *stash) +{ + for (LedPos pos = LED_FIRST; pos <= LED_LAST; pos++) { + stash->ledColorsStash[pos] = *Leds_led(pos); + } +} + +void Leds_restoreAll(const LedStash *stash) +{ + for (LedPos pos = LED_FIRST; pos <= LED_LAST; pos++) { + *Leds_led(pos) = stash->ledColorsStash[pos]; + } +} + +void Leds_adjustBrightnessIndex(LedPos target, uint8_t fadeBy) +{ + RGBColor_adjustBrightness(Leds_led(target), fadeBy); +} + +void Leds_adjustBrightnessRange(LedPos first, LedPos last, uint8_t fadeBy) +{ + for (LedPos pos = first; pos <= last; pos++) { + Leds_adjustBrightnessIndex(pos, fadeBy); + } +} + +void Leds_adjustBrightnessAll(uint8_t fadeBy) +{ + Leds_adjustBrightnessRange(LED_FIRST, LED_LAST, fadeBy); +} + +void Leds_blinkIndexOffset(LedPos target, uint32_t time, uint16_t offMs, uint16_t onMs, RGBColor col) +{ + if ((time % MS_TO_TICKS(offMs + onMs)) < MS_TO_TICKS(onMs)) { + Leds_setIndex(target, col); + } +} + +void Leds_blinkRangeOffset(LedPos first, LedPos last, uint32_t time, uint16_t offMs, uint16_t onMs, RGBColor col) +{ + if ((time % MS_TO_TICKS(offMs + onMs)) < MS_TO_TICKS(onMs)) { + Leds_setRange(first, last, col); + } +} + +void Leds_blinkIndex(LedPos target, uint16_t offMs, uint16_t onMs, RGBColor col) +{ + if ((Time_getCurtime() % MS_TO_TICKS(offMs + onMs)) < MS_TO_TICKS(onMs)) { + Leds_setIndex(target, col); + } +} + +void Leds_blinkRange(LedPos first, LedPos last, uint16_t offMs, uint16_t onMs, RGBColor col) +{ + if ((Time_getCurtime() % MS_TO_TICKS(offMs + onMs)) < MS_TO_TICKS(onMs)) { + Leds_setRange(first, last, col); + } +} + +void Leds_blinkMap(LedMap targets, uint16_t offMs, uint16_t onMs, RGBColor col) +{ + if ((Time_getCurtime() % MS_TO_TICKS(offMs + onMs)) < MS_TO_TICKS(onMs)) { + for (LedPos pos = LED_FIRST; pos < LED_COUNT; pos++) { + if (ledmapCheckLed(targets, pos)) { + Leds_setIndex(pos, col); + } + } + } +} + +void Leds_blinkAll(uint16_t offMs, uint16_t onMs, RGBColor col) +{ + if ((Time_getCurtime() % MS_TO_TICKS(offMs + onMs)) < MS_TO_TICKS(onMs)) { + Leds_setRange(LED_FIRST, LED_LAST, col); + } +} + +void Leds_blinkPair(Pair pair, uint16_t offMs, uint16_t onMs, RGBColor col) +{ + if ((Time_getCurtime() % MS_TO_TICKS(offMs + onMs)) < MS_TO_TICKS(onMs)) { + Leds_setRange(pairEven(pair), pairOdd(pair), col); + } +} + +void Leds_blinkPairs(Pair first, Pair last, uint16_t offMs, uint16_t onMs, RGBColor col) +{ + if ((Time_getCurtime() % MS_TO_TICKS(offMs + onMs)) < MS_TO_TICKS(onMs)) { + Leds_setRange(pairEven(first), pairOdd(last), col); + } +} + +void Leds_breatheIndex(LedPos target, uint8_t hue, uint32_t variance, uint32_t magnitude, uint8_t sat, uint8_t val) +{ + HSVColor hsv; + HSVColor_initHSV(&hsv, (uint8_t)(hue + ((sin(variance * 0.0174533) + 1) * magnitude)), sat, val); + RGBColor col; + RGBColor_assignHSV(&col, &hsv); + Leds_setIndex(target, col); +} + +void Leds_breatheRange(LedPos first, LedPos last, uint8_t hue, uint32_t variance, uint32_t magnitude, uint8_t sat, uint8_t val) +{ + HSVColor hsv; + HSVColor_initHSV(&hsv, (uint8_t)(hue + ((sin(variance * 0.0174533) + 1) * magnitude)), sat, val); + RGBColor col; + RGBColor_assignHSV(&col, &hsv); + Leds_setRange(first, last, col); +} + +void Leds_breatheIndexSat(LedPos target, uint8_t hue, uint32_t variance, uint32_t magnitude, uint8_t sat, uint8_t val) +{ + HSVColor hsv; + HSVColor_initHSV(&hsv, hue, 255 - (uint8_t)(sat + 128 + ((sin(variance * 0.0174533) + 1) * magnitude)), val); + RGBColor col; + RGBColor_assignHSV(&col, &hsv); + Leds_setIndex(target, col); +} + +void Leds_breatheIndexVal(LedPos target, uint8_t hue, uint32_t variance, uint32_t magnitude, uint8_t sat, uint8_t val) +{ + HSVColor hsv; + HSVColor_initHSV(&hsv, hue, sat, 255 - (uint8_t)(val + 128 + ((sin(variance * 0.0174533) + 1) * magnitude))); + RGBColor col; + RGBColor_assignHSV(&col, &hsv); + Leds_setIndex(target, col); +} + +void Leds_holdAll(RGBColor col) +{ + Leds_setAll(col); + Leds_update(); + Time_delayMilliseconds(250); +} + +RGBColor Leds_getLed(LedPos pos) +{ + return *Leds_led(pos); +} + +uint8_t Leds_getBrightness(void) +{ + return Leds_brightness; +} + +void Leds_setBrightness(uint8_t brightness) +{ + Leds_brightness = brightness; +} + +void Leds_update(void) +{ +#ifdef VORTEX_EMBEDDED + RGBColor ledbackups[LED_COUNT]; + memcpy(ledbackups, Leds_ledColors, sizeof(Leds_ledColors)); + for (int c = 0; c < LED_COUNT; ++c) { +#define SCALE8(i, scale) (((uint16_t)i * (uint16_t)(scale)) >> 8) + Leds_ledColors[c].red = SCALE8(Leds_ledColors[c].red, Leds_brightness); + Leds_ledColors[c].green = SCALE8(Leds_ledColors[c].green, Leds_brightness); + Leds_ledColors[c].blue = SCALE8(Leds_ledColors[c].blue, Leds_brightness); + } + { + uint8_t _tmp = Leds_ledColors[LED_1].red; + Leds_ledColors[LED_1].red = Leds_ledColors[LED_1].green; + Leds_ledColors[LED_1].green = _tmp; + } +#endif + +#ifdef VORTEX_LIB + Vortex_vcallbacks()->ledsShow(); +#endif + +#ifdef VORTEX_EMBEDDED + volatile uint16_t + i = LED_COUNT * sizeof(RGBColor); + volatile uint8_t + *ptr = (volatile uint8_t *)Leds_ledColors, + b = *ptr++, + hi, + lo; + + #if (F_CPU >= 9500000UL) && (F_CPU <= 11100000UL) + volatile uint8_t next; + + hi = *Leds_port | Leds_pinMask; + lo = *Leds_port & ~Leds_pinMask; + next = lo; + if (b & 0x80) { + next = hi; + } + + asm volatile( + "_head10:" "\n\t" + "st %a[port], %[hi]" "\n\t" + "rcall _bitTime10" "\n\t" + "st %a[port], %[hi]" "\n\t" + "rcall _bitTime10" "\n\t" + "st %a[port], %[hi]" "\n\t" + "rcall _bitTime10" "\n\t" + "st %a[port], %[hi]" "\n\t" + "rcall _bitTime10" "\n\t" + "st %a[port], %[hi]" "\n\t" + "rcall _bitTime10" "\n\t" + "st %a[port], %[hi]" "\n\t" + "rcall _bitTime10" "\n\t" + "st %a[port], %[hi]" "\n\t" + "rcall _bitTime10" "\n\t" + "st %a[port], %[hi]" "\n\t" + "st %a[port], %[next]" "\n\t" + "ld %[byte] , %a[ptr]+" "\n\t" + "st %a[port], %[next]" "\n\t" + "mov %[next] , %[lo]" "\n\t" + "sbrc %[byte] , 7" "\n\t" + "mov %[next] , %[hi]" "\n\t" + "st %a[port], %[lo]" "\n\t" + "sbiw %[count], 1" "\n\t" + "brne _head10" "\n\t" + "rjmp _done10" "\n\t" + "_bitTime10:" "\n\t" + "st %a[port], %[next]" "\n\t" + "mov %[next], %[lo]" "\n\t" + "lsl %[byte]" "\n\t" + "sbrc %[byte], 7" "\n\t" + "mov %[next], %[hi]" "\n\t" + "st %a[port], %[lo]" "\n\t" + "ret" "\n\t" + "_done10:" "\n" + : [ptr] "+e" (ptr), + [byte] "+r" (b), + [next] "+r" (next), + [count] "+w" (i) + : [port] "e" (Leds_port), + [hi] "r" (hi), + [lo] "r" (lo)); + +#elif (F_CPU >= 19000000UL) && (F_CPU <= 22000000L) + volatile uint8_t next, bit; + + hi = *Leds_port | Leds_pinMask; + lo = *Leds_port & ~Leds_pinMask; + next = lo; + bit = 8; + + asm volatile( + "head20:" "\n\t" + "st %a[port], %[hi]" "\n\t" + "sbrc %[byte], 7" "\n\t" + "mov %[next], %[hi]" "\n\t" + "dec %[bit]" "\n\t" + "nop" "\n\t" + "rjmp .+0" "\n\t" + "st %a[port], %[next]" "\n\t" + "mov %[next] , %[lo]" "\n\t" + "breq nextbyte20" "\n\t" + "rol %[byte]" "\n\t" + "rjmp .+0" "\n\t" + "rjmp .+0" "\n\t" + "st %a[port], %[lo]" "\n\t" + "nop" "\n\t" + "rjmp .+0" "\n\t" + "rjmp .+0" "\n\t" + "rjmp .+0" "\n\t" + "rjmp head20" "\n\t" + "nextbyte20:" "\n\t" + "ldi %[bit] , 8" "\n\t" + "ld %[byte] , %a[ptr]+" "\n\t" + "nop" "\n\t" + "st %a[port], %[lo]" "\n\t" + "nop" "\n\t" + "rjmp .+0" "\n\t" + "rjmp .+0" "\n\t" + "sbiw %[count], 1" "\n\t" + "brne head20" "\n" + : [ptr] "+e" (ptr), + [byte] "+r" (b), + [bit] "+d" (bit), + [next] "+r" (next), + [count] "+w" (i) + : [port] "e" (Leds_port), + [hi] "r" (hi), + [lo] "r" (lo)); + + #else + #error "CPU SPEED NOT SUPPORTED" + #endif +#endif + +#ifdef VORTEX_EMBEDDED + { + uint8_t _tmp = Leds_ledColors[LED_1].red; + Leds_ledColors[LED_1].red = Leds_ledColors[LED_1].green; + Leds_ledColors[LED_1].green = _tmp; + } + memcpy(Leds_ledColors, ledbackups, sizeof(Leds_ledColors)); +#endif +} diff --git a/VortexEngine/src/Leds/Leds.h b/VortexEngine/src/Leds/Leds.h index bdcd94af9b..00348ab582 100644 --- a/VortexEngine/src/Leds/Leds.h +++ b/VortexEngine/src/Leds/Leds.h @@ -2,134 +2,62 @@ #define LED_CONTROL_H #include - +#include #include "../Colors/ColorTypes.h" #include "LedTypes.h" -class LedStash; - -class Leds -{ - // private unimplemented constructor - Leds(); - -public: - // opting for static class here because there should only ever be one - // Led control object and I don't like singletons - static bool init(); - static void cleanup(); - - // control individual LED, these are appropriate to use in internal pattern logic - static void setIndex(LedPos target, RGBColor col); - static void setRange(LedPos first, LedPos last, RGBColor col); - static void setAll(RGBColor col); - - // Turn off individual LEDs, these are appropriate to use in internal pattern logic - static void clearIndex(LedPos target) { setIndex(target, RGB_OFF); } - static void clearRange(LedPos first, LedPos last) { setRange(first, last, RGB_OFF); } - static void clearAll() { setAll(RGB_OFF); } - - // control two LEDs on a pair, these are appropriate for use in internal pattern logic - static void setPair(Pair pair, RGBColor col); - static void setPairs(Pair first, Pair last, RGBColor col); - - // Turn off both LEDs on a pair, these are appropriate for use in internal pattern logic - static void clearPair(Pair pair) { setPair(pair, RGB_OFF); } - static void clearPairs(Pair first, Pair last) { setPairs(first, last, RGB_OFF); } - - // Controll pair evens - static void setRangeEvens(Pair first, Pair last, RGBColor); - static void setAllEvens(RGBColor col); - // Controll pair odds - static void setRangeOdds(Pair first, Pair last, RGBColor); - static void setAllOdds(RGBColor col); - - // Turn off tips - static void clearRangeEvens(Pair first, Pair last); - static void clearAllEvens(); - // Turn off tops - static void clearRangeOdds(Pair first, Pair last); - static void clearAllOdds(); - - // Turn on/off a mapping of leds with a color - static void setMap(LedMap map, RGBColor col); - static void clearMap(LedMap map); - - // stores Led for later use - static void stashAll(LedStash &stash); - - // restores Leds from stash - static void restoreAll(const LedStash &stash); - - // Dim individual LEDs, these are appropriate to use in internal pattern logic - static void adjustBrightnessIndex(LedPos target, uint8_t fadeBy); - static void adjustBrightnessRange(LedPos first, LedPos last, uint8_t fadeBy); - static void adjustBrightnessAll(uint8_t fadeBy); - - // Blink an led to blank or a color - // - // These APIs work by checking if the current time is within the - // 'on duration' then they apply the color to the target, otherwise they do - // nothing. The goal of this behaviour is to allow them to be used in any - // situation to 'blink' an led to either off or some color. - // - // However since these APIs modulate current time to check if within the 'on' - // threshold that makes them unsuitable for internal pattern usage because it - // is unpredictable whether they will blink on or off first - static void blinkIndexOffset(LedPos target, uint32_t time, uint16_t offMs = 250, uint16_t onMs = 500, RGBColor col = RGB_OFF); - static void blinkRangeOffset(LedPos first, LedPos last, uint32_t time, uint16_t offMs = 250, uint16_t onMs = 500, RGBColor col = RGB_OFF); - static void blinkIndex(LedPos target, uint16_t offMs = 250, uint16_t onMs = 500, RGBColor col = RGB_OFF); - static void blinkRange(LedPos first, LedPos last, uint16_t offMs = 250, uint16_t onMs = 500, RGBColor col = RGB_OFF); - static void blinkMap(LedMap targets, uint16_t offMs = 250, uint16_t onMs = 500, RGBColor col = RGB_OFF); - static void blinkAll(uint16_t offMs = 250, uint16_t onMs = 500, RGBColor col = RGB_OFF); - // Blink both LEDs on a pair - static void blinkPair(Pair pair, uint16_t offMs = 250, uint16_t onMs = 500, RGBColor col = RGB_OFF); - static void blinkPairs(Pair first, Pair last, uint16_t offMs = 250, uint16_t onMs = 500, RGBColor col = RGB_OFF); - - // breath the hue on an index - // warning: these use hsv to rgb in realtime! - static void breatheIndex(LedPos target, uint8_t hue, uint32_t variance, - uint32_t magnitude = 15, uint8_t sat = 255, uint8_t val = 210); - static void breatheRange(LedPos first, LedPos last, uint8_t hue, uint32_t variance, - uint32_t magnitude = 15, uint8_t sat = 255, uint8_t val = 210); - static void breatheIndexSat(LedPos target, uint8_t hue, uint32_t variance, - uint32_t magnitude = 15, uint8_t sat = 255, uint8_t val = 210); - static void breatheIndexVal(LedPos target, uint8_t hue, uint32_t variance, - uint32_t magnitude = 15, uint8_t sat = 255, uint8_t val = 210); - - // a very specialized api to hold all leds on a color for 250ms - static void holdAll(RGBColor col); - - // get the RGBColor of an Led index - static RGBColor getLed(LedPos pos) { return led(pos); } - - // global brightness - static uint8_t getBrightness() { return m_brightness; } - static void setBrightness(uint8_t brightness) { m_brightness = brightness; } - - // actually update the LEDs and show the changes - static void update(); - -private: - // accessor for led colors, use this for all access to allow for mapping - static inline RGBColor &led(LedPos pos) - { - if (pos > LED_LAST) { - pos = LED_LAST; - } - return m_ledColors[pos]; - } - - // the global brightness - static uint8_t m_brightness; - - // array of led color values - static RGBColor m_ledColors[LED_COUNT]; - - // Output PORT register - static volatile uint8_t *m_port; - // Output PORT bitmask - static uint8_t m_pinMask; -}; +typedef struct LedStash_s LedStash; + +bool Leds_init(void); +void Leds_cleanup(void); +void Leds_setIndex(LedPos target, RGBColor col); +void Leds_setRange(LedPos first, LedPos last, RGBColor col); +void Leds_setAll(RGBColor col); +void Leds_clearIndex(LedPos target); +void Leds_clearRange(LedPos first, LedPos last); +void Leds_clearAll(void); +void Leds_setPair(Pair pair, RGBColor col); +void Leds_setPairs(Pair first, Pair last, RGBColor col); +void Leds_clearPair(Pair pair); +void Leds_clearPairs(Pair first, Pair last); +void Leds_setRangeEvens(Pair first, Pair last, RGBColor col); +void Leds_setAllEvens(RGBColor col); +void Leds_setRangeOdds(Pair first, Pair last, RGBColor col); +void Leds_setAllOdds(RGBColor col); +void Leds_clearRangeEvens(Pair first, Pair last); +void Leds_clearAllEvens(void); +void Leds_clearRangeOdds(Pair first, Pair last); +void Leds_clearAllOdds(void); +void Leds_setMap(LedMap map, RGBColor col); +void Leds_clearMap(LedMap map); +void Leds_stashAll(LedStash *stash); +void Leds_restoreAll(const LedStash *stash); +void Leds_adjustBrightnessIndex(LedPos target, uint8_t fadeBy); +void Leds_adjustBrightnessRange(LedPos first, LedPos last, uint8_t fadeBy); +void Leds_adjustBrightnessAll(uint8_t fadeBy); +void Leds_blinkIndexOffset(LedPos target, uint32_t time, uint16_t offMs, uint16_t onMs, RGBColor col); +void Leds_blinkRangeOffset(LedPos first, LedPos last, uint32_t time, uint16_t offMs, uint16_t onMs, RGBColor col); +void Leds_blinkIndex(LedPos target, uint16_t offMs, uint16_t onMs, RGBColor col); +void Leds_blinkRange(LedPos first, LedPos last, uint16_t offMs, uint16_t onMs, RGBColor col); +void Leds_blinkMap(LedMap targets, uint16_t offMs, uint16_t onMs, RGBColor col); +void Leds_blinkAll(uint16_t offMs, uint16_t onMs, RGBColor col); +void Leds_blinkPair(Pair pair, uint16_t offMs, uint16_t onMs, RGBColor col); +void Leds_blinkPairs(Pair first, Pair last, uint16_t offMs, uint16_t onMs, RGBColor col); +void Leds_breatheIndex(LedPos target, uint8_t hue, uint32_t variance, uint32_t magnitude, uint8_t sat, uint8_t val); +void Leds_breatheRange(LedPos first, LedPos last, uint8_t hue, uint32_t variance, uint32_t magnitude, uint8_t sat, uint8_t val); +void Leds_breatheIndexSat(LedPos target, uint8_t hue, uint32_t variance, uint32_t magnitude, uint8_t sat, uint8_t val); +void Leds_breatheIndexVal(LedPos target, uint8_t hue, uint32_t variance, uint32_t magnitude, uint8_t sat, uint8_t val); +void Leds_holdAll(RGBColor col); +RGBColor Leds_getLed(LedPos pos); +uint8_t Leds_getBrightness(void); +void Leds_setBrightness(uint8_t brightness); +void Leds_update(void); + +extern uint8_t Leds_brightness; +extern RGBColor Leds_ledColors[LED_COUNT]; +extern volatile uint8_t *Leds_port; +extern uint8_t Leds_pinMask; + +RGBColor* Leds_led(LedPos pos); #endif diff --git a/VortexEngine/src/Log/Log.c b/VortexEngine/src/Log/Log.c new file mode 100644 index 0000000000..defabb61be --- /dev/null +++ b/VortexEngine/src/Log/Log.c @@ -0,0 +1,52 @@ +#include "Log.h" + +#include "../Time/TimeControl.h" +#include "../Serial/Serial.h" + +#include +#include +#include + +#ifdef VORTEX_LIB +#include "VortexLib.h" +#endif + +void Vortex_printlog(const char *file, const char *func, int line, const char *msg, va_list list); + +#if LOGGING_LEVEL > 0 +void InfoMsg(const char *msg, ...) +{ + va_list list; + va_start(list, msg); + Vortex_printlog(NULL, NULL, 0, msg, list); + va_end(list); +} +#endif + +#if LOGGING_LEVEL > 1 +void ErrorMsg(const char *func, const char *msg, ...) +{ + va_list list; + va_start(list, msg); + Vortex_printlog(NULL, func, 0, msg, list); + va_end(list); +} +#endif + +#if LOGGING_LEVEL > 2 +void DebugMsg(const char *file, const char *func, int line, const char *msg, ...) +{ + va_list list; + va_start(list, msg); + const char *ptr = file + strlen(file); + while (ptr > file) { + if (*ptr == '\\') { + file = ptr + 1; + break; + } + ptr--; + } + Vortex_printlog(file, func, line, msg, list); + va_end(list); +} +#endif diff --git a/VortexEngine/src/Log/Log.h b/VortexEngine/src/Log/Log.h index 52bb5f7786..fc09332be4 100644 --- a/VortexEngine/src/Log/Log.h +++ b/VortexEngine/src/Log/Log.h @@ -8,7 +8,6 @@ #if LOGGING_LEVEL > 0 #define INFO_LOG(msg) InfoMsg(msg) #define INFO_LOGF(msg, ...) InfoMsg(msg, __VA_ARGS__) -// infos are in final builds and they are meant to be standalone messages void InfoMsg(const char *msg, ...); #else #define INFO_LOG(msg) @@ -16,10 +15,8 @@ void InfoMsg(const char *msg, ...); #endif #if LOGGING_LEVEL > 1 -// in test build errors are just debug messages #define ERROR_LOG(msg) ErrorMsg(__FUNCTION__, msg) #define ERROR_LOGF(msg, ...) ErrorMsg(__FUNCTION__, msg, __VA_ARGS__) -// errors are in final builds so they only have the function name void ErrorMsg(const char *func, const char *msg, ...); #else #define ERROR_LOG(msg) @@ -27,17 +24,14 @@ void ErrorMsg(const char *func, const char *msg, ...); #endif #if LOGGING_LEVEL > 2 -// some compilers won't allow for ellipsis macro that's passed no args... #define DEBUG_LOG(msg) DebugMsg(__FILE__, __FUNCTION__, __LINE__, msg) #define DEBUG_LOGF(msg, ...) DebugMsg(__FILE__, __FUNCTION__, __LINE__, msg, __VA_ARGS__) -// debug messages are only in debug builds so they have full file + func + line info void DebugMsg(const char *file, const char *func, int line, const char *msg, ...); #else #define DEBUG_LOG(msg) #define DEBUG_LOGF(msg, ...) #endif -// report OOM #define ERROR_OUT_OF_MEMORY() ERROR_LOG("Out of memory") #endif diff --git a/VortexEngine/src/Memory/Memory.c b/VortexEngine/src/Memory/Memory.c new file mode 100644 index 0000000000..d27ebba9fb --- /dev/null +++ b/VortexEngine/src/Memory/Memory.c @@ -0,0 +1,111 @@ +#include "Memory.h" + +#include +#include + +#include "../Log/Log.h" + +#if DEBUG_ALLOCATIONS == 1 + +static uint32_t cur_mem_usage = 0; +static uint32_t background_usage = 0; + +struct memory_block +{ + uint32_t size; + uint8_t p[]; +}; + +void *_vmalloc(uint32_t size) +{ + if ((cur_memory_usage_total() + size) >= MAX_MEMORY) { + DEBUG_LOG("OVERMEM"); + return NULL; + } + struct memory_block *b = (struct memory_block *)malloc(size + sizeof(struct memory_block)); + if (!b) { + return NULL; + } + b->size = size; + cur_mem_usage += b->size; + background_usage += sizeof(struct memory_block); + return b->p; +} + +void *_vcalloc(uint32_t size, uint32_t amount) +{ + if ((cur_memory_usage_total() + (size * amount)) >= MAX_MEMORY) { + DEBUG_LOG("OVERMEM"); + return NULL; + } + uint32_t real_amount = (size * amount); + struct memory_block *b = (struct memory_block *)calloc(1, real_amount + sizeof(struct memory_block)); + if (!b) { + return NULL; + } + b->size = real_amount; + cur_mem_usage += b->size; + background_usage += sizeof(struct memory_block); + return b->p; +} + +static struct memory_block *get_base(void *b) +{ + return (struct memory_block *)(((uintptr_t)b) - sizeof(struct memory_block)); +} + +void *_vrealloc(void *ptr, uint32_t size) +{ + uint32_t old_size = 0; + struct memory_block *base = (struct memory_block *)ptr; + if (ptr) { + base = get_base(ptr); + old_size = base->size; + } + if (size == old_size) { + return ptr; + } + if ((cur_memory_usage_total() - old_size) + size >= MAX_MEMORY) { + DEBUG_LOG("OVERMEM"); + return NULL; + } + struct memory_block *b = (struct memory_block *)realloc(base, size + sizeof(struct memory_block)); + if (!b) { + return NULL; + } + b->size = size; + cur_mem_usage -= old_size; + cur_mem_usage += b->size; + if (!old_size) { + background_usage += sizeof(struct memory_block); + } + return b->p; +} + +void _vfree(void *ptr) +{ + if (!ptr) { + return; + } + struct memory_block *base = get_base(ptr); + cur_mem_usage -= base->size; + background_usage -= sizeof(struct memory_block); + free(base); +} + +uint32_t cur_memory_usage() +{ + return cur_mem_usage; +} + +uint32_t cur_memory_usage_background() +{ + return background_usage; +} + +uint32_t cur_memory_usage_total() +{ + return cur_mem_usage + background_usage; +} + +#endif diff --git a/VortexEngine/src/Memory/Memory.h b/VortexEngine/src/Memory/Memory.h index b851d781ec..f0646838c2 100644 --- a/VortexEngine/src/Memory/Memory.h +++ b/VortexEngine/src/Memory/Memory.h @@ -8,26 +8,21 @@ #if DEBUG_ALLOCATIONS == 1 -// monitored allocation routines #define vmalloc(size) _vmalloc(size) #define vcalloc(size, amount) _vcalloc(size, amount) #define vrealloc(ptr, size) _vrealloc(ptr, size) #define vfree(ptr) _vfree(ptr) -// Vortex allocation functions void *_vmalloc(uint32_t size); void *_vcalloc(uint32_t size, uint32_t amount); void *_vrealloc(void *ptr, uint32_t size); void _vfree(void *ptr); -// memory used by regular code uint32_t cur_memory_usage(); -// memory used by the memory tracker itself uint32_t cur_memory_usage_background(); -// memory used by everything uint32_t cur_memory_usage_total(); -#else // if DEBUG_ALLOCATIONS +#else #define vmalloc(size) malloc(size) #define vcalloc(size, amount) calloc(size, amount) @@ -36,21 +31,4 @@ uint32_t cur_memory_usage_total(); #endif -#ifndef VORTEX_LIB -void *operator new (size_t size); -void *operator new[](size_t size); -void operator delete (void *ptr); -void operator delete[](void *ptr); -void *operator new (size_t size, void *ptr) noexcept; -void *operator new[](size_t size, void *ptr) noexcept; -void operator delete (void *ptr, size_t size) noexcept; -void operator delete[](void *ptr, size_t size) noexcept; -//void *operator new (size_t size, std::align_val_t al); -//void *operator new[](size_t size, std::align_val_t al); -//void operator delete (void *ptr, std::align_val_t al) noexcept; -//void operator delete[](void *ptr, std::align_val_t al) noexcept; -//void operator delete (void *ptr, size_t size, std::align_val_t al) noexcept; -//void operator delete[](void *ptr, size_t size, std::align_val_t al) noexcept; -#endif - #endif diff --git a/VortexEngine/src/Menus/Menu.c b/VortexEngine/src/Menus/Menu.c new file mode 100644 index 0000000000..3ef226b52b --- /dev/null +++ b/VortexEngine/src/Menus/Menu.c @@ -0,0 +1,153 @@ +#include "Menu.h" + +#include "../Time/TimeControl.h" +#include "../Time/Timings.h" +#include "../Buttons/Button.h" +#include "../Menus/Menus.h" +#include "../Modes/Modes.h" +#include "../Modes/Mode.h" +#include "../Leds/Leds.h" +#include "../Log/Log.h" + +void Menu_construct(Menu *self, RGBColor col, bool advanced) +{ + self->vtable = &g_menuVTable; + Mode_init(&self->previewMode); + self->menuColor = col; + self->targetLeds = MAP_LED_ALL; + self->curSelection = 0; + self->ledSelected = false; + self->advanced = advanced; + self->shouldClose = false; +} + +void Menu_destroy(Menu *self) +{ + (void)self; +} + +bool Menu_init(Menu *self) +{ + if (!Modes_curMode()) { + if (Modes_numModes() > 0) { + return false; + } + { + Mode tmpMode; + Mode_initFromID(&tmpMode, PATTERN_STROBE, NULL); + if (!Modes_addModeMode(&tmpMode)) { + return false; + } + } + if (!Modes_curMode()) { + return false; + } + } + Mode_assign(&self->previewMode, Modes_curMode()); + Mode_init(&self->previewMode); + self->shouldClose = false; + return true; +} + +MenuAction Menu_run(Menu *self) +{ + if (self->shouldClose) { + self->shouldClose = false; + return MENU_QUIT; + } + + if (self->ledSelected) { + return MENU_CONTINUE; + } + + if (Button_onShortClick(g_pButton)) { + Menu_nextBulbSelection(self); + } + if (Button_onLongClick(g_pButton)) { + self->ledSelected = true; + self->vtable->onLedSelected(self); + DEBUG_LOGF("Led Selected: 0x%x (%s)", self->targetLeds, + (self->targetLeds == MAP_LED(LED_MULTI)) ? "multi" + : (self->targetLeds == MAP_LED_ALL) ? "all" + : "some singles"); + } + + Menu_showBulbSelection(self); + + return MENU_SKIP; +} + +void Menu_showBulbSelection(Menu *self) +{ + (void)self; + Leds_clearAll(); + Leds_blinkMap(self->targetLeds, BULB_SELECT_OFF_MS, BULB_SELECT_ON_MS, RGB_COLOR(RGB_MAGENTA1)); + Menus_showSelection(RGB_COLOR(RGB_MAGENTA1)); +} + +void Menu_showExit(Menu *self) +{ + (void)self; + if (Button_isPressed(g_pButton) && Button_holdDuration(g_pButton) > SHORT_CLICK_THRESHOLD_TICKS) { + Leds_setIndex(LED_1, RGB_COLOR(RGB_RED)); + } else { + Leds_clearIndex(LED_1); + Leds_blinkIndex(LED_0, EXIT_MENU_OFF_MS, EXIT_MENU_ON_MS, RGB_COLOR(RGB_WHITE0)); + Leds_blinkIndex(LED_1, EXIT_MENU_OFF_MS, EXIT_MENU_ON_MS, RGB_COLOR(RGB_RED0)); + } +} + +void Menu_nextBulbSelection(Menu *self) +{ + switch (self->targetLeds) { + case MAP_LED_ALL: + self->targetLeds = MAP_LED(LED_FIRST); + break; + case MAP_LED(LED_LAST): + self->targetLeds = MAP_LED_ALL; + break; + default: + self->targetLeds = MAP_LED(((ledmapGetFirstLed(self->targetLeds) + 1) % (LED_COUNT + 1))); + break; + } +} + +void Menu_bypassLedSelection(Menu *self, LedMap map) +{ + self->ledSelected = true; + self->targetLeds = map; + self->vtable->onLedSelected(self); +} + +void Menu_onLedSelected(Menu *self) +{ + (void)self; +} + +void Menu_onShortClick(Menu *self) +{ + (void)self; +} + +void Menu_onLongClick(Menu *self) +{ + Menu_leaveMenu(self, false); +} + +void Menu_leaveMenu(Menu *self, bool doSave) +{ + self->shouldClose = true; + if (doSave) { + Modes_saveStorage(); + } +} + +const MenuVTable g_menuVTable = { + .destroy = Menu_destroy, + .init = Menu_init, + .run = Menu_run, + .onLedSelected = Menu_onLedSelected, + .onShortClick = Menu_onShortClick, + .onLongClick = Menu_onLongClick, + .leaveMenu = Menu_leaveMenu, +}; diff --git a/VortexEngine/src/Menus/Menu.cpp b/VortexEngine/src/Menus/Menu.cpp deleted file mode 100644 index 97c904b763..0000000000 --- a/VortexEngine/src/Menus/Menu.cpp +++ /dev/null @@ -1,160 +0,0 @@ -#include "Menu.h" - -#include "../Time/TimeControl.h" -#include "../Time/Timings.h" -#include "../Buttons/Button.h" -#include "../Menus/Menus.h" -#include "../Modes/Modes.h" -#include "../Modes/Mode.h" -#include "../Leds/Leds.h" -#include "../Log/Log.h" - -Menu::Menu(const RGBColor &col, bool advanced) : - m_previewMode(), - m_menuColor(col), - m_targetLeds(MAP_LED_ALL), - m_curSelection(0), - m_ledSelected(false), - m_advanced(advanced), - m_shouldClose(false) -{ -} - -Menu::~Menu() -{ -} - -bool Menu::init() -{ - // menu is initialized before being run - if (!Modes::curMode()) { - // if you enter a menu and there's no modes, it will add an empty one - if (Modes::numModes() > 0) { - // some kind of serious error - return false; - } - if (!Modes::addMode(PATTERN_STROBE, RGBColor(RGB_OFF))) { - // some kind of serious error - return false; - } - if (!Modes::curMode()) { - // serious error again - return false; - } - } - // copy the current mode into the demo mode and initialize it - m_previewMode = *Modes::curMode(); - m_previewMode.init(); - // just in case - m_shouldClose = false; - return true; -} - -Menu::MenuAction Menu::run() -{ - // check whether the close flag has been set - if (m_shouldClose) { - // reset flag and close - m_shouldClose = false; - return MENU_QUIT; - } - - // if the led has already been selected continue as normal - // and allow derived menu logic to perform work - if (m_ledSelected) { - return MENU_CONTINUE; - } - - // using manual onShortClick and onLongClick check here because - // there is no guarantee the child class will call the parent - // class's onShortClick and onLongClick functions so - - // every time the button is clicked, change the target led - if (g_pButton->onShortClick()) { - nextBulbSelection(); - } - // on a long press of the button, lock in the target led - if (g_pButton->onLongClick()) { - m_ledSelected = true; - // call led selected callback - onLedSelected(); - // log about the selection - DEBUG_LOGF("Led Selected: 0x%x (%s)", m_targetLeds, - (m_targetLeds == MAP_LED(LED_MULTI)) ? "multi" - : (m_targetLeds == MAP_LED_ALL) ? "all" - : "some singles"); - } - - // render the bulb selection - showBulbSelection(); - - // return MENU_SKIP to indicate to derived menu classes that - // they should skip their logic and just return so that the - // bulb selection can be rendered - return MENU_SKIP; -} - -void Menu::showBulbSelection() -{ - Leds::clearAll(); - Leds::blinkMap(m_targetLeds, BULB_SELECT_OFF_MS, BULB_SELECT_ON_MS, RGB_MAGENTA1); - // blink when selecting - Menus::showSelection(RGB_MAGENTA1); -} - -void Menu::showExit() -{ - if (g_pButton->isPressed() && g_pButton->holdDuration() > SHORT_CLICK_THRESHOLD_TICKS) { - Leds::setIndex(LED_1, RGB_RED); - } else { - Leds::clearIndex(LED_1); - Leds::blinkIndex(LED_0, EXIT_MENU_OFF_MS, EXIT_MENU_ON_MS, RGB_WHITE0); - Leds::blinkIndex(LED_1, EXIT_MENU_OFF_MS, EXIT_MENU_ON_MS, RGB_RED0); - } -} - -void Menu::nextBulbSelection() -{ - // The target led can be 0 through LED_COUNT to represent any led or all leds - // modulo by LED_COUNT + 1 to include LED_COUNT (all) as a target - switch (m_targetLeds) { - case MAP_LED_ALL: - m_targetLeds = MAP_LED(LED_FIRST); - break; - case MAP_LED(LED_LAST): - m_targetLeds = MAP_LED_ALL; - break; - default: // LED_FIRST through LED_LAST - // iterate as normal - m_targetLeds = MAP_LED(((ledmapGetFirstLed(m_targetLeds) + 1) % (LED_COUNT + 1))); - break; - } -} - -void Menu::bypassLedSelection(LedMap map) -{ - m_ledSelected = true; - m_targetLeds = map; - onLedSelected(); -} - -void Menu::onLedSelected() -{ -} - -void Menu::onShortClick() -{ -} - -void Menu::onLongClick() -{ - leaveMenu(false); -} - -void Menu::leaveMenu(bool doSave) -{ - m_shouldClose = true; - if (doSave) { - Modes::saveStorage(); - } -} diff --git a/VortexEngine/src/Menus/Menu.h b/VortexEngine/src/Menus/Menu.h index 997a46ee9d..374361cc28 100644 --- a/VortexEngine/src/Menus/Menu.h +++ b/VortexEngine/src/Menus/Menu.h @@ -2,72 +2,56 @@ #define MENU_H #include +#include #include "../Colors/ColorTypes.h" #include "../Leds/LedTypes.h" #include "../Modes/Mode.h" -class Menu -{ -public: - Menu(const RGBColor &col, bool advanced); - virtual ~Menu(); - - virtual bool init(); - - // the action for the menu to execute - enum MenuAction : uint8_t { - // quit the menus - MENU_QUIT, - // continue running the menu - MENU_CONTINUE, - // don't run derived menu code, this is used internally - // by the menu class itself - MENU_SKIP - }; - virtual MenuAction run(); - - // optional handler for initializing data after user selected led - virtual void onLedSelected(); - - // optional handlers for clicks - virtual void onShortClick(); - virtual void onLongClick(); - - // close the current menu - virtual void leaveMenu(bool doSave = false); - -protected: - void showBulbSelection(); - void showExit(); +typedef uint8_t MenuAction; +enum { + MENU_QUIT, + MENU_CONTINUE, + MENU_SKIP +}; - // iterate to next bulb selection - void nextBulbSelection(); +typedef struct Menu Menu; +typedef struct MenuVTable { + void (*destroy)(Menu *self); + bool (*init)(Menu *self); + MenuAction (*run)(Menu *self); + void (*onLedSelected)(Menu *self); + void (*onShortClick)(Menu *self); + void (*onLongClick)(Menu *self); + void (*leaveMenu)(Menu *self, bool doSave); +} MenuVTable; + +struct Menu { + const MenuVTable *vtable; + Mode previewMode; + RGBColor menuColor; + LedMap targetLeds; + uint8_t curSelection; + bool ledSelected; + bool advanced; + bool shouldClose; +}; - // skip led selection - void bypassLedSelection(LedMap map); +void Menu_construct(Menu *self, RGBColor col, bool advanced); - // the mode copied from the current mode used to preview changes - Mode m_previewMode; - // the color of this menu - RGBColor m_menuColor; - // tracks the targetted leds for this menu - // note this is an led map - LedMap m_targetLeds; - // all menus have a 'current selection' - uint8_t m_curSelection; - // true once a an led is selected - bool m_ledSelected; - // whether advanced menu was activated - bool m_advanced; +void Menu_destroy(Menu *self); +bool Menu_init(Menu *self); +MenuAction Menu_run(Menu *self); +void Menu_onLedSelected(Menu *self); +void Menu_onShortClick(Menu *self); +void Menu_onLongClick(Menu *self); +void Menu_leaveMenu(Menu *self, bool doSave); -private: - // internal flag to close the menu - bool m_shouldClose; +void Menu_showBulbSelection(Menu *self); +void Menu_showExit(Menu *self); +void Menu_nextBulbSelection(Menu *self); +void Menu_bypassLedSelection(Menu *self, LedMap map); -#ifdef VORTEX_LIB - friend class Vortex; -#endif -}; +extern const MenuVTable g_menuVTable; #endif diff --git a/VortexEngine/src/Menus/MenuList/ColorSelect.c b/VortexEngine/src/Menus/MenuList/ColorSelect.c new file mode 100644 index 0000000000..7770d42bff --- /dev/null +++ b/VortexEngine/src/Menus/MenuList/ColorSelect.c @@ -0,0 +1,277 @@ +#include "ColorSelect.h" +#include + +#include "../../VortexEngine.h" + +#include "../../Time/TimeControl.h" +#include "../../Patterns/PatternBuilder.h" +#include "../../Patterns/Pattern.h" +#include "../../Colors/Colorset.h" +#include "../../Buttons/Button.h" +#include "../../Random/Random.h" +#include "../../Time/Timings.h" +#include "../../Menus/Menus.h" +#include "../../Modes/Modes.h" +#include "../../Modes/Mode.h" +#include "../../Leds/Leds.h" +#include "../../Log/Log.h" + +#define COL32(v) ((RGBColor){ ((v) >> 16) & 0xFF, ((v) >> 8) & 0xFF, (v) & 0xFF }) + +static const uint8_t s_sats[4] = { SAT_OPTION_4, SAT_OPTION_3, SAT_OPTION_2, SAT_OPTION_1 }; +static const uint8_t s_vals[4] = { VAL_OPTION_4, VAL_OPTION_3, VAL_OPTION_2, VAL_OPTION_1 }; + +static void showSlotSelection(ColorSelectMenu *self); +static void showSelection(ColorSelectMenu *self, ColorSelectState mode); +static void showFullSet(ColorSelectMenu *self, uint8_t offMs, uint8_t onMs); + +extern void VortexEngine_toggleForceSleep(bool enabled); + +Menu *ColorSelectMenu_Create(RGBColor col, bool advanced) +{ + ColorSelectMenu *self = (ColorSelectMenu *)malloc(sizeof(ColorSelectMenu)); + if (!self) return NULL; + Menu_construct(&self->base, col, advanced); + self->base.vtable = &g_colorSelectMenuVTable; + self->state = COLOR_SELECT_STATE_PICK_SLOT; + HSVColor_init(&self->newColor); + Colorset_init(&self->colorset); + self->targetSlot = 0; + self->targetHue1 = 0; + g_hsv_rgb_alg = HSV_TO_RGB_RAINBOW; + return &self->base; +} + +void ColorSelectMenu_destroy(Menu *self) +{ + (void)self; + g_hsv_rgb_alg = HSV_TO_RGB_GENERIC; + VortexEngine_toggleForceSleep(true); +} + +bool ColorSelectMenu_init(Menu *self) +{ + if (!Menu_init(self)) { + return false; + } + Mode *cur = Modes_curMode(); + if (Mode_isEmpty(cur)) { + return false; + } + ColorSelectMenu *cs = (ColorSelectMenu *)self; +#if VORTEX_SLIM == 0 + if (Mode_isMultiLed(cur)) { + Menu_bypassLedSelection(self, MAP_LED(LED_MULTI)); + } +#endif + cs->state = COLOR_SELECT_STATE_INIT; + DEBUG_LOG("Entered color select"); + return true; +} + +MenuAction ColorSelectMenu_run(Menu *self) +{ + MenuAction result = Menu_run(self); + if (result != MENU_CONTINUE) { + return result; + } + ColorSelectMenu *cs = (ColorSelectMenu *)self; + + Leds_clearAll(); + switch (cs->state) { + case COLOR_SELECT_STATE_INIT: + HSVColor_initHSV(&cs->newColor, 0, 255, 255); + cs->targetHue1 = 0; + cs->targetSlot = 0; + cs->state = COLOR_SELECT_STATE_PICK_SLOT; + break; + case COLOR_SELECT_STATE_PICK_SLOT: + showSlotSelection(cs); + break; + case COLOR_SELECT_STATE_PICK_HUE1: + case COLOR_SELECT_STATE_PICK_HUE2: + case COLOR_SELECT_STATE_PICK_SAT: + case COLOR_SELECT_STATE_PICK_VAL: + showSelection(cs, cs->state); + break; + } + Menus_showSelection(COL32(RGB_WHITE5)); + return MENU_CONTINUE; +} + +void ColorSelectMenu_onLedSelected(Menu *self) +{ + ColorSelectMenu *cs = (ColorSelectMenu *)self; + Mode *cur = Modes_curMode(); + if (self->targetLeds == MAP_LED_ALL) { + cs->colorset = Mode_getColorset(cur, LED_ALL); + } else { + cs->colorset = Mode_getColorset(cur, ledmapGetFirstLed(self->targetLeds)); + } +} + +void ColorSelectMenu_onShortClick(Menu *self) +{ + ColorSelectMenu *cs = (ColorSelectMenu *)self; + self->curSelection++; + if (cs->state == COLOR_SELECT_STATE_PICK_SLOT) { + self->curSelection %= (Colorset_numColors(&cs->colorset) + 1 + (Colorset_numColors(&cs->colorset) < MAX_COLOR_SLOTS)); + } else { + self->curSelection %= 5; + } +} + +void ColorSelectMenu_onLongClick(Menu *self) +{ + ColorSelectMenu *cs = (ColorSelectMenu *)self; + if (self->curSelection == 4 && cs->state > COLOR_SELECT_STATE_PICK_SLOT) { + cs->state = (ColorSelectState)(cs->state - 1); + self->curSelection = (cs->state == COLOR_SELECT_STATE_PICK_SLOT) ? cs->targetSlot : 0; + return; + } + uint8_t numColors = Colorset_numColors(&cs->colorset); + uint32_t holdDur = Button_holdDuration(g_pButton); + switch (cs->state) { + case COLOR_SELECT_STATE_INIT: + return; + case COLOR_SELECT_STATE_PICK_SLOT: + if (self->curSelection == numColors + (numColors < MAX_COLOR_SLOTS)) { + Mode *cur = Modes_curMode(); + Mode_setColorsetMap(cur, self->targetLeds, &cs->colorset); + Mode_init(cur); + Menu_leaveMenu(self, true); + return; + } + if (self->curSelection < numColors && + holdDur >= DELETE_THRESHOLD_TICKS && + (holdDur % (DELETE_CYCLE_TICKS * 2)) > (DELETE_CYCLE_TICKS)) { + Colorset_removeColor(&cs->colorset, self->curSelection); + if (self->curSelection > numColors) { + self->curSelection--; + } + return; + } + cs->targetSlot = self->curSelection; + break; + case COLOR_SELECT_STATE_PICK_HUE1: + cs->targetHue1 = self->curSelection; + cs->newColor.hue = cs->targetHue1 * (255 / 4); + break; + case COLOR_SELECT_STATE_PICK_HUE2: + cs->newColor.hue = (cs->targetHue1 * (255 / 4)) + self->curSelection * (255 / 16); + break; + case COLOR_SELECT_STATE_PICK_SAT: + cs->newColor.sat = s_sats[self->curSelection]; + break; + case COLOR_SELECT_STATE_PICK_VAL: + { + cs->newColor.val = s_vals[self->curSelection]; + RGBColor rgb; + RGBColor_initFromHSV(&rgb, &cs->newColor); + Colorset_set(&cs->colorset, cs->targetSlot, rgb); + self->curSelection = cs->targetSlot; + cs->state = COLOR_SELECT_STATE_PICK_SLOT; + return; + } + } + cs->state = (ColorSelectState)(cs->state + 1); + self->curSelection = 0; +} + +// ================== Private rendering ================== + +static void showSlotSelection(ColorSelectMenu *self) +{ + uint8_t exitIndex = Colorset_numColors(&self->colorset); + uint32_t holdDur = Button_holdDuration(g_pButton); + bool withinNumColors = self->base.curSelection < exitIndex; + bool holdDurationCheck = Button_isPressed(g_pButton) && holdDur >= DELETE_THRESHOLD_TICKS; + bool holdDurationModCheck = (holdDur % (DELETE_CYCLE_TICKS * 2)) > DELETE_CYCLE_TICKS; + RGBColor col = Colorset_get(&self->colorset, self->base.curSelection); + if (withinNumColors && holdDurationCheck && holdDurationModCheck) { + Leds_blinkIndex(LED_0, 50, 100, col); + Leds_breatheIndex(LED_1, 0, holdDur, 22, 255, 180); + } else if (withinNumColors) { + if (RGBColor_empty(&col)) { + Leds_setIndex(LED_0, COL32(RGB_WHITE0)); + } + Leds_blinkAll(150, 650, col); + } else if (exitIndex < MAX_COLOR_SLOTS) { + if (self->base.curSelection == exitIndex) { + Leds_blinkAll(100, 150, COL32(RGB_WHITE2)); + } + exitIndex++; + } + if (self->base.curSelection == exitIndex) { + showFullSet(self, 50, 100); + Leds_setIndex(LED_1, COL32(RGB_GREEN2)); + } else if (self->base.curSelection != Colorset_numColors(&self->colorset) && !holdDurationCheck) { + Leds_setIndex(LED_1, Button_isPressed(g_pButton) ? COL32(RGB_OFF) : COL32(RGB_WHITE2)); + } +} + +static void showSelection(ColorSelectMenu *self, ColorSelectState mode) +{ + if (self->base.curSelection >= 4) { + Menu_showExit(&self->base); + return; + } + uint32_t now = Time_getCurtime(); + uint8_t hue = self->newColor.hue; + uint8_t sat = self->newColor.sat; + uint8_t val = 255; + switch (mode) { + default: + return; + case COLOR_SELECT_STATE_PICK_HUE1: + hue = self->base.curSelection * (255 / 4); + Leds_breatheIndex(LED_0, hue, (now / 2), 22, 255, 180); + Leds_breatheIndex(LED_1, hue, (now / 2) + 125, 22, 255, 180); + sat = 255; + return; + case COLOR_SELECT_STATE_PICK_HUE2: + hue = self->targetHue1 * (255 / 4) + (self->base.curSelection * (255 / 16)); + Leds_setIndex(LED_1, COL32(RGB_WHITE0)); + sat = 255; + break; + case COLOR_SELECT_STATE_PICK_SAT: + sat = s_sats[self->base.curSelection]; + Leds_breatheIndexSat(LED_1, hue, (now / 3), 100, 150, 150); + break; + case COLOR_SELECT_STATE_PICK_VAL: + val = s_vals[self->base.curSelection]; + Leds_breatheIndexVal(LED_1, hue, (now / 3), 100, sat, 150); + break; + } + { + HSVColor hsv; + HSVColor_initHSV(&hsv, hue, sat, val); + RGBColor rgb; + RGBColor_initFromHSV(&rgb, &hsv); + Leds_setMap(MAP_PAIR_EVENS, rgb); + } +} + +static void showFullSet(ColorSelectMenu *self, uint8_t offMs, uint8_t onMs) +{ + uint8_t numCols = Colorset_numColors(&self->colorset); + uint32_t offOnMs = MS_TO_TICKS(offMs + onMs); + if (!numCols || !offOnMs) { + return; + } + uint32_t now = Time_getCurtime(); + if ((now % offOnMs) < MS_TO_TICKS(onMs)) { + Leds_setAll(Colorset_get(&self->colorset, (now / offOnMs) % numCols)); + } + Leds_setIndex(LED_1, COL32(RGB_GREEN0)); +} + +const MenuVTable g_colorSelectMenuVTable = { + .destroy = ColorSelectMenu_destroy, + .init = ColorSelectMenu_init, + .run = ColorSelectMenu_run, + .onLedSelected = ColorSelectMenu_onLedSelected, + .onShortClick = ColorSelectMenu_onShortClick, + .onLongClick = ColorSelectMenu_onLongClick, + .leaveMenu = Menu_leaveMenu, +}; diff --git a/VortexEngine/src/Menus/MenuList/ColorSelect.cpp b/VortexEngine/src/Menus/MenuList/ColorSelect.cpp deleted file mode 100644 index 3e56e6d603..0000000000 --- a/VortexEngine/src/Menus/MenuList/ColorSelect.cpp +++ /dev/null @@ -1,274 +0,0 @@ -#include "ColorSelect.h" - -#include "../../VortexEngine.h" - -#include "../../Time/TimeControl.h" -#include "../../Patterns/PatternBuilder.h" -#include "../../Patterns/Pattern.h" -#include "../../Colors/Colorset.h" -#include "../../Buttons/Button.h" -#include "../../Random/Random.h" -#include "../../Time/Timings.h" -#include "../../Menus/Menus.h" -#include "../../Modes/Modes.h" -#include "../../Modes/Mode.h" -#include "../../Leds/Leds.h" -#include "../../Log/Log.h" - -ColorSelect::ColorSelect(const RGBColor &col, bool advanced) : - Menu(col, advanced), - m_state(STATE_PICK_SLOT), - m_newColor(), - m_colorset(), - m_targetSlot(0), - m_targetHue1(0) -{ - // NOTE! Specifically using hsv_to_rgb_rainbow instead of generic because - // it will generate nicer looking colors and a nicer rainbow to select - // from than a regular hsv conversion will. This is because the rainbow - // function uses a different algorithm to generate the colors that results - // in a smaller color space with less bright colors. The tradeoff is you lose - // the bright colors but the rainbow looks a lot better - g_hsv_rgb_alg = HSV_TO_RGB_RAINBOW; -} - -ColorSelect::~ColorSelect() -{ - // revert the hsv to rgb algorithm to normal - g_hsv_rgb_alg = HSV_TO_RGB_GENERIC; - // make sure force sleep is re-enabled when we leave - VortexEngine::toggleForceSleep(true); -} - -bool ColorSelect::init() -{ - if (!Menu::init()) { - return false; - } - Mode *cur = Modes::curMode(); - if (cur->isEmpty()) { - // cannot work with an empty mode - return false; - } -#if VORTEX_SLIM == 0 - if (cur->isMultiLed()) { - bypassLedSelection(MAP_LED(LED_MULTI)); - } -#endif - m_state = STATE_INIT; - DEBUG_LOG("Entered color select"); - return true; -} - -Menu::MenuAction ColorSelect::run() -{ - MenuAction result = Menu::run(); - if (result != MENU_CONTINUE) { - return result; - } - - // all states start with a blank slate - Leds::clearAll(); - switch (m_state) { - case STATE_INIT: - // reset all the selection members - m_newColor = HSVColor(0, 255, 255); - m_targetHue1 = 0; - m_targetSlot = 0; - // move on to picking slot - m_state = STATE_PICK_SLOT; - break; - case STATE_PICK_SLOT: - showSlotSelection(); - break; - case STATE_PICK_HUE1: - case STATE_PICK_HUE2: - case STATE_PICK_SAT: - case STATE_PICK_VAL: - showSelection(m_state); - break; - } - // show selections - Menus::showSelection(); - return MENU_CONTINUE; -} - -// callback after the user selects the target led -void ColorSelect::onLedSelected() -{ - Mode *cur = Modes::curMode(); - // grab the colorset from our selected target led - if (m_targetLeds == MAP_LED_ALL) { - m_colorset = cur->getColorset(); - } else { - m_colorset = cur->getColorset(ledmapGetFirstLed(m_targetLeds)); - } -} - -void ColorSelect::onShortClick() -{ - // increment selection - m_curSelection++; - if (m_state == STATE_PICK_SLOT) { - m_curSelection %= (m_colorset.numColors() + 1 + (m_colorset.numColors() < MAX_COLOR_SLOTS)); - } else { - m_curSelection %= 5; - } -} - -void ColorSelect::onLongClick() -{ - // if we're on 'exit' and we're on any menu past the slot selection - if (m_curSelection == 4 && m_state > STATE_PICK_SLOT) { - // move back to the previous selection - m_state = (ColorSelectState)(m_state - 1); - // if we're back to the slot selection then set our position back to the - // slot we selected, otherwise it's more visually appealing to just return - // to the first selection on the previous menu - m_curSelection = (m_state == STATE_PICK_SLOT) ? m_targetSlot : 0; - return; - } - // reuse these variables lots - uint8_t numColors = m_colorset.numColors(); - uint32_t holdDur = g_pButton->holdDuration(); - switch (m_state) { - case STATE_INIT: - // nothing - return; - case STATE_PICK_SLOT: - // if the exit is selected then save and exit, this depends on the number - // of colors in the colorset. If the colorset is full then the exit is - // just the number of colors (8) but if it's not full then the exit is the - // number of colors + 1. Example: with 4 cols, cols are on 0, 1, 2, 3, - // add-color is 4, and exit is 5 - if (m_curSelection == numColors + (numColors < MAX_COLOR_SLOTS)) { - Mode *cur = Modes::curMode(); - cur->setColorsetMap(m_targetLeds, m_colorset); - cur->init(); - leaveMenu(true); - return; - } - // handle if user releases during the delete option - if (m_curSelection < numColors && - holdDur >= DELETE_THRESHOLD_TICKS && - (holdDur % (DELETE_CYCLE_TICKS * 2)) > (DELETE_CYCLE_TICKS)) { - // delete current slot - m_colorset.removeColor(m_curSelection); - if (m_curSelection > numColors) { - m_curSelection--; - } - return; - } - // otherwise store the target slot continue onto the hue selection - m_targetSlot = m_curSelection; - break; - case STATE_PICK_HUE1: - m_targetHue1 = m_curSelection; - m_newColor.hue = m_targetHue1 * (255 / 4); - break; - case STATE_PICK_HUE2: - m_newColor.hue = (m_targetHue1 * (255 / 4)) + m_curSelection * (255 / 16); - break; - case STATE_PICK_SAT: - m_newColor.sat = sats[m_curSelection]; - break; - case STATE_PICK_VAL: - // no m_targetVal because you can't go back after this - m_newColor.val = vals[m_curSelection]; - // specifically using hsv to rgb rainbow to generate the color - m_colorset.set(m_targetSlot, m_newColor); - m_curSelection = m_targetSlot; - m_state = STATE_PICK_SLOT; - return; - } - m_state = (ColorSelectState)(m_state + 1); - m_curSelection = 0; -} - -void ColorSelect::showSlotSelection() -{ - uint8_t exitIndex = m_colorset.numColors(); - uint32_t holdDur = g_pButton->holdDuration(); - bool withinNumColors = m_curSelection < exitIndex; - bool holdDurationCheck = g_pButton->isPressed() && holdDur >= DELETE_THRESHOLD_TICKS; - bool holdDurationModCheck = (holdDur % (DELETE_CYCLE_TICKS * 2)) > DELETE_CYCLE_TICKS; - const RGBColor &col = m_colorset[m_curSelection]; - if (withinNumColors && holdDurationCheck && holdDurationModCheck) { - // breath red for delete slot - Leds::blinkIndex(LED_0, 50, 100, col); - Leds::breatheIndex(LED_1, 0, holdDur); - } else if (withinNumColors) { - if (col.empty()) { - Leds::setIndex(LED_0, RGB_WHITE0); - } - Leds::blinkAll(150, 650, col); - } else if (exitIndex < MAX_COLOR_SLOTS) { - if (m_curSelection == exitIndex) { - // blink both leds and blink faster to indicate 'add' new color - Leds::blinkAll(100, 150, RGB_WHITE2); - } - exitIndex++; - } - if (m_curSelection == exitIndex) { - // display the full set - showFullSet(50, 100); - // set LED_1 to green to indicate save and exit - Leds::setIndex(LED_1, RGB_GREEN2); - // if not on exitIndex or add new color set LED_1 based on button state - } else if (m_curSelection != m_colorset.numColors() && !holdDurationCheck) { - Leds::setIndex(LED_1, g_pButton->isPressed() ? RGB_OFF : RGB_WHITE2); - } -} - -void ColorSelect::showSelection(ColorSelectState mode) -{ - if (m_curSelection >= 4) { - showExit(); - return; - } - uint32_t now = Time::getCurtime(); - uint8_t hue = m_newColor.hue; - uint8_t sat = m_newColor.sat; - uint8_t val = 255; - switch (mode) { - default: - return; - case STATE_PICK_HUE1: - hue = m_curSelection * (255 / 4); - Leds::breatheIndex(LED_0, hue, (now / 2), 22, 255, 180); - Leds::breatheIndex(LED_1, hue, (now / 2) + 125, 22, 255, 180); - // force sat at hue level1 - sat = 255; - // NOTE: return here - return; - case STATE_PICK_HUE2: - hue = m_targetHue1 * (255 / 4) + (m_curSelection * (255 / 16)); - Leds::setIndex(LED_1, RGB_WHITE0); - // force sat at hue level2 - sat = 255; - break; - case STATE_PICK_SAT: - sat = sats[m_curSelection]; - Leds::breatheIndexSat(LED_1, hue, (now / 3), 100, 150, 150); - break; - case STATE_PICK_VAL: - val = vals[m_curSelection]; - Leds::breatheIndexVal(LED_1, hue, (now / 3), 100, sat, 150); - break; - } - Leds::setMap(MAP_PAIR_EVENS, HSVColor(hue, sat, val)); -} - -void ColorSelect::showFullSet(uint8_t offMs, uint8_t onMs) -{ - uint8_t numCols = m_colorset.numColors(); - uint8_t offOnMs = MS_TO_TICKS(offMs + onMs); - if (!numCols || !offOnMs) { - return; - } - uint32_t now = Time::getCurtime(); - if ((now % offOnMs) < MS_TO_TICKS(onMs)) { - Leds::setAll(m_colorset.get((now / offOnMs) % numCols)); - } - Leds::setIndex(LED_1, RGB_GREEN0); -} diff --git a/VortexEngine/src/Menus/MenuList/ColorSelect.h b/VortexEngine/src/Menus/MenuList/ColorSelect.h index e0f0ed496a..7ecae65bf9 100644 --- a/VortexEngine/src/Menus/MenuList/ColorSelect.h +++ b/VortexEngine/src/Menus/MenuList/ColorSelect.h @@ -5,67 +5,32 @@ #include "../../Colors/Colorset.h" -class ColorSelect : public Menu -{ -public: - ColorSelect(const RGBColor &col, bool advanced); - ~ColorSelect(); - - bool init() override; - MenuAction run() override; - - // callback after the user selects the target led - void onLedSelected() override; - - // handlers for clicks - void onShortClick() override; - void onLongClick() override; - -private: - // private enumeration for internal state of color selection - enum ColorSelectState : uint8_t - { - STATE_INIT, - STATE_PICK_SLOT, - STATE_PICK_HUE1, - STATE_PICK_HUE2, - STATE_PICK_SAT, - STATE_PICK_VAL - }; - - void showSlotSelection(); - void showSelection(ColorSelectState mode); - void showFullSet(uint8_t offMs, uint8_t onMs); - - // the options for saturations - const uint8_t sats[4] = { - SAT_OPTION_4, - SAT_OPTION_3, - SAT_OPTION_2, - SAT_OPTION_1 - }; - - // the options for values - const uint8_t vals[4] = { - VAL_OPTION_4, - VAL_OPTION_3, - VAL_OPTION_2, - VAL_OPTION_1 - }; - - // the state of the color select menu - ColorSelectState m_state; - // the new color being built via hue, sat then val - HSVColor m_newColor; - // A copy of the colorset being changed - Colorset m_colorset; - - // below are the selection indexes for each level - - // the target values selected at each level to build the color, the value - // selected at the last level isn't stored because you can't go back after - uint8_t m_targetSlot; - uint8_t m_targetHue1; -}; +typedef enum { + COLOR_SELECT_STATE_INIT, + COLOR_SELECT_STATE_PICK_SLOT, + COLOR_SELECT_STATE_PICK_HUE1, + COLOR_SELECT_STATE_PICK_HUE2, + COLOR_SELECT_STATE_PICK_SAT, + COLOR_SELECT_STATE_PICK_VAL +} ColorSelectState; + +typedef struct ColorSelectMenu_s { + Menu base; + ColorSelectState state; + HSVColor newColor; + Colorset colorset; + uint8_t targetSlot; + uint8_t targetHue1; +} ColorSelectMenu; + +Menu *ColorSelectMenu_Create(RGBColor col, bool advanced); +void ColorSelectMenu_destroy(Menu *self); +bool ColorSelectMenu_init(Menu *self); +MenuAction ColorSelectMenu_run(Menu *self); +void ColorSelectMenu_onLedSelected(Menu *self); +void ColorSelectMenu_onShortClick(Menu *self); +void ColorSelectMenu_onLongClick(Menu *self); + +extern const MenuVTable g_colorSelectMenuVTable; #endif diff --git a/VortexEngine/src/Menus/MenuList/EditorConnection.c b/VortexEngine/src/Menus/MenuList/EditorConnection.c new file mode 100644 index 0000000000..f286217fd7 --- /dev/null +++ b/VortexEngine/src/Menus/MenuList/EditorConnection.c @@ -0,0 +1,544 @@ +#include "EditorConnection.h" + +#include "../../Patterns/PatternArgs.h" +#include "../../Serial/ByteStream.h" +#include "../../Serial/Serial.h" +#include "../../Storage/Storage.h" +#include "../../Wireless/VLSender.h" +#include "../../Time/TimeControl.h" +#include "../../Colors/Colorset.h" +#include "../../Modes/Modes.h" +#include "../../Modes/Mode.h" +#include "../../Leds/Leds.h" +#include "../../Log/Log.h" + +#include +#include + +#define COL32(v) ((RGBColor){ ((v) >> 16) & 0xFF, ((v) >> 8) & 0xFF, (v) & 0xFF }) + +static void clearDemo(EditorConnectionMenu *self); +static void handleErrors(EditorConnectionMenu *self); +static void handleCommand(EditorConnectionMenu *self); +static void handleState(EditorConnectionMenu *self); +static void showEditor(EditorConnectionMenu *self); +static void receiveData(EditorConnectionMenu *self); +static void sendModes(EditorConnectionMenu *self); +static void sendModeCount(EditorConnectionMenu *self); +static void sendCurMode(EditorConnectionMenu *self); +static void sendCurModeVL(EditorConnectionMenu *self); +static ReturnCode sendBrightness(EditorConnectionMenu *self); +static ReturnCode receiveBuffer(EditorConnectionMenu *self, ByteStream *buffer); +static ReturnCode receiveModes(EditorConnectionMenu *self); +static ReturnCode receiveModeCount(EditorConnectionMenu *self); +static ReturnCode receiveMode(EditorConnectionMenu *self); +static ReturnCode receiveDemoMode(EditorConnectionMenu *self); +static ReturnCode receiveMessage(EditorConnectionMenu *self, const char *message); +static ReturnCode receiveBrightness(EditorConnectionMenu *self); + +static const EditorConnectionCommandState s_commands[] = { + { EDITOR_VERB_PULL_MODES, STATE_PULL_MODES }, + { EDITOR_VERB_PUSH_MODES, STATE_PUSH_MODES }, + { EDITOR_VERB_DEMO_MODE, STATE_DEMO_MODE }, + { EDITOR_VERB_CLEAR_DEMO, STATE_CLEAR_DEMO }, + { EDITOR_VERB_PULL_EACH_MODE, STATE_PULL_EACH_MODE }, + { EDITOR_VERB_PUSH_EACH_MODE, STATE_PUSH_EACH_MODE }, + { EDITOR_VERB_TRANSMIT_VL, STATE_TRANSMIT_MODE_VL }, + { EDITOR_VERB_SET_GLOBAL_BRIGHTNESS, STATE_SET_GLOBAL_BRIGHTNESS }, + { EDITOR_VERB_GET_GLOBAL_BRIGHTNESS, STATE_GET_GLOBAL_BRIGHTNESS }, +}; +#define NUM_COMMANDS (sizeof(s_commands) / sizeof(s_commands[0])) + +Menu *EditorConnectionMenu_Create(RGBColor col, bool advanced) +{ + EditorConnectionMenu *self = (EditorConnectionMenu *)malloc(sizeof(EditorConnectionMenu)); + if (!self) return NULL; + Menu_construct(&self->base, col, advanced); + self->base.vtable = &g_editorConnectionMenuVTable; + self->state = STATE_DISCONNECTED; + memset(&self->receiveBuffer, 0, sizeof(self->receiveBuffer)); + self->allowReset = true; + self->previousModeIndex = 0; + self->numModesToReceive = 0; + self->rv = RV_OK; + return &self->base; +} + +void EditorConnectionMenu_destroy(Menu *self) +{ + EditorConnectionMenu *ec = (EditorConnectionMenu *)self; + clearDemo(ec); +} + +bool EditorConnectionMenu_init(Menu *self) +{ + if (!Menu_init(self)) { + return false; + } + self->ledSelected = true; + EditorConnectionMenu *ec = (EditorConnectionMenu *)self; + clearDemo(ec); + DEBUG_LOG("Entering Editor Connection"); + return true; +} + +MenuAction EditorConnectionMenu_run(Menu *self) +{ + MenuAction result = Menu_run(self); + if (result != MENU_CONTINUE) { + return result; + } + EditorConnectionMenu *ec = (EditorConnectionMenu *)self; + showEditor(ec); + receiveData(ec); + handleState(ec); + return MENU_CONTINUE; +} + +void EditorConnectionMenu_onShortClick(Menu *self) +{ + EditorConnectionMenu *ec = (EditorConnectionMenu *)self; + if (!ec->allowReset) { + return; + } + ec->state = STATE_DISCONNECTED; + clearDemo(ec); + ec->allowReset = false; +} + +void EditorConnectionMenu_onLongClick(Menu *self) +{ + Menu_leaveMenu(self, true); +} + +void EditorConnectionMenu_leaveMenu(Menu *self, bool doSave) +{ + (void)doSave; + SerialComs_write(EDITOR_VERB_GOODBYE); + Menu_leaveMenu(self, true); +} + +static void clearDemo(EditorConnectionMenu *self) +{ + Colorset set; + Colorset_init(&set); + Colorset_addColor(&set, COL32(RGB_WHITE0)); + PatternArgs args; + PatternArgs_init3(&args, 1, 0, 0); + Mode_setPattern(&self->base.previewMode, PATTERN_STROBE, LED_ALL, &args, &set); + Mode_init(&self->base.previewMode); +} + +static void handleErrors(EditorConnectionMenu *self) +{ + if (self->rv == RV_FAIL) { + self->rv = RV_OK; + ByteStream_clear(&self->receiveBuffer); + } +} + +static void handleCommand(EditorConnectionMenu *self) +{ + if (ByteStream_size(&self->receiveBuffer) < 1) { + return; + } + for (uint32_t i = 0; i < NUM_COMMANDS; ++i) { + const uint8_t *data = ByteStream_data(&self->receiveBuffer); + char receiveCmd = (char)data[0]; + if (receiveCmd != s_commands[i].cmd[0]) { + continue; + } + uint8_t dummy; + ByteStream_consume8(&self->receiveBuffer, &dummy); + self->state = s_commands[i].cmdState; + self->allowReset = false; + break; + } +} + +static void handleState(EditorConnectionMenu *self) +{ + switch (self->state) { + case STATE_DISCONNECTED: + default: + if (!SerialComs_isConnected()) { + if (!SerialComs_checkSerial()) { + break; + } + } + self->state = STATE_GREETING; + break; + + case STATE_GREETING: + SerialComs_write(EDITOR_VERB_GREETING); + self->state = STATE_IDLE; + break; + + case STATE_IDLE: + handleErrors(self); + handleCommand(self); + if (!SerialComs_isConnected()) { + Leds_holdAll(COL32(RGB_RED)); + Menu_leaveMenu(&self->base, true); + } + break; + + case STATE_PULL_MODES: + sendModes(self); + self->state = STATE_PULL_MODES_SEND; + break; + case STATE_PULL_MODES_SEND: + if (receiveMessage(self, EDITOR_VERB_PULL_MODES_DONE) == RV_WAIT) { + break; + } + self->state = STATE_PULL_MODES_DONE; + break; + case STATE_PULL_MODES_DONE: + SerialComs_write(EDITOR_VERB_PULL_MODES_ACK); + self->state = STATE_IDLE; + break; + + case STATE_PUSH_MODES: + SerialComs_write(EDITOR_VERB_READY); + self->state = STATE_PUSH_MODES_RECEIVE; + break; + case STATE_PUSH_MODES_RECEIVE: + if (receiveModes(self) == RV_WAIT) { + break; + } + self->state = STATE_PUSH_MODES_DONE; + break; + case STATE_PUSH_MODES_DONE: + SerialComs_write(EDITOR_VERB_PUSH_MODES_ACK); + self->state = STATE_IDLE; + break; + + case STATE_DEMO_MODE: + SerialComs_write(EDITOR_VERB_READY); + self->state = STATE_DEMO_MODE_RECEIVE; + break; + case STATE_DEMO_MODE_RECEIVE: + if (receiveDemoMode(self) == RV_WAIT) { + break; + } + self->state = STATE_DEMO_MODE_DONE; + break; + case STATE_DEMO_MODE_DONE: + SerialComs_write(EDITOR_VERB_DEMO_MODE_ACK); + self->state = STATE_IDLE; + break; + + case STATE_CLEAR_DEMO: + clearDemo(self); + SerialComs_write(EDITOR_VERB_CLEAR_DEMO_ACK); + self->state = STATE_IDLE; + break; + + case STATE_TRANSMIT_MODE_VL: +#if VL_ENABLE_SENDER == 1 + VLSender_send(&self->base.previewMode); +#endif + self->state = STATE_TRANSMIT_MODE_VL_DONE; + break; + case STATE_TRANSMIT_MODE_VL_DONE: + SerialComs_write(EDITOR_VERB_TRANSMIT_VL_ACK); + self->state = STATE_IDLE; + break; + + case STATE_PULL_EACH_MODE: + sendModeCount(self); + self->state = STATE_PULL_EACH_MODE_COUNT; + break; + case STATE_PULL_EACH_MODE_COUNT: + if (receiveMessage(self, EDITOR_VERB_PULL_EACH_MODE_ACK) == RV_WAIT) { + break; + } + if (Modes_numModes() == 0) { + self->state = STATE_PULL_EACH_MODE_DONE; + } else { + self->previousModeIndex = Modes_curModeIndex(); + self->state = STATE_PULL_EACH_MODE_SEND; + } + break; + case STATE_PULL_EACH_MODE_SEND: + sendCurMode(self); + self->state = STATE_PULL_EACH_MODE_WAIT; + break; + case STATE_PULL_EACH_MODE_WAIT: + if (receiveMessage(self, EDITOR_VERB_PULL_EACH_MODE_ACK) == RV_WAIT) { + break; + } + if (Modes_curModeIndex() < (Modes_numModes() - 1)) { + Modes_nextMode(); + self->state = STATE_PULL_EACH_MODE_SEND; + } else { + self->state = STATE_PULL_EACH_MODE_DONE; + } + break; + case STATE_PULL_EACH_MODE_DONE: + SerialComs_write(EDITOR_VERB_PULL_EACH_MODE_DONE); + Modes_setCurMode(self->previousModeIndex); + self->state = STATE_IDLE; + break; + + case STATE_PUSH_EACH_MODE: + SerialComs_write(EDITOR_VERB_PUSH_EACH_MODE_ACK); + self->state = STATE_PUSH_EACH_MODE_COUNT; + break; + case STATE_PUSH_EACH_MODE_COUNT: + if (receiveModeCount(self) == RV_WAIT) { + break; + } + Modes_clearModes(); + SerialComs_write(EDITOR_VERB_PUSH_EACH_MODE_ACK); + self->state = STATE_PUSH_EACH_MODE_RECEIVE; + break; + case STATE_PUSH_EACH_MODE_RECEIVE: + if (receiveMode(self) == RV_WAIT) { + break; + } + SerialComs_write(EDITOR_VERB_PUSH_EACH_MODE_ACK); + if (self->numModesToReceive > 0) { + self->numModesToReceive--; + } + if (!self->numModesToReceive) { + self->state = STATE_PUSH_EACH_MODE_DONE; + } + break; + case STATE_PUSH_EACH_MODE_DONE: + Modes_saveStorage(); + self->state = STATE_IDLE; + break; + + case STATE_SET_GLOBAL_BRIGHTNESS: + SerialComs_write(EDITOR_VERB_READY); + self->state = STATE_SET_GLOBAL_BRIGHTNESS_RECEIVE; + break; + case STATE_SET_GLOBAL_BRIGHTNESS_RECEIVE: + if (receiveBrightness(self) == RV_WAIT) { + break; + } + self->state = STATE_IDLE; + break; + + case STATE_GET_GLOBAL_BRIGHTNESS: + sendBrightness(self); + self->state = STATE_IDLE; + break; + } +} + +static void showEditor(EditorConnectionMenu *self) +{ + switch (self->state) { + case STATE_DISCONNECTED: + Leds_clearAll(); + Leds_blinkAll(250, 150, COL32(RGB_WHITE0)); + break; + case STATE_IDLE: + Mode_play(&self->base.previewMode); + break; + default: + break; + } +} + +static void receiveData(EditorConnectionMenu *self) +{ + SerialComs_read(&self->receiveBuffer); +} + +static void sendModes(EditorConnectionMenu *self) +{ + (void)self; + ByteStream modesBuffer; + memset(&modesBuffer, 0, sizeof(modesBuffer)); + Modes_saveToBuffer(&modesBuffer); + SerialComs_writeStream(&modesBuffer); +} + +static void sendModeCount(EditorConnectionMenu *self) +{ + (void)self; + ByteStream buffer; + memset(&buffer, 0, sizeof(buffer)); + ByteStream_serialize8(&buffer, Modes_numModes()); + SerialComs_writeStream(&buffer); +} + +static void sendCurMode(EditorConnectionMenu *self) +{ + (void)self; + Mode *cur = Modes_curMode(); + if (!cur) { + return; + } + ByteStream modeBuffer; + memset(&modeBuffer, 0, sizeof(modeBuffer)); + if (!Mode_saveToBuffer(cur, &modeBuffer, 0)) { + return; + } + SerialComs_writeStream(&modeBuffer); +} + +static void sendCurModeVL(EditorConnectionMenu *self) +{ +#if VL_ENABLE_SENDER == 1 + self->state = STATE_TRANSMIT_MODE_VL; +#else + (void)self; +#endif +} + +static ReturnCode sendBrightness(EditorConnectionMenu *self) +{ + (void)self; + ByteStream brightnessBuf; + memset(&brightnessBuf, 0, sizeof(brightnessBuf)); + if (!ByteStream_serialize8(&brightnessBuf, Leds_getBrightness())) { + return RV_FAIL; + } + SerialComs_writeStream(&brightnessBuf); + return RV_OK; +} + +static ReturnCode receiveBuffer(EditorConnectionMenu *self, ByteStream *buffer) +{ + uint32_t size = 0; + if (ByteStream_size(&self->receiveBuffer) < sizeof(size)) { + return RV_WAIT; + } + ByteStream_resetUnserializer(&self->receiveBuffer); + size = ByteStream_peek32(&self->receiveBuffer); + if (ByteStream_size(&self->receiveBuffer) < (size + sizeof(size))) { + return RV_WAIT; + } + if (!ByteStream_consume32(&self->receiveBuffer, &size)) { + return RV_FAIL; + } + if (!ByteStream_init(buffer, size, NULL)) { + return RV_FAIL; + } + uint8_t *rawData = (uint8_t *)ByteStream_rawData(buffer); + if (!ByteStream_consume(&self->receiveBuffer, size, rawData)) { + return RV_FAIL; + } + ByteStream_sanity(buffer); + if (!ByteStream_checkCRC(buffer)) { + ByteStream_clear(buffer); + return RV_FAIL; + } + return RV_OK; +} + +static ReturnCode receiveModes(EditorConnectionMenu *self) +{ + ByteStream buf; + memset(&buf, 0, sizeof(buf)); + self->rv = receiveBuffer(self, &buf); + if (self->rv != RV_OK) { + return self->rv; + } + if (!Modes_loadFromBuffer(&buf) || !Modes_saveStorage()) { + return RV_FAIL; + } + return RV_OK; +} + +static ReturnCode receiveModeCount(EditorConnectionMenu *self) +{ + ByteStream buf; + memset(&buf, 0, sizeof(buf)); + self->rv = receiveBuffer(self, &buf); + if (self->rv != RV_OK) { + return self->rv; + } + uint8_t count; + if (!ByteStream_consume8(&buf, &count)) { + return RV_FAIL; + } + self->numModesToReceive = count; + if (self->numModesToReceive > MAX_MODES) { + return RV_FAIL; + } + return RV_OK; +} + +static ReturnCode receiveMode(EditorConnectionMenu *self) +{ + ByteStream buf; + memset(&buf, 0, sizeof(buf)); + self->rv = receiveBuffer(self, &buf); + if (self->rv != RV_OK) { + return self->rv; + } + if (!Modes_addModeFromBuffer(&buf)) { + return RV_FAIL; + } + return RV_OK; +} + +static ReturnCode receiveDemoMode(EditorConnectionMenu *self) +{ + ByteStream buf; + memset(&buf, 0, sizeof(buf)); + self->rv = receiveBuffer(self, &buf); + if (self->rv != RV_OK) { + return self->rv; + } + if (!Mode_loadFromBuffer(&self->base.previewMode, &buf)) { + return RV_FAIL; + } + return RV_OK; +} + +static ReturnCode receiveMessage(EditorConnectionMenu *self, const char *message) +{ + size_t len = strlen(message); + uint8_t byte = 0; + if (ByteStream_size(&self->receiveBuffer) < len) { + return RV_WAIT; + } + if (memcmp(ByteStream_data(&self->receiveBuffer), message, len) != 0) { + return RV_FAIL; + } + for (size_t i = 0; i < len; ++i) { + if (!ByteStream_consume8(&self->receiveBuffer, &byte)) { + return RV_FAIL; + } + } + self->allowReset = false; + return RV_OK; +} + +static ReturnCode receiveBrightness(EditorConnectionMenu *self) +{ + ByteStream buf; + memset(&buf, 0, sizeof(buf)); + self->rv = receiveBuffer(self, &buf); + if (self->rv != RV_OK) { + return self->rv; + } + if (ByteStream_size(&buf) == 0) { + return RV_FAIL; + } + uint8_t brightness = 255; + if (!ByteStream_consume8(&buf, &brightness) || brightness == 0) { + return RV_FAIL; + } + if (brightness > 0) { + Leds_setBrightness(brightness); + Modes_saveHeader(); + } + return RV_OK; +} + +const MenuVTable g_editorConnectionMenuVTable = { + .destroy = EditorConnectionMenu_destroy, + .init = EditorConnectionMenu_init, + .run = EditorConnectionMenu_run, + .onLedSelected = Menu_onLedSelected, + .onShortClick = EditorConnectionMenu_onShortClick, + .onLongClick = EditorConnectionMenu_onLongClick, + .leaveMenu = EditorConnectionMenu_leaveMenu, +}; diff --git a/VortexEngine/src/Menus/MenuList/EditorConnection.cpp b/VortexEngine/src/Menus/MenuList/EditorConnection.cpp deleted file mode 100644 index c1fc1d5927..0000000000 --- a/VortexEngine/src/Menus/MenuList/EditorConnection.cpp +++ /dev/null @@ -1,602 +0,0 @@ -#include "EditorConnection.h" - -#include "../../Patterns/PatternArgs.h" -#include "../../Serial/ByteStream.h" -#include "../../Serial/Serial.h" -#include "../../Storage/Storage.h" -#include "../../Wireless/VLSender.h" -#include "../../Time/TimeControl.h" -#include "../../Colors/Colorset.h" -#include "../../Modes/Modes.h" -#include "../../Modes/Mode.h" -#include "../../Leds/Leds.h" -#include "../../Log/Log.h" - -#include - -EditorConnection::EditorConnection(const RGBColor &col, bool advanced) : - Menu(col, advanced), - m_state(STATE_DISCONNECTED), - m_allowReset(true), - m_previousModeIndex(0), - m_numModesToReceive(0), - m_rv(RV_OK) -{ -} - -EditorConnection::~EditorConnection() -{ - clearDemo(); -} - -bool EditorConnection::init() -{ - if (!Menu::init()) { - return false; - } - // skip led selection - m_ledSelected = true; - clearDemo(); - DEBUG_LOG("Entering Editor Connection"); - return true; -} - -Menu::MenuAction EditorConnection::run() -{ - MenuAction result = Menu::run(); - if (result != MENU_CONTINUE) { - return result; - } - // TODO: auto leave the editor menu when unplugged - // show the editor - showEditor(); - // receive any data from serial into the receive buffer - receiveData(); - // handle the current state - handleState(); - return MENU_CONTINUE; -} - -void EditorConnection::onShortClick() -{ - // if the device has received any commands do not reset! - if (!m_allowReset) { - return; - } - // reset, this won't actually disconnect the com port - m_state = STATE_DISCONNECTED; - // clear the demo - clearDemo(); - // sent a reset, do not allow another - m_allowReset = false; -} - -void EditorConnection::onLongClick() -{ - leaveMenu(true); -} - -void EditorConnection::leaveMenu(bool doSave) -{ - SerialComs::write(EDITOR_VERB_GOODBYE); - Menu::leaveMenu(true); -} - -void EditorConnection::clearDemo() -{ - Colorset set(RGB_WHITE0); - PatternArgs args(1, 0, 0); - m_previewMode.setPattern(PATTERN_STROBE, LED_ALL, &args, &set); - m_previewMode.init(); -} - -void EditorConnection::handleErrors() -{ - if (m_rv == RV_FAIL) { - // handle failure from before, reset rv - m_rv = RV_OK; - // clear the buffer I guess - m_receiveBuffer.clear(); - } - // TODO: Custom error codes? -} - -const EditorConnection::CommandState EditorConnection::commands[] = { - { EDITOR_VERB_PULL_MODES, STATE_PULL_MODES }, - { EDITOR_VERB_PUSH_MODES, STATE_PUSH_MODES }, - { EDITOR_VERB_DEMO_MODE, STATE_DEMO_MODE }, - { EDITOR_VERB_CLEAR_DEMO, STATE_CLEAR_DEMO }, - { EDITOR_VERB_PULL_EACH_MODE, STATE_PULL_EACH_MODE }, - { EDITOR_VERB_PUSH_EACH_MODE, STATE_PUSH_EACH_MODE }, - { EDITOR_VERB_TRANSMIT_VL, STATE_TRANSMIT_MODE_VL }, - { EDITOR_VERB_SET_GLOBAL_BRIGHTNESS, STATE_SET_GLOBAL_BRIGHTNESS }, - { EDITOR_VERB_GET_GLOBAL_BRIGHTNESS, STATE_GET_GLOBAL_BRIGHTNESS }, -}; -#define NUM_COMMANDS (sizeof(commands) / sizeof(commands[0])) - -void EditorConnection::handleCommand() -{ - if (m_receiveBuffer.size() < 1) { - return; - } - for (uint32_t i = 0; i < NUM_COMMANDS; ++i) { - // commands are just one letter, don't consume it yet so we can compare - // each cmd in the list, this probably should be a lookup table - char receiveCmd = m_receiveBuffer.data()[0]; - // if the command matches - if (receiveCmd != commands[i].cmd[0]) { - continue; - } - // then consume the data from the receive buffer and update the state - m_receiveBuffer.consume8(); - m_state = commands[i].cmdState; - // we have now received at least one command, do not allow resetting - m_allowReset = false; - // don't need to check any more commands - break; - } -} - -void EditorConnection::handleState() -{ - // operate on the state of the editor connection - switch (m_state) { - // ------------------------------- - // Disconnected - case STATE_DISCONNECTED: - default: - // not connected yet so check for connections - if (!SerialComs::isConnected()) { - if (!SerialComs::checkSerial()) { - // no connection found just continue waiting - break; - } - } - // a connection was found, say hello - m_state = STATE_GREETING; - break; - - // ------------------------------- - // Send Greeting - case STATE_GREETING: - // send the hello greeting with our version number and build time - SerialComs::write(EDITOR_VERB_GREETING); - m_state = STATE_IDLE; - break; - - // ------------------------------- - // Chillin - case STATE_IDLE: - // handle any errors that may have occurred last run - handleErrors(); - // parse the receive buffer for any commands from the editor - handleCommand(); - // watch for disconnects - if (!SerialComs::isConnected()) { - Leds::holdAll(RGB_RED); - leaveMenu(true); - } - break; - - // ------------------------------- - // Send Modes to PC - case STATE_PULL_MODES: - // editor requested pull modes, send the modes - sendModes(); - m_state = STATE_PULL_MODES_SEND; - break; - case STATE_PULL_MODES_SEND: - // recive the send modes ack from the editor - if (receiveMessage(EDITOR_VERB_PULL_MODES_DONE) == RV_WAIT) { - // just wait - break; - } - m_state = STATE_PULL_MODES_DONE; - break; - case STATE_PULL_MODES_DONE: - // send our acknowledgement that the modes were sent - SerialComs::write(EDITOR_VERB_PULL_MODES_ACK); - // go idle - m_state = STATE_IDLE; - break; - - // ------------------------------- - // Receive Modes from PC - case STATE_PUSH_MODES: - // now say we are ready - SerialComs::write(EDITOR_VERB_READY); - // move to receiving - m_state = STATE_PUSH_MODES_RECEIVE; - break; - case STATE_PUSH_MODES_RECEIVE: - // receive the modes into the receive buffer - if (receiveModes() == RV_WAIT) { - // just keep waiting - break; - } - // success modes were received send the done - m_state = STATE_PUSH_MODES_DONE; - break; - case STATE_PUSH_MODES_DONE: - SerialComs::write(EDITOR_VERB_PUSH_MODES_ACK); - m_state = STATE_IDLE; - break; - - // ------------------------------- - // Demo Mode from PC - case STATE_DEMO_MODE: - // now say we are ready - SerialComs::write(EDITOR_VERB_READY); - // move to receiving - m_state = STATE_DEMO_MODE_RECEIVE; - break; - case STATE_DEMO_MODE_RECEIVE: - // receive the modes into the receive buffer - if (receiveDemoMode() == RV_WAIT) { - // just wait - break; - } - // success modes were received send the done - m_state = STATE_DEMO_MODE_DONE; - break; - case STATE_DEMO_MODE_DONE: - // say we are done - SerialComs::write(EDITOR_VERB_DEMO_MODE_ACK); - m_state = STATE_IDLE; - break; - - // ------------------------------- - // Reset Demo to Nothing - case STATE_CLEAR_DEMO: - clearDemo(); - SerialComs::write(EDITOR_VERB_CLEAR_DEMO_ACK); - m_state = STATE_IDLE; - break; - - // ------------------------------- - // Send Mode to Duo - case STATE_TRANSMIT_MODE_VL: -#if VL_ENABLE_SENDER == 1 - // immediately load the mode and send it now - VLSender::send(&m_previewMode); -#endif - m_state = STATE_TRANSMIT_MODE_VL_DONE; - break; - case STATE_TRANSMIT_MODE_VL_DONE: - // done transmitting - SerialComs::write(EDITOR_VERB_TRANSMIT_VL_ACK); - m_state = STATE_IDLE; - break; - - // ------------------------------- - // Send Modes to PC Safer - case STATE_PULL_EACH_MODE: - // editor requested pull modes, send the modes - sendModeCount(); - m_state = STATE_PULL_EACH_MODE_COUNT; - break; - case STATE_PULL_EACH_MODE_COUNT: - if (receiveMessage(EDITOR_VERB_PULL_EACH_MODE_ACK) == RV_WAIT) { - // just wait - break; - } - if (Modes::numModes() == 0) { - m_state = STATE_PULL_EACH_MODE_DONE; - } else { - m_previousModeIndex = Modes::curModeIndex(); - m_state = STATE_PULL_EACH_MODE_SEND; - } - break; - case STATE_PULL_EACH_MODE_SEND: - // send the current mode - sendCurMode(); - // wait for the ack - m_state = STATE_PULL_EACH_MODE_WAIT; - break; - case STATE_PULL_EACH_MODE_WAIT: - // recive the ack from the editor to send next mode - if (receiveMessage(EDITOR_VERB_PULL_EACH_MODE_ACK) == RV_WAIT) { - // just wait - break; - } - // if there is still more modes - if (Modes::curModeIndex() < (Modes::numModes() - 1)) { - // then iterate to the next mode and send - Modes::nextMode(); - m_state = STATE_PULL_EACH_MODE_SEND; - } else { - // otherwise done sending modes - m_state = STATE_PULL_EACH_MODE_DONE; - } - break; - case STATE_PULL_EACH_MODE_DONE: - // send our acknowledgement that the modes were sent - SerialComs::write(EDITOR_VERB_PULL_EACH_MODE_DONE); - // switch back to the previous mode - Modes::setCurMode(m_previousModeIndex); - // go idle - m_state = STATE_IDLE; - break; - - // ------------------------------- - // Receive Modes from PC Safer - case STATE_PUSH_EACH_MODE: - // editor requested to push modes, find out how many - // ack the command and wait for the amount of modes - SerialComs::write(EDITOR_VERB_PUSH_EACH_MODE_ACK); - m_state = STATE_PUSH_EACH_MODE_COUNT; - break; - case STATE_PUSH_EACH_MODE_COUNT: - if (receiveModeCount() == RV_WAIT) { - // just wait - break; - } - // clear modes and start receiving - Modes::clearModes(); - // write out an ack - SerialComs::write(EDITOR_VERB_PUSH_EACH_MODE_ACK); - // ready to receive a mode - m_state = STATE_PUSH_EACH_MODE_RECEIVE; - break; - case STATE_PUSH_EACH_MODE_RECEIVE: - // receive the modes into the receive buffer - if (receiveMode() == RV_WAIT) { - // just wait - break; - } - SerialComs::write(EDITOR_VERB_PUSH_EACH_MODE_ACK); - if (m_numModesToReceive > 0) { - m_numModesToReceive--; - } - if (!m_numModesToReceive) { - // success modes were received send the done - m_state = STATE_PUSH_EACH_MODE_DONE; - } - break; - case STATE_PUSH_EACH_MODE_DONE: - // save the new modes - Modes::saveStorage(); - // did originally receive/send a DONE message here but it wasn't working - // on lightshow.lol so just skip to IDLE - m_state = STATE_IDLE; - break; - - // ------------------------------- - // Set Global Brightness - case STATE_SET_GLOBAL_BRIGHTNESS: - SerialComs::write(EDITOR_VERB_READY); - m_state = STATE_SET_GLOBAL_BRIGHTNESS_RECEIVE; - break; - case STATE_SET_GLOBAL_BRIGHTNESS_RECEIVE: - // set the brightness of the device - if (receiveBrightness() == RV_WAIT) { - // just keep waiting - break; - } - m_state = STATE_IDLE; - break; - - // ------------------------------- - // Get Global Brightness - case STATE_GET_GLOBAL_BRIGHTNESS: - sendBrightness(); - m_state = STATE_IDLE; - break; - } -} - -void EditorConnection::showEditor() -{ - switch (m_state) { - case STATE_DISCONNECTED: - Leds::clearAll(); - Leds::blinkAll(250, 150, RGB_WHITE0); - break; - case STATE_IDLE: - m_previewMode.play(); - break; - default: - // do nothing! - // Note if you clear the leds while selecting color - // it may make the color selection choppy - break; - } -} - -void EditorConnection::receiveData() -{ - // read more data into the receive buffer - SerialComs::read(m_receiveBuffer); -} - -void EditorConnection::sendModes() -{ - ByteStream modesBuffer; - Modes::saveToBuffer(modesBuffer); - SerialComs::write(modesBuffer); -} - -void EditorConnection::sendModeCount() -{ - ByteStream buffer; - buffer.serialize8(Modes::numModes()); - SerialComs::write(buffer); -} - -void EditorConnection::sendCurMode() -{ - ByteStream modeBuffer; - Mode *cur = Modes::curMode(); - if (!cur) { - // ?? - return; - } - if (!cur->saveToBuffer(modeBuffer)) { - // ?? - return; - } - SerialComs::write(modeBuffer); -} - -void EditorConnection::sendCurModeVL() -{ -#if VL_ENABLE_SENDER == 1 - m_state = STATE_TRANSMIT_MODE_VL; -#endif -} - -ReturnCode EditorConnection::sendBrightness() -{ - ByteStream brightnessBuf; - if (!brightnessBuf.serialize8(Leds::getBrightness())) { - return RV_FAIL; - } - SerialComs::write(brightnessBuf); - return RV_OK; -} - -ReturnCode EditorConnection::receiveBuffer(ByteStream &buffer) -{ - // need at least the buffer size first - uint32_t size = 0; - if (m_receiveBuffer.size() < sizeof(size)) { - // wait, not enough data available yet - return RV_WAIT; - } - // grab the size out of the start - m_receiveBuffer.resetUnserializer(); - size = m_receiveBuffer.peek32(); - if (m_receiveBuffer.size() < (size + sizeof(size))) { - // don't unserialize yet, not ready - return RV_WAIT; - } - // okay unserialize now, first unserialize the size - if (!m_receiveBuffer.consume32(&size)) { - // this shouldn't fail! - return RV_FAIL; - } - // create a new ByteStream that will hold the full buffer of data - if (!buffer.init(size)) { - return RV_FAIL; - } - // then copy everything from the receive buffer into the rawdata - // which is going to overwrite the crc/size/flags of the ByteStream - if (!m_receiveBuffer.consume(size, buffer.rawData())) { - return RV_FAIL; - } - buffer.sanity(); - if (!buffer.checkCRC()) { - buffer.clear(); - // return true otherwise we'll get locked in this state forever - return RV_FAIL; - } - return RV_OK; -} - -ReturnCode EditorConnection::receiveModes() -{ - // create a new ByteStream that will hold the full buffer of data - ByteStream buf; - m_rv = receiveBuffer(buf); - if (m_rv != RV_OK) { - return m_rv; - } - if (!Modes::loadFromBuffer(buf) || !Modes::saveStorage()) { - return RV_FAIL; - } - return RV_OK; -} - -ReturnCode EditorConnection::receiveModeCount() -{ - ByteStream buf; - m_rv = receiveBuffer(buf); - if (m_rv != RV_OK) { - return m_rv; - } - // unserialize the mode count - if (!buf.consume8(&m_numModesToReceive)) { - return RV_FAIL; - } - if (m_numModesToReceive > MAX_MODES) { - return RV_FAIL; - } - // good mode count - return RV_OK; -} - -ReturnCode EditorConnection::receiveMode() -{ - ByteStream buf; - m_rv = receiveBuffer(buf); - if (m_rv != RV_OK) { - return m_rv; - } - // unserialize the mode into the demo mode - if (!Modes::addModeFromBuffer(buf)) { - return RV_FAIL; - } - return RV_OK; -} - -ReturnCode EditorConnection::receiveDemoMode() -{ - // create a new ByteStream that will hold the full buffer of data - ByteStream buf; - m_rv = receiveBuffer(buf); - if (m_rv != RV_OK) { - return m_rv; - } - // unserialize the mode into the demo mode - if (!m_previewMode.loadFromBuffer(buf)) { - // failure - return RV_FAIL; - } - return RV_OK; -} - -ReturnCode EditorConnection::receiveMessage(const char *message) -{ - size_t len = strlen(message); - uint8_t byte = 0; - // wait for the editor to ack the idle - if (m_receiveBuffer.size() < len) { - return RV_WAIT; - } - if (memcmp(m_receiveBuffer.data(), message, len) != 0) { - return RV_FAIL; - } - for (size_t i = 0; i < len; ++i) { - if (!m_receiveBuffer.consume8(&byte)) { - return RV_FAIL; - } - } - // we have now received at least one command, do not allow resetting - m_allowReset = false; - return RV_OK; -} - -ReturnCode EditorConnection::receiveBrightness() -{ - // create a new ByteStream that will hold the full buffer of data - ByteStream buf; - m_rv = receiveBuffer(buf); - if (m_rv != RV_OK) { - // RV_WAIT or RV_FAIL - return m_rv; - } - if (!buf.size()) { - // failure - return RV_FAIL; - } - uint8_t brightness = 255; - if (!buf.consume8(&brightness) || brightness == 0) { - // they should never send 0 brightness - return RV_FAIL; - } - if (brightness > 0) { - Leds::setBrightness(brightness); - Modes::saveHeader(); - } - return RV_OK; -} diff --git a/VortexEngine/src/Menus/MenuList/EditorConnection.h b/VortexEngine/src/Menus/MenuList/EditorConnection.h index fa499db7fe..87382d819c 100644 --- a/VortexEngine/src/Menus/MenuList/EditorConnection.h +++ b/VortexEngine/src/Menus/MenuList/EditorConnection.h @@ -6,122 +6,66 @@ #include "../../Serial/ByteStream.h" #include "../../Modes/Mode.h" -// various return codes that internal functions use -enum ReturnCode -{ +typedef enum { + STATE_DISCONNECTED, + STATE_GREETING, + STATE_IDLE, + STATE_PULL_MODES, + STATE_PULL_MODES_SEND, + STATE_PULL_MODES_DONE, + STATE_PUSH_MODES, + STATE_PUSH_MODES_RECEIVE, + STATE_PUSH_MODES_DONE, + STATE_DEMO_MODE, + STATE_DEMO_MODE_RECEIVE, + STATE_DEMO_MODE_DONE, + STATE_CLEAR_DEMO, + STATE_TRANSMIT_MODE_VL, + STATE_TRANSMIT_MODE_VL_DONE, + STATE_PULL_EACH_MODE, + STATE_PULL_EACH_MODE_COUNT, + STATE_PULL_EACH_MODE_SEND, + STATE_PULL_EACH_MODE_WAIT, + STATE_PULL_EACH_MODE_DONE, + STATE_PUSH_EACH_MODE, + STATE_PUSH_EACH_MODE_COUNT, + STATE_PUSH_EACH_MODE_RECEIVE, + STATE_PUSH_EACH_MODE_WAIT, + STATE_PUSH_EACH_MODE_DONE, + STATE_SET_GLOBAL_BRIGHTNESS, + STATE_SET_GLOBAL_BRIGHTNESS_RECEIVE, + STATE_GET_GLOBAL_BRIGHTNESS, +} EditorConnectionState; + +typedef struct EditorConnectionCommandState_s { + const char *cmd; + EditorConnectionState cmdState; +} EditorConnectionCommandState; + +typedef enum { RV_FAIL = 0, RV_OK, RV_WAIT, -}; - -class EditorConnection : public Menu -{ -public: - EditorConnection(const RGBColor &col, bool advanced); - ~EditorConnection(); - - bool init() override; - MenuAction run() override; - - // handlers for clicks - void onShortClick() override; - void onLongClick() override; - - void leaveMenu(bool doSave = false) override; - -private: - void clearDemo(); - void handleErrors(); - void handleCommand(); - void handleState(); - void showEditor(); - void receiveData(); - void sendModes(); - void sendModeCount(); - void sendCurMode(); - void sendCurModeVL(); - ReturnCode sendBrightness(); - ReturnCode receiveBuffer(ByteStream &buffer); - ReturnCode receiveModes(); - ReturnCode receiveModeCount(); - ReturnCode receiveMode(); - ReturnCode receiveDemoMode(); - ReturnCode receiveMessage(const char *message); - ReturnCode receiveBrightness(); - - enum EditorConnectionState { - // the editor is not connected - STATE_DISCONNECTED, - - // Sending the greeting message - STATE_GREETING, - - // entirely idle, waiting for commands - STATE_IDLE, - - // editor pulls the modes from device, then wait for the sent modes ack - STATE_PULL_MODES, - STATE_PULL_MODES_SEND, - STATE_PULL_MODES_DONE, - - // editor pushes modes to device, then waits for done - STATE_PUSH_MODES, - STATE_PUSH_MODES_RECEIVE, - STATE_PUSH_MODES_DONE, - - // editor pushes mode to device for demo while idle - STATE_DEMO_MODE, - STATE_DEMO_MODE_RECEIVE, - STATE_DEMO_MODE_DONE, - - // editor tells device to clear the demo preview, device acknowledge - STATE_CLEAR_DEMO, - - // transmit the mode over visible light - STATE_TRANSMIT_MODE_VL, - STATE_TRANSMIT_MODE_VL_DONE, - - // editor pulls the modes from device (safer version) - STATE_PULL_EACH_MODE, - STATE_PULL_EACH_MODE_COUNT, - STATE_PULL_EACH_MODE_SEND, - STATE_PULL_EACH_MODE_WAIT, - STATE_PULL_EACH_MODE_DONE, - - // editor pushes modes to device (safer version) - STATE_PUSH_EACH_MODE, - STATE_PUSH_EACH_MODE_COUNT, - STATE_PUSH_EACH_MODE_RECEIVE, - STATE_PUSH_EACH_MODE_WAIT, - STATE_PUSH_EACH_MODE_DONE, - - // set global brightness - STATE_SET_GLOBAL_BRIGHTNESS, - STATE_SET_GLOBAL_BRIGHTNESS_RECEIVE, - - // get global brightness - STATE_GET_GLOBAL_BRIGHTNESS, - }; - - struct CommandState - { - const char *cmd; - EditorConnection::EditorConnectionState cmdState; - }; - static const CommandState commands[]; - - // state of the editor - EditorConnectionState m_state; - // the data that is received - ByteStream m_receiveBuffer; - // Whether at least one command has been received yet - bool m_allowReset; - // the mode index to return to after iterating the modes to send them - uint8_t m_previousModeIndex; - // the number of modes that should be received - uint8_t m_numModesToReceive; - // internal return value tracker - ReturnCode m_rv; -}; +} ReturnCode; + +typedef struct EditorConnectionMenu_s { + Menu base; + EditorConnectionState state; + ByteStream receiveBuffer; + bool allowReset; + uint8_t previousModeIndex; + uint8_t numModesToReceive; + ReturnCode rv; +} EditorConnectionMenu; + +Menu *EditorConnectionMenu_Create(RGBColor col, bool advanced); +void EditorConnectionMenu_destroy(Menu *self); +bool EditorConnectionMenu_init(Menu *self); +MenuAction EditorConnectionMenu_run(Menu *self); +void EditorConnectionMenu_onShortClick(Menu *self); +void EditorConnectionMenu_onLongClick(Menu *self); +void EditorConnectionMenu_leaveMenu(Menu *self, bool doSave); + +extern const MenuVTable g_editorConnectionMenuVTable; #endif diff --git a/VortexEngine/src/Menus/MenuList/FactoryReset.c b/VortexEngine/src/Menus/MenuList/FactoryReset.c new file mode 100644 index 0000000000..ad7ec418a9 --- /dev/null +++ b/VortexEngine/src/Menus/MenuList/FactoryReset.c @@ -0,0 +1,146 @@ +#include "FactoryReset.h" +#include + +#include "../../VortexEngine.h" +#include "../../Modes/DefaultModes.h" +#include "../../Time/TimeControl.h" +#include "../../Patterns/Pattern.h" +#include "../../Buttons/Button.h" +#include "../../Time/Timings.h" +#include "../../Modes/Modes.h" +#include "../../Modes/Mode.h" +#include "../../Leds/Leds.h" +#include "../../Log/Log.h" + +#include "../../VortexConfig.h" + +#define COL32(v) ((RGBColor){ ((v) >> 16) & 0xFF, ((v) >> 8) & 0xFF, (v) & 0xFF }) + +static void showReset(FactoryResetMenu *self); + +extern void VortexEngine_setAutoCycle(bool enabled); + +Menu *FactoryResetMenu_Create(RGBColor col, bool advanced) +{ + FactoryResetMenu *self = (FactoryResetMenu *)malloc(sizeof(FactoryResetMenu)); + if (!self) return NULL; + Menu_construct(&self->base, col, advanced); + self->base.vtable = &g_factoryResetMenuVTable; + return &self->base; +} + +void FactoryResetMenu_destroy(Menu *self) +{ + (void)self; +} + +bool FactoryResetMenu_init(Menu *self) +{ + if (!Menu_init(self)) { + return false; + } + Mode *cur = Modes_curMode(); + if (!cur) { + return false; + } + if (Mode_isMultiLed(cur)) { + self->ledSelected = true; + self->targetLeds = MAP_LED(LED_MULTI); + } + if (!self->advanced) { + self->ledSelected = true; + } + self->curSelection = 0; + DEBUG_LOG("Entered factory reset"); + return true; +} + +MenuAction FactoryResetMenu_run(Menu *self) +{ + MenuAction result = Menu_run(self); + if (result != MENU_CONTINUE) { + return result; + } + showReset((FactoryResetMenu *)self); + return MENU_CONTINUE; +} + +void FactoryResetMenu_onShortClick(Menu *self) +{ + self->curSelection = (uint8_t)!self->curSelection; +} + +void FactoryResetMenu_onLongClick(Menu *self) +{ + if (self->curSelection == 0) { + Menu_leaveMenu(self, false); + return; + } + if (Button_holdDuration(g_pButton) <= (FACTORY_RESET_THRESHOLD_TICKS + MS_TO_TICKS(10))) { + return; + } + if (self->advanced) { + const struct DefaultModeEntry *defMode = &defaultModes[Modes_curModeIndex()]; + Mode *cur = Modes_curMode(); + MAP_FOREACH_LED(self->targetLeds) { + const struct DefaultLedEntry *led = &defMode->leds[pos]; + Colorset set; + Colorset_initFromU32s(&set, led->numColors, led->cols); + Mode_setPattern(cur, led->patternID, pos, NULL, &set); + } + Mode_init(cur); + } else { + Leds_setBrightness(DEFAULT_BRIGHTNESS); + VortexEngine_setAutoCycle(false); + Modes_setDefaults(); + Modes_setCurMode(0); + Modes_resetFlags(); + } + Menu_leaveMenu(self, true); +} + +static void showReset(FactoryResetMenu *self) +{ + (void)self; + if (self->base.curSelection == 0) { + Leds_clearAll(); + Leds_blinkAll(350, 350, COL32(RGB_WHITE0)); + return; + } + bool isPressed = Button_isPressed(g_pButton); + if (!isPressed) { + Leds_clearAll(); + Leds_blinkAll(50, 50, COL32(RGB_RED4)); + return; + } + uint32_t holdDur = Button_holdDuration(g_pButton); + if (holdDur < MS_TO_TICKS(100)) { + return; + } + uint16_t progress = ((holdDur * 100) / FACTORY_RESET_THRESHOLD_TICKS); + DEBUG_LOGF("progress: %d", progress); + if (progress >= 100) { + Leds_setAll(COL32(RGB_WHITE)); + return; + } + uint8_t offMs = 100; + uint8_t onMs = (progress > 60) ? 30 : 100; + uint8_t sat = (uint8_t)((progress * 5) >> 1); + Leds_clearAll(); + HSVColor hsv; + HSVColor_initHSV(&hsv, 0, 255 - sat, 180); + RGBColor rgb; + RGBColor_initFromHSV(&rgb, &hsv); + Leds_blinkIndex(LED_0, offMs, onMs, rgb); + Leds_blinkIndex(LED_1, offMs, onMs, COL32(RGB_WHITE0)); +} + +const MenuVTable g_factoryResetMenuVTable = { + .destroy = FactoryResetMenu_destroy, + .init = FactoryResetMenu_init, + .run = FactoryResetMenu_run, + .onLedSelected = Menu_onLedSelected, + .onShortClick = FactoryResetMenu_onShortClick, + .onLongClick = FactoryResetMenu_onLongClick, + .leaveMenu = Menu_leaveMenu, +}; diff --git a/VortexEngine/src/Menus/MenuList/FactoryReset.cpp b/VortexEngine/src/Menus/MenuList/FactoryReset.cpp deleted file mode 100644 index 5489144ed0..0000000000 --- a/VortexEngine/src/Menus/MenuList/FactoryReset.cpp +++ /dev/null @@ -1,129 +0,0 @@ -#include "FactoryReset.h" - -#include "../../VortexEngine.h" -#include "../../Modes/DefaultModes.h" -#include "../../Time/TimeControl.h" -#include "../../Patterns/Pattern.h" -#include "../../Buttons/Button.h" -#include "../../Time/Timings.h" -#include "../../Modes/Modes.h" -#include "../../Modes/Mode.h" -#include "../../Leds/Leds.h" -#include "../../Log/Log.h" - -#include "../../VortexConfig.h" - -FactoryReset::FactoryReset(const RGBColor &col, bool advanced) : - Menu(col, advanced) -{ -} - -FactoryReset::~FactoryReset() -{ -} - -bool FactoryReset::init() -{ - if (!Menu::init()) { - return false; - } - // bypass led selection for fac reset if a multi was set on - // the current slot because it doesn't make sense to pick - Mode *cur = Modes::curMode(); - if (!cur) { - return false; - } - if (cur->isMultiLed()) { - m_ledSelected = true; - m_targetLeds = MAP_LED(LED_MULTI); - } - if (!m_advanced) { - // skip led selection - m_ledSelected = true; - } - // Start on exit by default - m_curSelection = false; - DEBUG_LOG("Entered factory reset"); - return true; -} - -Menu::MenuAction FactoryReset::run() -{ - MenuAction result = Menu::run(); - if (result != MENU_CONTINUE) { - return result; - } - showReset(); - return MENU_CONTINUE; -} - -void FactoryReset::onShortClick() -{ - m_curSelection = (uint8_t)!m_curSelection; -} - -void FactoryReset::onLongClick() -{ - if (m_curSelection == 0) { - // if the selection isn't actually on factory reset then just leave - leaveMenu(); - return; - } - // if the button hasn't been held long enough just return - if (g_pButton->holdDuration() <= (FACTORY_RESET_THRESHOLD_TICKS + MS_TO_TICKS(10))) { - return; - } - // the button was held down long enough so actually perform the factory reset - // restore defaults and then leave menu and save - if (m_advanced) { - // reset the target mode slot on the target led - const DefaultModeEntry &defMode = defaultModes[Modes::curModeIndex()]; - Mode *cur = Modes::curMode(); - MAP_FOREACH_LED(m_targetLeds) { - const DefaultLedEntry &led = defMode.leds[pos]; - Colorset set(led.numColors, led.cols); - cur->setPattern(led.patternID, pos, nullptr, &set); - } - cur->init(); - } else { - Leds::setBrightness(DEFAULT_BRIGHTNESS); - VortexEngine::setAutoCycle(false); - Modes::setDefaults(); - Modes::setCurMode(0); - Modes::resetFlags(); - } - leaveMenu(true); -} - -void FactoryReset::showReset() -{ - if (m_curSelection == 0) { - Leds::clearAll(); - Leds::blinkAll(350, 350, RGB_WHITE0); - return; - } - bool isPressed = g_pButton->isPressed(); - if (!isPressed) { - Leds::clearAll(); - Leds::blinkAll(50, 50, RGB_RED4); - return; - } - // don't start the fill until the button has been held for a bit - uint32_t holdDur = g_pButton->holdDuration(); - if (holdDur < MS_TO_TICKS(100)) { - return; - } - uint16_t progress = ((holdDur * 100) / FACTORY_RESET_THRESHOLD_TICKS); - DEBUG_LOGF("progress: %d", progress); - if (progress >= 100) { - Leds::setAll(RGB_WHITE); - return; - } - uint8_t offMs = 100; - uint8_t onMs = (progress > 60) ? 30 : 100; - uint8_t sat = (uint8_t)((progress * 5) >> 1); // Using bit shift for division by 2 - Leds::clearAll(); - Leds::blinkIndex(LED_0, offMs, onMs, HSVColor(0, 255 - sat, 180)); - Leds::blinkIndex(LED_1, offMs, onMs, RGB_WHITE0); -} - diff --git a/VortexEngine/src/Menus/MenuList/FactoryReset.h b/VortexEngine/src/Menus/MenuList/FactoryReset.h index e1fd64414e..31d4e6caea 100644 --- a/VortexEngine/src/Menus/MenuList/FactoryReset.h +++ b/VortexEngine/src/Menus/MenuList/FactoryReset.h @@ -3,21 +3,17 @@ #include "../Menu.h" -class FactoryReset : public Menu -{ -public: - FactoryReset(const RGBColor &col, bool advanced); - ~FactoryReset(); +typedef struct FactoryResetMenu_s { + Menu base; +} FactoryResetMenu; - bool init() override; - MenuAction run() override; +Menu *FactoryResetMenu_Create(RGBColor col, bool advanced); +void FactoryResetMenu_destroy(Menu *self); +bool FactoryResetMenu_init(Menu *self); +MenuAction FactoryResetMenu_run(Menu *self); +void FactoryResetMenu_onShortClick(Menu *self); +void FactoryResetMenu_onLongClick(Menu *self); - // handlers for clicks - void onShortClick() override; - void onLongClick() override; - -private: - void showReset(); -}; +extern const MenuVTable g_factoryResetMenuVTable; #endif diff --git a/VortexEngine/src/Menus/MenuList/GlobalBrightness.c b/VortexEngine/src/Menus/MenuList/GlobalBrightness.c new file mode 100644 index 0000000000..919b08b665 --- /dev/null +++ b/VortexEngine/src/Menus/MenuList/GlobalBrightness.c @@ -0,0 +1,204 @@ +#include "GlobalBrightness.h" +#include + +#include "../../VortexEngine.h" + +#include "../../Patterns/PatternArgs.h" +#include "../../Patterns/Pattern.h" +#include "../../Time/TimeControl.h" +#include "../../Colors/Colorset.h" +#include "../../Buttons/Button.h" +#include "../../Time/Timings.h" +#include "../../Menus/Menus.h" +#include "../../Modes/Modes.h" +#include "../../Leds/Leds.h" +#include "../../Log/Log.h" + +#define COL32(v) ((RGBColor){ ((v) >> 16) & 0xFF, ((v) >> 8) & 0xFF, (v) & 0xFF }) + +static const uint8_t s_brightnessOptions[4] = { + BRIGHTNESS_OPTION_1, BRIGHTNESS_OPTION_2, BRIGHTNESS_OPTION_3, BRIGHTNESS_OPTION_4 +}; + +#define NUM_BRIGHTNESS_OPTIONS (sizeof(s_brightnessOptions) / sizeof(s_brightnessOptions[0])) + +static void showBrightnessSelection(GlobalBrightnessMenu *self); +static void setKeychainModeState(GlobalBrightnessMenu *self, KeychainModeState newState); +static MenuAction runKeychainMode(GlobalBrightnessMenu *self); + +extern void VortexEngine_toggleForceSleep(bool enabled); +extern void VortexEngine_enterSleep(bool save); + +Menu *GlobalBrightnessMenu_Create(RGBColor col, bool advanced) +{ + GlobalBrightnessMenu *self = (GlobalBrightnessMenu *)malloc(sizeof(GlobalBrightnessMenu)); + if (!self) return NULL; + Menu_construct(&self->base, col, advanced); + self->base.vtable = &g_globalBrightnessMenuVTable; + self->keychainModeState = KEYCHAIN_MODE_STATE_OFF; + self->lastStateChange = 0; + self->colorIndex = 0; + return &self->base; +} + +void GlobalBrightnessMenu_destroy(Menu *self) +{ + (void)self; + VortexEngine_toggleForceSleep(true); +} + +bool GlobalBrightnessMenu_init(Menu *self) +{ + if (!Menu_init(self)) { + return false; + } + GlobalBrightnessMenu *gb = (GlobalBrightnessMenu *)self; + self->ledSelected = true; + for (uint8_t i = 0; i < NUM_BRIGHTNESS_OPTIONS; ++i) { + if (s_brightnessOptions[i] == Leds_getBrightness()) { + self->curSelection = i; + } + } + if (self->advanced) { + VortexEngine_toggleForceSleep(false); + Modes_setKeychainMode(true, true); + setKeychainModeState(gb, KEYCHAIN_MODE_STATE_SOLID); + gb->colorIndex = 0; + } + DEBUG_LOG("Entered global brightness"); + return true; +} + +MenuAction GlobalBrightnessMenu_run(Menu *self) +{ + MenuAction result = Menu_run(self); + if (result != MENU_CONTINUE) { + return result; + } + GlobalBrightnessMenu *gb = (GlobalBrightnessMenu *)self; + if (self->advanced) { + return runKeychainMode(gb); + } + showBrightnessSelection(gb); + Menus_showSelection(COL32(RGB_WHITE5)); + return MENU_CONTINUE; +} + +void GlobalBrightnessMenu_onShortClick(Menu *self) +{ + if (self->advanced) { + return; + } + self->curSelection = (self->curSelection + 1) % (NUM_BRIGHTNESS_OPTIONS + 1); +} + +void GlobalBrightnessMenu_onLongClick(Menu *self) +{ + if (self->advanced) { + return; + } + if (self->curSelection >= NUM_BRIGHTNESS_OPTIONS) { + Menu_leaveMenu(self, false); + return; + } + Leds_setBrightness(s_brightnessOptions[self->curSelection]); + Menu_leaveMenu(self, true); +} + +static void showBrightnessSelection(GlobalBrightnessMenu *self) +{ + if (self->base.curSelection >= NUM_BRIGHTNESS_OPTIONS) { + Menu_showExit(&self->base); + return; + } + HSVColor hsv; + HSVColor_initHSV(&hsv, 38, 255, s_brightnessOptions[self->base.curSelection]); + RGBColor rgb; + RGBColor_initFromHSV(&rgb, &hsv); + Leds_setAll(rgb); +} + +// ==================== KEYCHAIN_MODE STUFF ==================== + +#define KEYCHAIN_MODE_TIMER_MS 2100 +#define KEYCHAIN_MODE_TIMER_TICKS MS_TO_TICKS(KEYCHAIN_MODE_TIMER_MS) +#define KEYCHAIN_MODE_EXIT_CLICKS 8 +#define KEYCHAIN_MODE_SLEEP_TICKS SEC_TO_TICKS(300) + +static MenuAction runKeychainMode(GlobalBrightnessMenu *self) +{ + if (Button_onConsecutivePresses(g_pButton, KEYCHAIN_MODE_EXIT_CLICKS)) { + Modes_setKeychainMode(false, true); + return MENU_QUIT; + } + uint32_t now = Time_getCurtime(); + if (Button_onPress(g_pButton) && self->keychainModeState != KEYCHAIN_MODE_STATE_OFF && now > (self->lastStateChange + KEYCHAIN_MODE_TIMER_TICKS)) { + setKeychainModeState(self, KEYCHAIN_MODE_STATE_OFF); + return MENU_CONTINUE; + } + if (Button_onRelease(g_pButton) && Button_pressTime(g_pButton) > self->lastStateChange) { + setKeychainModeState(self, (KeychainModeState)(self->keychainModeState + 1)); + return MENU_CONTINUE; + } + if (Button_isPressed(g_pButton)) { + Leds_clearAll(); + return MENU_CONTINUE; + } + if (self->keychainModeState == KEYCHAIN_MODE_STATE_OFF && now > (self->lastStateChange + KEYCHAIN_MODE_SLEEP_TICKS)) { + VortexEngine_enterSleep(false); + return MENU_QUIT; + } + if (self->keychainModeState == KEYCHAIN_MODE_STATE_SIGNAL) { + Leds_clearAll(); + Colorset cs = Mode_getColorset(&self->base.previewMode, LED_ALL); + RGBColor col = Colorset_get(&cs, self->colorIndex); + Leds_blinkIndexOffset(LED_ALL, + Time_getCurtime() - self->lastStateChange, + SIGNAL_OFF_DURATION, + SIGNAL_ON_DURATION, + col); + } else { + Mode_play(&self->base.previewMode); + } + return MENU_CONTINUE; +} + +static void setKeychainModeState(GlobalBrightnessMenu *self, KeychainModeState newState) +{ + self->keychainModeState = (KeychainModeState)(newState % KEYCHAIN_MODE_STATE_COUNT); + self->lastStateChange = Time_getCurtime(); + Colorset curColorset = Mode_getColorset(Modes_curMode(), LED_ALL); + uint8_t numCols = Colorset_numColors(&curColorset); + PatternArgs args; + PatternArgs_init(&args); + switch (self->keychainModeState) { + case KEYCHAIN_MODE_STATE_OFF: + default: + self->colorIndex++; + if (self->colorIndex >= numCols) { + self->colorIndex = 0; + } + break; + case KEYCHAIN_MODE_STATE_SOLID: + PatternArgs_init1(&args, 200); + break; + case KEYCHAIN_MODE_STATE_DOPS: + PatternArgs_init2(&args, 1, 10); + break; + case KEYCHAIN_MODE_STATE_SIGNAL: + break; + } + args.arg6 = self->colorIndex; + Mode_setPattern(&self->base.previewMode, PATTERN_SOLID, LED_ALL, &args, NULL); + Mode_init(&self->base.previewMode); +} + +const MenuVTable g_globalBrightnessMenuVTable = { + .destroy = GlobalBrightnessMenu_destroy, + .init = GlobalBrightnessMenu_init, + .run = GlobalBrightnessMenu_run, + .onLedSelected = Menu_onLedSelected, + .onShortClick = GlobalBrightnessMenu_onShortClick, + .onLongClick = GlobalBrightnessMenu_onLongClick, + .leaveMenu = Menu_leaveMenu, +}; diff --git a/VortexEngine/src/Menus/MenuList/GlobalBrightness.cpp b/VortexEngine/src/Menus/MenuList/GlobalBrightness.cpp deleted file mode 100644 index 96b186498e..0000000000 --- a/VortexEngine/src/Menus/MenuList/GlobalBrightness.cpp +++ /dev/null @@ -1,212 +0,0 @@ -#include "GlobalBrightness.h" - -#include "../../VortexEngine.h" - -#include "../../Patterns/PatternArgs.h" -#include "../../Patterns/Pattern.h" -#include "../../Time/TimeControl.h" -#include "../../Colors/Colorset.h" -#include "../../Buttons/Button.h" -#include "../../Time/Timings.h" -#include "../../Menus/Menus.h" -#include "../../Modes/Modes.h" -#include "../../Time/Timer.h" -#include "../../Leds/Leds.h" -#include "../../Log/Log.h" - -// allow the number of brightness options to be adjusted dynamically -#define NUM_BRIGHTNESS_OPTIONS (sizeof(m_brightnessOptions) / sizeof(m_brightnessOptions[0])) - -GlobalBrightness::GlobalBrightness(const RGBColor &col, bool advanced) : - Menu(col, advanced), - m_keychain_modeState(KEYCHAIN_MODE_STATE_OFF), - m_lastStateChange(0), - m_colorIndex(0) -{ -} - -GlobalBrightness::~GlobalBrightness() -{ - // ensure force sleep is turned back on when we leave - VortexEngine::toggleForceSleep(true); -} - -bool GlobalBrightness::init() -{ - if (!Menu::init()) { - return false; - } - // bypass led selection - m_ledSelected = true; - // would be nice if there was a more elegant way to do this - for (uint8_t i = 0; i < NUM_BRIGHTNESS_OPTIONS; ++i) { - if (m_brightnessOptions[i] == Leds::getBrightness()) { - // make sure the default selection matches cur value - m_curSelection = i; - } - } - if (m_advanced) { - // turn off force sleep while running keychain mode - VortexEngine::toggleForceSleep(false); - // enable keychain mode so if the device sleeps it will wake into keychain mode - Modes::setKeychainMode(true); - // start in the off position, this call is necessary to update the - // lastStateChange time to the current time of init - setKeychainModeState(KEYCHAIN_MODE_STATE_SOLID); - // reset the color index to ensure it starts on first color - m_colorIndex = 0; - } - DEBUG_LOG("Entered global brightness"); - return true; -} - -Menu::MenuAction GlobalBrightness::run() -{ - MenuAction result = Menu::run(); - if (result != MENU_CONTINUE) { - return result; - } - if (m_advanced) { - return runKeychainMode(); - } - // show the current brightness - showBrightnessSelection(); - // show selections - Menus::showSelection(); - // continue - return MENU_CONTINUE; -} - -void GlobalBrightness::onShortClick() -{ - if (m_advanced) { - return; - } - // include one extra option for the exit slot - m_curSelection = (m_curSelection + 1) % (NUM_BRIGHTNESS_OPTIONS + 1); -} - -void GlobalBrightness::onLongClick() -{ - if (m_advanced) { - return; - } - if (m_curSelection >= NUM_BRIGHTNESS_OPTIONS) { - // no save exit - leaveMenu(); - return; - } - // set the global brightness - Leds::setBrightness(m_brightnessOptions[m_curSelection]); - // done here, save settings with new brightness - leaveMenu(true); -} - -void GlobalBrightness::showBrightnessSelection() -{ - if (m_curSelection >= NUM_BRIGHTNESS_OPTIONS) { - showExit(); - return; - } - Leds::setAll(HSVColor(38, 255, m_brightnessOptions[m_curSelection])); -} - -// ==================== KEYCHAIN_MODE STUFF ==================== - -// The general timer for keychain mode to remain active -#define KEYCHAIN_MODE_TIMER_MS 2100 -#define KEYCHAIN_MODE_TIMER_TICKS MS_TO_TICKS(KEYCHAIN_MODE_TIMER_MS) -// the number of clicks needed to exit keychain mode -#define KEYCHAIN_MODE_EXIT_CLICKS 8 -// This is the duration of idle time on blank before it will auto-sleep -// give a solid 300 seconds (5 minutes) before it goes to sleep -#define KEYCHAIN_MODE_SLEEP_TICKS SEC_TO_TICKS(300) - -// bonus simulate keychain_mode in this menu -Menu::MenuAction GlobalBrightness::runKeychainMode() -{ - // check for exit - if (g_pButton->onConsecutivePresses(KEYCHAIN_MODE_EXIT_CLICKS)) { - // turn off keychain mode now - Modes::setKeychainMode(false); - return MENU_QUIT; - } - uint32_t now = Time::getCurtime(); - // whether the button was pressed before the timer expired - // when the button is first pressed after the window has expired switch off - if (g_pButton->onPress() && m_keychain_modeState != KEYCHAIN_MODE_STATE_OFF && now > (m_lastStateChange + KEYCHAIN_MODE_TIMER_TICKS)) { - setKeychainModeState(KEYCHAIN_MODE_STATE_OFF); - return MENU_CONTINUE; - } - // when the button is released, but only if they pressed it within this state - if (g_pButton->onRelease() && g_pButton->pressTime() > m_lastStateChange) { - // advance the state or turn off based on press time - setKeychainModeState((keychain_mode_state)(m_keychain_modeState + 1)); - return MENU_CONTINUE; - } - // as long as the button is held down the leds clear - if (g_pButton->isPressed()) { - Leds::clearAll(); - return MENU_CONTINUE; - } - // check for sleep after some seconds - if (m_keychain_modeState == KEYCHAIN_MODE_STATE_OFF && now > (m_lastStateChange + KEYCHAIN_MODE_SLEEP_TICKS)) { - // Optional: indicate when the keychain is turning off (useful mainly for debugging) - //Leds::holdAll(RGB_PURPLE); - VortexEngine::enterSleep(false); - return MENU_QUIT; - } - // render the signal mode in a different way - if (m_keychain_modeState == KEYCHAIN_MODE_STATE_SIGNAL) { - // use a custom blink because the SIGNAL_OFF duration is too large for uint8 params - Leds::clearAll(); - Leds::blinkIndexOffset(LED_ALL, - Time::getCurtime() - m_lastStateChange, - SIGNAL_OFF_DURATION, - SIGNAL_ON_DURATION, - m_previewMode.getColorset().get(m_colorIndex)); - } else { - // play the keychain_mode mode - m_previewMode.play(); - } - return MENU_CONTINUE; -} - -void GlobalBrightness::setKeychainModeState(keychain_mode_state newState) -{ - // update the keychain_mode state - m_keychain_modeState = (keychain_mode_state)(newState % KEYCHAIN_MODE_STATE_COUNT); - // record the time of this statechange - m_lastStateChange = Time::getCurtime(); - // this could - uint8_t numCols = Modes::curMode()->getColorset().numColors(); - // the args that will define the timing of the blink on the solid pattern - PatternArgs args; - switch (m_keychain_modeState) { - case KEYCHAIN_MODE_STATE_OFF: - default: - // iterate to next color and wrap to 0, don't use modulus because numCols could be 0 - m_colorIndex++; - if (m_colorIndex >= numCols) { - m_colorIndex = 0; - } - break; - case KEYCHAIN_MODE_STATE_SOLID: - args.init(200); - break; - case KEYCHAIN_MODE_STATE_DOPS: - args.init(1, 10); - break; - case KEYCHAIN_MODE_STATE_SIGNAL: - // unfortunately the signal blink timing is too large to fit into the uint8 params - // so we just use a custom blink to render that one, but use the previewMode for - // the other two states - break; - } - // update the mode and ensure current colorset is always used, use PATTERN_SOLID - // because that will never iterate to the next color and allows us to force - // the color index via argument 6 - args.arg6 = m_colorIndex; - m_previewMode.setPattern(PATTERN_SOLID, LED_ALL, &args); - m_previewMode.init(); -} diff --git a/VortexEngine/src/Menus/MenuList/GlobalBrightness.h b/VortexEngine/src/Menus/MenuList/GlobalBrightness.h index 5efabf408a..7954efef5b 100644 --- a/VortexEngine/src/Menus/MenuList/GlobalBrightness.h +++ b/VortexEngine/src/Menus/MenuList/GlobalBrightness.h @@ -3,56 +3,28 @@ #include "../Menu.h" -class GlobalBrightness : public Menu -{ -public: - GlobalBrightness(const RGBColor &col, bool advanced); - ~GlobalBrightness(); - - bool init() override; - MenuAction run() override; - - // handlers for clicks - void onShortClick() override; - void onLongClick() override; - -private: - void showBrightnessSelection(); - - // the list of brightness options - const uint8_t m_brightnessOptions[4] = { - BRIGHTNESS_OPTION_1, - BRIGHTNESS_OPTION_2, - BRIGHTNESS_OPTION_3, - BRIGHTNESS_OPTION_4 - }; - -private: - // don't worry about this stuff - enum keychain_mode_state : uint8_t - { - // sleeping / fake off - KEYCHAIN_MODE_STATE_OFF = 0, - - // solid/tracer - KEYCHAIN_MODE_STATE_SOLID, - - // dops blink 4 / 16 - KEYCHAIN_MODE_STATE_DOPS, - - // signal blink 16 / 120 - KEYCHAIN_MODE_STATE_SIGNAL, - - // total states - KEYCHAIN_MODE_STATE_COUNT - }; - - keychain_mode_state m_keychain_modeState; - uint32_t m_lastStateChange; - uint8_t m_colorIndex; - - void setKeychainModeState(keychain_mode_state newState); - Menu::MenuAction runKeychainMode(); -}; +typedef enum { + KEYCHAIN_MODE_STATE_OFF = 0, + KEYCHAIN_MODE_STATE_SOLID, + KEYCHAIN_MODE_STATE_DOPS, + KEYCHAIN_MODE_STATE_SIGNAL, + KEYCHAIN_MODE_STATE_COUNT +} KeychainModeState; + +typedef struct GlobalBrightnessMenu_s { + Menu base; + KeychainModeState keychainModeState; + uint32_t lastStateChange; + uint8_t colorIndex; +} GlobalBrightnessMenu; + +Menu *GlobalBrightnessMenu_Create(RGBColor col, bool advanced); +void GlobalBrightnessMenu_destroy(Menu *self); +bool GlobalBrightnessMenu_init(Menu *self); +MenuAction GlobalBrightnessMenu_run(Menu *self); +void GlobalBrightnessMenu_onShortClick(Menu *self); +void GlobalBrightnessMenu_onLongClick(Menu *self); + +extern const MenuVTable g_globalBrightnessMenuVTable; #endif diff --git a/VortexEngine/src/Menus/MenuList/ModeSharing.c b/VortexEngine/src/Menus/MenuList/ModeSharing.c new file mode 100644 index 0000000000..df48bf8d1e --- /dev/null +++ b/VortexEngine/src/Menus/MenuList/ModeSharing.c @@ -0,0 +1,181 @@ +#include "ModeSharing.h" +#include + +#include "../../VortexEngine.h" +#include "../../Serial/ByteStream.h" +#include "../../Serial/Serial.h" +#include "../../Time/TimeControl.h" +#include "../../Time/Timings.h" +#include "../../Wireless/VLReceiver.h" +#include "../../Wireless/VLSender.h" +#include "../../Patterns/Pattern.h" +#include "../../Buttons/Button.h" +#include "../../Modes/Modes.h" +#include "../../Modes/Mode.h" +#include "../../Leds/Leds.h" +#include "../../Log/Log.h" + +#define COL32(v) ((RGBColor){ ((v) >> 16) & 0xFF, ((v) >> 8) & 0xFF, (v) & 0xFF }) + +static void receiveMode(ModeSharingMenu *self); +static void showReceiveMode(ModeSharingMenu *self); + +extern void VortexEngine_toggleForceSleep(bool enabled); + +Menu *ModeSharingMenu_Create(RGBColor col, bool advanced) +{ + ModeSharingMenu *self = (ModeSharingMenu *)malloc(sizeof(ModeSharingMenu)); + if (!self) return NULL; + Menu_construct(&self->base, col, advanced); + self->base.vtable = &g_modeSharingMenuVTable; + self->sharingMode = SHARE_SEND_RECEIVE; + self->timeOutStartTime = 0; + self->lastPercentChange = 0; + self->lastPercent = 0; + return &self->base; +} + +void ModeSharingMenu_destroy(Menu *self) +{ + (void)self; + VLReceiver_endReceiving(); + VortexEngine_toggleForceSleep(true); +} + +bool ModeSharingMenu_init(Menu *self) +{ + if (!Menu_init(self)) { + return false; + } + ModeSharingMenu *ms = (ModeSharingMenu *)self; + if (!self->advanced) { + self->ledSelected = true; + } + VLReceiver_beginReceiving(); + VortexEngine_toggleForceSleep(false); + DEBUG_LOG("Entering Mode Sharing"); + return true; +} + +MenuAction ModeSharingMenu_run(Menu *self) +{ + MenuAction result = Menu_run(self); + if (result != MENU_CONTINUE) { + return result; + } + ModeSharingMenu *ms = (ModeSharingMenu *)self; + if (ms->sharingMode == SHARE_EXIT) { + Menu_showExit(self); + return MENU_CONTINUE; + } + if (Button_isPressed(g_pButton) && Button_holdDuration(g_pButton) >= CLICK_THRESHOLD) { + if (ms->sharingMode == SHARE_SEND_RECEIVE) { + VLSender_send(&self->previewMode); + } else { + VLSender_sendLegacy(&self->previewMode); + } + } + showReceiveMode(ms); + receiveMode(ms); + return MENU_CONTINUE; +} + +void ModeSharingMenu_onLedSelected(Menu *self) +{ + if (self->targetLeds == MAP_LED(LED_1)) { + Mode_swapPatterns(&self->previewMode, LED_0, LED_1); + } +} + +void ModeSharingMenu_onShortClick(Menu *self) +{ + if (Button_holdDuration(g_pButton) >= CLICK_THRESHOLD) { + return; + } + ModeSharingMenu *ms = (ModeSharingMenu *)self; + switch (ms->sharingMode) { + case SHARE_SEND_RECEIVE: + VLReceiver_setLegacyReceiver(true); + ms->sharingMode = SHARE_SEND_RECEIVE_LEGACY; + break; + case SHARE_SEND_RECEIVE_LEGACY: + VLReceiver_setLegacyReceiver(false); + ms->sharingMode = SHARE_EXIT; + break; + case SHARE_EXIT: + ms->sharingMode = SHARE_SEND_RECEIVE; + break; + } + Leds_clearAll(); +} + +void ModeSharingMenu_onLongClick(Menu *self) +{ + ModeSharingMenu *ms = (ModeSharingMenu *)self; + if (ms->sharingMode == SHARE_EXIT) { + Menu_leaveMenu(self, false); + } +} + +static void receiveMode(ModeSharingMenu *self) +{ + uint32_t now = Time_getCurtime(); + if (VLReceiver_onNewData()) { + self->timeOutStartTime = now; + } else if (self->timeOutStartTime > 0 && (self->timeOutStartTime + MAX_TIMEOUT_DURATION) < now) { + VLReceiver_resetVLState(); + self->timeOutStartTime = 0; + return; + } + if (!VLReceiver_dataReady()) { + uint8_t percent = VLReceiver_percentReceived(); + if (percent != self->lastPercent) { + self->lastPercent = percent; + self->lastPercentChange = now; + } + return; + } + DEBUG_LOG("Mode ready to receive! Receiving..."); + if (!VLReceiver_receiveMode(&self->base.previewMode)) { + ERROR_LOG("Failed to receive mode"); + return; + } + DEBUG_LOGF("Success receiving mode: %u", Mode_getPatternID(&self->base.previewMode, LED_ALL)); + if (self->base.advanced && self->base.targetLeds != MAP_LED_ALL) { + LedPos target = ledmapGetFirstLed(self->base.targetLeds); + LedPos other = LED_1; + if (target == LED_1) { + other = LED_0; + Mode_swapPatterns(&self->base.previewMode, LED_0, LED_1); + } + Mode_copyPatternFrom(&self->base.previewMode, Modes_curMode(), other, other); + } + Modes_updateCurMode(&self->base.previewMode); + Menu_leaveMenu(&self->base, true); +} + +static void showReceiveMode(ModeSharingMenu *self) +{ + if (VLReceiver_isReceiving()) { + uint32_t diff = (Time_getCurtime() - self->lastPercentChange); + if (diff > 100) { + Leds_setIndex(LED_0, COL32(RGB_RED3)); + } else { + uint8_t pct = VLReceiver_percentReceived(); + Leds_setIndex(LED_0, (RGBColor){ 0, pct, 0 }); + } + Leds_clearIndex(LED_1); + } else { + Leds_setAll((self->sharingMode == SHARE_SEND_RECEIVE) ? COL32(0x000F05) : COL32(RGB_WHITE0)); + } +} + +const MenuVTable g_modeSharingMenuVTable = { + .destroy = ModeSharingMenu_destroy, + .init = ModeSharingMenu_init, + .run = ModeSharingMenu_run, + .onLedSelected = ModeSharingMenu_onLedSelected, + .onShortClick = ModeSharingMenu_onShortClick, + .onLongClick = ModeSharingMenu_onLongClick, + .leaveMenu = Menu_leaveMenu, +}; diff --git a/VortexEngine/src/Menus/MenuList/ModeSharing.cpp b/VortexEngine/src/Menus/MenuList/ModeSharing.cpp deleted file mode 100644 index dfee67f5e5..0000000000 --- a/VortexEngine/src/Menus/MenuList/ModeSharing.cpp +++ /dev/null @@ -1,184 +0,0 @@ -#include "ModeSharing.h" - -#include "../../VortexEngine.h" -#include "../../Serial/ByteStream.h" -#include "../../Serial/Serial.h" -#include "../../Time/TimeControl.h" -#include "../../Time/Timings.h" -#include "../../Wireless/VLReceiver.h" -#include "../../Wireless/VLSender.h" -#include "../../Patterns/Pattern.h" -#include "../../Buttons/Button.h" -#include "../../Modes/Modes.h" -#include "../../Modes/Mode.h" -#include "../../Leds/Leds.h" -#include "../../Log/Log.h" - -ModeSharing::ModeSharing(const RGBColor &col, bool advanced) : - Menu(col, advanced), - m_sharingMode(ModeShareState::SHARE_SEND_RECEIVE), - m_timeOutStartTime(0), - m_lastPercentChange(0), - m_lastPercent(0) - -{ -} - -ModeSharing::~ModeSharing() -{ - VLReceiver::endReceiving(); - VortexEngine::toggleForceSleep(true); -} - -bool ModeSharing::init() -{ - if (!Menu::init()) { - return false; - } - // skip led selection - if (!m_advanced) { - // skip led selection - m_ledSelected = true; - } - // start on receive because it's the more responsive of the two - // the odds of opening receive and then accidentally receiving - // a mode that is being broadcast nearby is completely unlikely - VLReceiver::beginReceiving(); - // turn off force sleep while using modesharing - VortexEngine::toggleForceSleep(false); - DEBUG_LOG("Entering Mode Sharing"); - return true; -} - -Menu::MenuAction ModeSharing::run() -{ - MenuAction result = Menu::run(); - if (result != MENU_CONTINUE) { - return result; - } - if (m_sharingMode == ModeShareState::SHARE_EXIT) { - showExit(); - return MENU_CONTINUE; - } - // if the button is held for at least a long click, then continuously send - if (g_pButton->isPressed() && g_pButton->holdDuration() >= CLICK_THRESHOLD) { - // exit is handled above so the only two options are normal or legacy - if (m_sharingMode == ModeShareState::SHARE_SEND_RECEIVE) { - // send normal - VLSender::send(&m_previewMode); - } else { - // send legacy - VLSender::sendLegacy(&m_previewMode); - } - } - // render the 'receive mode' lights whether legacy or not - showReceiveMode(); - receiveMode(); - return MENU_CONTINUE; -} - -void ModeSharing::onLedSelected() -{ - // if we selected leds that implies advanced mode - if (m_targetLeds == MAP_LED(LED_1)) { - // if we selected the top led then simply swap the two patterns - // so that the top led is sent first -- if the receiver is receiving - // into one slot then it will only use the first pattern to do so - m_previewMode.swapPatterns(LED_0, LED_1); - } -} - -// handlers for clicks -void ModeSharing::onShortClick() -{ - if (g_pButton->holdDuration() >= CLICK_THRESHOLD) { - return; - } - switch (m_sharingMode) { - case ModeShareState::SHARE_SEND_RECEIVE: - // stop receiving, send the mode, go back to receiving - VLReceiver::setLegacyReceiver(true); - m_sharingMode = ModeShareState::SHARE_SEND_RECEIVE_LEGACY; - break; - case ModeShareState::SHARE_SEND_RECEIVE_LEGACY: - VLReceiver::setLegacyReceiver(false); - m_sharingMode = ModeShareState::SHARE_EXIT; - break; - case ModeShareState::SHARE_EXIT: - m_sharingMode = ModeShareState::SHARE_SEND_RECEIVE; - break; - default: - break; - } - Leds::clearAll(); -} - -void ModeSharing::onLongClick() -{ - if (m_sharingMode == ModeShareState::SHARE_EXIT) { - leaveMenu(); - } -} - -void ModeSharing::receiveMode() -{ - uint32_t now = Time::getCurtime(); - // if reveiving new data set our last data time - if (VLReceiver::onNewData()) { - m_timeOutStartTime = now; - // if our last data was more than time out duration reset the recveiver - } else if (m_timeOutStartTime > 0 && (m_timeOutStartTime + MAX_TIMEOUT_DURATION) < now) { - VLReceiver::resetVLState(); - m_timeOutStartTime = 0; - return; - } - // check if the VLReceiver has a full packet available - if (!VLReceiver::dataReady()) { - uint8_t percent = VLReceiver::percentReceived(); - if (percent != m_lastPercent) { - m_lastPercent = percent; - m_lastPercentChange = now; - } - // nothing available yet - return; - } - DEBUG_LOG("Mode ready to receive! Receiving..."); - // receive the VL mode into the current mode - if (!VLReceiver::receiveMode(&m_previewMode)) { - ERROR_LOG("Failed to receive mode"); - return; - } - DEBUG_LOGF("Success receiving mode: %u", m_previewMode.getPatternID()); - if (m_advanced && m_targetLeds != MAP_LED_ALL) { - LedPos target = ledmapGetFirstLed(m_targetLeds); - LedPos other = LED_1; - // if the user picked the top led to copy into then swap the patterns - // in the incoming mode so the 0th pattern is on the top led - if (target == LED_1) { - other = LED_0; - m_previewMode.swapPatterns(LED_0, LED_1); - } - m_previewMode.copyPatternFrom(Modes::curMode(), other, other); - } - Modes::updateCurMode(&m_previewMode); - // leave menu and save settings, even if the mode was the same whatever - leaveMenu(true); -} - -void ModeSharing::showReceiveMode() -{ - // if the receiver is actively receiving right now then - if (VLReceiver::isReceiving()) { - uint32_t diff = (Time::getCurtime() - m_lastPercentChange); - // this generates the red flash when the receiver hasn't received something for - // some amount of time, 100 is just arbitray idk if it could be a better value - if (diff > 100) { - Leds::setIndex(LED_0, RGB_RED3); - } else { - Leds::setIndex(LED_0, RGBColor(0, VLReceiver::percentReceived(), 0)); - } - Leds::clearIndex(LED_1); - } else { - Leds::setAll((m_sharingMode == ModeShareState::SHARE_SEND_RECEIVE) ? 0x000F05 : RGB_WHITE0); - } -} diff --git a/VortexEngine/src/Menus/MenuList/ModeSharing.h b/VortexEngine/src/Menus/MenuList/ModeSharing.h index 826b08fa06..b672ff0b58 100644 --- a/VortexEngine/src/Menus/MenuList/ModeSharing.h +++ b/VortexEngine/src/Menus/MenuList/ModeSharing.h @@ -3,42 +3,28 @@ #include "../Menu.h" -class ModeSharing : public Menu -{ -public: - ModeSharing(const RGBColor &col, bool advanced); - ~ModeSharing(); - - bool init() override; - MenuAction run() override; - - // callback after the user selects the target led - void onLedSelected() override; - - // handlers for clicks - void onShortClick() override; - void onLongClick() override; - -private: - void receiveMode(); - void showReceiveMode(); - - enum class ModeShareState { - // these three are the main options that can be iterated through - // and if the button is held it will send, otherwise receive - SHARE_SEND_RECEIVE, // send/receive mode - SHARE_SEND_RECEIVE_LEGACY, // send/receive mode legacy - SHARE_EXIT, // exit mode sharing - }; - - ModeShareState m_sharingMode; - - // the start time when checking for timing out - uint32_t m_timeOutStartTime; - - // used to track when the receive percentage changes for timeout purposes - uint32_t m_lastPercentChange; - uint8_t m_lastPercent; -}; +typedef enum { + SHARE_SEND_RECEIVE, + SHARE_SEND_RECEIVE_LEGACY, + SHARE_EXIT, +} ModeShareState; + +typedef struct ModeSharingMenu_s { + Menu base; + ModeShareState sharingMode; + uint32_t timeOutStartTime; + uint32_t lastPercentChange; + uint8_t lastPercent; +} ModeSharingMenu; + +Menu *ModeSharingMenu_Create(RGBColor col, bool advanced); +void ModeSharingMenu_destroy(Menu *self); +bool ModeSharingMenu_init(Menu *self); +MenuAction ModeSharingMenu_run(Menu *self); +void ModeSharingMenu_onLedSelected(Menu *self); +void ModeSharingMenu_onShortClick(Menu *self); +void ModeSharingMenu_onLongClick(Menu *self); + +extern const MenuVTable g_modeSharingMenuVTable; #endif diff --git a/VortexEngine/src/Menus/MenuList/PatternSelect.c b/VortexEngine/src/Menus/MenuList/PatternSelect.c new file mode 100644 index 0000000000..78da3e671f --- /dev/null +++ b/VortexEngine/src/Menus/MenuList/PatternSelect.c @@ -0,0 +1,96 @@ +#include "PatternSelect.h" +#include + +#include "../../Patterns/PatternBuilder.h" +#include "../../Patterns/PatternArgs.h" +#include "../../Patterns/Pattern.h" +#include "../../Serial/ByteStream.h" +#include "../../Time/TimeControl.h" +#include "../../Buttons/Button.h" +#include "../../Random/Random.h" +#include "../../Time/Timings.h" +#include "../../Modes/Modes.h" +#include "../../Menus/Menus.h" +#include "../../Leds/Leds.h" +#include "../../Log/Log.h" + +#define COL32(v) ((RGBColor){ ((v) >> 16) & 0xFF, ((v) >> 8) & 0xFF, (v) & 0xFF }) + +Menu *PatternSelectMenu_Create(RGBColor col, bool advanced) +{ + PatternSelectMenu *self = (PatternSelectMenu *)malloc(sizeof(PatternSelectMenu)); + if (!self) return NULL; + Menu_construct(&self->base, col, advanced); + self->base.vtable = &g_patternSelectMenuVTable; + self->srcLed = LED_FIRST; + self->started = false; + return &self->base; +} + +void PatternSelectMenu_destroy(Menu *self) +{ + (void)self; +} + +bool PatternSelectMenu_init(Menu *self) +{ + if (!Menu_init(self)) { + return false; + } + DEBUG_LOG("Entered pattern select"); + return true; +} + +MenuAction PatternSelectMenu_run(Menu *self) +{ + MenuAction result = Menu_run(self); + if (result != MENU_CONTINUE) { + return result; + } + Mode_play(&self->previewMode); + Menus_showSelection(COL32(RGB_WHITE5)); + return MENU_CONTINUE; +} + +void PatternSelectMenu_onLedSelected(Menu *self) +{ + PatternSelectMenu *ps = (PatternSelectMenu *)self; + ps->srcLed = ledmapGetFirstLed(self->targetLeds); +} + +void PatternSelectMenu_onShortClick(Menu *self) +{ + PatternSelectMenu *ps = (PatternSelectMenu *)self; + PatternID newID = (PatternID)(Mode_getPatternID(&self->previewMode, ps->srcLed) + 1); + if (newID > PATTERN_SINGLE_LAST) { + newID = PATTERN_SINGLE_FIRST; + Leds_holdAll(COL32(RGB_WHITE)); + } + if (!ps->started) { + ps->started = true; + newID = PATTERN_FIRST; + } + if (isMultiLedPatternID(newID)) { + Mode_setPattern(&self->previewMode, newID, LED_ALL, NULL, NULL); + } else { + Mode_setPatternMap(&self->previewMode, self->targetLeds, newID, NULL, NULL); + } + Mode_init(&self->previewMode); + DEBUG_LOGF("Iterated to pattern id %d", newID); +} + +void PatternSelectMenu_onLongClick(Menu *self) +{ + Modes_updateCurMode(&self->previewMode); + Menu_leaveMenu(self, true); +} + +const MenuVTable g_patternSelectMenuVTable = { + .destroy = PatternSelectMenu_destroy, + .init = PatternSelectMenu_init, + .run = PatternSelectMenu_run, + .onLedSelected = PatternSelectMenu_onLedSelected, + .onShortClick = PatternSelectMenu_onShortClick, + .onLongClick = PatternSelectMenu_onLongClick, + .leaveMenu = Menu_leaveMenu, +}; diff --git a/VortexEngine/src/Menus/MenuList/PatternSelect.cpp b/VortexEngine/src/Menus/MenuList/PatternSelect.cpp deleted file mode 100644 index 227a69f835..0000000000 --- a/VortexEngine/src/Menus/MenuList/PatternSelect.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include "PatternSelect.h" - -#include "../../Patterns/PatternBuilder.h" -#include "../../Patterns/PatternArgs.h" -#include "../../Patterns/Pattern.h" -#include "../../Serial/ByteStream.h" -#include "../../Time/TimeControl.h" -#include "../../Buttons/Button.h" -#include "../../Random/Random.h" -#include "../../Time/Timings.h" -#include "../../Modes/Modes.h" -#include "../../Menus/Menus.h" -#include "../../Leds/Leds.h" -#include "../../Log/Log.h" - -PatternSelect::PatternSelect(const RGBColor &col, bool advanced) : - Menu(col, advanced), - m_srcLed(LED_FIRST), - m_started(false) -{ -} - -PatternSelect::~PatternSelect() -{ -} - -bool PatternSelect::init() -{ - if (!Menu::init()) { - return false; - } - DEBUG_LOG("Entered pattern select"); - return true; -} - -Menu::MenuAction PatternSelect::run() -{ - MenuAction result = Menu::run(); - if (result != MENU_CONTINUE) { - return result; - } - // run the current mode - m_previewMode.play(); - // show dimmer selections in advanced mode - Menus::showSelection(RGB_WHITE5); - return MENU_CONTINUE; -} - -void PatternSelect::onLedSelected() -{ - m_srcLed = ledmapGetFirstLed(m_targetLeds); -} - -void PatternSelect::onShortClick() -{ - PatternID newID = (PatternID)(m_previewMode.getPatternID(m_srcLed) + 1); - if (newID > PATTERN_SINGLE_LAST) { - newID = PATTERN_SINGLE_FIRST; - Leds::holdAll(RGB_WHITE); - } - if (!m_started) { - m_started = true; - newID = PATTERN_FIRST; - } - // set the new pattern id - if (isMultiLedPatternID(newID)) { - m_previewMode.setPattern(newID); - } else { - m_previewMode.setPatternMap(m_targetLeds, newID); - } - m_previewMode.init(); - DEBUG_LOGF("Iterated to pattern id %d", newID); -} - -void PatternSelect::onLongClick() -{ - // store the mode as current mode - Modes::updateCurMode(&m_previewMode); - leaveMenu(true); -} diff --git a/VortexEngine/src/Menus/MenuList/PatternSelect.h b/VortexEngine/src/Menus/MenuList/PatternSelect.h index e825f56b54..d1931a1f3d 100644 --- a/VortexEngine/src/Menus/MenuList/PatternSelect.h +++ b/VortexEngine/src/Menus/MenuList/PatternSelect.h @@ -6,29 +6,20 @@ #include "../../Colors/Colorset.h" #include "../../Modes/Mode.h" -class PatternSelect : public Menu -{ -public: - PatternSelect(const RGBColor &col, bool advanced); - ~PatternSelect(); - - bool init() override; - MenuAction run() override; - - // callback after the user selects the target led - void onLedSelected() override; - - // handlers for clicks - void onShortClick() override; - void onLongClick() override; - -private: - // helpful member - LedPos m_srcLed; - - // the pat select starts by showing the current pattern - // then the first click begin cycling the list of pats - bool m_started; -}; +typedef struct PatternSelectMenu_s { + Menu base; + LedPos srcLed; + bool started; +} PatternSelectMenu; + +Menu *PatternSelectMenu_Create(RGBColor col, bool advanced); +void PatternSelectMenu_destroy(Menu *self); +bool PatternSelectMenu_init(Menu *self); +MenuAction PatternSelectMenu_run(Menu *self); +void PatternSelectMenu_onLedSelected(Menu *self); +void PatternSelectMenu_onShortClick(Menu *self); +void PatternSelectMenu_onLongClick(Menu *self); + +extern const MenuVTable g_patternSelectMenuVTable; #endif diff --git a/VortexEngine/src/Menus/MenuList/Randomizer.c b/VortexEngine/src/Menus/MenuList/Randomizer.c new file mode 100644 index 0000000000..e6672f2e6e --- /dev/null +++ b/VortexEngine/src/Menus/MenuList/Randomizer.c @@ -0,0 +1,361 @@ +#include "Randomizer.h" +#include + +#include "../../Memory/Memory.h" + +#include "../../Patterns/PatternBuilder.h" +#include "../../Patterns/Pattern.h" +#include "../../Colors/Colorset.h" +#include "../../Random/Random.h" +#include "../../Time/Timings.h" +#include "../../Buttons/Button.h" +#include "../../Menus/Menus.h" +#include "../../Modes/Modes.h" +#include "../../Modes/Mode.h" +#include "../../Leds/Leds.h" +#include "../../Log/Log.h" + +#define COL32(v) ((RGBColor){ ((v) >> 16) & 0xFF, ((v) >> 8) & 0xFF, (v) & 0xFF }) + +static bool reRoll(RandomizerMenu *self); +static void showRandomizationSelect(RandomizerMenu *self); +#if VORTEX_SLIM == 0 +static bool reRollMulti(RandomizerMenu *self); +static PatternID rollMultiLedPatternID(Random *ctx); +#endif +static bool reRollSingles(RandomizerMenu *self); +static PatternID rollSingleLedPatternID(Random *ctx); +static Colorset rollColorset(Random *ctx); +static bool rollCustomPattern(Random *ctx, Mode *pMode, LedPos pos); +static void traditionalPattern(Random *ctx, PatternArgs *outArgs); +static void gapPattern(Random *ctx, PatternArgs *outArgs); +static void dashPattern(Random *ctx, PatternArgs *outArgs); +static void crushPattern(Random *ctx, PatternArgs *outArgs); + +Menu *RandomizerMenu_Create(RGBColor col, bool advanced) +{ + RandomizerMenu *self = (RandomizerMenu *)malloc(sizeof(RandomizerMenu)); + if (!self) return NULL; + Menu_construct(&self->base, col, advanced); + self->base.vtable = &g_randomizerMenuVTable; + self->lastRandomization = 0; + self->flags = advanced ? RANDOMIZE_COLORSET : RANDOMIZE_BOTH; + self->displayHue = 0; + self->needToSelect = advanced; + self->autoCycle = false; + return &self->base; +} + +void RandomizerMenu_destroy(Menu *self) +{ + (void)self; +} + +bool RandomizerMenu_init(Menu *self) +{ + if (!Menu_init(self)) { + return false; + } + RandomizerMenu *r = (RandomizerMenu *)self; + Mode *cur = Modes_curMode(); +#if VORTEX_SLIM == 0 + if (Mode_hasMultiLed(cur)) { + ByteStream ledData; + ByteStream_init(&ledData); + Pattern *pat = Mode_getPattern(cur, LED_MULTI); + if (pat) { + if (!Pattern_serialize(pat, &ledData)) { + return false; + } + } + Random_seed(&r->multiRandCtx, ByteStream_recalcCRC(&ledData)); + } +#endif + for (LedPos l = LED_FIRST; l < LED_COUNT; ++l) { + ByteStream ledData; + ByteStream_init(&ledData, 0, NULL); + Pattern *pat = Mode_getPattern(cur, l); + if (pat) { + if (!Pattern_serialize(pat, &ledData)) { + return false; + } + } + Random_seed(&r->singlesRandCtx[l], ByteStream_recalcCRC(&ledData, false)); + } + DEBUG_LOG("Entered randomizer"); + return true; +} + +MenuAction RandomizerMenu_run(Menu *self) +{ + MenuAction result = Menu_run(self); + if (result != MENU_CONTINUE) { + return result; + } + RandomizerMenu *r = (RandomizerMenu *)self; + if (r->needToSelect) { + showRandomizationSelect(r); + return MENU_CONTINUE; + } +#if VORTEX_SLIM == 0 + if (Mode_isMultiLed(&self->previewMode) && self->targetLeds != MAP_LED(LED_MULTI)) { + PatternID newID = (PatternID)((Mode_getPatternID(&self->previewMode, LED_ALL) - PATTERN_MULTI_FIRST) % PATTERN_SINGLE_COUNT); + if (newID == PATTERN_SOLID) ++newID; + Mode_setPattern(&self->previewMode, newID, LED_ALL, NULL, NULL); + Mode_init(&self->previewMode); + } +#endif + uint32_t now = Time_getCurtime(); + if (r->autoCycle && (r->lastRandomization + AUTO_RANDOM_DELAY_TICKS < now)) { + r->lastRandomization = now; + reRoll(r); + } + Mode_play(&self->previewMode); + Menus_showSelection(COL32(RGB_WHITE5)); + return MENU_CONTINUE; +} + +void RandomizerMenu_onShortClick(Menu *self) +{ + RandomizerMenu *r = (RandomizerMenu *)self; + if (r->needToSelect) { + if (r->flags == RANDOMIZE_BOTH) { + r->flags = RANDOMIZE_COLORSET; + } else { + r->flags = (RandomizeFlags)(r->flags + 1); + } + return; + } + if (r->autoCycle || Button_onConsecutivePresses(g_pButton, AUTO_CYCLE_RANDOMIZER_CLICKS)) { + r->autoCycle = !r->autoCycle; + Leds_holdAll(r->autoCycle ? COL32(RGB_GREEN) : COL32(RGB_RED)); + return; + } + reRoll(r); +} + +void RandomizerMenu_onLongClick(Menu *self) +{ + RandomizerMenu *r = (RandomizerMenu *)self; + if (r->needToSelect) { + r->needToSelect = false; + return; + } + Modes_updateCurMode(&self->previewMode); + Menu_leaveMenu(self, true); +} + +static bool reRoll(RandomizerMenu *self) +{ +#if VORTEX_SLIM == 0 + if (self->base.targetLeds == MAP_LED(LED_MULTI)) { + if (!reRollMulti(self)) { + return false; + } + } else if (!reRollSingles(self)) { + return false; + } +#else + if (!reRollSingles(self)) { + return false; + } +#endif + Mode_init(&self->base.previewMode); + return true; +} + +static void showRandomizationSelect(RandomizerMenu *self) +{ + HSVColor hsv; + HSVColor_initHSV(&hsv, self->displayHue++, (self->flags & RANDOMIZE_COLORSET) * 255, 84); + RGBColor rgb; + RGBColor_initFromHSV(&rgb, &hsv); + Leds_setAll(rgb); + if (self->flags & RANDOMIZE_PATTERN) { + { RGBColor _c = {0,0,0}; Leds_blinkAll(DOPS_ON_DURATION, DOPS_OFF_DURATION, _c); } + } + Leds_setIndex(LED_1, Button_isPressed(g_pButton) ? COL32(RGB_OFF) : COL32(RGB_WHITE1)); + Menus_showSelection(COL32(RGB_WHITE5)); +} + +#if VORTEX_SLIM == 0 +static bool reRollMulti(RandomizerMenu *self) +{ + if (self->flags & RANDOMIZE_PATTERN) { + if (!Mode_setPattern(&self->base.previewMode, rollMultiLedPatternID(&self->multiRandCtx), LED_MULTI, NULL, NULL)) { + ERROR_LOG("Failed to select pattern"); + return false; + } + } + if (self->flags & RANDOMIZE_COLORSET) { + Colorset cs = rollColorset(&self->multiRandCtx); + if (!Mode_setColorset(&self->base.previewMode, &cs, LED_MULTI)) { + ERROR_LOG("Failed to roll new colorset"); + return false; + } + } + return true; +} + +static PatternID rollMultiLedPatternID(Random *ctx) +{ + return (PatternID)Random_next8(ctx, PATTERN_MULTI_FIRST, PATTERN_MULTI_LAST); +} +#endif + +static bool reRollSingles(RandomizerMenu *self) +{ + MAP_FOREACH_LED(self->base.targetLeds) { + Random *ctx = &self->singlesRandCtx[pos]; + if (self->flags & RANDOMIZE_PATTERN) { + if (self->base.advanced) { + if (!rollCustomPattern(ctx, &self->base.previewMode, pos)) { + ERROR_LOG("Failed to roll custom pattern"); + return false; + } + } else { + if (!Mode_setPattern(&self->base.previewMode, rollSingleLedPatternID(ctx), pos, NULL, NULL)) { + ERROR_LOG("Failed to select pattern"); + return false; + } + } + } + if (self->flags & RANDOMIZE_COLORSET) { + Colorset cs = rollColorset(ctx); + if (!Mode_setColorset(&self->base.previewMode, &cs, pos)) { + ERROR_LOG("Failed to roll new colorset"); + return false; + } + } + } + return true; +} + +static PatternID rollSingleLedPatternID(Random *ctx) +{ + PatternID newPat; + do { + newPat = (PatternID)Random_next8(ctx, PATTERN_SINGLE_FIRST, PATTERN_SINGLE_LAST); + } while (newPat == PATTERN_SOLID || newPat == PATTERN_RIBBON || newPat == PATTERN_MINIRIBBON); + return newPat; +} + +static Colorset rollColorset(Random *ctx) +{ + Colorset randomSet; + Colorset_init(&randomSet); + uint8_t randType = Random_next8(ctx, 0, 8); + switch (randType) { + default: + case 0: + Colorset_randomize(&randomSet, ctx, 0); + break; + case 1: + Colorset_randomizeColors(&randomSet, ctx, 0, COLOR_MODE_THEORY); + break; + case 2: + Colorset_randomizeColors(&randomSet, ctx, 0, COLOR_MODE_MONOCHROMATIC); + break; + case 3: + Colorset_randomizeColors2(&randomSet, ctx, COLOR_MODE2_DOUBLE_SPLIT_COMPLIMENTARY); + break; + case 4: + Colorset_randomizeColors2(&randomSet, ctx, COLOR_MODE2_TETRADIC); + break; + case 5: + Colorset_randomize(&randomSet, ctx, 1); + break; + case 6: + Colorset_randomizeColors(&randomSet, ctx, 0, COLOR_MODE_EVENLY_SPACED); + break; + case 7: + Colorset_randomizeColors(&randomSet, ctx, 2, COLOR_MODE_EVENLY_SPACED); + break; + case 8: + Colorset_randomizeColors(&randomSet, ctx, 3, COLOR_MODE_EVENLY_SPACED); + break; + } + if (Colorset_numColors(&randomSet) <= 4 && Random_next8(ctx, 0, 1) != 0) { + int16_t startingNumColors = Colorset_numColors(&randomSet); + for (int16_t i = 0; i < startingNumColors - 1; ++i) { + Colorset_addColor(&randomSet, Colorset_get(&randomSet, startingNumColors - (i + 2))); + } + } + return randomSet; +} + +static bool rollCustomPattern(Random *ctx, Mode *pMode, LedPos pos) +{ + PatternArgs args; + PatternArgs_init(&args); + uint8_t patternType = Random_next8(ctx, 0, 3); + switch (patternType) { + default: + case 0: + traditionalPattern(ctx, &args); + break; + case 1: + gapPattern(ctx, &args); + break; + case 2: + dashPattern(ctx, &args); + break; + case 3: + crushPattern(ctx, &args); + break; + } + PatternID newPat = PATTERN_STROBE; + if (!Random_next8(ctx, 0, 3)) { + newPat = PATTERN_BLEND; + args.arg6 = Random_next8(ctx, 1, 10); + if (!Random_next8(ctx, 0, 2)) { + args.arg7 = Random_next8(ctx, 0, 3); + } + args.numArgs = 7; + } + return Mode_setPattern(pMode, newPat, pos, &args, NULL); +} + +static void traditionalPattern(Random *ctx, PatternArgs *outArgs) +{ + uint8_t off = Random_next8(ctx, 8, 60); + uint8_t on = Random_next8(ctx, 1, 20); + PatternArgs_init2(outArgs, on, off); +} + +static void gapPattern(Random *ctx, PatternArgs *outArgs) +{ + uint8_t gap = Random_next8(ctx, 40, 100); + uint8_t off = Random_next8(ctx, 0, 6); + uint8_t on = Random_next8(ctx, 1, 10); + PatternArgs_init3(outArgs, on, off, gap); +} + +static void dashPattern(Random *ctx, PatternArgs *outArgs) +{ + uint8_t dash = Random_next8(ctx, 20, 30); + uint8_t gap = Random_next8(ctx, 20, 30); + uint8_t off = Random_next8(ctx, 0, 10); + uint8_t on = Random_next8(ctx, 1, 10); + PatternArgs_init4(outArgs, on, off, gap, dash); +} + +static void crushPattern(Random *ctx, PatternArgs *outArgs) +{ + uint8_t group = Random_next8(ctx, 0, 8); + uint8_t dash = 0; + uint8_t gap = Random_next8(ctx, 20, 40); + uint8_t off = Random_next8(ctx, 0, 10); + uint8_t on = Random_next8(ctx, 1, 10); + PatternArgs_init5(outArgs, on, off, gap, dash, group); +} + +const MenuVTable g_randomizerMenuVTable = { + .destroy = RandomizerMenu_destroy, + .init = RandomizerMenu_init, + .run = RandomizerMenu_run, + .onLedSelected = Menu_onLedSelected, + .onShortClick = RandomizerMenu_onShortClick, + .onLongClick = RandomizerMenu_onLongClick, + .leaveMenu = Menu_leaveMenu, +}; diff --git a/VortexEngine/src/Menus/MenuList/Randomizer.cpp b/VortexEngine/src/Menus/MenuList/Randomizer.cpp deleted file mode 100644 index ffa65bd7db..0000000000 --- a/VortexEngine/src/Menus/MenuList/Randomizer.cpp +++ /dev/null @@ -1,390 +0,0 @@ -#include "Randomizer.h" - -#include "../../Memory/Memory.h" - -#include "../../Patterns/PatternBuilder.h" -#include "../../Patterns/Pattern.h" -#include "../../Colors/Colorset.h" -#include "../../Random/Random.h" -#include "../../Time/Timings.h" -#include "../../Buttons/Button.h" -#include "../../Menus/Menus.h" -#include "../../Modes/Modes.h" -#include "../../Modes/Mode.h" -#include "../../Leds/Leds.h" -#include "../../Log/Log.h" - -Randomizer::Randomizer(const RGBColor &col, bool advanced) : - Menu(col, advanced), - m_lastRandomization(0), - m_flags(advanced ? RANDOMIZE_COLORSET : RANDOMIZE_BOTH), - m_displayHue(0), - m_needToSelect(advanced), - m_autoCycle(false) -{ -} - -Randomizer::~Randomizer() -{ -} - -bool Randomizer::init() -{ - if (!Menu::init()) { - return false; - } - // grab the multi ld pattern colorset crc if it's present - Mode *cur = Modes::curMode(); -#if VORTEX_SLIM == 0 - if (cur->hasMultiLed()) { - ByteStream ledData; - Pattern *pat = cur->getPattern(LED_MULTI); - if (pat) { - if (!pat->serialize(ledData)) { - return false; - } - } - m_multiRandCtx.seed(ledData.recalcCRC()); - } -#endif - // initialize the randomseed of each led with the - // CRC of the colorset on the respective LED - for (LedPos l = LED_FIRST; l < LED_COUNT; ++l) { - ByteStream ledData; - Pattern *pat = cur->getPattern(l); - if (pat) { - if (!pat->serialize(ledData)) { - return false; - } - } - m_singlesRandCtx[l].seed(ledData.recalcCRC()); - } - DEBUG_LOG("Entered randomizer"); - return true; -} - -Menu::MenuAction Randomizer::run() -{ - // run the base menu logic - MenuAction result = Menu::run(); - if (result != MENU_CONTINUE) { - return result; - } - // if the ranomization flags haven't been set yet just show a selection - if (m_needToSelect) { - // display the randomization selection menu - showRandomizationSelect(); - return MENU_CONTINUE; - } -#if VORTEX_SLIM == 0 - // if they are trying to randomize a multi-led pattern just convert - // the pattern to all singles with the same colorset upon entry - if (m_previewMode.isMultiLed() && m_targetLeds != MAP_LED(LED_MULTI)) { - // convert the pattern to a single led pattern, this will map - // all multi led patterns to single led patterns using modulo - // so no matter which multi-led pattern they have selected it - // will convert to a single led pattern of some kind - PatternID newID = (PatternID)((m_previewMode.getPatternID() - PATTERN_MULTI_FIRST) % PATTERN_SINGLE_COUNT); - // solid sucks - if (newID == PATTERN_SOLID) ++newID; - m_previewMode.setPattern(newID); - m_previewMode.init(); - } -#endif - uint32_t now = Time::getCurtime(); - if (m_autoCycle && (m_lastRandomization + AUTO_RANDOM_DELAY_TICKS < now)) { - m_lastRandomization = now; - reRoll(); - } - // display the randomized mode - m_previewMode.play(); - // show the selection - Menus::showSelection(); - // return true to continue staying in randomizer menu - return MENU_CONTINUE; -} - -void Randomizer::onShortClick() -{ - if (m_needToSelect) { - if (m_flags == RANDOMIZE_BOTH) { - m_flags = RANDOMIZE_COLORSET; - } else { - m_flags = (RandomizeFlags)(m_flags + 1); - } - return; - } - // if the user fast-clicks 3 times then toggle automode - if (m_autoCycle || g_pButton->onConsecutivePresses(AUTO_CYCLE_RANDOMIZER_CLICKS)) { - // toggle the auto cycle flag - m_autoCycle = !m_autoCycle; - // display a quick flash of either green or red to indicate whether auto mode is on or not - Leds::holdAll(m_autoCycle ? RGB_GREEN : RGB_RED); - return; - } - // shortClick re-roll the randomization - reRoll(); -} - -void Randomizer::onLongClick() -{ - // if done the randomization selection part - if (m_needToSelect) { - // we are complete the randomization selection stage - m_needToSelect = false; - return; - } - // update the current mode with the new randomized mode - Modes::updateCurMode(&m_previewMode); - // then done here, save if the mode was different - leaveMenu(true); -} - -bool Randomizer::reRoll() -{ -#if VORTEX_SLIM == 0 - if (m_targetLeds == MAP_LED(LED_MULTI)) { - if (!reRollMulti()) { - return false; - } - } else if (!reRollSingles()) { - return false; - } -#else - if (!reRollSingles()) { - return false; - } -#endif - // initialize the mode with the new pattern and colorset - m_previewMode.init(); - return true; -} - -void Randomizer::showRandomizationSelect() -{ - // show iterating rainbow if they are randomizing color, otherwise 0 sat if they - // are only randomizing the pattern - Leds::setAll(HSVColor(m_displayHue++, (m_flags & RANDOMIZE_COLORSET) * 255, 84)); - if (m_flags & RANDOMIZE_PATTERN) { - // this is blinking the light to off so the params are switched but still effectively correct - Leds::blinkAll(DOPS_ON_DURATION, DOPS_OFF_DURATION); - } - // indicate on the 2nd led whether the button is pressed - Leds::setIndex(LED_1, g_pButton->isPressed() ? RGB_OFF : RGB_WHITE1); - // render the click selection blink - Menus::showSelection(); -} - -#if VORTEX_SLIM == 0 -bool Randomizer::reRollMulti() -{ - if (m_flags & RANDOMIZE_PATTERN) { - // TODO: Roll custom multi pattern? - //if (m_advanced) { - // if (!rollCustomPattern(ctx, &m_previewMode, pos)) { - // ERROR_LOG("Failed to roll custom pattern"); - // return false; - // } - //} - if (!m_previewMode.setPattern(rollMultiLedPatternID(m_multiRandCtx), LED_MULTI)) { - ERROR_LOG("Failed to select pattern"); - return false; - } - } - if (m_flags & RANDOMIZE_COLORSET) { - if (!m_previewMode.setColorset(rollColorset(m_multiRandCtx), LED_MULTI)) { - ERROR_LOG("Failed to roll new colorset"); - return false; - } - } - return true; -} - -PatternID Randomizer::rollMultiLedPatternID(Random &ctx) -{ - return (PatternID)ctx.next8(PATTERN_MULTI_FIRST, PATTERN_MULTI_LAST); -} -#endif - -bool Randomizer::reRollSingles() -{ - // re-roll each led position with it's respective random context - MAP_FOREACH_LED(m_targetLeds) { - Random &ctx = m_singlesRandCtx[pos]; - if (m_flags & RANDOMIZE_PATTERN) { - // in advanced mode, when not randomizing the multi position, use a - // special function to randomize totally custom led pattern timings - if (m_advanced) { - if (!rollCustomPattern(ctx, &m_previewMode, pos)) { - ERROR_LOG("Failed to roll custom pattern"); - return false; - } - } else { - if (!m_previewMode.setPattern(rollSingleLedPatternID(ctx), pos)) { - ERROR_LOG("Failed to select pattern"); - return false; - } - } - } - if (m_flags & RANDOMIZE_COLORSET) { - if (!m_previewMode.setColorset(rollColorset(ctx), pos)) { - ERROR_LOG("Failed to roll new colorset"); - return false; - } - } - } - return true; -} - -PatternID Randomizer::rollSingleLedPatternID(Random &ctx) -{ - PatternID newPat; - // the random range begin/end - do { - // continuously re-randomize the pattern so we don't get undesirable patterns - newPat = (PatternID)ctx.next8(PATTERN_SINGLE_FIRST, PATTERN_SINGLE_LAST); - } while (newPat == PATTERN_SOLID || newPat == PATTERN_RIBBON || newPat == PATTERN_MINIRIBBON); - return newPat; -} - -Colorset Randomizer::rollColorset(Random &ctx) -{ - Colorset randomSet; - // pick a random type of randomizer to use then use - // the randomizer to generate a random colorset - uint8_t randType = ctx.next8(0, 8); - switch (randType) { - default: - case 0: - randomSet.randomize(ctx); - break; - case 1: - randomSet.randomizeColors(ctx, 0, Colorset::ColorMode::THEORY); - break; - case 2: - randomSet.randomizeColors(ctx, 0, Colorset::ColorMode::MONOCHROMATIC); - break; - case 3: - randomSet.randomizeColors2(ctx, Colorset::ColorMode2::DOUBLE_SPLIT_COMPLIMENTARY); - break; - case 4: - randomSet.randomizeColors2(ctx, Colorset::ColorMode2::TETRADIC); - break; - case 5: - randomSet.randomize(ctx, 1); - break; - case 6: - randomSet.randomizeColors(ctx, 0, Colorset::ColorMode::EVENLY_SPACED); - break; - case 7: - randomSet.randomizeColors(ctx, 2, Colorset::ColorMode::EVENLY_SPACED); - break; - case 8: - randomSet.randomizeColors(ctx, 3, Colorset::ColorMode::EVENLY_SPACED); - break; - } - - // take the randomized colorset and repeate colors to make an additional pattern - if (randomSet.numColors() <= 4 && ctx.next8(0, 1) != 0) { - // using uint8_t here actually costs more bytes than int16_t - int16_t startingNumColors = randomSet.numColors(); - for (int16_t i = 0; i < startingNumColors - 1; ++i) { - // add a duplicate of each color in the colorset to in reverse - randomSet.addColor(randomSet.get(startingNumColors - (i + 2))); - } - } - return randomSet; -} - -bool Randomizer::rollCustomPattern(Random &ctx, Mode *pMode, LedPos pos) -{ - PatternArgs args; - // pick a random type of randomizer to use then use - // the randomizer to generate a random pattern - uint8_t patternType = ctx.next8(0, 3); - switch (patternType) { - default: - case 0: - traditionalPattern(ctx, args); - break; - case 1: - gapPattern(ctx, args); - break; - case 2: - dashPattern(ctx, args); - break; - case 3: - crushPattern(ctx, args); - break; - } - PatternID newPat = PATTERN_STROBE; - // 1/3 chance to roll a blend pattern instead which will animate between - // colors instead of blinking each color in the set - if (!ctx.next8(0, 3)) { - newPat = PATTERN_BLEND; - // set the blend speed - args.arg6 = ctx.next8(1, 10); - // 1/2 chance to make the blend a flipping blend of some kind - if (!ctx.next8(0, 2)) { - // this is the number of blinks to a complementary color - args.arg7 = ctx.next8(0, 3); - } - // up to arg7 is filled now - args.numArgs = 7; - } - return pMode->setPattern(newPat, pos, &args); -} - -void Randomizer::traditionalPattern(Random &ctx, PatternArgs &outArgs) -{ - // call next8 explicitly in this order because the order they - // are called is undefined when called as parameters to another function. - // ex: f(a,b,c) may call in the order a,b,c or c,b,a depending on compiler. - // So different compilers may produce different results, - // but like this it is explicit - uint8_t off = ctx.next8(8, 60); // off duration 0 -> 60 - uint8_t on = ctx.next8(1, 20); // on duration 1 -> 20 - outArgs.init(on, off); -} - -void Randomizer::gapPattern(Random &ctx, PatternArgs &outArgs) -{ - // call next8 explicitly in this order because the order they - // are called is undefined when called as parameters to another function. - // ex: f(a,b,c) may call in the order a,b,c or c,b,a depending on compiler. - // So different compilers may produce different results, - // but like this it is explicit - uint8_t gap = ctx.next8(40, 100); // gap duration 40 -> 100 - uint8_t off = ctx.next8(0, 6); // off duration 0 -> 6 - uint8_t on = ctx.next8(1, 10); // on duration 1 -> 10 - outArgs.init(on, off, gap); -} - -void Randomizer::dashPattern(Random &ctx, PatternArgs &outArgs) -{ - // call next8 explicitly in this order because the order they - // are called is undefined when called as parameters to another function. - // ex: f(a,b,c) may call in the order a,b,c or c,b,a depending on compiler. - // So different compilers may produce different results, - // but like this it is explicit - uint8_t dash = ctx.next8(20, 30); // dash duration 20 -> 30 - uint8_t gap = ctx.next8(20, 30); // need gap 20 -> 30 - uint8_t off = ctx.next8(0, 10); // off duration 0 -> 10 - uint8_t on = ctx.next8(1, 10); // on duration 1 -> 10 - outArgs.init(on, off, gap, dash); -} - -void Randomizer::crushPattern(Random &ctx, PatternArgs &outArgs) -{ - // call next8 explicitly in this order because the order they - // are called is undefined when called as parameters to another function. - // ex: f(a,b,c) may call in the order a,b,c or c,b,a depending on compiler. - // So different compilers may produce different results, - // but like this it is explicit - uint8_t group = ctx.next8(0, 8); // groupsize 0 to 8 - uint8_t dash = 0; // dash 0 - uint8_t gap = ctx.next8(20, 40); // need gap 20 -> 40 - uint8_t off = ctx.next8(0, 10); // off duration 0 -> 5 - uint8_t on = ctx.next8(1, 10); // on duration 1 -> 10 - outArgs.init(on, off, gap, dash, group); -} diff --git a/VortexEngine/src/Menus/MenuList/Randomizer.h b/VortexEngine/src/Menus/MenuList/Randomizer.h index 0575ab132f..61efa2a04b 100644 --- a/VortexEngine/src/Menus/MenuList/Randomizer.h +++ b/VortexEngine/src/Menus/MenuList/Randomizer.h @@ -6,81 +6,35 @@ #include "../../Random/Random.h" #include "../../Modes/Mode.h" -class Mode; - -class Randomizer : public Menu -{ -public: - Randomizer(const RGBColor &col, bool advanced); - ~Randomizer(); - - bool init() override; - MenuAction run() override; - - // handlers for clicks - void onShortClick() override; - void onLongClick() override; - - // re-roll a new randomization with a given context on an led - bool reRoll(); - -private: - // random context for each single led - Random m_singlesRandCtx[LED_COUNT]; +typedef struct Mode Mode; + +typedef enum { + RANDOMIZE_NONE = 0, + RANDOMIZE_COLORSET = (1 << 0), + RANDOMIZE_PATTERN = (1 << 1), + RANDOMIZE_BOTH = (RANDOMIZE_COLORSET | RANDOMIZE_PATTERN) +} RandomizeFlags; + +typedef struct RandomizerMenu_s { + Menu base; + Random singlesRandCtx[LED_COUNT]; #if VORTEX_SLIM == 0 - // random context for the multi led position - Random m_multiRandCtx; + Random multiRandCtx; #endif - - // the time of the last randomization - uint32_t m_lastRandomization; - - enum RandomizeFlags : uint8_t { - // this isn't a valid randomization state, if the flags are - // on this state then the user will be prompted to pick - RANDOMIZE_NONE = 0, - - // the two main kinds of randomization - RANDOMIZE_COLORSET = (1 << 0), - RANDOMIZE_PATTERN = (1 << 1), - - // compound flags both colorset and pattern randomization - RANDOMIZE_BOTH = (RANDOMIZE_COLORSET | RANDOMIZE_PATTERN) - }; - - // the randomization flags above - uint8_t m_flags; - - // auxilliary variable to display cycling hue at menus - uint8_t m_displayHue; - - // whether still need to select a randomization type - bool m_needToSelect; - // whether auto cycling - bool m_autoCycle; - - // show the randomization type selection - void showRandomizationSelect(); - - // main reRoll functions -#if VORTEX_SLIM == 0 - bool reRollMulti(); - PatternID rollMultiLedPatternID(Random &ctx); -#endif - bool reRollSingles(); - PatternID rollSingleLedPatternID(Random &ctx); - - // generate a random colorset with a random context - Colorset rollColorset(Random &ctx); - - // roll a custom pattern by generating random arguments - bool rollCustomPattern(Random &ctx, Mode *pMode, LedPos pos); - - // more specific random pattern generators that just generate patternargs - void traditionalPattern(Random &ctx, PatternArgs &outArgs); - void gapPattern(Random &ctx, PatternArgs &outArgs); - void dashPattern(Random &ctx, PatternArgs &outArgs); - void crushPattern(Random &ctx, PatternArgs &outArgs); -}; + uint32_t lastRandomization; + uint8_t flags; + uint8_t displayHue; + bool needToSelect; + bool autoCycle; +} RandomizerMenu; + +Menu *RandomizerMenu_Create(RGBColor col, bool advanced); +void RandomizerMenu_destroy(Menu *self); +bool RandomizerMenu_init(Menu *self); +MenuAction RandomizerMenu_run(Menu *self); +void RandomizerMenu_onShortClick(Menu *self); +void RandomizerMenu_onLongClick(Menu *self); + +extern const MenuVTable g_randomizerMenuVTable; #endif diff --git a/VortexEngine/src/Menus/Menus.c b/VortexEngine/src/Menus/Menus.c new file mode 100644 index 0000000000..2cdb68f374 --- /dev/null +++ b/VortexEngine/src/Menus/Menus.c @@ -0,0 +1,249 @@ +#include "Menus.h" + +#include "Menu.h" +#include "MenuList/GlobalBrightness.h" +#include "MenuList/EditorConnection.h" +#include "MenuList/FactoryReset.h" +#include "MenuList/ModeSharing.h" +#include "MenuList/ColorSelect.h" +#include "MenuList/PatternSelect.h" +#include "MenuList/Randomizer.h" + +#include "../Time/TimeControl.h" +#include "../Time/Timings.h" +#include "../Buttons/Button.h" +#include "../Modes/Modes.h" +#include "../Leds/Leds.h" +#include "../Log/Log.h" + +#define COL32(v) ((RGBColor){ ((v) >> 16) & 0xFF, ((v) >> 8) & 0xFF, (v) & 0xFF }) + +// Menu state +static enum { + MENU_STATE_NOT_OPEN = 0, + MENU_STATE_MENU_SELECTION, + MENU_STATE_IN_MENU +} m_menuState; + +static uint8_t m_selection; +static uint32_t m_openTime; +static Menu *m_pCurMenu; + +typedef Menu *(*initMenuFn_t)(RGBColor col, bool advanced); + +struct MenuEntry { +#if LOGGING_LEVEL > 2 + const char *menuName; +#endif + initMenuFn_t initMenu; + RGBColor color; +}; + +#if LOGGING_LEVEL > 2 +#define ENTRY(init_fn, color) { #init_fn, init_fn, COL32(color) } +#else +#define ENTRY(init_fn, color) { init_fn, COL32(color) } +#endif + +const struct MenuEntry menuList[] = { + ENTRY(RandomizerMenu_Create, RGB_MENU_RANDOMIZER), + ENTRY(ModeSharingMenu_Create, RGB_MENU_MODE_SHARING), +#if ENABLE_EDITOR_CONNECTION == 1 + ENTRY(EditorConnectionMenu_Create, RGB_MENU_EDITOR_CONNECTION), +#endif + ENTRY(ColorSelectMenu_Create, RGB_MENU_COLOR_SELECT), + ENTRY(PatternSelectMenu_Create, RGB_MENU_PATTERN_SELECT), + ENTRY(GlobalBrightnessMenu_Create, RGB_MENU_BRIGHTNESS_SELECT), + ENTRY(FactoryResetMenu_Create, RGB_MENU_FACTORY_RESET), +}; + +#define NUM_MENUS (sizeof(menuList) / sizeof(menuList[0])) + +static bool runMenuSelection(void); +static bool runCurMenu(void); +static void closeCurMenu(void); + +bool Menus_init(void) +{ + m_menuState = MENU_STATE_NOT_OPEN; + m_selection = 0; + m_openTime = 0; + m_pCurMenu = NULL; + return true; +} + +void Menus_cleanup(void) +{ + if (m_pCurMenu) { + m_pCurMenu->vtable->destroy(m_pCurMenu); + m_pCurMenu = NULL; + } + m_menuState = MENU_STATE_NOT_OPEN; + m_selection = 0; + m_openTime = 0; +} + +bool Menus_run(void) +{ + switch (m_menuState) { + case MENU_STATE_NOT_OPEN: + default: + return false; + case MENU_STATE_MENU_SELECTION: + return runMenuSelection(); + case MENU_STATE_IN_MENU: + return runCurMenu(); + } +} + +static bool runMenuSelection(void) +{ + if (Button_onShortClick(g_pButton)) { + m_selection = (m_selection + 1) % NUM_MENUS; +#if ENABLE_EDITOR_CONNECTION == 1 + if (m_selection == MENU_EDITOR_CONNECTION) { + m_selection++; + } +#endif + DEBUG_LOGF("Cyling to ring menu %u", m_selection); + m_openTime = Time_getCurtime(); + Leds_clearAll(); + return true; + } + Leds_clearAll(); + uint8_t offtime = 200; + uint8_t ontime = 200; + bool advMenus = Modes_advancedMenusEnabled(); + if (Button_pressTime(g_pButton) >= m_openTime) { + bool openAdv = (Button_holdDuration(g_pButton) > ADV_MENU_DURATION_TICKS) && advMenus; + if (Button_onLongClick(g_pButton)) { + DEBUG_LOGF("Selected ringmenu %s", menuList[m_selection].menuName); + if (!Menus_openMenu(m_selection, openAdv)) { + DEBUG_LOGF("Failed to initialize %s menu", menuList[m_selection].menuName); + return false; + } + return true; + } + if (Button_isPressed(g_pButton) && openAdv) { + offtime = HYPERSTROBE_OFF_DURATION; + ontime = HYPERSTROBE_ON_DURATION; + } + } + for (Pair p = PAIR_FIRST; p < PAIR_COUNT; ++p) { + if (pairEven(p) < LED_COUNT) { + Leds_blinkIndex(pairEven(p), offtime, ontime, menuList[m_selection].color); + } + if (pairOdd(p) < LED_COUNT) { + Leds_setIndex(pairOdd(p), menuList[m_selection].color); + Leds_blinkIndex(pairOdd(p), offtime, ontime, COL32(RGB_OFF)); + } + } + if (Button_onConsecutivePresses(g_pButton, ADVANCED_MENU_CLICKS)) { + Modes_setAdvancedMenus(!advMenus, true); + for (uint16_t i = 0; i < 2; ++i) { + Leds_holdAll(advMenus ? COL32(RGB_RED) : COL32(RGB_PINK)); + } + } + RGBColor selCol; + selCol.red = menuList[m_selection].color.red << 3; + selCol.green = menuList[m_selection].color.green << 3; + selCol.blue = menuList[m_selection].color.blue << 3; + Menus_showSelection(selCol); + return true; +} + +static bool runCurMenu(void) +{ + MenuAction action = m_pCurMenu->vtable->run(m_pCurMenu); + switch (action) { + case MENU_QUIT: + closeCurMenu(); + return false; + case MENU_CONTINUE: + if (Button_onShortClick(g_pButton)) { + m_pCurMenu->vtable->onShortClick(m_pCurMenu); + } + if (Button_onLongClick(g_pButton)) { + m_pCurMenu->vtable->onLongClick(m_pCurMenu); + } + break; + case MENU_SKIP: + break; + } + return true; +} + +bool Menus_openMenuSelection(void) +{ + if (m_menuState != MENU_STATE_NOT_OPEN) { + return false; + } + m_openTime = Time_getCurtime(); + m_menuState = MENU_STATE_MENU_SELECTION; + Leds_clearAll(); + return true; +} + +bool Menus_openMenu(uint32_t index, bool advanced) +{ + if (index >= NUM_MENUS) { + return false; + } + m_selection = index; + Menu *newMenu = menuList[m_selection].initMenu(menuList[m_selection].color, advanced); + if (!newMenu) { + return false; + } + if (!newMenu->vtable->init(newMenu)) { + DEBUG_LOGF("Failed to initialize %s menu", menuList[m_selection].menuName); + newMenu->vtable->destroy(newMenu); + return false; + } + if (m_pCurMenu) { + m_pCurMenu->vtable->destroy(m_pCurMenu); + } + m_pCurMenu = newMenu; + Leds_clearAll(); + m_menuState = MENU_STATE_IN_MENU; + return true; +} + +void Menus_showSelection(RGBColor colval) +{ + if (Button_isPressed(g_pButton) && + Button_holdDuration(g_pButton) > SHORT_CLICK_THRESHOLD_TICKS && + Button_holdDuration(g_pButton) < (SHORT_CLICK_THRESHOLD_TICKS + MS_TO_TICKS(250))) { + Leds_setAll(colval); + } +} + +bool Menus_checkOpen(void) +{ + return m_menuState != MENU_STATE_NOT_OPEN && Button_releaseTime(g_pButton) > m_openTime; +} + +bool Menus_checkInMenu(void) +{ + return m_menuState == MENU_STATE_IN_MENU; +} + +Menu *Menus_curMenu(void) +{ + return m_pCurMenu; +} + +enum MenuEntryID Menus_curMenuID(void) +{ + return (enum MenuEntryID)m_selection; +} + +static void closeCurMenu(void) +{ + if (m_pCurMenu) { + m_pCurMenu->vtable->destroy(m_pCurMenu); + m_pCurMenu = NULL; + } + m_menuState = MENU_STATE_NOT_OPEN; + m_selection = 0; + Leds_clearAll(); +} diff --git a/VortexEngine/src/Menus/Menus.cpp b/VortexEngine/src/Menus/Menus.cpp deleted file mode 100644 index abeaffa074..0000000000 --- a/VortexEngine/src/Menus/Menus.cpp +++ /dev/null @@ -1,296 +0,0 @@ -#include "Menus.h" - -// menus -#include "MenuList/GlobalBrightness.h" -#include "MenuList/EditorConnection.h" -#include "MenuList/FactoryReset.h" -#include "MenuList/ModeSharing.h" -#include "MenuList/ColorSelect.h" -#include "MenuList/PatternSelect.h" -#include "MenuList/Randomizer.h" - -#include "../Time/TimeControl.h" -#include "../Time/Timings.h" -#include "../VortexEngine.h" -#include "../Buttons/Button.h" -#include "../Serial/Serial.h" -#include "../Modes/Modes.h" -#include "../Leds/Leds.h" -#include "../Log/Log.h" - -// static members -Menus::MenuState Menus::m_menuState = MENU_STATE_NOT_OPEN; -uint8_t Menus::m_selection = 0; -uint32_t Menus::m_openTime = 0; -Menu *Menus::m_pCurMenu = nullptr; - -// typedef for the menu initialization function -typedef Menu *(*initMenuFn_t)(const RGBColor &col, bool advanced); - -// entries for the ring menu -struct MenuEntry { -#if LOGGING_LEVEL > 2 - // only store the name if the logging is enabled - const char *menuName; -#endif - initMenuFn_t initMenu; - RGBColor color; -}; - -// a template to initialize ringmenu functions -template -Menu *initMenu(const RGBColor &col, bool advanced) { return new T(col, advanced); } - -// a simple macro to simplify the entries in the menu list -#if LOGGING_LEVEL > 2 -// if the logging is enabled then we need to store the name of the menu -#define ENTRY(classname, color) { #classname, initMenu, color } -#else -#define ENTRY(classname, color) { initMenu, color } -#endif - -// The list of menus that are registered with colors to show in ring menu -const MenuEntry menuList[] = { - // ========================= - // Default menu setup: - ENTRY(Randomizer, RGB_MENU_RANDOMIZER), - ENTRY(ModeSharing, RGB_MENU_MODE_SHARING), -#if ENABLE_EDITOR_CONNECTION == 1 - ENTRY(EditorConnection, RGB_MENU_EDITOR_CONNECTION), -#endif - ENTRY(ColorSelect, RGB_MENU_COLOR_SELECT), - ENTRY(PatternSelect, RGB_MENU_PATTERN_SELECT), - ENTRY(GlobalBrightness, RGB_MENU_BRIGHTNESS_SELECT), - ENTRY(FactoryReset, RGB_MENU_FACTORY_RESET), -}; - -// the number of menus in the above array -#define NUM_MENUS (sizeof(menuList) / sizeof(menuList[0])) - -bool Menus::init() -{ - m_menuState = MENU_STATE_NOT_OPEN; - // sub-menus are initialized right before they run - return true; -} - -void Menus::cleanup() -{ - if (m_pCurMenu) { - delete m_pCurMenu; - m_pCurMenu = nullptr; - } - m_menuState = MENU_STATE_NOT_OPEN; - m_selection = 0; - m_openTime = 0; -} - -bool Menus::run() -{ - // if there are no menus, then we don't need to run - switch (m_menuState) { - case MENU_STATE_NOT_OPEN: - default: - // nothing to run - return false; - case MENU_STATE_MENU_SELECTION: - return runMenuSelection(); - case MENU_STATE_IN_MENU: - return runCurMenu(); - } -} - -bool Menus::runMenuSelection() -{ - if (g_pButton->onShortClick()) { - // otherwise increment selection and wrap around at num menus - m_selection = (m_selection + 1) % NUM_MENUS; -#if ENABLE_EDITOR_CONNECTION == 1 - // Hide the editor connection menu because it opens automatically - // TODO: Create a better way to hide this menu color, this menu - // will automatically open when the device is plugged in - if (m_selection == MENU_EDITOR_CONNECTION) { - m_selection++; - } -#endif - DEBUG_LOGF("Cyling to ring menu %u", m_selection); - // reset the open time so that it starts again - m_openTime = Time::getCurtime(); - // clear the leds - Leds::clearAll(); - return true; - } - // clear the leds so it always fills instead of replacing - Leds::clearAll(); - // timings for blink later - uint8_t offtime = 200; - uint8_t ontime = 200; - // whether advanced menus are enabled - bool advMenus = Modes::advancedMenusEnabled(); - // if the button was long pressed then select this menu, but we - // need to check the presstime to ensure we don't catch the initial - // release after opening the ringmenu - if (g_pButton->pressTime() >= m_openTime) { - // whether to open advanced menus or not - bool openAdv = (g_pButton->holdDuration() > ADV_MENU_DURATION_TICKS) && advMenus; - if (g_pButton->onLongClick()) { - // ringmenu is open so select the menu - DEBUG_LOGF("Selected ringmenu %s", menuList[m_selection].menuName); - // open the menu we have selected - if (!openMenu(m_selection, openAdv)) { - DEBUG_LOGF("Failed to initialize %s menu", menuList[m_selection].menuName); - return false; - } - // display the newly opened menu - return true; - } - // if holding down to select the menu option - if (g_pButton->isPressed() && openAdv) { - // make it strobe aw yiss - offtime = HYPERSTROBE_OFF_DURATION; - ontime = HYPERSTROBE_ON_DURATION; - } - } - // blink every even/odd of every pair - for (Pair p = PAIR_FIRST; p < PAIR_COUNT; ++p) { - if (pairEven(p) < LED_COUNT) { - Leds::blinkIndex(pairEven(p), offtime, ontime, menuList[m_selection].color); - } - if (pairOdd(p) < LED_COUNT) { - Leds::setIndex(pairOdd(p), menuList[m_selection].color); - Leds::blinkIndex(pairOdd(p), offtime, ontime, RGB_OFF); - } - } - // check if the advanced menus have been enabled - if (g_pButton->onConsecutivePresses(ADVANCED_MENU_CLICKS)) { - // toggle the advanced menu - Modes::setAdvancedMenus(!advMenus); - // display a pink or red depending on whether the menu was enabled - for (uint16_t i = 0; i < 2; ++i) { - Leds::holdAll(advMenus ? RGB_RED : RGB_PINK); - } - } - // show when the user selects a menu option - showSelection(RGBColor( - menuList[m_selection].color.red << 3, - menuList[m_selection].color.green << 3, - menuList[m_selection].color.blue << 3)); - // continue in the menu - return true; -} - -bool Menus::runCurMenu() -{ - // if the menu run handler returns false that signals the - // menu was closed by the user leaving the menu - switch (m_pCurMenu->run()) { - case Menu::MENU_QUIT: - // close the current menu when run returns false - closeCurMenu(); - // return false to let the modes play - return false; - case Menu::MENU_CONTINUE: - // if Menu continue run the click handlers for the menu - if (g_pButton->onShortClick()) { - m_pCurMenu->onShortClick(); - } - if (g_pButton->onLongClick()) { - m_pCurMenu->onLongClick(); - } - break; - case Menu::MENU_SKIP: - // dont run click handlers in this case - // so the core menu class can handle them - break; - } - // the opened menu and don't play modes - return true; -} - -// open the menu selection ring -bool Menus::openMenuSelection() -{ - if (m_menuState != MENU_STATE_NOT_OPEN) { - return false; - } - // save the time of when we open the menu so we can fill based on curtime from then - m_openTime = Time::getCurtime(); - // open the menu - m_menuState = MENU_STATE_MENU_SELECTION; - // clear the leds - Leds::clearAll(); - return true; -} - -bool Menus::openMenu(uint32_t index, bool advanced) -{ - if (index >= NUM_MENUS) { - return false; - } - m_selection = index; - Menu *newMenu = menuList[m_selection].initMenu(menuList[m_selection].color, advanced); - if (!newMenu) { - return false; - } - // initialiaze the new menu with the current mode - if (!newMenu->init()) { - DEBUG_LOGF("Failed to initialize %s menu", menuList[m_selection].menuName); - // if the menu failed to init, don't open it - delete newMenu; - return false; - } - if (m_pCurMenu) { - delete m_pCurMenu; - } - // assign the new menu - m_pCurMenu = newMenu; - // and the menus are open just in case - // clear all the leds - Leds::clearAll(); - m_menuState = MENU_STATE_IN_MENU; - return true; -} - -void Menus::showSelection(RGBColor colval) -{ - // blink the tip led white for 150ms when the short - // click threshold has been surpassed - if (g_pButton->isPressed() && - g_pButton->holdDuration() > SHORT_CLICK_THRESHOLD_TICKS && - g_pButton->holdDuration() < (SHORT_CLICK_THRESHOLD_TICKS + MS_TO_TICKS(250))) { - Leds::setAll(colval); - } -} - -bool Menus::checkOpen() -{ - return m_menuState != MENU_STATE_NOT_OPEN && g_pButton->releaseTime() > m_openTime; -} - -bool Menus::checkInMenu() -{ - return m_menuState == MENU_STATE_IN_MENU; -} - -Menu *Menus::curMenu() -{ - return m_pCurMenu; -} - -MenuEntryID Menus::curMenuID() -{ - return (MenuEntryID)m_selection; -} - -void Menus::closeCurMenu() -{ - // delete the currently open menu object - if (m_pCurMenu) { - delete m_pCurMenu; - m_pCurMenu = nullptr; - } - m_menuState = MENU_STATE_NOT_OPEN; - m_selection = 0; - // clear the leds - Leds::clearAll(); -} diff --git a/VortexEngine/src/Menus/Menus.h b/VortexEngine/src/Menus/Menus.h index d352a69cf9..413cd561a0 100644 --- a/VortexEngine/src/Menus/Menus.h +++ b/VortexEngine/src/Menus/Menus.h @@ -1,7 +1,8 @@ -#ifndef RING_MENU_H -#define RING_MENU_H +#ifndef MENUS_H +#define MENUS_H #include +#include #include "../Colors/ColorTypes.h" #include "../Leds/LedTypes.h" @@ -21,79 +22,26 @@ enum MenuEntryID MENU_PATTERN_SELECT, MENU_GLOBAL_BRIGHTNESS, MENU_FACTORY_RESET, - // add new menus here - // the total number of menus MENU_COUNT }; -class Menu; +typedef struct Menu Menu; -class Menus -{ - // private unimplemented constructor - Menus(); - -public: - // opting for static class here because there should only ever be one - // Menu control object and I don't like singletons - static bool init(); - static void cleanup(); - - // Run the menus returns true if the menu remains open, false if closed - static bool run(); - - // open the ring menu - static bool openMenuSelection(); - - // open a menu by index in the menu table, optionally specify to - // open the advanced version of the menu (default basic version) - static bool openMenu(uint32_t index, bool advanced = false); +bool Menus_init(void); +void Menus_cleanup(void); - // if you call this then a blink will be shown when the user has - // held the button for at least the long click duration - static void showSelection(RGBColor colval = RGB_WHITE5); +bool Menus_run(void); - // whether the menus are actually open (The user has let go after opening) - static bool checkOpen(); - // whether the user is actually in a menu - static bool checkInMenu(); - static Menu *curMenu(); - static MenuEntryID curMenuID(); +bool Menus_openMenuSelection(void); -private: - // run the currently open menu - static bool runCurMenu(); - // run the ring filling logic - static bool runMenuSelection(); - // helper to calculate the relative hold time for the current menu - static LedPos calcLedPos(); - // close the currently open menu - static void closeCurMenu(); +bool Menus_openMenu(uint32_t index, bool advanced); - // ===================== - // private members +void Menus_showSelection(RGBColor colval); - enum MenuState { - // menus aren't open at all - MENU_STATE_NOT_OPEN = 0, - - // menus are open and the user is choosing a menu - MENU_STATE_MENU_SELECTION, - - // the menus are open and we opened a menu - MENU_STATE_IN_MENU - }; - - // the current menus states - static MenuState m_menuState; - - // the ring menu section - static uint8_t m_selection; - // the time when we first opened the ringmenu - static uint32_t m_openTime; - // the current sub menu that is open - static Menu *m_pCurMenu; -}; +bool Menus_checkOpen(void); +bool Menus_checkInMenu(void); +Menu *Menus_curMenu(void); +enum MenuEntryID Menus_curMenuID(void); #endif diff --git a/VortexEngine/src/Modes/DefaultModes.c b/VortexEngine/src/Modes/DefaultModes.c new file mode 100644 index 0000000000..54d8082aaf --- /dev/null +++ b/VortexEngine/src/Modes/DefaultModes.c @@ -0,0 +1,55 @@ +#include "DefaultModes.h" + +#include "../Colors/ColorTypes.h" + +const uint32_t rgbCols[] = { RGB_RED, RGB_GREEN, RGB_BLUE, }; +const uint32_t pat2TipCols = 0x97709F; +const uint32_t pat2TopCols = 0x4D00B2; +const uint32_t pat4TipCols[] = { 0xC4003B, 0x700000, 0x000000, 0x0017E9, 0x22004E, 0x000000, }; +const uint32_t pat4TopCols[] = { 0xC4003B, 0x4D00B2, 0x0017E9 }; +const uint32_t pat5TipCols[] = { 0xFFC600, 0x235500, 0x00FF66, 0x004355, 0x0600FF, 0x480055, 0xFF0057 }; +const uint32_t pat5TopCols[] = { 0xFFC600, 0x235500, 0x00FF66, 0x004355, 0x0600FF, 0x480055, 0xFF0057 }; +const uint32_t pat6Cols[] = { 0x9FFF00, 0x00FF66, 0x009FFF, 0x5A00FF, 0xFF009F }; +const uint32_t pat7Cols[] = { 0x00AA90, 0x00B1FF, 0x300055, 0xFF002D, 0x541B00 }; +const uint32_t pat8TipCols[] = { 0x0000FF, 0x400055, 0x54000B, 0x525400, 0x26AA00 }; +const uint32_t pat8TopCols[] = { 0xFFC600, 0x235500, 0x00FF66, 0x004355, 0x0600FF, 0x480055, 0xFF0057 }; +const uint32_t pat9Cols[] = { 0xFFF600, 0x000880 }; + +const struct DefaultModeEntry defaultModes[MAX_MODES] = { + {{ + { PATTERN_STROBEGAP, 3, rgbCols }, + { PATTERN_STROBEGAP, 3, rgbCols } + }}, + {{ + { PATTERN_COMPLEMENTARY_BLEND, 3, rgbCols }, + { PATTERN_BLEND, 3, rgbCols } + }}, + {{ + { PATTERN_STROBE, 1, &pat2TipCols }, + { PATTERN_HYPERGAP, 1, &pat2TopCols } + }}, + {{ + { PATTERN_STROBE, 6, pat4TipCols }, + { PATTERN_STROBIE, 3, pat4TopCols } + }}, + {{ + { PATTERN_DOUBLEDOPS, 7, pat5TipCols }, + { PATTERN_DOUBLEDOPS, 7, pat5TopCols } + }}, + {{ + { PATTERN_GHOSTCRUSH, 5, pat6Cols }, + { PATTERN_GHOSTCRUSH, 5, pat6Cols } + }}, + {{ + { PATTERN_ULTRADOPS, 5, pat7Cols }, + { PATTERN_ULTRADOPS, 5, pat7Cols } + }}, + {{ + { PATTERN_PICOGAP, 5, pat8TipCols }, + { PATTERN_PICOGAP, 7, pat8TopCols } + }}, + {{ + { PATTERN_DOPS, 2, pat9Cols }, + { PATTERN_HYPERSTROBE, 2, pat9Cols } + }}, +}; diff --git a/VortexEngine/src/Modes/DefaultModes.h b/VortexEngine/src/Modes/DefaultModes.h index 6c685a0d81..581cb36ee2 100644 --- a/VortexEngine/src/Modes/DefaultModes.h +++ b/VortexEngine/src/Modes/DefaultModes.h @@ -5,7 +5,6 @@ #include "../Leds/LedTypes.h" #include "../VortexConfig.h" -// structure of the entries in the default modes array struct DefaultLedEntry { PatternID patternID; @@ -15,10 +14,9 @@ struct DefaultLedEntry struct DefaultModeEntry { - DefaultLedEntry leds[LED_COUNT]; + struct DefaultLedEntry leds[LED_COUNT]; }; -// exposed global array of default modes -extern const DefaultModeEntry defaultModes[MAX_MODES]; +extern const struct DefaultModeEntry defaultModes[MAX_MODES]; #endif diff --git a/VortexEngine/src/Modes/Mode.c b/VortexEngine/src/Modes/Mode.c new file mode 100644 index 0000000000..1ff6f09787 --- /dev/null +++ b/VortexEngine/src/Modes/Mode.c @@ -0,0 +1,1035 @@ +#include "Mode.h" + +#include "../Patterns/Patterns.h" +#include "../Patterns/PatternArgs.h" +#include "../Patterns/Pattern.h" +#include "../Patterns/PatternBuilder.h" +#include "../Colors/Colorset.h" +#include "../Serial/ByteStream.h" +#include "../Leds/Leds.h" +#include "../Memory/Memory.h" +#include "../Log/Log.h" +#include "../VortexEngine.h" + +void Mode_initFromEntry(Mode *self, const struct DefaultModeEntry *entry) +{ + Mode_init(self); + for (LedPos pos = LED_FIRST; pos < LED_COUNT; ++pos) { + Colorset set; + Colorset_initFromU32s(&set, entry->leds[pos].numColors, entry->leds[pos].cols); + Mode_setPattern(self, entry->leds[pos].patternID, pos, NULL, &set); + } +} + +#if FIXED_LED_COUNT == 0 +#define MODE_LEDCOUNT self->m_numLeds + +static void Mode_initInternal(Mode *self, uint8_t numLeds) +{ +#if VORTEX_SLIM == 0 + self->m_multiPat = NULL; +#endif + self->m_numLeds = numLeds; + self->m_singlePats = NULL; + Mode_setLedCount(self, self->m_numLeds); +} + +void Mode_init(Mode *self) +{ + Mode_initInternal(self, LED_COUNT); +} +#else +#define MODE_LEDCOUNT LED_COUNT + +void Mode_init(Mode *self) +{ +#if VORTEX_SLIM == 0 + self->m_multiPat = NULL; +#endif + for (LedPos i = LED_FIRST; i < LED_COUNT; ++i) { + self->m_singlePats[i] = NULL; + } +} +#endif + +void Mode_initFromID(Mode *self, PatternID id, const Colorset *set) +{ + Mode_init(self); + Mode_setPattern(self, id, LED_ANY, NULL, set); +} + +void Mode_initFromIDArgsSet(Mode *self, PatternID id, const PatternArgs *args, const Colorset *set) +{ + Mode_init(self); + Mode_setPattern(self, id, LED_ANY, args, set); +} + +void Mode_initFromIDArgsSetPtr(Mode *self, PatternID id, const PatternArgs *args, const Colorset *set) +{ + Mode_init(self); + Mode_setPattern(self, id, LED_ANY, args, set); +} + +void Mode_initFromOther(Mode *self, const Mode *other) +{ + Mode_init(self); + if (!other) { + return; + } + Mode_assign(self, other); +} + +void Mode_initCopy(Mode *self, const Mode *other) +{ + Mode_init(self); + Mode_assign(self, other); +} + +void Mode_cleanup(Mode *self) +{ + Mode_clearPattern(self, LED_ALL); +#if FIXED_LED_COUNT == 0 + vfree(self->m_singlePats); +#endif +} + +void Mode_assign(Mode *self, const Mode *other) +{ +#if FIXED_LED_COUNT == 0 + Mode_setLedCount(self, Mode_getLedCount(other)); +#endif + Mode_clearPattern(self, LED_ALL); +#if VORTEX_SLIM == 0 + if (other->m_multiPat) { + self->m_multiPat = PatternBuilder_dupe(other->m_multiPat); + } +#endif + for (LedPos i = LED_FIRST; i < Mode_getLedCount(other); ++i) { + Pattern *otherPat = other->m_singlePats[i]; + if (!otherPat) { + continue; + } + self->m_singlePats[i] = PatternBuilder_dupe(otherPat); + } +} + +bool Mode_notEquals(const Mode *self, const Mode *other) +{ + return !Mode_equals(self, other); +} + +void Mode_initMode(Mode *self) +{ +#if VORTEX_SLIM == 0 + if (self->m_multiPat) { + Pattern_init(self->m_multiPat, NULL); + } +#endif + for (LedPos pos = LED_FIRST; pos < MODE_LEDCOUNT; ++pos) { + Pattern *entry = self->m_singlePats[pos]; + if (!entry) { + continue; + } + Pattern_init(entry, NULL); + } +} + +void Mode_play(Mode *self) +{ +#if VORTEX_SLIM == 0 + if (self->m_multiPat) { + Pattern_play(self->m_multiPat); + } +#endif + for (LedPos pos = LED_FIRST; pos < MODE_LEDCOUNT; ++pos) { + Pattern *entry = self->m_singlePats[pos]; + if (!entry) { +#if VORTEX_SLIM == 0 + if (!self->m_multiPat) { + Leds_clearIndex(pos); + } +#else + Leds_clearIndex(pos); +#endif + continue; + } + Pattern_play(entry); + } +} + +bool Mode_saveToBuffer(const Mode *self, ByteStream *saveBuffer, uint8_t numLeds) +{ + VortexEngine_serializeVersion(saveBuffer); + Mode_serialize(self, saveBuffer, numLeds); + DEBUG_LOGF("Serialized mode, uncompressed size: %u", ByteStream_size(saveBuffer)); + return ByteStream_compress(saveBuffer); +} + +bool Mode_loadFromBuffer(Mode *self, ByteStream *saveBuffer) +{ + if (!ByteStream_decompress(saveBuffer)) { + return false; + } + ByteStream_resetUnserializer(saveBuffer); + uint8_t major = 0; + uint8_t minor = 0; + if (!ByteStream_unserialize8(saveBuffer, &major)) { + return false; + } + if (!ByteStream_unserialize8(saveBuffer, &minor)) { + return false; + } + if (!VortexEngine_checkVersion(major, minor)) { + ERROR_LOGF("Incompatible savefile version: %u.%u", major, minor); + return false; + } + if (!Mode_unserialize(self, saveBuffer)) { + return false; + } + Mode_initMode(self); + return true; +} + +bool Mode_serialize(const Mode *self, ByteStream *buffer, uint8_t numLeds) +{ + if (!numLeds) { + numLeds = MODE_LEDCOUNT; + } + if (!ByteStream_serialize8(buffer, numLeds)) { + return false; + } + if (!numLeds) { + return true; + } + ModeFlags flags = Mode_getFlags(self); + if (!ByteStream_serialize8(buffer, flags)) { + return false; + } +#if VORTEX_SLIM == 0 + if ((flags & MODE_FLAG_MULTI_LED) && self->m_multiPat) { + if (!Pattern_serialize(self->m_multiPat, buffer)) { + return false; + } + } +#endif + if (!(flags & MODE_FLAG_SINGLE_LED)) { + return true; + } + if (flags & MODE_FLAG_SPARSE_SINGLES) { + if (!ByteStream_serialize32(buffer, (uint32_t)Mode_getSingleLedMap(self))) { + return false; + } + } + for (LedPos pos = LED_FIRST; pos < numLeds; ++pos) { + const Pattern *entry = self->m_singlePats[pos]; + if (!entry) { + continue; + } + if (!Pattern_serialize(entry, buffer)) { + return false; + } + if (flags & MODE_FLAG_ALL_SAME_SINGLE) { + break; + } + } + return true; +} + +bool Mode_unserialize(Mode *self, ByteStream *buffer) +{ + Mode_clearPattern(self, LED_ALL); + uint8_t ledCount = LED_COUNT; + if (!ByteStream_unserialize8(buffer, &ledCount)) { + return false; + } +#if FIXED_LED_COUNT == 0 + if (ledCount > self->m_numLeds) { + Mode_setLedCount(self, ledCount); + } +#endif + if (!ledCount) { + return true; + } + ModeFlags flags = 0; + if (!ByteStream_unserialize8(buffer, &flags)) { + return false; + } + Pattern *firstPat = NULL; + if (flags & MODE_FLAG_MULTI_LED) { +#if VORTEX_SLIM == 1 + return false; +#else + self->m_multiPat = PatternBuilder_unserialize(buffer); + if (!self->m_multiPat) { + return false; + } + Pattern_init(self->m_multiPat, NULL); +#endif + } + if (!(flags & MODE_FLAG_SINGLE_LED)) { + return true; + } + LedMap map = (1 << ledCount) - 1; + if (flags & MODE_FLAG_SPARSE_SINGLES) { + return false; + } + MAP_FOREACH_LED(map) { + if (pos >= LED_COUNT) { + continue; + } + if (pos == LED_FIRST || (flags & MODE_FLAG_ALL_SAME_SINGLE) == 0) { + self->m_singlePats[pos] = firstPat = PatternBuilder_unserialize(buffer); + } else { + self->m_singlePats[pos] = PatternBuilder_dupe(self->m_singlePats[LED_FIRST]); + } + if (!self->m_singlePats[pos]) { + Mode_clearPattern(self, LED_ALL); + return false; + } + Pattern_bind(self->m_singlePats[pos], pos); + } + if (ledCount >= LED_COUNT) { + return true; + } + LedPos src = LED_FIRST; + for (LedPos pos = (LedPos)ledCount; pos < LED_COUNT; ++pos) { + self->m_singlePats[pos] = PatternBuilder_dupe(self->m_singlePats[src]); + if (!self->m_singlePats[pos]) { + return false; + } + Pattern_bind(self->m_singlePats[pos], pos); + src = (LedPos)((src + 1) % ledCount); + } + return true; +} + +bool Mode_equals(const Mode *self, const Mode *other) +{ + if (!other) { + return false; + } + if (Mode_getLedCount(other) != MODE_LEDCOUNT) { + return false; + } +#if VORTEX_SLIM == 0 + if ((self->m_multiPat && !Pattern_equals(self->m_multiPat, other->m_multiPat)) || + (!self->m_multiPat && other->m_multiPat)) { + return false; + } +#endif + for (LedPos pos = LED_FIRST; pos < MODE_LEDCOUNT; ++pos) { + if (self->m_singlePats[pos]) { + if (!Pattern_equals(self->m_singlePats[pos], other->m_singlePats[pos])) { + return false; + } + continue; + } + if (other->m_singlePats[pos]) { + return false; + } + } + return true; +} + +#if FIXED_LED_COUNT == 0 +bool Mode_setLedCount(Mode *self, uint8_t numLeds) +{ + if (self->m_singlePats) { + Mode_clearPattern(self, LED_ALL); + vfree(self->m_singlePats); + } + self->m_numLeds = numLeds; + self->m_singlePats = (Pattern **)vcalloc(self->m_numLeds, sizeof(Pattern *)); + if (!self->m_singlePats) { + ERROR_OUT_OF_MEMORY(); + return false; + } + return true; +} +#endif + +uint8_t Mode_getLedCount(const Mode *self) +{ +#if FIXED_LED_COUNT == 0 + return self->m_numLeds; +#else + return LED_COUNT; +#endif +} + +const Pattern *Mode_getPatternConst(const Mode *self, LedPos pos) +{ + return Mode_getPattern((Mode *)self, pos); +} + +Pattern *Mode_getPattern(Mode *self, LedPos pos) +{ + switch (pos) { + case LED_ALL: + return NULL; + case LED_ANY: +#if VORTEX_SLIM == 0 + case LED_MULTI: + if (self->m_multiPat) { + return self->m_multiPat; + } + if (pos == LED_MULTI) { + break; + } +#endif + case LED_ALL_SINGLE: +#if FIXED_LED_COUNT == 0 + if (!self->m_singlePats) { + return NULL; + } +#endif + for (LedPos p = LED_FIRST; p < LED_COUNT; ++p) { + if (self->m_singlePats[p]) { + return self->m_singlePats[p]; + } + } + break; + default: +#if FIXED_LED_COUNT == 0 + if (!self->m_singlePats) { + return NULL; + } +#endif + if (pos < LED_COUNT && self->m_singlePats[pos]) { + return self->m_singlePats[pos]; + } + break; + } + return NULL; +} + +Colorset Mode_getColorset(const Mode *self, LedPos pos) +{ + return Mode_getColorsetMut((Mode *)self, pos); +} + +Colorset Mode_getColorsetMut(Mode *self, LedPos pos) +{ + Pattern *pat = Mode_getPattern(self, pos); + if (!pat) { + Colorset empty; + Colorset_init(&empty); + return empty; + } + return Pattern_getColorset(pat); +} + +PatternID Mode_getPatternID(const Mode *self, LedPos pos) +{ + const Pattern *pat = Mode_getPatternConst(self, pos); + if (!pat) { + return PATTERN_NONE; + } + return Pattern_getPatternID(pat); +} + +bool Mode_setPattern(Mode *self, PatternID pat, LedPos pos, const PatternArgs *args, const Colorset *set) +{ + Colorset newSet = set ? *set : Mode_getColorsetMut(self, ((pos < LED_COUNT) && self->m_singlePats[pos]) ? pos : LED_ANY); + switch (pos) { + case LED_ANY: + case LED_ALL: +#if VORTEX_SLIM == 0 + case LED_MULTI: + if (self->m_multiPat) { + Pattern_destroy(self->m_multiPat); + vfree(self->m_multiPat); + self->m_multiPat = NULL; + } + if (isMultiLedPatternID(pat)) { + self->m_multiPat = PatternBuilder_makeMulti(pat, args); + if (self->m_multiPat) { + Pattern_setColorset(self->m_multiPat, &newSet); + } + Mode_clearPattern(self, LED_ALL_SINGLE); + return true; + } + if (pos == LED_MULTI) { + return false; + } +#endif + case LED_ALL_SINGLE: +#if FIXED_LED_COUNT == 0 + if (!self->m_singlePats) { + return false; + } +#endif + for (LedPos p = LED_FIRST; p < LED_COUNT; ++p) { + if (!Mode_setPattern(self, pat, p, args, &newSet)) { + return false; + } + } + return true; + default: +#if FIXED_LED_COUNT == 0 + if (!self->m_singlePats) { + return false; + } +#endif + if (pos >= LED_COUNT) { + return false; + } + if (self->m_singlePats[pos]) { + Pattern_destroy(self->m_singlePats[pos]); + vfree(self->m_singlePats[pos]); + } + self->m_singlePats[pos] = PatternBuilder_makeSingle(pat, args); + if (self->m_singlePats[pos]) { + Pattern_setColorset(self->m_singlePats[pos], &newSet); + Pattern_bind(self->m_singlePats[pos], pos); + } + return true; + } + return false; +} + +bool Mode_setPatternMap(Mode *self, LedMap map, PatternID pat, const PatternArgs *args, const Colorset *set) +{ + MAP_FOREACH_LED(map) { + if (!Mode_setPattern(self, pat, pos, args, set)) { + return false; + } + } + return true; +} + +void Mode_copyPatternFrom(Mode *self, const Mode *other, LedPos to, LedPos from) +{ + if (to >= LED_COUNT || from >= LED_COUNT) { + return; + } + if (self->m_singlePats[to]) { + Pattern_destroy(self->m_singlePats[to]); + vfree(self->m_singlePats[to]); + } + self->m_singlePats[to] = PatternBuilder_dupe(other->m_singlePats[from]); +} + +void Mode_swapPatterns(Mode *self, LedPos a, LedPos b) +{ + if (a >= LED_COUNT || b >= LED_COUNT) { + return; + } + Pattern *temp = self->m_singlePats[a]; + self->m_singlePats[a] = self->m_singlePats[b]; + self->m_singlePats[b] = temp; +} + +bool Mode_setColorset(Mode *self, const Colorset *set, LedPos pos) +{ + switch (pos) { + case LED_ANY: + case LED_ALL: +#if VORTEX_SLIM == 0 + case LED_MULTI: + if (self->m_multiPat) { + Pattern_setColorset(self->m_multiPat, set); + } + if (pos == LED_MULTI) { + return true; + } +#endif + case LED_ALL_SINGLE: +#if FIXED_LED_COUNT == 0 + if (!self->m_singlePats) { + return false; + } +#endif + for (LedPos p = LED_FIRST; p < LED_COUNT; ++p) { + if (self->m_singlePats[p]) { + Pattern_setColorset(self->m_singlePats[p], set); + } + } + return true; + default: +#if FIXED_LED_COUNT == 0 + if (!self->m_singlePats) { + return false; + } +#endif + if (pos < LED_COUNT && self->m_singlePats[pos]) { + Pattern_setColorset(self->m_singlePats[pos], set); + return true; + } + break; + } + return false; +} + +bool Mode_setColorsetMap(Mode *self, LedMap map, const Colorset *set) +{ + MAP_FOREACH_LED(map) { + if (!Mode_setColorset(self, set, pos)) { + return false; + } + } + return true; +} + +void Mode_clearPattern(Mode *self, LedPos pos) +{ + Mode_setPattern(self, PATTERN_NONE, pos, NULL, NULL); +} + +void Mode_clearPatternMap(Mode *self, LedMap map) +{ + MAP_FOREACH_LED(map) { + Mode_clearPattern(self, pos); + } +} + +void Mode_clearColorset(Mode *self, LedPos pos) +{ + Colorset empty; + Colorset_init(&empty); + Mode_setColorset(self, &empty, pos); +} + +void Mode_clearColorsetMap(Mode *self, LedMap map) +{ + MAP_FOREACH_LED(map) { + Mode_clearColorset(self, pos); + } +} + +void Mode_setArg(Mode *self, uint8_t param, uint8_t value, LedMap map) +{ + MAP_FOREACH_LED(map) { + Pattern *pat = Mode_getPattern(self, pos); + if (!pat) { + continue; + } + Pattern_setArg(pat, param, value); + } +} + +uint8_t Mode_getArg(Mode *self, uint8_t index, LedPos pos) +{ + Pattern *pat = Mode_getPattern(self, pos); + if (!pat) { + return 0; + } + return Pattern_getArg(pat, index); +} + +ModeFlags Mode_getFlags(const Mode *self) +{ + ModeFlags flags = 0; + if (Mode_hasMultiLed(self)) flags |= MODE_FLAG_MULTI_LED; + if (Mode_hasSingleLed(self)) flags |= MODE_FLAG_SINGLE_LED; + if (Mode_hasSameSingleLed(self)) flags |= MODE_FLAG_ALL_SAME_SINGLE; + if (Mode_hasSparseSingleLed(self)) flags |= MODE_FLAG_SPARSE_SINGLES; + return flags; +} + +bool Mode_hasMultiLed(const Mode *self) +{ +#if VORTEX_SLIM == 0 + return (self->m_multiPat != NULL); +#else + return false; +#endif +} + +bool Mode_hasSingleLed(const Mode *self) +{ + for (LedPos i = LED_FIRST; i < MODE_LEDCOUNT; ++i) { + if (self->m_singlePats[i]) { + return true; + } + } + return false; +} + +bool Mode_hasSameSingleLed(const Mode *self) +{ + Pattern *firstPat = NULL; + for (LedPos i = LED_FIRST; i < MODE_LEDCOUNT; ++i) { + if (!self->m_singlePats[i]) { + continue; + } + if (!firstPat) { + firstPat = self->m_singlePats[i]; + continue; + } + if (!Pattern_equals(firstPat, self->m_singlePats[i])) { + return false; + } + } + return (firstPat != NULL); +} + +bool Mode_hasSparseSingleLed(const Mode *self) +{ + switch (Mode_getSingleLedMap(self)) { + case MAP_LED_ALL: + case 0: + return false; + default: + return true; + } +} + +bool Mode_isEmpty(const Mode *self) +{ +#if VORTEX_SLIM == 0 + if (self->m_multiPat && Pattern_getPatternID(self->m_multiPat) != PATTERN_NONE) { + return false; + } +#endif + for (LedPos i = LED_FIRST; i < MODE_LEDCOUNT; ++i) { + if (self->m_singlePats[i] && Pattern_getPatternID(self->m_singlePats[i]) != PATTERN_NONE) { + return false; + } + } + return true; +} + +LedMap Mode_getSingleLedMap(const Mode *self) +{ + LedMap map = 0; + for (LedPos pos = LED_FIRST; pos < MODE_LEDCOUNT; pos++) { + if (self->m_singlePats[pos]) { + ledmapSetLed(&map, pos); + } + } + return map; +} + +bool Mode_isMultiLed(const Mode *self) +{ +#if VORTEX_SLIM == 0 + return Mode_hasMultiLed(self) && !Mode_hasSingleLed(self); +#else + return false; +#endif +} + +#if MODES_TEST == 1 +#include +#include + +void Mode_test(void) +{ + INFO_LOG("== Beginning Mode Tests ==\n"); + + INFO_LOG("= Testing Mode::setPattern =\n"); + + Mode modeTest; + Mode_init(&modeTest); + + assert(Mode_getPatternID(&modeTest, LED_MULTI) == PATTERN_NONE); + assert(Mode_getColorsetConst(&modeTest, LED_MULTI).numColors == 0); + + for (LedPos pos = LED_FIRST; pos < LED_COUNT; ++pos) { + assert(Mode_getPatternIDAt(&modeTest, pos) == PATTERN_NONE); + assert(Mode_getColorsetAtConst(&modeTest, pos).numColors == 0); + assert(Mode_getPatternAt(&modeTest, pos) == NULL); + } + + INFO_LOG("Test Mode default constructor passed.\n"); + + PatternID testPatternID = PATTERN_SOLID; + PatternArgs testPatternArgs; + PatternArgs_init4(&testPatternArgs, 100, 255, 0, 10); + Colorset testColorset; + Colorset_initColors(&testColorset, (RGBColor){0, 0, 255}); + + Mode_setPattern(&modeTest, testPatternID, LED_ANY, &testPatternArgs, &testColorset); + + for (LedPos pos = LED_FIRST; pos < LED_COUNT; ++pos) { + assert(Mode_getPatternIDAt(&modeTest, pos) == testPatternID); + assert(Mode_getColorsetAt(&modeTest, pos)->equals(&testColorset)); + } + INFO_LOG("Test setPattern passed.\n"); + + Mode modeMultiLedTest; + Mode_init(&modeMultiLedTest); + PatternID multiPatternID = PATTERN_MULTI_FIRST; + PatternArgs multiPatternArgs; + PatternArgs_init4(&multiPatternArgs, 200, 200, 200, 5); + Colorset multiColorset; + Colorset_initColors(&multiColorset, (RGBColor){255, 0, 0}); + + Mode_setPattern(&modeMultiLedTest, multiPatternID, LED_ANY, &multiPatternArgs, &multiColorset); + + assert(Mode_getPatternID(&modeMultiLedTest, LED_ANY) == multiPatternID); + assert(Mode_getColorsetConst(&modeMultiLedTest, LED_ANY).equals(&multiColorset)); + INFO_LOG("Test setPattern with multi-led pattern passed.\n"); + + Mode modeBothTest; + Mode_init(&modeBothTest); + PatternID singlePatternID = PATTERN_SOLID; + PatternArgs singlePatternArgs; + PatternArgs_init4(&singlePatternArgs, 100, 255, 0, 10); + Colorset singleColorset; + Colorset_initColors(&singleColorset, (RGBColor){0, 0, 255}); + + Mode_setPattern(&modeBothTest, singlePatternID, LED_ANY, &singlePatternArgs, &singleColorset); + Mode_setPattern(&modeBothTest, multiPatternID, LED_ANY, &multiPatternArgs, &multiColorset); + + assert(Mode_hasMultiLed(&modeBothTest) && Mode_hasSingleLed(&modeBothTest)); + INFO_LOG("Test setPattern with single-led and multi-led patterns simultaneously passed.\n"); + + Mode modeSetPatternAtTest; + Mode_init(&modeSetPatternAtTest); + LedPos setPatternAtPosition = LED_5; + Mode_setPatternAt(&modeSetPatternAtTest, setPatternAtPosition, testPatternID, &testPatternArgs, &testColorset); + + for (LedPos pos = LED_FIRST; pos < LED_COUNT; ++pos) { + if (pos == setPatternAtPosition) { + assert(Mode_getPatternIDAt(&modeSetPatternAtTest, pos) == testPatternID); + assert(Mode_getColorsetAt(&modeSetPatternAtTest, pos)->equals(&testColorset)); + } else { + assert(Mode_getPatternIDAt(&modeSetPatternAtTest, pos) == PATTERN_NONE); + } + } + INFO_LOG("Test setPatternAt passed.\n"); + + Colorset newColorset; + Colorset_initColors(&newColorset, (RGBColor){255, 255, 0}, (RGBColor){255, 165, 0}, (RGBColor){0, 255, 0}); + LedPos setColorsetPosition = LED_3; + Mode_setColorsetAt(&modeTest, setColorsetPosition, &newColorset); + + for (LedPos pos = LED_FIRST; pos < LED_COUNT; ++pos) { + if (pos == setColorsetPosition) { + assert(Mode_getColorsetAt(&modeTest, pos)->equals(&newColorset)); + } else { + assert(Mode_getColorsetAt(&modeTest, pos)->equals(&testColorset)); + } + } + INFO_LOG("Test setColorsetAt passed.\n"); + + Mode modeClearSingleLedTest; + Mode_init(&modeClearSingleLedTest); + Mode_setPattern(&modeClearSingleLedTest, testPatternID, LED_ANY, &testPatternArgs, &testColorset); + Mode_clearPatterns(&modeClearSingleLedTest); + + for (LedPos pos = LED_FIRST; pos < LED_COUNT; ++pos) { + assert(Mode_getPatternIDAt(&modeClearSingleLedTest, pos) == PATTERN_NONE); + } + INFO_LOG("Test clearPattern with single-led pattern passed.\n"); + + Mode modeClearMultiLedTest; + Mode_init(&modeClearMultiLedTest); + Mode_setPattern(&modeClearMultiLedTest, multiPatternID, LED_ANY, &multiPatternArgs, &multiColorset); + Mode_clearPatterns(&modeClearMultiLedTest); + + assert(Mode_getPatternID(&modeClearMultiLedTest, LED_ANY) == PATTERN_NONE); + INFO_LOG("Test clearPattern with multi-led pattern passed.\n"); + + Mode_clearPatterns(&modeBothTest); + + assert(Mode_getPatternID(&modeBothTest, LED_ANY) == PATTERN_NONE); + for (LedPos pos = LED_FIRST; pos < LED_COUNT; ++pos) { + assert(Mode_getPatternIDAt(&modeBothTest, pos) == PATTERN_NONE); + } + INFO_LOG("Test clearPattern with both single-led and multi-led patterns passed.\n"); + + Mode modeClearColorsetTest; + Mode_init(&modeClearColorsetTest); + Mode_setPattern(&modeClearColorsetTest, testPatternID, LED_ANY, &testPatternArgs, &testColorset); + Mode_clearColorsets(&modeClearColorsetTest); + + for (LedPos pos = LED_FIRST; pos < LED_COUNT; ++pos) { + assert(Colorset_numColors(Mode_getColorsetAt(&modeClearColorsetTest, pos)) == 0); + } + INFO_LOG("Test clearColorset passed.\n"); + + Mode modeClearColorsetAtTest; + Mode_init(&modeClearColorsetAtTest); + Mode_setPattern(&modeClearColorsetAtTest, testPatternID, LED_ANY, &testPatternArgs, &testColorset); + LedPos clearColorsetAtPosition = LED_3; + Mode_clearColorsetAt(&modeClearColorsetAtTest, clearColorsetAtPosition); + + for (LedPos pos = LED_FIRST; pos < LED_COUNT; ++pos) { + if (pos == clearColorsetAtPosition) { + assert(Colorset_numColors(Mode_getColorsetAt(&modeClearColorsetAtTest, pos)) == 0); + } else { + assert(Colorset_numColors(Mode_getColorsetAt(&modeClearColorsetAtTest, pos)) == Colorset_numColors(&testColorset)); + } + } + INFO_LOG("Test clearColorsetAt passed.\n"); + + Mode modeClearMultiColorsetsTest; + Mode_init(&modeClearMultiColorsetsTest); + Mode_setPattern(&modeClearMultiColorsetsTest, multiPatternID, LED_ANY, &multiPatternArgs, &multiColorset); + Mode_clearColorsets(&modeClearMultiColorsetsTest); + + assert(Colorset_numColors(Mode_getColorsetConst(&modeClearMultiColorsetsTest, LED_ANY)) == 0); + INFO_LOG("Test clearColorsets with multi-led pattern passed.\n"); + + Mode_setPattern(&modeBothTest, PATTERN_BASIC, LED_ANY, NULL, NULL); + Mode_clearColorsets(&modeBothTest); + + assert(Colorset_numColors(Mode_getColorsetConst(&modeBothTest, LED_ANY)) == 0); + for (LedPos pos = LED_FIRST; pos < LED_COUNT; ++pos) { + assert(Colorset_numColors(Mode_getColorsetAt(&modeBothTest, pos)) == 0); + } + INFO_LOG("Test clearColorsets with both single-led and multi-led patterns passed.\n"); + + Mode modeSparseTest; + Mode_init(&modeSparseTest); + Mode_setPatternAt(&modeSparseTest, LED_3, testPatternID, &testPatternArgs, &testColorset); + Mode_setPatternAt(&modeSparseTest, LED_5, testPatternID, &testPatternArgs, &testColorset); + Mode_setPatternAt(&modeSparseTest, LED_7, testPatternID, &testPatternArgs, &testColorset); + + assert(Mode_hasSparseSingleLed(&modeSparseTest)); + INFO_LOG("Test hasSparseSingleLed with sparse single-led patterns passed.\n"); + + Mode modeNoSingleLedTest; + Mode_init(&modeNoSingleLedTest); + assert(Mode_getSingleLedMap(&modeNoSingleLedTest) == 0); + INFO_LOG("Test getSingleLedMap with no single-led patterns passed.\n"); + + Mode modeAllSingleLedTest; + Mode_init(&modeAllSingleLedTest); + Mode_setPattern(&modeAllSingleLedTest, testPatternID, LED_ANY, &testPatternArgs, &testColorset); + assert(Mode_getSingleLedMap(&modeAllSingleLedTest) == MAP_LED_ALL); + INFO_LOG("Test getSingleLedMap with all single-led patterns passed.\n"); + + assert(Mode_getSingleLedMap(&modeSparseTest) == ((LedMap)1 << LED_3 | (LedMap)1 << LED_5 | (LedMap)1 << LED_7)); + INFO_LOG("Test getSingleLedMap with sparse single-led patterns passed.\n"); + + Mode modeOnlyMultiTest; + Mode_init(&modeOnlyMultiTest); + Mode_setPattern(&modeOnlyMultiTest, multiPatternID, LED_ANY, &multiPatternArgs, &multiColorset); + assert(Mode_isMultiLed(&modeOnlyMultiTest)); + INFO_LOG("Test isMultiLed with only multi-led pattern passed.\n"); + + assert(!Mode_isMultiLed(&modeBothTest)); + INFO_LOG("Test isMultiLed with both single-led and multi-led patterns passed.\n"); + + Mode modeSetClearAtTest; + Mode_init(&modeSetClearAtTest); + Mode_setPatternAt(&modeSetClearAtTest, LED_5, testPatternID, &testPatternArgs, &testColorset); + Mode_clearPatternAt(&modeSetClearAtTest, LED_5); + + assert(Mode_getPatternIDAt(&modeSetClearAtTest, LED_5) == PATTERN_NONE); + assert(Mode_getColorsetAt(&modeSetClearAtTest, LED_5) == NULL); + INFO_LOG("Test setPatternAt and clearPatternAt for specific position passed.\n"); + + Mode modeSetClearColorsetAtTest; + Mode_init(&modeSetClearColorsetAtTest); + Mode_setPatternAt(&modeSetClearColorsetAtTest, LED_5, testPatternID, &testPatternArgs, &testColorset); + Mode_clearColorsetAt(&modeSetClearColorsetAtTest, LED_5); + + assert(Colorset_numColors(Mode_getColorsetAt(&modeSetClearColorsetAtTest, LED_5)) == 0); + INFO_LOG("Test setColorsetAt and clearColorsetAt for specific position passed.\n"); + + Mode modeMultiClearTest; + Mode_init(&modeMultiClearTest); + Mode_setPattern(&modeMultiClearTest, multiPatternID, LED_ANY, &multiPatternArgs, &multiColorset); + Mode_clearMultiColorset(&modeMultiClearTest); + + assert(Mode_getPatternID(&modeMultiClearTest, LED_ANY) == multiPatternID); + assert(Colorset_numColors(Mode_getColorsetConst(&modeMultiClearTest, LED_ANY)) == 0); + INFO_LOG("Test clearMultiColorset with multi-led pattern passed.\n"); + + PatternID invalidID = (PatternID)(PATTERN_COUNT + 1); + + Mode modeInvalidPatternIDTest; + Mode_init(&modeInvalidPatternIDTest); + bool success = Mode_setPattern(&modeInvalidPatternIDTest, invalidID, LED_ANY, &testPatternArgs, &testColorset); + assert(!success); + INFO_LOG("Test setPattern with invalid pattern ID passed.\n"); + + Mode modeInvalidPatternIDAtTest; + Mode_init(&modeInvalidPatternIDAtTest); + success = Mode_setPatternAt(&modeInvalidPatternIDAtTest, LED_3, invalidID, &testPatternArgs, &testColorset); + assert(!success); + INFO_LOG("Test setPatternAt with invalid pattern ID passed.\n"); + + Mode modeInvalidMultiPatternIDTest; + Mode_init(&modeInvalidMultiPatternIDTest); + success = Mode_setMultiPat(&modeInvalidMultiPatternIDTest, PATTERN_SINGLE_LAST, &testPatternArgs, &testColorset); + assert(!success); + INFO_LOG("Test setMultiPat with invalid pattern ID passed.\n"); + + LedPos invalidPos = (LedPos)(LED_COUNT + 1); + + Mode modeInvalidLEDPositionTest; + Mode_init(&modeInvalidLEDPositionTest); + success = Mode_setPatternAt(&modeInvalidLEDPositionTest, invalidPos, testPatternID, &testPatternArgs, &testColorset); + assert(success); + INFO_LOG("Test setPattern with invalid LED position passed.\n"); + + Mode modeInvalidLEDPositionSetColorsetTest; + Mode_init(&modeInvalidLEDPositionSetColorsetTest); + success = Mode_setColorsetAt(&modeInvalidLEDPositionSetColorsetTest, invalidPos, &testColorset); + assert(success); + INFO_LOG("Test setColorsetAt with invalid LED position passed.\n"); + + Mode modeInvalidLEDPositionClearPatternTest; + Mode_init(&modeInvalidLEDPositionClearPatternTest); + Mode_setPattern(&modeInvalidLEDPositionClearPatternTest, PATTERN_BLENDSTROBE, LED_ANY, NULL, NULL); + Mode_clearPatternAt(&modeInvalidLEDPositionClearPatternTest, invalidPos); + for (LedPos pos = LED_FIRST; pos < LED_COUNT; ++pos) { + assert(Mode_getPatternAt(&modeInvalidLEDPositionClearPatternTest, pos) != NULL); + } + INFO_LOG("Test clearPatternAt with invalid LED position passed.\n"); + + Mode modeInvalidLEDPositionClearColorsetTest; + Mode_init(&modeInvalidLEDPositionClearColorsetTest); + Mode_setPattern(&modeInvalidLEDPositionClearColorsetTest, PATTERN_BLEND, LED_ANY, NULL, NULL); + Mode_clearColorsetAt(&modeInvalidLEDPositionClearColorsetTest, invalidPos); + for (LedPos pos = LED_FIRST; pos < LED_COUNT; ++pos) { + assert(Mode_getColorsetAt(&modeInvalidLEDPositionClearColorsetTest, pos) != NULL); + } + INFO_LOG("Test clearColorsetAt with invalid LED position passed.\n"); + + Mode modePatternNoneTest; + Mode_init(&modePatternNoneTest); + Mode_setPatternAt(&modePatternNoneTest, LED_2, PATTERN_NONE, &testPatternArgs, &testColorset); + assert(Mode_getPatternIDAt(&modePatternNoneTest, LED_2) == PATTERN_NONE); + INFO_LOG("Test setPatternAt with PATTERN_NONE passed.\n"); + + Mode modeSetPatternNoneTest; + Mode_init(&modeSetPatternNoneTest); + Mode_setPattern(&modeSetPatternNoneTest, PATTERN_NONE, LED_ANY, &testPatternArgs, &testColorset); + for (LedPos pos = LED_FIRST; pos < LED_COUNT; ++pos) { + assert(Mode_getPatternIDAt(&modeSetPatternNoneTest, pos) == PATTERN_NONE); + } + INFO_LOG("Test setPattern with PATTERN_NONE passed.\n"); + + Mode modeSetPatternMultiLastTest; + Mode_init(&modeSetPatternMultiLastTest); + Mode_setPattern(&modeSetPatternMultiLastTest, PATTERN_MULTI_LAST, LED_ANY, &testPatternArgs, &testColorset); + assert(Mode_getPatternID(&modeSetPatternMultiLastTest, LED_ANY) == PATTERN_MULTI_LAST); + assert(Mode_getMultiPatID(&modeSetPatternMultiLastTest) == PATTERN_MULTI_LAST); + for (LedPos pos = LED_FIRST; pos < LED_COUNT; ++pos) { + assert(Mode_getPatternIDAt(&modeSetPatternNoneTest, pos) == PATTERN_NONE); + } + INFO_LOG("Test setPattern with PATTERN_MULTI_LAST passed.\n"); + + Mode modeSetMultiPatMultiFirstTest; + Mode_init(&modeSetMultiPatMultiFirstTest); + Mode_setMultiPat(&modeSetMultiPatMultiFirstTest, PATTERN_MULTI_FIRST, &testPatternArgs, &testColorset); + assert(Mode_getPatternID(&modeSetMultiPatMultiFirstTest, LED_ANY) == PATTERN_MULTI_FIRST); + INFO_LOG("Test setMultiPat with PATTERN_MULTI_FIRST passed.\n"); + + Mode modeSetMultiPatMultiLastTest; + Mode_init(&modeSetMultiPatMultiLastTest); + Mode_setMultiPat(&modeSetMultiPatMultiLastTest, PATTERN_MULTI_LAST, &testPatternArgs, &testColorset); + assert(Mode_getPatternID(&modeSetMultiPatMultiLastTest, LED_ANY) == PATTERN_MULTI_LAST); + INFO_LOG("Test setMultiPat with PATTERN_MULTI_LAST passed.\n"); + + Mode modeSetMultiPatSingleFirstTest; + Mode_init(&modeSetMultiPatSingleFirstTest); + bool setMultiPatSingleFirstSuccess = Mode_setMultiPat(&modeSetMultiPatSingleFirstTest, PATTERN_SINGLE_FIRST, &testPatternArgs, &testColorset); + assert(!setMultiPatSingleFirstSuccess); + INFO_LOG("Test setMultiPat with PATTERN_SINGLE_FIRST passed.\n"); + + Mode modeSetMultiPatSingleLastTest; + Mode_init(&modeSetMultiPatSingleLastTest); + bool setMultiPatSingleLastSuccess = Mode_setMultiPat(&modeSetMultiPatSingleLastTest, PATTERN_SINGLE_LAST, &testPatternArgs, &testColorset); + assert(!setMultiPatSingleLastSuccess); + INFO_LOG("Test setMultiPat with PATTERN_SINGLE_LAST passed.\n"); +} + +#endif diff --git a/VortexEngine/src/Modes/Mode.h b/VortexEngine/src/Modes/Mode.h index 3c0c574b6d..06779c4c97 100644 --- a/VortexEngine/src/Modes/Mode.h +++ b/VortexEngine/src/Modes/Mode.h @@ -1,164 +1,99 @@ #ifndef MODE_H #define MODE_H +#include +#include #include "../Leds/LedTypes.h" #include "../Patterns/Patterns.h" #include "../VortexConfig.h" #include "DefaultModes.h" -class SingleLedPattern; -class MultiLedPattern; -class PatternArgs; -class ByteStream; -class Colorset; -class Pattern; +typedef struct Pattern Pattern; +typedef struct PatternArgs_s PatternArgs; +typedef struct ByteStream ByteStream; +typedef struct Colorset_s Colorset; +typedef struct SingleLedPattern SingleLedPattern; +typedef struct MultiLedPattern MultiLedPattern; typedef uint8_t ModeFlags; -// Bitflags for saving modes #define MODE_FLAG_NONE 0 -// the mode is utilizing a multi-led pattern #define MODE_FLAG_MULTI_LED (1 << 0) -// the mode is utilizing at least one single-led pattern #define MODE_FLAG_SINGLE_LED (1 << 1) -// the mode is utilizing the same single-led pattern on each finger -// optimization will be done when saving and only one led is saved #define MODE_FLAG_ALL_SAME_SINGLE (1 << 2) -// the mode is missing leds on some single slots, an led map is -// present in savefile in order to convey which leds are missing #define MODE_FLAG_SPARSE_SINGLES (1 << 3) -// A mode is the container for instances of patterns. A pattern -// must be bound to a colorset and led position with bind() and -// the Mode class allows that to be done in one step. Other things -// can be done in the Mode class such as accelerometer reaction, -// microphone reaction, timed colorset or pattern switches, or any -// other kind of logic that would involve manipulating the pattern -// or colorset as a whole -class Mode -{ -public: +typedef struct Mode { +#if VORTEX_SLIM == 0 + Pattern *m_multiPat; +#endif #if FIXED_LED_COUNT == 0 - Mode(uint8_t numLeds); + uint8_t m_numLeds; + Pattern **m_singlePats; +#else + Pattern *m_singlePats[LED_COUNT]; #endif - Mode(); - - Mode(const DefaultModeEntry &entry); - Mode(PatternID id, const Colorset &set); - Mode(PatternID id, const PatternArgs &args, const Colorset &set); - Mode(PatternID id, const PatternArgs *args, const Colorset *set); - Mode(const Mode *other); - ~Mode(); - - // copy and assignment operators - Mode(const Mode &other); - void operator=(const Mode &other); - - // equality operators - bool operator==(const Mode &other) const; - bool operator!=(const Mode &other) const; - - // initialize the mode to initial state - void init(); - - // Play the mode - void play(); - - // save/load from a buffer for individual mode sharing - bool saveToBuffer(ByteStream &saveBuffer, uint8_t numLeds = 0) const; - bool loadFromBuffer(ByteStream &saveBuffer); - - // save the mode to serial - bool serialize(ByteStream &buffer, uint8_t numLeds = 0) const; - // load the mode from serial (optional led count) - bool unserialize(ByteStream &buffer); - - // mode comparison - bool equals(const Mode *other) const; - +} Mode; + +void Mode_init(Mode *self); +void Mode_initFromEntry(Mode *self, const struct DefaultModeEntry *entry); +void Mode_initFromID(Mode *self, PatternID id, const Colorset *set); +void Mode_initFromIDArgsSet(Mode *self, PatternID id, const PatternArgs *args, const Colorset *set); +void Mode_initFromIDArgsSetPtr(Mode *self, PatternID id, const PatternArgs *args, const Colorset *set); +void Mode_initFromOther(Mode *self, const Mode *other); +void Mode_initCopy(Mode *self, const Mode *other); +void Mode_cleanup(Mode *self); + +void Mode_assign(Mode *self, const Mode *other); +bool Mode_equals(const Mode *self, const Mode *other); +bool Mode_notEquals(const Mode *self, const Mode *other); + +void Mode_initMode(Mode *self); +void Mode_play(Mode *self); +bool Mode_saveToBuffer(const Mode *self, ByteStream *saveBuffer, uint8_t numLeds); +bool Mode_loadFromBuffer(Mode *self, ByteStream *saveBuffer); +bool Mode_serialize(const Mode *self, ByteStream *buffer, uint8_t numLeds); +bool Mode_unserialize(Mode *self, ByteStream *buffer); #if FIXED_LED_COUNT == 0 - // change the internal pattern count in the mode object - bool setLedCount(uint8_t numLeds); +bool Mode_setLedCount(Mode *self, uint8_t numLeds); #endif - uint8_t getLedCount() const; - - // get the pattern at a position - const Pattern *getPattern(LedPos pos = LED_ANY) const; - Pattern *getPattern(LedPos pos = LED_ANY); +uint8_t Mode_getLedCount(const Mode *self); - // get the colorset at a position - const Colorset getColorset(LedPos pos = LED_ANY) const; - Colorset getColorset(LedPos pos = LED_ANY); +const Pattern *Mode_getPatternConst(const Mode *self, LedPos pos); +Pattern *Mode_getPattern(Mode *self, LedPos pos); - // get the pattern ID of the given pattern - PatternID getPatternID(LedPos pos = LED_ANY) const; +Colorset Mode_getColorset(const Mode *self, LedPos pos); +Colorset Mode_getColorsetMut(Mode *self, LedPos pos); - // change the pattern on a mode (NOTE: you may need to call init() after!) - bool setPattern(PatternID pat, LedPos pos = LED_ANY, const PatternArgs *args = nullptr, const Colorset *set = nullptr); - bool setPatternMap(LedMap pos, PatternID pat, const PatternArgs *args = nullptr, const Colorset *set = nullptr); +PatternID Mode_getPatternID(const Mode *self, LedPos pos); - // copy a pattern from another mode ledpos into this mode - void copyPatternFrom(const Mode *other, LedPos to, LedPos from); +bool Mode_setPattern(Mode *self, PatternID pat, LedPos pos, const PatternArgs *args, const Colorset *set); +bool Mode_setPatternMap(Mode *self, LedMap map, PatternID pat, const PatternArgs *args, const Colorset *set); - // swap two patterns - void swapPatterns(LedPos a, LedPos b); +void Mode_copyPatternFrom(Mode *self, const Mode *other, LedPos to, LedPos from); +void Mode_swapPatterns(Mode *self, LedPos a, LedPos b); +bool Mode_setColorset(Mode *self, const Colorset *set, LedPos pos); +bool Mode_setColorsetMap(Mode *self, LedMap map, const Colorset *set); - // set colorset at a specific position - bool setColorset(const Colorset &set, LedPos pos = LED_ANY); - // set colorset at each position in a map - bool setColorsetMap(LedMap map, const Colorset &set); +void Mode_clearPattern(Mode *self, LedPos pos); +void Mode_clearPatternMap(Mode *self, LedMap map); +void Mode_clearColorset(Mode *self, LedPos pos); +void Mode_clearColorsetMap(Mode *self, LedMap map); - // clear stored patterns in various ways - void clearPattern(LedPos pos = LED_ALL); - void clearPatternMap(LedMap map); +void Mode_setArg(Mode *self, uint8_t index, uint8_t value, LedMap map); +uint8_t Mode_getArg(Mode *self, uint8_t index, LedPos pos); - // clear colorset in various ways - void clearColorset(LedPos pos = LED_ALL); - void clearColorsetMap(LedMap map); - - // set/get a single argument on various positions - void setArg(uint8_t index, uint8_t value, LedMap map = MAP_LED_ALL); - uint8_t getArg(uint8_t index, LedPos pos = LED_ANY); - - // get the flags associated with this mode - ModeFlags getFlags() const; - - // whether a multi-led pattern is present in the mode - bool hasMultiLed() const; - // whether at least one single-led pattern is present in the mode - bool hasSingleLed() const; - // whether this mode has all same single-led patterns - bool hasSameSingleLed() const; - // whether this mode has sparse single-led patterns (missing some slots) - bool hasSparseSingleLed() const; - - // whether the mode contains no patterns, or all patterns are PATTERN_NONE - bool isEmpty() const; - - // get the led map of which singles are set - LedMap getSingleLedMap() const; - - // whether this mode purely a multi-led pattern with no singles - bool isMultiLed() const; +ModeFlags Mode_getFlags(const Mode *self); +bool Mode_hasMultiLed(const Mode *self); +bool Mode_hasSingleLed(const Mode *self); +bool Mode_hasSameSingleLed(const Mode *self); +bool Mode_hasSparseSingleLed(const Mode *self); +bool Mode_isEmpty(const Mode *self); +LedMap Mode_getSingleLedMap(const Mode *self); +bool Mode_isMultiLed(const Mode *self); #if MODES_TEST == 1 - static void test(); -#endif - -private: -#if VORTEX_SLIM == 0 - // the multi-led pattern slot is only present in non-slim builds - Pattern *m_multiPat; -#endif - // a list of slots for each single led pattern -#if FIXED_LED_COUNT == 0 - // the number of leds the mode is targetting - uint8_t m_numLeds; - // list of pointers to Patterns, one for each led - Pattern **m_singlePats; -#else - Pattern *m_singlePats[LED_COUNT]; +void Mode_test(void); #endif -}; #endif diff --git a/VortexEngine/src/Modes/Modes.c b/VortexEngine/src/Modes/Modes.c new file mode 100644 index 0000000000..f213d106dd --- /dev/null +++ b/VortexEngine/src/Modes/Modes.c @@ -0,0 +1,1044 @@ +#include "Modes.h" +#include "DefaultModes.h" +#include "Mode.h" + +#include "../Patterns/Patterns.h" +#include "../Patterns/PatternArgs.h" +#include "../Patterns/Pattern.h" +#include "../Patterns/PatternBuilder.h" +#include "../Colors/Colorset.h" +#include "../Serial/ByteStream.h" +#include "../Time/TimeControl.h" +#include "../Storage/Storage.h" +#include "../Buttons/Buttons.h" +#include "../Buttons/Button.h" +#include "../Time/Timings.h" +#include "../Leds/Leds.h" +#include "../Log/Log.h" +#include "../Memory/Memory.h" + +// file-scope globals (replaces static class members) +static bool s_loaded = false; +static uint8_t s_curMode = 0; +static uint8_t s_numModes = 0; +static ModeLink *s_pCurModeLink = NULL; +static ModeLink *s_storedModes = NULL; +uint8_t Modes_globalFlags = 0; +static uint32_t s_lastSwitchTime = 0; + +bool Modes_init(void) +{ +#if MODES_TEST == 1 + Mode_test(); + Modes_test(); + return true; +#endif + ByteStream headerBuffer; + ByteStream_init(&headerBuffer, 0, NULL); + if (!Storage_read(0, &headerBuffer) || !Modes_unserializeSaveHeader(&headerBuffer)) { + Modes_globalFlags |= MODES_FLAG_NEW_FIRMWARE; + } + ByteStream_destroy(&headerBuffer); + s_loaded = false; +#ifdef VORTEX_LIB + Modes_globalFlags |= MODES_FLAG_ADV_MENUS; +#endif + return true; +} + +void Modes_cleanup(void) +{ + Modes_clearModes(); +} + +bool Modes_load(void) +{ + if (s_loaded) { + return true; + } + if (!Modes_loadStorage()) { + if (!Modes_setDefaults()) { + return false; + } + if (!Modes_saveStorage()) { + return false; + } + } + s_loaded = true; + return true; +} + +void Modes_play(void) +{ + if (!s_numModes) { + Leds_clearAll(); + return; + } + if (!s_pCurModeLink && !Modes_initCurMode(false)) { + DEBUG_LOG("Error failed to load any modes!"); + return; + } + if (Button_onShortClick(g_pButton)) { + if (Modes_oneClickModeEnabled()) { + extern void VortexEngine_enterSleep(bool save); + VortexEngine_enterSleep(false); + return; + } + Modes_nextMode(); + } + ModeLink_play(s_pCurModeLink); +} + +bool Modes_saveToBuffer(ByteStream *modesBuffer) +{ + (void)modesBuffer; + return true; +} + +bool Modes_loadFromBuffer(ByteStream *modesBuffer) +{ + if (!ByteStream_decompress(modesBuffer)) { + return false; + } + if (!Modes_unserializeSaveHeader(modesBuffer)) { + return false; + } + if (!Modes_unserialize(modesBuffer)) { + return false; + } + if (Modes_oneClickModeEnabled()) { + Modes_switchToStartupMode(); + } + return true; +} + +bool Modes_saveHeader(void) +{ + ByteStream headerBuffer; + ByteStream_init(&headerBuffer, MAX_MODE_SIZE, NULL); + if (!Modes_serializeSaveHeader(&headerBuffer)) { + ByteStream_destroy(&headerBuffer); + return false; + } + if (!Storage_write(0, &headerBuffer)) { + ByteStream_destroy(&headerBuffer); + return false; + } + ByteStream_destroy(&headerBuffer); + return true; +} + +bool Modes_loadHeader(void) +{ + ByteStream headerBuffer; + ByteStream_init(&headerBuffer, 0, NULL); + if (!Storage_read(0, &headerBuffer) || !ByteStream_size(&headerBuffer)) { + DEBUG_LOG("Empty buffer read from storage"); + ByteStream_destroy(&headerBuffer); + return false; + } + Modes_clearModes(); + if (!Modes_unserializeSaveHeader(&headerBuffer)) { + ByteStream_destroy(&headerBuffer); + return false; + } + ByteStream_destroy(&headerBuffer); + return true; +} + +bool Modes_saveStorage(void) +{ + DEBUG_LOG("Saving modes..."); + Modes_saveHeader(); + Modes_saveCurMode(); + if (s_pCurModeLink) { + ModeLink_uninstantiate(s_pCurModeLink); + } + uint8_t i = 0; + ModeLink *ptr = s_storedModes; + while (ptr && i < MAX_MODES) { + ByteStream modeBuffer; + ByteStream_init(&modeBuffer, MAX_MODE_SIZE, NULL); + Mode *mode = ModeLink_instantiate(ptr); + if (!mode) { + ERROR_OUT_OF_MEMORY(); + ByteStream_destroy(&modeBuffer); + return false; + } + if (!Mode_serialize(mode, &modeBuffer, 0)) { + ByteStream_destroy(&modeBuffer); + return false; + } + ModeLink_uninstantiate(ptr); + ptr = ModeLink_next(ptr); + if (!Storage_write(++i, &modeBuffer)) { + ByteStream_destroy(&modeBuffer); + return false; + } + ByteStream_destroy(&modeBuffer); + } + if (s_pCurModeLink && !ModeLink_instantiate(s_pCurModeLink)) { + return false; + } + DEBUG_LOGF("Serialized num modes: %u", s_numModes); + return true; +} + +bool Modes_loadStorage(void) +{ + ByteStream headerBuffer; + ByteStream_init(&headerBuffer, 0, NULL); + if (!Storage_read(0, &headerBuffer) || !ByteStream_size(&headerBuffer)) { + DEBUG_LOG("Empty buffer read from storage"); + ByteStream_destroy(&headerBuffer); + return false; + } + Modes_clearModes(); + if (!Modes_unserializeSaveHeader(&headerBuffer)) { + ByteStream_destroy(&headerBuffer); + return false; + } + uint8_t numModes = 0; + if (!ByteStream_unserialize8(&headerBuffer, &numModes)) { + ByteStream_destroy(&headerBuffer); + return false; + } + if (!numModes) { + DEBUG_LOG("Did not find any modes"); + ByteStream_destroy(&headerBuffer); + return false; + } + for (uint8_t i = 0; i < numModes; ++i) { + ByteStream modeBuffer; + ByteStream_init(&modeBuffer, MAX_MODE_SIZE, NULL); + if (!Storage_read(i + 1, &modeBuffer) || !Modes_addSerializedMode(&modeBuffer)) { + ByteStream_destroy(&modeBuffer); + ByteStream_destroy(&headerBuffer); + return false; + } + ByteStream_destroy(&modeBuffer); + } + if (Modes_oneClickModeEnabled()) { + Modes_switchToStartupMode(); + } + ByteStream_destroy(&headerBuffer); + return true; +} + +bool Modes_serializeSaveHeader(ByteStream *saveBuffer) +{ + extern bool VortexEngine_serializeVersion(ByteStream *); + if (!VortexEngine_serializeVersion(saveBuffer)) { + return false; + } + if (!ByteStream_serialize8(saveBuffer, Modes_globalFlags)) { + return false; + } + if (!ByteStream_serialize8(saveBuffer, (uint8_t)Leds_getBrightness())) { + return false; + } + if (!ByteStream_serialize8(saveBuffer, s_numModes)) { + return false; + } +#ifdef VORTEX_EMBEDDED + if (!ByteStream_serialize8(saveBuffer, (uint8_t)VORTEX_BUILD_NUMBER)) { + return false; + } +#endif + DEBUG_LOGF("Serialized all modes, uncompressed size: %u", ByteStream_size(saveBuffer)); + return true; +} + +bool Modes_unserializeSaveHeader(ByteStream *saveHeader) +{ + if (!ByteStream_decompress(saveHeader)) { + return false; + } + ByteStream_resetUnserializer(saveHeader); + uint8_t major = 0; + uint8_t minor = 0; + if (!ByteStream_unserialize8(saveHeader, &major)) { + return false; + } + if (!ByteStream_unserialize8(saveHeader, &minor)) { + return false; + } + extern bool VortexEngine_checkVersion(uint8_t major, uint8_t minor); + if (!VortexEngine_checkVersion(major, minor)) { + ERROR_LOGF("Incompatible savefile version: %u.%u", major, minor); + return false; + } + if (!ByteStream_unserialize8(saveHeader, &Modes_globalFlags)) { + return false; + } + uint8_t brightness = 0; + if (!ByteStream_unserialize8(saveHeader, &brightness)) { + return false; + } + if (brightness) { + Leds_setBrightness(brightness); + } + return true; +} + +bool Modes_serialize(ByteStream *modesBuffer) +{ + if (!ByteStream_serialize8(modesBuffer, s_numModes)) { + return false; + } + Modes_saveCurMode(); + if (s_pCurModeLink) { + ModeLink_uninstantiate(s_pCurModeLink); + } + ModeLink *ptr = s_storedModes; + while (ptr) { + Mode *mode = ModeLink_instantiate(ptr); + if (!mode) { + ERROR_OUT_OF_MEMORY(); + return false; + } + if (!Mode_serialize(mode, modesBuffer, 0)) { + return false; + } + ModeLink_uninstantiate(ptr); + ptr = ModeLink_next(ptr); + } + if (s_pCurModeLink && !ModeLink_instantiate(s_pCurModeLink)) { + return false; + } + DEBUG_LOGF("Serialized num modes: %u", s_numModes); + return true; +} + +bool Modes_unserialize(ByteStream *modesBuffer) +{ + DEBUG_LOG("Loading modes..."); + Modes_clearModes(); + uint8_t numModes = 0; + if (!ByteStream_unserialize8(modesBuffer, &numModes)) { + return false; + } + if (!numModes) { + DEBUG_LOG("Did not find any modes"); + return false; + } + for (uint8_t i = 0; i < numModes; ++i) { + if (!Modes_addSerializedMode(modesBuffer)) { + DEBUG_LOGF("Failed to add mode %u after unserialization", i); + Modes_clearModes(); + return false; + } + } + DEBUG_LOGF("Loaded %u modes from storage (%u bytes)", numModes, ByteStream_size(modesBuffer)); + return (s_numModes == numModes); +} + +bool Modes_setDefaults(void) +{ + Modes_clearModes(); + for (uint8_t i = 0; i < MAX_MODES; ++i) { + Mode defMode; + Mode_initFromEntry(&defMode, &defaultModes[i]); + if (!Modes_addModeMode(&defMode)) { + ERROR_LOGF("Failed to add default mode %u", i); + return false; + } + } + return true; +} + +bool Modes_addSerializedMode(ByteStream *serializedMode) +{ +#if MAX_MODES != 0 + if (s_numModes >= MAX_MODES) { + return false; + } +#endif + Mode tmpMode; + Mode_init(&tmpMode); + if (!Mode_unserialize(&tmpMode, serializedMode)) { + return false; + } + Mode_initMode(&tmpMode); + return Modes_addModeMode(&tmpMode); +} + +bool Modes_addModeFromBuffer(ByteStream *serializedMode) +{ +#if MAX_MODES != 0 + if (s_numModes >= MAX_MODES) { + return false; + } +#endif + if (!s_storedModes) { + s_storedModes = (ModeLink *)vmalloc(sizeof(ModeLink)); + if (!s_storedModes) { + ERROR_OUT_OF_MEMORY(); + return false; + } + ModeLink_initFromStream(s_storedModes, serializedMode, false); + } else { + if (!ModeLink_appendStream(s_storedModes, serializedMode)) { + ERROR_OUT_OF_MEMORY(); + return false; + } + } + s_numModes++; + return true; +} + +bool Modes_shiftCurMode(int32_t offset) +{ + uint32_t newPos = (uint32_t)((int32_t)s_curMode + offset); + if (newPos >= s_numModes) { + return false; + } + if (newPos == s_curMode) { + return true; + } + ModeLink *target = Modes_getModeLink(newPos); + if (!target) { + return false; + } + if (!s_curMode && offset > 0) { + s_storedModes = ModeLink_next(s_storedModes); + } + ModeLink_unlinkSelf(s_pCurModeLink); + s_curMode = (uint8_t)newPos; + if (offset < 0) { + ModeLink_linkBefore(target, s_pCurModeLink); + if (!s_curMode) { + s_storedModes = s_pCurModeLink; + } + } else { + ModeLink_linkAfter(target, s_pCurModeLink); + } + return true; +} + +bool Modes_addMode(PatternID id, RGBColor c1, RGBColor c2, RGBColor c3, + RGBColor c4, RGBColor c5, RGBColor c6, RGBColor c7, RGBColor c8) +{ + Colorset set; + Colorset_initColors(&set, c1, c2, c3, c4, c5, c6, c7, c8); + return Modes_addModeArgsSet(id, NULL, &set); +} + +bool Modes_addModeArgsSet(PatternID id, const PatternArgs *args, const Colorset *set) +{ +#if MAX_MODES != 0 + if (s_numModes >= MAX_MODES) { + return false; + } +#endif + if (id >= PATTERN_COUNT) { + return false; + } + Mode tmpMode; + Mode_initFromIDArgsSetPtr(&tmpMode, id, args, set); + Mode_initMode(&tmpMode); + return Modes_addModeMode(&tmpMode); +} + +bool Modes_addModeMode(const Mode *mode) +{ +#if MAX_MODES != 0 + if (s_numModes >= MAX_MODES) { + return false; + } +#endif + if (!s_storedModes) { + s_storedModes = (ModeLink *)vmalloc(sizeof(ModeLink)); + if (!s_storedModes) { + ERROR_OUT_OF_MEMORY(); + return false; + } + ModeLink_init(s_storedModes, mode, false); + } else { + if (!ModeLink_appendMode(s_storedModes, mode)) { + ERROR_OUT_OF_MEMORY(); + return false; + } + } + s_numModes++; + return true; +} + +bool Modes_updateCurMode(const Mode *mode) +{ + if (!mode) { + return false; + } + Mode *pCur = Modes_curMode(); + if (!pCur) { + return false; + } + Mode_assign(pCur, mode); + if (!Modes_saveCurMode()) { + return false; + } + return Modes_initCurMode(false) != NULL; +} + +Mode *Modes_setCurMode(uint8_t index) +{ + if (!s_numModes) { + return NULL; + } + Leds_clearAll(); + if (s_pCurModeLink) { + ModeLink_uninstantiate(s_pCurModeLink); + } + int8_t newModeIdx = index % s_numModes; + ModeLink *newCurLink = Modes_getModeLink((uint32_t)newModeIdx); + if (!newCurLink) { + return NULL; + } + Mode *newCur = ModeLink_instantiate(newCurLink); + if (!newCur) { + ERROR_OUT_OF_MEMORY(); + return NULL; + } + s_curMode = (uint8_t)newModeIdx; + s_pCurModeLink = newCurLink; + s_lastSwitchTime = Time_getCurtime(); + Modes_setStartupMode((uint8_t)newModeIdx); + DEBUG_LOGF("Switch to Mode: %u / %u (pattern id: %u)", + s_curMode, s_numModes - 1, Pattern_getPatternID(Mode_getPatternConst(newCur, LED_ANY))); + return newCur; +} + +Mode *Modes_curMode(void) +{ + if (!s_numModes) { + return NULL; + } + if (!s_pCurModeLink) { + if (!Modes_initCurMode(false)) { + ERROR_LOG("Failed to initialize current mode"); + return NULL; + } + } + return ModeLink_instantiate(s_pCurModeLink); +} + +Mode *Modes_nextMode(void) +{ + if (!s_numModes) { + return NULL; + } + return Modes_setCurMode(s_curMode + 1); +} + +Mode *Modes_previousMode(void) +{ + if (!s_numModes) { + return NULL; + } + if (!s_curMode) { + return Modes_setCurMode(s_numModes - 1); + } + return Modes_setCurMode(s_curMode - 1); +} + +Mode *Modes_nextModeSkipEmpty(void) +{ + do { + if (Modes_setCurMode(s_curMode + 1) && !Mode_isEmpty(Modes_curMode())) { + break; + } + } while (s_curMode != 0); + return Modes_curMode(); +} + +void Modes_deleteCurMode(void) +{ + if (!s_numModes || !s_pCurModeLink) { + return; + } + ModeLink *newCur = ModeLink_unlinkSelf(s_pCurModeLink); + ModeLink_cleanup(s_pCurModeLink); + vfree(s_pCurModeLink); + s_pCurModeLink = newCur; + if (s_curMode) { + s_curMode--; + } else { + s_storedModes = s_pCurModeLink; + } + s_numModes--; + if (!s_numModes) { + s_storedModes = NULL; + } +} + +void Modes_clearModes(void) +{ + if (!s_numModes || !s_storedModes) { + return; + } + ModeLink_cleanup(s_storedModes); + vfree(s_storedModes); + s_pCurModeLink = NULL; + s_storedModes = NULL; + s_numModes = 0; + Leds_clearAll(); +} + +void Modes_setStartupMode(uint8_t index) +{ + Modes_globalFlags &= 0x0F; + Modes_globalFlags |= (index << 4) & 0xF0; +} + +uint8_t Modes_startupMode(void) +{ + return (Modes_globalFlags & 0xF0) >> 4; +} + +Mode *Modes_switchToStartupMode(void) +{ + return Modes_setCurMode(Modes_startupMode()); +} + +bool Modes_setFlag(uint8_t flag, bool enable, bool save) +{ + if (enable) { + Modes_globalFlags |= flag; + } else { + Modes_globalFlags &= ~flag; + } + DEBUG_LOGF("Toggled instant on/off to %s", enable ? "on" : "off"); + if (!save) { + return true; + } + ByteStream headerBuffer; + ByteStream_init(&headerBuffer, 0, NULL); + if (!Storage_read(0, &headerBuffer) || !ByteStream_size(&headerBuffer)) { + ByteStream_destroy(&headerBuffer); + return Modes_saveHeader(); + } + typedef struct { + uint8_t vMajor; + uint8_t vMinor; + uint8_t globalFlags; + uint8_t brightness; + uint8_t numModes; + } SaveHeader; + SaveHeader *pHeader = (SaveHeader *)ByteStream_data(&headerBuffer); + pHeader->globalFlags = Modes_globalFlags; + ByteStream_setCRCDirty(&headerBuffer); + bool result = Storage_write(0, &headerBuffer); + ByteStream_destroy(&headerBuffer); + return result; +} + +bool Modes_getFlag(uint8_t flag) +{ + return ((Modes_globalFlags & flag) == flag); +} + +void Modes_resetFlags(void) +{ + Modes_globalFlags = 0; +} + +bool Modes_setOneClickMode(bool enable, bool save) +{ + return Modes_setFlag(MODES_FLAG_ONE_CLICK, enable, save); +} + +bool Modes_oneClickModeEnabled(void) +{ + return Modes_getFlag(MODES_FLAG_ONE_CLICK); +} + +bool Modes_setLocked(bool locked, bool save) +{ + return Modes_setFlag(MODES_FLAG_LOCKED, locked, save); +} + +bool Modes_locked(void) +{ + return Modes_getFlag(MODES_FLAG_LOCKED); +} + +bool Modes_setAdvancedMenus(bool active, bool save) +{ + return Modes_setFlag(MODES_FLAG_ADV_MENUS, active, save); +} + +bool Modes_advancedMenusEnabled(void) +{ + return Modes_getFlag(MODES_FLAG_ADV_MENUS); +} + +bool Modes_setKeychainMode(bool active, bool save) +{ + return Modes_setFlag(MODES_FLAG_KEYCHAIN, active, save); +} + +bool Modes_keychainModeEnabled(void) +{ + return Modes_getFlag(MODES_FLAG_KEYCHAIN); +} + +uint8_t Modes_numModes(void) { return s_numModes; } +uint8_t Modes_curModeIndex(void) { return s_curMode; } +uint32_t Modes_lastSwitchTime(void) { return s_lastSwitchTime; } + +#ifdef VORTEX_LIB +uint32_t Modes_maxModeSize(void) +{ + Mode maxMode; + Mode_init(&maxMode); + uint8_t x = 0; + for (LedPos p = LED_FIRST; p < LED_COUNT; ++p) { + PatternArgs maxArgs; + PatternArgs_init8(&maxArgs, + (uint8_t)p + 0xd2, (uint8_t)p + 0xd3, (uint8_t)p + 0xd4, (uint8_t)p + 0xd5, + (uint8_t)p + 0xd6, (uint8_t)p + 0xd7, (uint8_t)p + 0xd8, (uint8_t)p + 0xd9); + Colorset maxSet; + Colorset_init(&maxSet); + for (uint8_t i = 0; i < MAX_COLOR_SLOTS; ++i) { + RGBColor col; + col.red = ++x; + col.green = ++x; + col.blue = ++x; + Colorset_addColor(&maxSet, col); + } + Mode_setPattern(&maxMode, PATTERN_BLEND, p, &maxArgs, &maxSet); + } + ByteStream stream; + ByteStream_init(&stream, 0, NULL); + Mode_saveToBuffer(&maxMode, &stream, 0); + uint32_t size = ByteStream_size(&stream); + ByteStream_destroy(&stream); + return size; +} + +uint32_t Modes_maxSaveSize(void) +{ +#if MAX_MODES == 0 + return 0; +#else + ByteStream backupModes; + ByteStream_init(&backupModes, 0, NULL); + Modes_saveToBuffer(&backupModes); + for (uint32_t i = 0; i < MAX_MODES; ++i) { + Mode maxMode; + Mode_init(&maxMode); + for (LedPos p = LED_FIRST; p < LED_COUNT; ++p) { + PatternArgs maxArgs; + PatternArgs_init8(&maxArgs, + (uint8_t)p + 2, (uint8_t)p + 3, (uint8_t)p + 4, (uint8_t)p + 5, + (uint8_t)p + 6, (uint8_t)p + 7, (uint8_t)p + 8, (uint8_t)p + 9); + Colorset maxSet; + Colorset_init(&maxSet); + for (uint32_t j = 0; j < MAX_COLOR_SLOTS; ++j) { + RGBColor col; + col.red = (uint8_t)p + (j * 3); + col.green = (uint8_t)p + (j * 3) + 1; + col.blue = (uint8_t)p + (j * 3) + 2; + Colorset_addColor(&maxSet, col); + } + Mode_setPattern(&maxMode, PATTERN_BLEND, p, &maxArgs, &maxSet); + } + Modes_addModeMode(&maxMode); + } + ByteStream stream; + ByteStream_init(&stream, 0, NULL); + Modes_saveToBuffer(&stream); + uint32_t size = ByteStream_size(&stream); + Modes_loadFromBuffer(&backupModes); + ByteStream_destroy(&stream); + ByteStream_destroy(&backupModes); + return size; +#endif +} + +uint8_t Modes_getGlobalFlags(void) +{ + return Modes_globalFlags; +} +#endif + +ModeLink *Modes_getModeLink(uint32_t index) +{ + if (index >= s_numModes) { + return NULL; + } + ModeLink *ptr = s_storedModes; + while (index > 0 && ptr) { + ptr = ModeLink_next(ptr); + index--; + } + return ptr; +} + +Mode *Modes_initCurMode(bool force) +{ + if (!s_numModes) { + return NULL; + } + if (s_pCurModeLink) { + ModeLink_uninstantiate(s_pCurModeLink); + } + s_pCurModeLink = Modes_getModeLink(s_curMode); + if (!s_pCurModeLink) { + return NULL; + } + if (force) { + ModeLink_uninstantiate(s_pCurModeLink); + } + return ModeLink_instantiate(s_pCurModeLink); +} + +bool Modes_saveCurMode(void) +{ + if (!s_pCurModeLink) { + return false; + } + return ModeLink_save(s_pCurModeLink); +} + +// ModeLink implementation +void ModeLink_init(ModeLink *self, const Mode *src, bool inst) +{ + self->m_pInstantiatedMode = NULL; + ByteStream_init(&self->m_storedMode, 0, NULL); + self->m_next = NULL; + self->m_prev = NULL; + if (src) { + ModeLink_initMode(self, src); + } + if (src && inst) { + ModeLink_instantiate(self); + } +} + +void ModeLink_initFromStream(ModeLink *self, const ByteStream *src, bool inst) +{ + self->m_pInstantiatedMode = NULL; + ByteStream_copy(&self->m_storedMode, src); + self->m_next = NULL; + self->m_prev = NULL; + if (ByteStream_size(&self->m_storedMode) && inst) { + ModeLink_instantiate(self); + } +} + +void ModeLink_cleanup(ModeLink *self) +{ + if (self->m_next) { + ModeLink_cleanup(self->m_next); + vfree(self->m_next); + } + if (self->m_pInstantiatedMode) { + Mode_cleanup(self->m_pInstantiatedMode); + vfree(self->m_pInstantiatedMode); + } + ByteStream_destroy(&self->m_storedMode); +} + +bool ModeLink_initMode(ModeLink *self, const Mode *mode) +{ + if (!mode) { + return false; + } + ByteStream_clear(&self->m_storedMode); + return Mode_saveToBuffer(mode, &self->m_storedMode, 0); +} + +bool ModeLink_appendMode(ModeLink *self, const Mode *next) +{ + if (!next) { + return false; + } + if (self->m_next) { + return ModeLink_appendMode(self->m_next, next); + } + self->m_next = (ModeLink *)vmalloc(sizeof(ModeLink)); + if (!self->m_next) { + ERROR_OUT_OF_MEMORY(); + return false; + } + ModeLink_init(self->m_next, next, false); + self->m_next->m_prev = self; + return true; +} + +bool ModeLink_appendStream(ModeLink *self, const ByteStream *next) +{ + if (!ByteStream_size(next)) { + return false; + } + if (self->m_next) { + return ModeLink_appendStream(self->m_next, next); + } + self->m_next = (ModeLink *)vmalloc(sizeof(ModeLink)); + if (!self->m_next) { + ERROR_OUT_OF_MEMORY(); + return false; + } + ModeLink_initFromStream(self->m_next, next, false); + self->m_next->m_prev = self; + return true; +} + +void ModeLink_play(ModeLink *self) +{ + if (!self->m_pInstantiatedMode) { + return; + } + Mode_play(self->m_pInstantiatedMode); +} + +ModeLink *ModeLink_unlinkSelf(ModeLink *self) +{ + if (self->m_prev) { + self->m_prev->m_next = self->m_next; + } + if (self->m_next) { + self->m_next->m_prev = self->m_prev; + } + ModeLink *newLink = self->m_prev ? self->m_prev : self->m_next; + self->m_prev = NULL; + self->m_next = NULL; + return newLink; +} + +void ModeLink_linkAfter(ModeLink *self, ModeLink *link) +{ + if (!link) { + return; + } + if (self->m_next) { + self->m_next->m_prev = link; + link->m_next = self->m_next; + } + self->m_next = link; + link->m_prev = self; +} + +void ModeLink_linkBefore(ModeLink *self, ModeLink *link) +{ + if (!link) { + return; + } + if (self->m_prev) { + self->m_prev->m_next = link; + link->m_prev = self->m_prev; + } + self->m_prev = link; + link->m_next = self; +} + +Mode *ModeLink_instantiate(ModeLink *self) +{ + if (self->m_pInstantiatedMode) { + return self->m_pInstantiatedMode; + } + Mode *newMode = (Mode *)vmalloc(sizeof(Mode)); + if (!newMode) { + ERROR_OUT_OF_MEMORY(); + return NULL; + } + Mode_init(newMode); + ByteStream_resetUnserializer(&self->m_storedMode); + if (!Mode_loadFromBuffer(newMode, &self->m_storedMode)) { + vfree(newMode); + return NULL; + } + self->m_pInstantiatedMode = newMode; + return self->m_pInstantiatedMode; +} + +void ModeLink_uninstantiate(ModeLink *self) +{ + if (self->m_pInstantiatedMode) { + Mode_cleanup(self->m_pInstantiatedMode); + vfree(self->m_pInstantiatedMode); + self->m_pInstantiatedMode = NULL; + } +} + +bool ModeLink_save(ModeLink *self) +{ + if (!self->m_pInstantiatedMode) { + return false; + } + ByteStream_clear(&self->m_storedMode); + return Mode_saveToBuffer(self->m_pInstantiatedMode, &self->m_storedMode, 0); +} + +ByteStream *ModeLink_buffer(ModeLink *self) { return &self->m_storedMode; } +Mode *ModeLink_mode(ModeLink *self) { return self->m_pInstantiatedMode; } +ModeLink *ModeLink_next(ModeLink *self) { return self->m_next; } +ModeLink *ModeLink_prev(ModeLink *self) { return self->m_prev; } + +#if MODES_TEST == 1 +#include +#include + +void Modes_test(void) +{ + INFO_LOG("== Beginning Modes Test ==\n"); + + RGBColor col = RGB_RED; + assert(!Modes_addMode(PATTERN_COUNT, col, RGB_OFF, RGB_OFF, RGB_OFF, RGB_OFF, RGB_OFF, RGB_OFF, RGB_OFF)); + Modes_clearModes(); + for (PatternID pat = PATTERN_FIRST; pat < PATTERN_COUNT; ++pat) { + assert(Modes_addMode(pat, col, RGB_OFF, RGB_OFF, RGB_OFF, RGB_OFF, RGB_OFF, RGB_OFF, RGB_OFF)); + } + assert(Modes_numModes() == PATTERN_COUNT); + Modes_clearModes(); + assert(Modes_numModes() == 0); + + Colorset set; + Colorset_initColors(&set, (RGBColor){255, 0, 0}, (RGBColor){0, 255, 0}, (RGBColor){0, 0, 255}); + assert(!Modes_addModeArgsSet(PATTERN_COUNT, NULL, &set)); + for (PatternID pat = PATTERN_FIRST; pat < PATTERN_COUNT; ++pat) { + assert(Modes_addModeArgsSet(pat, NULL, &set)); + } + assert(Modes_numModes() == PATTERN_COUNT); + Modes_clearModes(); + assert(Modes_numModes() == 0); + + Colorset set2; + Colorset_initColors(&set2, (RGBColor){255, 0, 0}, (RGBColor){0, 255, 0}, (RGBColor){0, 0, 255}); + for (PatternID pat = PATTERN_FIRST; pat < PATTERN_COUNT; ++pat) { + Mode tmpMode; + Mode_initFromIDArgsSetPtr(&tmpMode, pat, NULL, &set2); + assert(Modes_addModeMode(&tmpMode)); + } + assert(Modes_numModes() == PATTERN_COUNT); + Modes_clearModes(); + assert(Modes_numModes() == 0); + + INFO_LOG("addMode(): success\n"); + + ByteStream modebuf; + ByteStream_init(&modebuf, 0, NULL); + ByteStream modesave; + ByteStream_init(&modesave, 0, NULL); + PatternArgs args = PatternBuilder_getDefaultArgs(PATTERN_BASIC); + Mode tmpMode; + Mode_initFromIDArgsSet(&tmpMode, PATTERN_BASIC, &args, &set); + Mode_serialize(&tmpMode, &modebuf, 0); + Mode_saveToBuffer(&tmpMode, &modesave, 0); + assert(Modes_addSerializedMode(&modebuf)); + assert(Modes_numModes() == 1); + assert(Modes_addModeFromBuffer(&modesave)); + assert(Modes_numModes() == 2); + assert(Modes_getModeLink(0) != NULL); + Mode *mode1 = ModeLink_instantiate(Modes_getModeLink(0)); + assert(mode1 != NULL); + Mode *mode2 = ModeLink_instantiate(Modes_getModeLink(1)); + assert(mode2 != NULL); + assert(Mode_equals(mode1, mode2)); + + INFO_LOG("addSerializedMode(): success\n"); + + INFO_LOG("== Success Running Modes Test ==\n"); +} +#endif diff --git a/VortexEngine/src/Modes/Modes.h b/VortexEngine/src/Modes/Modes.h index 4e415a49b5..81c706c6fc 100644 --- a/VortexEngine/src/Modes/Modes.h +++ b/VortexEngine/src/Modes/Modes.h @@ -1,261 +1,125 @@ #ifndef MODES_H #define MODES_H +#include +#include #include "../Serial/ByteStream.h" #include "../Colors/ColorTypes.h" #include "../Patterns/Patterns.h" #include "../Leds/Leds.h" - #include "../VortexConfig.h" -#include - -class PatternArgs; -class Colorset; -class Mode; +typedef struct PatternArgs_s PatternArgs; +typedef struct Colorset_s Colorset; +typedef struct Mode Mode; -// the device is locked and needs to be unlocked to turn on #define MODES_FLAG_LOCKED (1 << 0) -// the device will turn on and off with a single click #define MODES_FLAG_ONE_CLICK (1 << 1) -// toggles the advanced menus being accessible #define MODES_FLAG_ADV_MENUS (1 << 2) -// keychain light mode (behave like a keychain) #define MODES_FLAG_KEYCHAIN (1 << 3) -// WARNING!! The upper 4 bits of the flags are taken by the startup mode id, -// you can only use 4 global flags! - -// This is a new special feature used by the Chromadeck when it flashes a new -// firmware to the Duo it will set the global flags to 0xF0 like this: -// -// 0 = button lock enabled -// 0 = one click mode enabled -// 0 = advanced menus enabled -// 0 = keychain mode enabled -// 1111 = Startup Mode Index 15 (impossible) -// -// If this flag is present then the Duo needs to turn on and write out it's -// save header because a new firmware has been flashed and the save header -// will still have the old version number saved inside #define MODES_FLAG_NEW_FIRMWARE 0xF0 -class Modes -{ - // private unimplemented constructor - Modes(); - -public: - // opting for static class here because there should only ever be one - // Modes control object and I don't like singletons - static bool init(); - static void cleanup(); - - // load modes so they are ready to play - static bool load(); - - // play the current mode - static void play(); - - // full save/load to/from buffer - static bool saveToBuffer(ByteStream &saveBuffer); - static bool loadFromBuffer(ByteStream &saveBuffer); - - // save/load the global settings to/from storage - static bool saveHeader(); - static bool loadHeader(); - - // full save/load to/from storage - static bool saveStorage(); - static bool loadStorage(); - - // save load the savefile header from storage - static bool serializeSaveHeader(ByteStream &saveBuffer); - static bool unserializeSaveHeader(ByteStream &saveBuffer); - - // saves all modes to a buffer - static bool serialize(ByteStream &buffer); - // load all modes from a buffer, optionally specify number of leds - static bool unserialize(ByteStream &buffer); - - // set default settings (must save after) - static bool setDefaults(); - - // shift the current mode to a different position relative to current position - // negative values for up, positive values for down, 0 for no move - static bool shiftCurMode(int32_t offset = 1); - - // add a new mode in various different ways - static bool addMode(PatternID id, RGBColor c1, RGBColor c2 = RGB_OFF, - RGBColor c3 = RGB_OFF, RGBColor c4 = RGB_OFF, RGBColor c5 = RGB_OFF, - RGBColor c6 = RGB_OFF, RGBColor c7 = RGB_OFF, RGBColor c8 = RGB_OFF); - static bool addMode(PatternID id, const PatternArgs *args, const Colorset *set); - static bool addMode(const Mode *mode); - - // add a new mode by unserializing raw - static bool addSerializedMode(ByteStream &serializedMode); - // add a new mode by loading from a save buffer - static bool addModeFromBuffer(ByteStream &serializedMode); - - // update the current mode to match the given mode, optionally save - static bool updateCurMode(const Mode *mode); - - // set the current active mode by index - static Mode *setCurMode(uint8_t index); - - // get the current mode - static Mode *curMode(); - // iterate to next mode and return it - static Mode *nextMode(); - // iterate to previous mode and return it - static Mode *previousMode(); - - // iterate to the next mode but skip empty modes, will not skip mode 0 - // to prevent possibly skipping all modes and ending in an infinite loop - static Mode *nextModeSkipEmpty(); - - // the number of modes - static uint8_t numModes() { return m_numModes; } - static uint8_t curModeIndex() { return m_curMode; } - - // the last time the modes switched to a new mode - static uint32_t lastSwitchTime() { return m_lastSwitchTime; } - - // delete the current mode - static void deleteCurMode(); - - // delete all modes in the list - static void clearModes(); - - // set the startup mode index (which mode will be displayed on startup) - static void setStartupMode(uint8_t index); - static uint8_t startupMode(); - static Mode *switchToStartupMode(); - - // set or get flags - static bool setFlag(uint8_t flag, bool enable, bool save = true); - static bool getFlag(uint8_t flag) { return ((m_globalFlags & flag) == flag); } - - // reset flags to factory default (must save after) - static void resetFlags() { m_globalFlags = 0; } - - // inline functions to toggle the various flags - static bool setOneClickMode(bool enable, bool save = true) { - return setFlag(MODES_FLAG_ONE_CLICK, enable, save); - } - static bool oneClickModeEnabled() { - return getFlag(MODES_FLAG_ONE_CLICK); - } - // toggle the locked state - static bool setLocked(bool locked, bool save = true) { - return setFlag(MODES_FLAG_LOCKED, locked, save); - } - static bool locked() { - return getFlag(MODES_FLAG_LOCKED); - } - // toggle advanced menus - static bool setAdvancedMenus(bool active, bool save = true) { - return setFlag(MODES_FLAG_ADV_MENUS, active, save); - } - static bool advancedMenusEnabled() { - return getFlag(MODES_FLAG_ADV_MENUS); - } - // toggle the keychain light mode - static bool setKeychainMode(bool active, bool save = true) { - return setFlag(MODES_FLAG_KEYCHAIN, active, save); - } - static bool keychainModeEnabled() { - return getFlag(MODES_FLAG_KEYCHAIN); - } +typedef struct ModeLink { + Mode *m_pInstantiatedMode; + ByteStream m_storedMode; + struct ModeLink *m_next; + struct ModeLink *m_prev; +} ModeLink; + +void ModeLink_init(ModeLink *self, const Mode *src, bool inst); +void ModeLink_initFromStream(ModeLink *self, const ByteStream *src, bool inst); +void ModeLink_cleanup(ModeLink *self); + +bool ModeLink_initMode(ModeLink *self, const Mode *mode); +bool ModeLink_appendMode(ModeLink *self, const Mode *next); +bool ModeLink_appendStream(ModeLink *self, const ByteStream *next); + +void ModeLink_play(ModeLink *self); +ModeLink *ModeLink_unlinkSelf(ModeLink *self); +void ModeLink_linkAfter(ModeLink *self, ModeLink *link); +void ModeLink_linkBefore(ModeLink *self, ModeLink *link); + +Mode *ModeLink_instantiate(ModeLink *self); +void ModeLink_uninstantiate(ModeLink *self); +bool ModeLink_save(ModeLink *self); + +ByteStream *ModeLink_buffer(ModeLink *self); +Mode *ModeLink_mode(ModeLink *self); +ModeLink *ModeLink_next(ModeLink *self); +ModeLink *ModeLink_prev(ModeLink *self); + +// Modes module-level functions +bool Modes_init(void); +void Modes_cleanup(void); +bool Modes_load(void); +void Modes_play(void); + +bool Modes_serialize(ByteStream *modesBuffer); +bool Modes_unserialize(ByteStream *modesBuffer); +bool Modes_saveToBuffer(ByteStream *saveBuffer); +bool Modes_loadFromBuffer(ByteStream *saveBuffer); +bool Modes_saveHeader(void); +bool Modes_loadHeader(void); +bool Modes_saveStorage(void); +bool Modes_loadStorage(void); + +bool Modes_serializeSaveHeader(ByteStream *saveBuffer); +bool Modes_unserializeSaveHeader(ByteStream *saveHeader); + +bool Modes_setDefaults(void); +bool Modes_shiftCurMode(int32_t offset); +bool Modes_addMode(PatternID id, RGBColor c1, RGBColor c2, RGBColor c3, RGBColor c4, RGBColor c5, RGBColor c6, RGBColor c7, RGBColor c8); +bool Modes_addModeArgsSet(PatternID id, const PatternArgs *args, const Colorset *set); +bool Modes_addModeMode(const Mode *mode); +bool Modes_addSerializedMode(ByteStream *serializedMode); +bool Modes_addModeFromBuffer(ByteStream *serializedMode); +bool Modes_updateCurMode(const Mode *mode); +Mode *Modes_setCurMode(uint8_t index); +Mode *Modes_curMode(void); +Mode *Modes_nextMode(void); +Mode *Modes_previousMode(void); +Mode *Modes_nextModeSkipEmpty(void); + +uint8_t Modes_numModes(void); +uint8_t Modes_curModeIndex(void); +uint32_t Modes_lastSwitchTime(void); + +void Modes_deleteCurMode(void); +void Modes_clearModes(void); + +void Modes_setStartupMode(uint8_t index); +uint8_t Modes_startupMode(void); +Mode *Modes_switchToStartupMode(void); + +bool Modes_setFlag(uint8_t flag, bool enable, bool save); +bool Modes_getFlag(uint8_t flag); +void Modes_resetFlags(void); + +bool Modes_setOneClickMode(bool enable, bool save); +bool Modes_oneClickModeEnabled(void); +bool Modes_setLocked(bool locked, bool save); +bool Modes_locked(void); +bool Modes_setAdvancedMenus(bool active, bool save); +bool Modes_advancedMenusEnabled(void); +bool Modes_setKeychainMode(bool active, bool save); +bool Modes_keychainModeEnabled(void); #if MODES_TEST == 1 - static void test(); +void Modes_test(void); #endif #ifdef VORTEX_LIB - // get the maximum size a mode can occupy - static uint32_t maxModeSize(); - // get the maximum size a savefile can occupy - static uint32_t maxSaveSize(); - // global flags value - static uint8_t globalFlags(); +uint32_t Modes_maxModeSize(void); +uint32_t Modes_maxSaveSize(void); +uint8_t Modes_getGlobalFlags(void); #endif -private: - // linked list of internal mode storage - class ModeLink { - public: - // construct a link and optionally instantiate the link - ModeLink(const Mode *src = nullptr, bool inst = false); - ModeLink(const ByteStream &src, bool inst = false); - ~ModeLink(); - - // init the link and append another link - bool init(const Mode *mode = nullptr); - bool append(const Mode *next); - bool append(const ByteStream &next); - - // play the instantiated mode inside - void play(); - - // unlink self from the chain, returns link that takes position - ModeLink *unlinkSelf(); - - // link in a node before or after self - void linkAfter(ModeLink *link); - void linkBefore(ModeLink *link); - - // instantiate/destroy the mode - Mode *instantiate(); - void uninstantiate(); - - // if the mode is instantiated and the instantiated version - // has changed at all then save will re-save it to the buffer - bool save(); - - // accessors - ByteStream &buffer() { return m_storedMode; } - Mode *mode() { return m_pInstantiatedMode; } - ModeLink *next() { return m_next; } - ModeLink *prev() { return m_prev; } - - operator ByteStream &() { return m_storedMode; } - operator ByteStream() { return m_storedMode; } - operator Mode *() { return m_pInstantiatedMode; } - private: - Mode *m_pInstantiatedMode; - ByteStream m_storedMode; - ModeLink *m_next; - ModeLink *m_prev; - }; - - // fetch a link from the chain by index - static ModeLink *getModeLink(uint32_t index); - - // initialize current mode from ByteStream, optionally force re-init which - // will destroy the current instantiated mode and re-load it from serial - static Mode *initCurMode(bool force = false); - static bool saveCurMode(); - - // whether modes have been loaded - static bool m_loaded; - - // the current mode we're on - static uint8_t m_curMode; - - // the number of modes loaded - static uint8_t m_numModes; - - // the current instantiated mode and it's respective link - static ModeLink *m_pCurModeLink; - - // list of serialized version of bufers - static ModeLink *m_storedModes; - - // global flags for all modes - static uint8_t m_globalFlags; +// internal functions +ModeLink *Modes_getModeLink(uint32_t index); +Mode *Modes_initCurMode(bool force); +bool Modes_saveCurMode(void); - // the last switch time of the modes - static uint32_t m_lastSwitchTime; -}; +// internal globals (accessed only through functions) +extern uint8_t Modes_globalFlags; #endif diff --git a/VortexEngine/src/Patterns/Multi/BackStrobePattern.c b/VortexEngine/src/Patterns/Multi/BackStrobePattern.c new file mode 100644 index 0000000000..f7319fd191 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/BackStrobePattern.c @@ -0,0 +1,15 @@ +#include "BackStrobePattern.h" + +static const PatternVTable BackStrobePattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + +void BackStrobePattern_init(BackStrobePattern *self, const PatternArgs *args) { +#if VORTEX_SLIM == 0 + CompoundPattern_init((CompoundPattern *)self, args); + self->base.base.base.vtable = &BackStrobePattern_vtable; + self->base.base.base.patternID = PATTERN_BACKSTROBE; + Pattern_setArgs((Pattern *)self, args); +#else + (void)self; + (void)args; +#endif +} diff --git a/VortexEngine/src/Patterns/Multi/BouncePattern.c b/VortexEngine/src/Patterns/Multi/BouncePattern.c new file mode 100644 index 0000000000..d9deeb3e06 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/BouncePattern.c @@ -0,0 +1,15 @@ +#include "BouncePattern.h" + +static const PatternVTable BouncePattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + +void BouncePattern_init(BouncePattern *self, const PatternArgs *args) { +#if VORTEX_SLIM == 0 + BlinkStepPattern_init((BlinkStepPattern *)self, args); + self->base.base.base.vtable = &BouncePattern_vtable; + self->base.base.base.patternID = PATTERN_BOUNCE; + Pattern_setArgs((Pattern *)self, args); +#else + (void)self; + (void)args; +#endif +} diff --git a/VortexEngine/src/Patterns/Multi/CompoundPattern.c b/VortexEngine/src/Patterns/Multi/CompoundPattern.c new file mode 100644 index 0000000000..f67daa9349 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/CompoundPattern.c @@ -0,0 +1,14 @@ +#include "CompoundPattern.h" + +static const PatternVTable CompoundPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + +void CompoundPattern_init(CompoundPattern *self, const PatternArgs *args) { +#if VORTEX_SLIM == 0 + MultiLedPattern_init((MultiLedPattern *)self, args); + self->base.base.vtable = &CompoundPattern_vtable; + Pattern_setArgs((Pattern *)self, args); +#else + (void)self; + (void)args; +#endif +} diff --git a/VortexEngine/src/Patterns/Multi/CrossDopsPattern.c b/VortexEngine/src/Patterns/Multi/CrossDopsPattern.c new file mode 100644 index 0000000000..9d8a46476c --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/CrossDopsPattern.c @@ -0,0 +1,15 @@ +#include "CrossDopsPattern.h" + +static const PatternVTable CrossDopsPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + +void CrossDopsPattern_init(CrossDopsPattern *self, const PatternArgs *args) { +#if VORTEX_SLIM == 0 + BlinkStepPattern_init((BlinkStepPattern *)self, args); + self->base.base.base.vtable = &CrossDopsPattern_vtable; + self->base.base.base.patternID = PATTERN_CROSSDOPS; + Pattern_setArgs((Pattern *)self, args); +#else + (void)self; + (void)args; +#endif +} diff --git a/VortexEngine/src/Patterns/Multi/DoubleStrobePattern.c b/VortexEngine/src/Patterns/Multi/DoubleStrobePattern.c new file mode 100644 index 0000000000..f1d0029d2d --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/DoubleStrobePattern.c @@ -0,0 +1,15 @@ +#include "DoubleStrobePattern.h" + +static const PatternVTable DoubleStrobePattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + +void DoubleStrobePattern_init(DoubleStrobePattern *self, const PatternArgs *args) { +#if VORTEX_SLIM == 0 + BlinkStepPattern_init((BlinkStepPattern *)self, args); + self->base.base.base.vtable = &DoubleStrobePattern_vtable; + self->base.base.base.patternID = PATTERN_DOUBLESTROBE; + Pattern_setArgs((Pattern *)self, args); +#else + (void)self; + (void)args; +#endif +} diff --git a/VortexEngine/src/Patterns/Multi/DripMorphPattern.c b/VortexEngine/src/Patterns/Multi/DripMorphPattern.c new file mode 100644 index 0000000000..63da135386 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/DripMorphPattern.c @@ -0,0 +1,15 @@ +#include "DripMorphPattern.h" + +static const PatternVTable DripMorphPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + +void DripMorphPattern_init(DripMorphPattern *self, const PatternArgs *args) { +#if VORTEX_SLIM == 0 + MultiLedPattern_init((MultiLedPattern *)self, args); + self->base.base.vtable = &DripMorphPattern_vtable; + self->base.base.patternID = PATTERN_DRIPMORPH; + Pattern_setArgs((Pattern *)self, args); +#else + (void)self; + (void)args; +#endif +} diff --git a/VortexEngine/src/Patterns/Multi/DripPattern.c b/VortexEngine/src/Patterns/Multi/DripPattern.c new file mode 100644 index 0000000000..811c44331a --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/DripPattern.c @@ -0,0 +1,15 @@ +#include "DripPattern.h" + +static const PatternVTable DripPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + +void DripPattern_init(DripPattern *self, const PatternArgs *args) { +#if VORTEX_SLIM == 0 + BlinkStepPattern_init((BlinkStepPattern *)self, args); + self->base.base.base.vtable = &DripPattern_vtable; + self->base.base.base.patternID = PATTERN_DRIP; + Pattern_setArgs((Pattern *)self, args); +#else + (void)self; + (void)args; +#endif +} diff --git a/VortexEngine/src/Patterns/Multi/FillPattern.c b/VortexEngine/src/Patterns/Multi/FillPattern.c new file mode 100644 index 0000000000..74a4f9bc43 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/FillPattern.c @@ -0,0 +1,15 @@ +#include "FillPattern.h" + +static const PatternVTable FillPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + +void FillPattern_init(FillPattern *self, const PatternArgs *args) { +#if VORTEX_SLIM == 0 + BlinkStepPattern_init((BlinkStepPattern *)self, args); + self->base.base.base.vtable = &FillPattern_vtable; + self->base.base.base.patternID = PATTERN_FILL; + Pattern_setArgs((Pattern *)self, args); +#else + (void)self; + (void)args; +#endif +} diff --git a/VortexEngine/src/Patterns/Multi/HueShiftPattern.c b/VortexEngine/src/Patterns/Multi/HueShiftPattern.c new file mode 100644 index 0000000000..02f9aabc1a --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/HueShiftPattern.c @@ -0,0 +1,15 @@ +#include "HueShiftPattern.h" + +static const PatternVTable HueShiftPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + +void HueShiftPattern_init(HueShiftPattern *self, const PatternArgs *args) { +#if VORTEX_SLIM == 0 + MultiLedPattern_init((MultiLedPattern *)self, args); + self->base.base.vtable = &HueShiftPattern_vtable; + self->base.base.patternID = PATTERN_HUE_SCROLL; + Pattern_setArgs((Pattern *)self, args); +#else + (void)self; + (void)args; +#endif +} diff --git a/VortexEngine/src/Patterns/Multi/LighthousePattern.c b/VortexEngine/src/Patterns/Multi/LighthousePattern.c new file mode 100644 index 0000000000..9d2a9cb00f --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/LighthousePattern.c @@ -0,0 +1,15 @@ +#include "LighthousePattern.h" + +static const PatternVTable LighthousePattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + +void LighthousePattern_init(LighthousePattern *self, const PatternArgs *args) { +#if VORTEX_SLIM == 0 + BlinkStepPattern_init((BlinkStepPattern *)self, args); + self->base.base.base.vtable = &LighthousePattern_vtable; + self->base.base.base.patternID = PATTERN_LIGHTHOUSE; + Pattern_setArgs((Pattern *)self, args); +#else + (void)self; + (void)args; +#endif +} diff --git a/VortexEngine/src/Patterns/Multi/MeteorPattern.c b/VortexEngine/src/Patterns/Multi/MeteorPattern.c new file mode 100644 index 0000000000..348993ba62 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/MeteorPattern.c @@ -0,0 +1,15 @@ +#include "MeteorPattern.h" + +static const PatternVTable MeteorPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + +void MeteorPattern_init(MeteorPattern *self, const PatternArgs *args) { +#if VORTEX_SLIM == 0 + BlinkStepPattern_init((BlinkStepPattern *)self, args); + self->base.base.base.vtable = &MeteorPattern_vtable; + self->base.base.base.patternID = PATTERN_METEOR; + Pattern_setArgs((Pattern *)self, args); +#else + (void)self; + (void)args; +#endif +} diff --git a/VortexEngine/src/Patterns/Multi/MultiLedPattern.c b/VortexEngine/src/Patterns/Multi/MultiLedPattern.c new file mode 100644 index 0000000000..bdb0c5c4ec --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/MultiLedPattern.c @@ -0,0 +1,32 @@ +#include "MultiLedPattern.h" + +#include "../Pattern.h" + +const PatternVTable MultiLedPattern_vtable = { + NULL, + NULL, + MultiLedPattern_initVirtual, + MultiLedPattern_bind, + NULL, + NULL, + NULL, + NULL +}; + +void MultiLedPattern_init(MultiLedPattern *self, const PatternArgs *args) +{ + Pattern_init(&self->base, args); + self->base.vtable = &MultiLedPattern_vtable; + self->base.patternFlags |= PATTERN_FLAG_MULTI; +} + +void MultiLedPattern_bind(Pattern *self, LedPos pos) +{ + (void)pos; + Pattern_setLedPos(self, LED_COUNT); +} + +void MultiLedPattern_initVirtual(Pattern *self) +{ + Pattern_initBase(self); +} diff --git a/VortexEngine/src/Patterns/Multi/MultiLedPattern.h b/VortexEngine/src/Patterns/Multi/MultiLedPattern.h index 9e105be4ff..b8a6904f71 100644 --- a/VortexEngine/src/Patterns/Multi/MultiLedPattern.h +++ b/VortexEngine/src/Patterns/Multi/MultiLedPattern.h @@ -3,23 +3,14 @@ #include "../Pattern.h" -class MultiLedPattern : public Pattern -{ -protected: - // MultiLedPattern is an abstract class - MultiLedPattern(const PatternArgs &args); +typedef struct MultiLedPattern { + Pattern base; +} MultiLedPattern; -public: - virtual ~MultiLedPattern(); +extern const PatternVTable MultiLedPattern_vtable; - // multi-led and single-led have different bind functions - virtual void bind(LedPos pos) override; - - // init the pattern to initial state - virtual void init() override; - -private: - -}; +void MultiLedPattern_init(MultiLedPattern *self, const PatternArgs *args); +void MultiLedPattern_bind(Pattern *self, LedPos pos); +void MultiLedPattern_initVirtual(Pattern *self); #endif diff --git a/VortexEngine/src/Patterns/Multi/PulsishPattern.c b/VortexEngine/src/Patterns/Multi/PulsishPattern.c new file mode 100644 index 0000000000..c10a34b7e7 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/PulsishPattern.c @@ -0,0 +1,15 @@ +#include "PulsishPattern.h" + +static const PatternVTable PulsishPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + +void PulsishPattern_init(PulsishPattern *self, const PatternArgs *args) { +#if VORTEX_SLIM == 0 + MultiLedPattern_init((MultiLedPattern *)self, args); + self->base.base.vtable = &PulsishPattern_vtable; + self->base.base.patternID = PATTERN_PULSISH; + Pattern_setArgs((Pattern *)self, args); +#else + (void)self; + (void)args; +#endif +} diff --git a/VortexEngine/src/Patterns/Multi/Sequencer/ChaserPattern.c b/VortexEngine/src/Patterns/Multi/Sequencer/ChaserPattern.c new file mode 100644 index 0000000000..6bd764e789 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/Sequencer/ChaserPattern.c @@ -0,0 +1,15 @@ +#include "../../Patterns/Multi/Sequencer/ChaserPattern.h" + +static const PatternVTable ChaserPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + +void ChaserPattern_init(ChaserPattern *self, const PatternArgs *args) { +#if VORTEX_SLIM == 0 + SequencedPattern_init((SequencedPattern *)self, args); + self->base.base.base.base.vtable = &ChaserPattern_vtable; + self->base.base.base.base.patternID = PATTERN_CHASER; + Pattern_setArgs((Pattern *)self, args); +#else + (void)self; + (void)args; +#endif +} diff --git a/VortexEngine/src/Patterns/Multi/Sequencer/Sequence.c b/VortexEngine/src/Patterns/Multi/Sequencer/Sequence.c new file mode 100644 index 0000000000..4f66444f93 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/Sequencer/Sequence.c @@ -0,0 +1,79 @@ +#include "../../Patterns/Multi/Sequencer/Sequence.h" + +#include + +#if VORTEX_SLIM == 1 + +void PatternMap_init(PatternMap *self) { + memset(self, 0, sizeof(*self)); +} + +void PatternMap_initWithMap(PatternMap *self, PatternID pattern, LedMap positions) { + PatternMap_init(self); + PatternMap_setPatternAt(self, pattern, positions); +} + +void PatternMap_setPatternAt(PatternMap *self, PatternID pattern, LedMap positions) { + for (LedPos pos = LED_FIRST; pos < LED_COUNT; ++pos) { + if (ledmapCheckLed(positions, pos)) { + self->m_patternMap[pos] = pattern; + } + } +} + +PatternID PatternMap_get(const PatternMap *self, LedPos index) { + return self->m_patternMap[index]; +} + +void ColorsetMap_init(ColorsetMap *self) { + memset(self, 0, sizeof(*self)); +} + +void ColorsetMap_initWithMap(ColorsetMap *self, const Colorset *colorset, LedMap positions) { + ColorsetMap_init(self); + ColorsetMap_setColorsetAt(self, colorset, positions); +} + +void ColorsetMap_setColorsetAt(ColorsetMap *self, const Colorset *colorset, LedMap positions) { + for (LedPos pos = LED_FIRST; pos < LED_COUNT; ++pos) { + if (ledmapCheckLed(positions, pos)) { + self->m_colorsetMap[pos] = *colorset; + } + } +} + +const Colorset *ColorsetMap_get(const ColorsetMap *self, LedPos index) { + return &self->m_colorsetMap[index]; +} + +void SequenceStep_init(SequenceStep *self) { + self->m_duration = 0; + PatternMap_init(&self->m_patternMap); + ColorsetMap_init(&self->m_colorsetMap); +} + +void SequenceStep_initFull(SequenceStep *self, uint16_t duration, const PatternMap *patternMap, const ColorsetMap *colorsetMap) { + self->m_duration = duration; + self->m_patternMap = *patternMap; + self->m_colorsetMap = *colorsetMap; +} + +void Sequence_init(Sequence *self) { + self->m_sequenceSteps = NULL; + self->m_numSteps = 0; +} + +void Sequence_cleanup(Sequence *self) { + Sequence_clear(self); +} + +uint8_t Sequence_numSteps(const Sequence *self) { + return self->m_numSteps; +} + +void Sequence_clear(Sequence *self) { + self->m_sequenceSteps = NULL; + self->m_numSteps = 0; +} + +#endif diff --git a/VortexEngine/src/Patterns/Multi/Sequencer/SequencedPattern.c b/VortexEngine/src/Patterns/Multi/Sequencer/SequencedPattern.c new file mode 100644 index 0000000000..768fe3cacc --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/Sequencer/SequencedPattern.c @@ -0,0 +1,14 @@ +#include "SequencedPattern.h" + +static const PatternVTable SequencedPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + +void SequencedPattern_init(SequencedPattern *self, const PatternArgs *args) { +#if VORTEX_SLIM == 0 + CompoundPattern_init((CompoundPattern *)self, args); + self->base.base.base.vtable = &SequencedPattern_vtable; + Pattern_setArgs((Pattern *)self, args); +#else + (void)self; + (void)args; +#endif +} diff --git a/VortexEngine/src/Patterns/Multi/SnowballPattern.c b/VortexEngine/src/Patterns/Multi/SnowballPattern.c new file mode 100644 index 0000000000..3505fb2f91 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/SnowballPattern.c @@ -0,0 +1,15 @@ +#include "SnowballPattern.h" + +static const PatternVTable SnowballPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + +void SnowballPattern_init(SnowballPattern *self, const PatternArgs *args) { +#if VORTEX_SLIM == 0 + BlinkStepPattern_init((BlinkStepPattern *)self, args); + self->base.base.base.vtable = &SnowballPattern_vtable; + self->base.base.base.patternID = PATTERN_SNOWBALL; + Pattern_setArgs((Pattern *)self, args); +#else + (void)self; + (void)args; +#endif +} diff --git a/VortexEngine/src/Patterns/Multi/SparkleTracePattern.c b/VortexEngine/src/Patterns/Multi/SparkleTracePattern.c new file mode 100644 index 0000000000..294b9a32ba --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/SparkleTracePattern.c @@ -0,0 +1,15 @@ +#include "SparkleTracePattern.h" + +static const PatternVTable SparkleTracePattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + +void SparkleTracePattern_init(SparkleTracePattern *self, const PatternArgs *args) { +#if VORTEX_SLIM == 0 + BlinkStepPattern_init((BlinkStepPattern *)self, args); + self->base.base.base.vtable = &SparkleTracePattern_vtable; + self->base.base.base.patternID = PATTERN_SPARKLETRACE; + Pattern_setArgs((Pattern *)self, args); +#else + (void)self; + (void)args; +#endif +} diff --git a/VortexEngine/src/Patterns/Multi/TheaterChasePattern.c b/VortexEngine/src/Patterns/Multi/TheaterChasePattern.c new file mode 100644 index 0000000000..c9433b4b44 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/TheaterChasePattern.c @@ -0,0 +1,15 @@ +#include "TheaterChasePattern.h" + +static const PatternVTable TheaterChasePattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + +void TheaterChasePattern_init(TheaterChasePattern *self, const PatternArgs *args) { +#if VORTEX_SLIM == 0 + BlinkStepPattern_init((BlinkStepPattern *)self, args); + self->base.base.base.vtable = &TheaterChasePattern_vtable; + self->base.base.base.patternID = PATTERN_THEATER_CHASE; + Pattern_setArgs((Pattern *)self, args); +#else + (void)self; + (void)args; +#endif +} diff --git a/VortexEngine/src/Patterns/Multi/VortexPattern.c b/VortexEngine/src/Patterns/Multi/VortexPattern.c new file mode 100644 index 0000000000..7bcd78e7e5 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/VortexPattern.c @@ -0,0 +1,15 @@ +#include "VortexPattern.h" + +static const PatternVTable VortexPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + +void VortexPattern_init(VortexPattern *self, const PatternArgs *args) { +#if VORTEX_SLIM == 0 + BlinkStepPattern_init((BlinkStepPattern *)self, args); + self->base.base.base.vtable = &VortexPattern_vtable; + self->base.base.base.patternID = PATTERN_VORTEX; + Pattern_setArgs((Pattern *)self, args); +#else + (void)self; + (void)args; +#endif +} diff --git a/VortexEngine/src/Patterns/Multi/VortexWipePattern.c b/VortexEngine/src/Patterns/Multi/VortexWipePattern.c new file mode 100644 index 0000000000..11ca260802 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/VortexWipePattern.c @@ -0,0 +1,15 @@ +#include "VortexWipePattern.h" + +static const PatternVTable VortexWipePattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + +void VortexWipePattern_init(VortexWipePattern *self, const PatternArgs *args) { +#if VORTEX_SLIM == 0 + BlinkStepPattern_init((BlinkStepPattern *)self, args); + self->base.base.base.vtable = &VortexWipePattern_vtable; + self->base.base.base.patternID = PATTERN_VORTEXWIPE; + Pattern_setArgs((Pattern *)self, args); +#else + (void)self; + (void)args; +#endif +} diff --git a/VortexEngine/src/Patterns/Multi/WarpPattern.c b/VortexEngine/src/Patterns/Multi/WarpPattern.c new file mode 100644 index 0000000000..02ed134f9e --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/WarpPattern.c @@ -0,0 +1,15 @@ +#include "WarpPattern.h" + +static const PatternVTable WarpPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + +void WarpPattern_init(WarpPattern *self, const PatternArgs *args) { +#if VORTEX_SLIM == 0 + BlinkStepPattern_init((BlinkStepPattern *)self, args); + self->base.base.base.vtable = &WarpPattern_vtable; + self->base.base.base.patternID = PATTERN_WARP; + Pattern_setArgs((Pattern *)self, args); +#else + (void)self; + (void)args; +#endif +} diff --git a/VortexEngine/src/Patterns/Multi/WarpWormPattern.c b/VortexEngine/src/Patterns/Multi/WarpWormPattern.c new file mode 100644 index 0000000000..bf948658a8 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/WarpWormPattern.c @@ -0,0 +1,15 @@ +#include "WarpWormPattern.h" + +static const PatternVTable WarpWormPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + +void WarpWormPattern_init(WarpWormPattern *self, const PatternArgs *args) { +#if VORTEX_SLIM == 0 + BlinkStepPattern_init((BlinkStepPattern *)self, args); + self->base.base.base.vtable = &WarpWormPattern_vtable; + self->base.base.base.patternID = PATTERN_WARPWORM; + Pattern_setArgs((Pattern *)self, args); +#else + (void)self; + (void)args; +#endif +} diff --git a/VortexEngine/src/Patterns/Multi/ZigzagPattern.c b/VortexEngine/src/Patterns/Multi/ZigzagPattern.c new file mode 100644 index 0000000000..0ab9fe5bd2 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/ZigzagPattern.c @@ -0,0 +1,15 @@ +#include "ZigzagPattern.h" + +static const PatternVTable ZigzagPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + +void ZigzagPattern_init(ZigzagPattern *self, const PatternArgs *args) { +#if VORTEX_SLIM == 0 + MultiLedPattern_init((MultiLedPattern *)self, args); + self->base.base.vtable = &ZigzagPattern_vtable; + self->base.base.patternID = PATTERN_ZIGZAG; + Pattern_setArgs((Pattern *)self, args); +#else + (void)self; + (void)args; +#endif +} diff --git a/VortexEngine/src/Patterns/Pattern.c b/VortexEngine/src/Patterns/Pattern.c new file mode 100644 index 0000000000..05c7682b85 --- /dev/null +++ b/VortexEngine/src/Patterns/Pattern.c @@ -0,0 +1,237 @@ +#include "Pattern.h" + +#include "../Patterns/PatternBuilder.h" +#include "../Serial/ByteStream.h" +#include "../Time/TimeControl.h" +#include "../Colors/Colorset.h" +#include "../Memory/Memory.h" +#include "../Log/Log.h" +#include "../VortexConfig.h" + +void Pattern_init(Pattern *self, const PatternArgs *args) +{ + (void)args; + self->vtable = NULL; + self->patternID = PATTERN_FIRST; + self->patternFlags = 0; + Colorset_init(&self->colorset); + self->ledPos = LED_FIRST; + self->numArgs = 0; +} + +void Pattern_bindBase(Pattern *self, LedPos pos) +{ + self->ledPos = pos; +} + +void Pattern_initBase(Pattern *self) +{ + Colorset_resetIndex(&self->colorset); +} + +void Pattern_destroy(Pattern *self) +{ + if (self && self->vtable && self->vtable->destroy) { + self->vtable->destroy(self); + } +} + +void Pattern_bind(Pattern *self, LedPos pos) +{ + if (self && self->vtable && self->vtable->bind) { + self->vtable->bind(self, pos); + } +} + +void Pattern_initVirtual(Pattern *self) +{ + if (self && self->vtable && self->vtable->init) { + self->vtable->init(self); + } +} + +void Pattern_play(Pattern *self) +{ + if (self && self->vtable && self->vtable->play) { + self->vtable->play(self); + } +} + +#ifdef VORTEX_LIB +void Pattern_skip(Pattern *self, uint32_t ticks) +{ + Time_startSimulation(); + for (uint32_t i = 0; i < ticks; ++i) { + Pattern_play(self); + Time_tickSimulation(); + } + Time_endSimulation(); +} +#endif + +bool Pattern_serialize(const Pattern *self, ByteStream *buffer) +{ + if (!ByteStream_serialize8(buffer, (uint8_t)self->patternID)) { + return false; + } + if (!Colorset_serialize(&self->colorset, buffer)) { + return false; + } + PatternArgs args; + Pattern_getArgs(self, &args); + PatternArgs defaults = PatternBuilder_getDefaultArgs(self->patternID); + uint8_t argmap = ARG_NONE; + for (uint32_t i = 0; i < MAX_ARGS; ++i) { + if (args.args[i] != defaults.args[i]) { + ARGMAP_SET(argmap, i); + } + } + if (!PatternArgs_serialize(&args, buffer, argmap)) { + return false; + } + return true; +} + +bool Pattern_unserialize(Pattern *self, ByteStream *buffer) +{ + if (!Colorset_unserialize(&self->colorset, buffer)) { + return false; + } + PatternArgs args = PatternBuilder_getDefaultArgs(self->patternID); + PatternArgs_unserialize(&args, buffer); + Pattern_setArgs(self, &args); + return true; +} + +void Pattern_setArg(Pattern *self, uint8_t index, uint8_t value) +{ + if (index >= self->numArgs) { + return; + } + *((uint8_t *)self + self->argList[index]) = value; +} + +uint8_t Pattern_getArg(const Pattern *self, uint8_t index) +{ + if (index >= self->numArgs) { + return 0; + } + return *((uint8_t *)self + self->argList[index]); +} + +uint8_t *Pattern_argRef(Pattern *self, uint8_t index) +{ + if (index >= self->numArgs) { + index = 0; + } + return (uint8_t *)self + self->argList[index]; +} + +void Pattern_setArgs(Pattern *self, const PatternArgs *args) +{ + for (uint32_t i = 0; i < self->numArgs; ++i) { + *((uint8_t *)self + self->argList[i]) = args->args[i]; + } +} + +void Pattern_getArgs(const Pattern *self, PatternArgs *args) +{ + PatternArgs_init(args); + for (uint32_t i = 0; i < self->numArgs; ++i) { + args->args[i] = *((uint8_t *)self + self->argList[i]); + } + args->numArgs = self->numArgs; +} + +uint8_t Pattern_getNumArgs(const Pattern *self) +{ + return self->numArgs; +} + +bool Pattern_equals(const Pattern *self, const Pattern *other) +{ + if (!other) { + return false; + } + if (self->patternID != other->patternID) { + return false; + } + if (!Colorset_equals(&self->colorset, &other->colorset)) { + return false; + } + if (self->numArgs != other->numArgs) { + return false; + } + for (uint8_t i = 0; i < self->numArgs; ++i) { + if (Pattern_getArg(self, i) != Pattern_getArg(other, i)) { + return false; + } + } + return true; +} + +Colorset Pattern_getColorset(const Pattern *self) +{ + return self->colorset; +} + +void Pattern_setColorset(Pattern *self, const Colorset *set) +{ + Colorset_copy(&self->colorset, set); +} + +void Pattern_clearColorset(Pattern *self) +{ + Colorset_clear(&self->colorset); +} + +void Pattern_setLedPos(Pattern *self, LedPos pos) +{ + self->ledPos = pos; +} + +PatternID Pattern_getPatternID(const Pattern *self) +{ + return self->patternID; +} + +LedPos Pattern_getLedPos(const Pattern *self) +{ + return self->ledPos; +} + +uint32_t Pattern_getFlags(const Pattern *self) +{ + return self->patternFlags; +} + +bool Pattern_hasFlags(const Pattern *self, uint32_t flags) +{ + return (self->patternFlags & flags) != 0; +} + +#ifdef VORTEX_LIB +void Pattern_registerArgName(Pattern *self, const char *name, arg_offset_t argOffset) +{ + if (self->numArgs >= MAX_PATTERN_ARGS) { + ERROR_LOG("too many args"); + return; + } + self->argNameList[self->numArgs] = name; + self->argList[self->numArgs++] = argOffset; +} + +const char *Pattern_getArgName(const Pattern *self, uint8_t index) +{ + return index >= self->numArgs ? "" : self->argNameList[index]; +} +#else +void Pattern_registerArg(Pattern *self, arg_offset_t argOffset) +{ + if (self->numArgs >= MAX_PATTERN_ARGS) { + ERROR_LOG("too many args"); + return; + } + self->argList[self->numArgs++] = argOffset; +} +#endif diff --git a/VortexEngine/src/Patterns/Pattern.h b/VortexEngine/src/Patterns/Pattern.h index 0a3454ce04..e7f6258f89 100644 --- a/VortexEngine/src/Patterns/Pattern.h +++ b/VortexEngine/src/Patterns/Pattern.h @@ -1,146 +1,110 @@ #ifndef PATTERN_H #define PATTERN_H +#include +#include +#include + #include "../Leds/LedTypes.h" #include "../Colors/Colorset.h" - #include "Patterns.h" #include "PatternArgs.h" #define MAX_PATTERN_ARGS 8 -// The heirarchy of pattern currently looks like this: /* - * pattern* + * Pattern * / \ - * single led pat* multi led pat* - * / \ / - * basic pat tracer pat hybrid pat* - * / / \ - * Advanced pattern Rabbit Pattern sequenced pattern - * - * * = abstract class that cannot be instantiated + * SingleLedPattern MultiLedPattern + * / \ + * BasicPattern + * / \ + * SolidPattern BlendPattern */ #define PATTERN_FLAGS_NONE 0 - -// the pattern is a multi-pattern #define PATTERN_FLAG_MULTI (1<<0) -// and arg_offset_t is the distance from the base of the object -// to the desired argument, in slim vortex we only need a single byte -// but in multi-led patterns they can be a bit further in #if VORTEX_SLIM == 1 typedef uint8_t arg_offset_t; #else typedef uint16_t arg_offset_t; #endif -// macro to register args of a pattern #ifdef VORTEX_LIB -#define REGISTER_ARG(arg) \ - registerArg(#arg, (arg_offset_t)(((uintptr_t)&arg - (uintptr_t)this))); +#define REGISTER_ARG(self, arg) Pattern_registerArgName((Pattern *)(self), #arg, (arg_offset_t)((uintptr_t)&(arg) - (uintptr_t)(self))) #else -#define REGISTER_ARG(arg) \ - registerArg((arg_offset_t)(((uintptr_t)&arg - (uintptr_t)this))); +#define REGISTER_ARG(self, arg) Pattern_registerArg((Pattern *)(self), (arg_offset_t)((uintptr_t)&(arg) - (uintptr_t)(self))) #endif -class ByteStream; - -class Pattern -{ - // PatternBuilder can access the Pattern internals - friend class PatternBuilder; - -protected: - // Pattern is an abstract class - Pattern(); - - Pattern(const PatternArgs &args); - -public: - virtual ~Pattern(); - - // bind a colorset and position to the pattern - virtual void bind(LedPos pos); - - // init the pattern to initial state - virtual void init(); - - // pure virtual must override the play function - virtual void play() = 0; - +typedef struct ByteStream ByteStream; +typedef struct Pattern Pattern; + +typedef struct PatternVTable { + void (*destroy)(Pattern *self); + void (*play)(Pattern *self); + void (*init)(Pattern *self); + void (*bind)(Pattern *self, LedPos pos); + void (*onBlinkOn)(Pattern *self); + void (*onBlinkOff)(Pattern *self); + void (*beginGap)(Pattern *self); + void (*beginDash)(Pattern *self); +} PatternVTable; + +struct Pattern { + const PatternVTable *vtable; + PatternID patternID; + uint8_t patternFlags; + Colorset colorset; + LedPos ledPos; + uint8_t numArgs; + arg_offset_t argList[MAX_PATTERN_ARGS]; #ifdef VORTEX_LIB - // skip the pattern ahead some ticks - void skip(uint32_t ticks); + const char *argNameList[MAX_PATTERN_ARGS]; #endif +}; - // serialize and unserialize a pattern to a bytestream - bool serialize(ByteStream &buffer) const; - bool unserialize(ByteStream &buffer); - - // get or set a single arg - void setArg(uint8_t index, uint8_t value); - uint8_t getArg(uint8_t index) const; - - uint8_t &argRef(uint8_t index); +void Pattern_init(Pattern *self, const PatternArgs *args); +void Pattern_initBase(Pattern *self); +void Pattern_initVirtual(Pattern *self); +void Pattern_destroy(Pattern *self); +void Pattern_bind(Pattern *self, LedPos pos); +void Pattern_bindBase(Pattern *self, LedPos pos); +void Pattern_play(Pattern *self); #ifdef VORTEX_LIB - // get the name of an arg - const char *getArgName(uint8_t index) const { return index >= m_numArgs ? "" : m_argNameList[index]; } +void Pattern_skip(Pattern *self, uint32_t ticks); #endif - // get or set the entire list of pattern args - void setArgs(const PatternArgs &args); - void getArgs(PatternArgs &args) const; - - // number of args the pattern has - uint8_t getNumArgs() const { return m_numArgs; } - - // comparison to other pattern - // NOTE: That may cause problems because the parameter is still a Pattern * - // which means comparison would need to cast the other upwards first - // NOTE2: Removing virtual because this probably shouldn't be overridden - bool equals(const Pattern *other); - - // change the colorset - const Colorset getColorset() const { return m_colorset; } - Colorset getColorset() { return m_colorset; } - void setColorset(const Colorset &set); - void clearColorset(); +bool Pattern_serialize(const Pattern *self, ByteStream *buffer); +bool Pattern_unserialize(Pattern *self, ByteStream *buffer); - // change the led position - void setLedPos(LedPos pos) { m_ledPos = pos; } +void Pattern_setArg(Pattern *self, uint8_t index, uint8_t value); +uint8_t Pattern_getArg(const Pattern *self, uint8_t index); +uint8_t *Pattern_argRef(Pattern *self, uint8_t index); - // get/set the ID of the pattern (set by mode builder) - PatternID getPatternID() const { return m_patternID; } - - // get a pointer to the colorset that is bound to the pattern - LedPos getLedPos() const { return m_ledPos; } - - // get the pattern flags - uint32_t getFlags() const { return m_patternFlags; } - bool hasFlags(uint32_t flags) const { return (m_patternFlags & flags) != 0; } +#ifdef VORTEX_LIB +const char *Pattern_getArgName(const Pattern *self, uint8_t index); +#endif -protected: - // the ID of this pattern (set by pattern builder) - PatternID m_patternID; - // any flags the pattern has - uint8_t m_patternFlags; - // a copy of the colorset that this pattern is initialized with - Colorset m_colorset; - // the Led the pattern is running on - LedPos m_ledPos; +void Pattern_setArgs(Pattern *self, const PatternArgs *args); +void Pattern_getArgs(const Pattern *self, PatternArgs *args); +uint8_t Pattern_getNumArgs(const Pattern *self); +bool Pattern_equals(const Pattern *self, const Pattern *other); - uint8_t m_numArgs; - arg_offset_t m_argList[MAX_PATTERN_ARGS]; +Colorset Pattern_getColorset(const Pattern *self); +void Pattern_setColorset(Pattern *self, const Colorset *set); +void Pattern_clearColorset(Pattern *self); +void Pattern_setLedPos(Pattern *self, LedPos pos); +PatternID Pattern_getPatternID(const Pattern *self); +LedPos Pattern_getLedPos(const Pattern *self); +uint32_t Pattern_getFlags(const Pattern *self); +bool Pattern_hasFlags(const Pattern *self, uint32_t flags); #ifdef VORTEX_LIB - void registerArg(const char *name, arg_offset_t argOffset); - const char *m_argNameList[MAX_PATTERN_ARGS]; +void Pattern_registerArgName(Pattern *self, const char *name, arg_offset_t argOffset); #else - void registerArg(arg_offset_t argOffset); +void Pattern_registerArg(Pattern *self, arg_offset_t argOffset); #endif -}; #endif diff --git a/VortexEngine/src/Patterns/PatternArgs.c b/VortexEngine/src/Patterns/PatternArgs.c new file mode 100644 index 0000000000..6988bbade1 --- /dev/null +++ b/VortexEngine/src/Patterns/PatternArgs.c @@ -0,0 +1,169 @@ +#include "PatternArgs.h" + +#include "../Serial/ByteStream.h" + +void PatternArgs_init(PatternArgs *self) { + self->arg1 = 0; self->arg2 = 0; self->arg3 = 0; self->arg4 = 0; + self->arg5 = 0; self->arg6 = 0; self->arg7 = 0; self->arg8 = 0; + self->numArgs = 0; +} + +void PatternArgs_init1(PatternArgs *self, uint8_t a1) { + self->arg1 = a1; self->arg2 = 0; self->arg3 = 0; self->arg4 = 0; + self->arg5 = 0; self->arg6 = 0; self->arg7 = 0; self->arg8 = 0; + self->numArgs = 1; +} + +void PatternArgs_init2(PatternArgs *self, uint8_t a1, uint8_t a2) { + self->arg1 = a1; self->arg2 = a2; self->arg3 = 0; self->arg4 = 0; + self->arg5 = 0; self->arg6 = 0; self->arg7 = 0; self->arg8 = 0; + self->numArgs = 2; +} + +void PatternArgs_init3(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3) { + self->arg1 = a1; self->arg2 = a2; self->arg3 = a3; self->arg4 = 0; + self->arg5 = 0; self->arg6 = 0; self->arg7 = 0; self->arg8 = 0; + self->numArgs = 3; +} + +void PatternArgs_init4(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4) { + self->arg1 = a1; self->arg2 = a2; self->arg3 = a3; self->arg4 = a4; + self->arg5 = 0; self->arg6 = 0; self->arg7 = 0; self->arg8 = 0; + self->numArgs = 4; +} + +void PatternArgs_init5(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5) { + self->arg1 = a1; self->arg2 = a2; self->arg3 = a3; self->arg4 = a4; + self->arg5 = a5; self->arg6 = 0; self->arg7 = 0; self->arg8 = 0; + self->numArgs = 5; +} + +void PatternArgs_init6(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6) { + self->arg1 = a1; self->arg2 = a2; self->arg3 = a3; self->arg4 = a4; + self->arg5 = a5; self->arg6 = a6; self->arg7 = 0; self->arg8 = 0; + self->numArgs = 6; +} + +void PatternArgs_init7(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7) { + self->arg1 = a1; self->arg2 = a2; self->arg3 = a3; self->arg4 = a4; + self->arg5 = a5; self->arg6 = a6; self->arg7 = a7; self->arg8 = 0; + self->numArgs = 7; +} + +void PatternArgs_init8(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7, uint8_t a8) { + self->arg1 = a1; self->arg2 = a2; self->arg3 = a3; self->arg4 = a4; + self->arg5 = a5; self->arg6 = a6; self->arg7 = a7; self->arg8 = a8; + self->numArgs = 8; +} + +void PatternArgs_addArgs1(PatternArgs *self, uint8_t a1) { + self->args[self->numArgs++] = a1; +} + +void PatternArgs_addArgs2(PatternArgs *self, uint8_t a1, uint8_t a2) { + self->args[self->numArgs++] = a1; + self->args[self->numArgs++] = a2; +} + +void PatternArgs_addArgs3(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3) { + self->args[self->numArgs++] = a1; + self->args[self->numArgs++] = a2; + self->args[self->numArgs++] = a3; +} + +void PatternArgs_addArgs4(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4) { + self->args[self->numArgs++] = a1; + self->args[self->numArgs++] = a2; + self->args[self->numArgs++] = a3; + self->args[self->numArgs++] = a4; +} + +void PatternArgs_addArgs5(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5) { + self->args[self->numArgs++] = a1; + self->args[self->numArgs++] = a2; + self->args[self->numArgs++] = a3; + self->args[self->numArgs++] = a4; + self->args[self->numArgs++] = a5; +} + +void PatternArgs_addArgs6(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6) { + self->args[self->numArgs++] = a1; + self->args[self->numArgs++] = a2; + self->args[self->numArgs++] = a3; + self->args[self->numArgs++] = a4; + self->args[self->numArgs++] = a5; + self->args[self->numArgs++] = a6; +} + +void PatternArgs_addArgs7(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7) { + self->args[self->numArgs++] = a1; + self->args[self->numArgs++] = a2; + self->args[self->numArgs++] = a3; + self->args[self->numArgs++] = a4; + self->args[self->numArgs++] = a5; + self->args[self->numArgs++] = a6; + self->args[self->numArgs++] = a7; +} + +void PatternArgs_addArgs8(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7, uint8_t a8) { + self->args[self->numArgs++] = a1; + self->args[self->numArgs++] = a2; + self->args[self->numArgs++] = a3; + self->args[self->numArgs++] = a4; + self->args[self->numArgs++] = a5; + self->args[self->numArgs++] = a6; + self->args[self->numArgs++] = a7; + self->args[self->numArgs++] = a8; +} + +bool PatternArgs_equals(const PatternArgs *self, const PatternArgs *rhs) { + if (rhs->numArgs != self->numArgs) { + return false; + } + for (uint8_t i = 0; i < self->numArgs; ++i) { + if (self->args[i] != rhs->args[i]) { + return false; + } + } + return true; +} + +bool PatternArgs_notEquals(const PatternArgs *self, const PatternArgs *rhs) { + return !PatternArgs_equals(self, rhs); +} + +uint8_t PatternArgs_get(const PatternArgs *self, int index) { + if (index > 7) { + return 0; + } + return self->args[index]; +} + +bool PatternArgs_serialize(const PatternArgs *self, ByteStream *buffer, ArgMap argmap) { + if (!ByteStream_serialize8(buffer, argmap)) { + return false; + } + for (uint8_t i = 0; i < MAX_ARGS; ++i) { + if (ARGMAP_ISSET(argmap, i)) { + if (!ByteStream_serialize8(buffer, self->args[i])) { + return false; + } + } + } + return true; +} + +ArgMap PatternArgs_unserialize(PatternArgs *self, ByteStream *buffer) { + ArgMap argmap = ARG_NONE; + if (!ByteStream_unserialize8(buffer, &argmap)) { + return ARG_NONE; + } + for (uint8_t i = 0; i < MAX_ARGS; ++i) { + if (ARGMAP_ISSET(argmap, i)) { + if (!ByteStream_unserialize8(buffer, self->args + i)) { + break; + } + } + } + return argmap; +} diff --git a/VortexEngine/src/Patterns/PatternArgs.h b/VortexEngine/src/Patterns/PatternArgs.h index 67759ad682..3da1255eb6 100644 --- a/VortexEngine/src/Patterns/PatternArgs.h +++ b/VortexEngine/src/Patterns/PatternArgs.h @@ -2,18 +2,14 @@ #define PATTERN_ARGS_H #include +#include -// maximum number of args, this isn't really configurable so I'm -// not putting it in VortexConfig.h #define MAX_ARGS 8 -class ByteStream; +typedef struct ByteStream ByteStream; -// a bit map of arguments, this is used to indicate which arguments -// are present in a pattern when saved to storage typedef uint8_t ArgMap; -// some macros to access each arg in the argmap #define ARG_NONE 0 #define ARG(x) (1 << x) #define ARG1 (1 << 0) @@ -26,57 +22,11 @@ typedef uint8_t ArgMap; #define ARG8 (1 << 7) #define ARG_ALL 0xFF -// some helpers to set/clear/check #define ARGMAP_SET(map, arg) (map |= ARG(arg)) #define ARGMAP_CLEAR(map, arg) (map &= ~ARG(arg)) #define ARGMAP_ISSET(map, arg) ((map & ARG(arg)) != 0) -// a structured method for passing arguments to patterns, there's definitely more dynamic -// ways to approach this but I'm aiming for simple and lightweight here -class PatternArgs -{ -public: - // constructor for each number of arguments, automatically fills the 'numArgs' field accordingly - PatternArgs(); - PatternArgs(uint8_t a1); - PatternArgs(uint8_t a1, uint8_t a2); - PatternArgs(uint8_t a1, uint8_t a2, uint8_t a3); - PatternArgs(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4); - PatternArgs(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5); - PatternArgs(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6); - PatternArgs(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7); - PatternArgs(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7, uint8_t a8); - - void init(); - void init(uint8_t a1); - void init(uint8_t a1, uint8_t a2); - void init(uint8_t a1, uint8_t a2, uint8_t a3); - void init(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4); - void init(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5); - void init(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6); - void init(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7); - void init(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7, uint8_t a8); - - void addArgs(uint8_t a1); - void addArgs(uint8_t a1, uint8_t a2); - void addArgs(uint8_t a1, uint8_t a2, uint8_t a3); - void addArgs(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4); - void addArgs(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5); - void addArgs(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6); - void addArgs(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7); - void addArgs(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7, uint8_t a8); - - bool operator!=(const PatternArgs &rhs); - bool operator==(const PatternArgs &rhs); - uint8_t operator[](int index) const; - - // serialize the pattern args with a specific mapping of which args to store - bool serialize(ByteStream &buffer, ArgMap argmap = ARG_ALL) const; - // unserialize the pattern args and return the argmap of which args were loaded - // NOTE: You should start with an instance of the default args before you unserialize - ArgMap unserialize(ByteStream &buffer); - - // public access to args +typedef struct PatternArgs_s { union { uint8_t args[8]; struct { @@ -89,7 +39,6 @@ class PatternArgs uint8_t arg7; uint8_t arg8; }; - // arg access for basic pattern struct { uint8_t onDuration; uint8_t offDuration; @@ -97,7 +46,6 @@ class PatternArgs uint8_t dashDuration; uint8_t groupSize; } basic; - // arg access for blend struct { uint8_t onDuration; uint8_t offDuration; @@ -107,7 +55,6 @@ class PatternArgs uint8_t blendSpeed; uint8_t numFlips; } blend; - // arg access for solid struct { uint8_t onDuration; uint8_t offDuration; @@ -117,8 +64,33 @@ class PatternArgs uint8_t colorIndex; } solid; }; - // indicates how many of the above args are filled uint8_t numArgs; -}; +} PatternArgs; + +void PatternArgs_init(PatternArgs *self); +void PatternArgs_init1(PatternArgs *self, uint8_t a1); +void PatternArgs_init2(PatternArgs *self, uint8_t a1, uint8_t a2); +void PatternArgs_init3(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3); +void PatternArgs_init4(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4); +void PatternArgs_init5(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5); +void PatternArgs_init6(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6); +void PatternArgs_init7(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7); +void PatternArgs_init8(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7, uint8_t a8); + +void PatternArgs_addArgs1(PatternArgs *self, uint8_t a1); +void PatternArgs_addArgs2(PatternArgs *self, uint8_t a1, uint8_t a2); +void PatternArgs_addArgs3(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3); +void PatternArgs_addArgs4(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4); +void PatternArgs_addArgs5(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5); +void PatternArgs_addArgs6(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6); +void PatternArgs_addArgs7(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7); +void PatternArgs_addArgs8(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7, uint8_t a8); + +bool PatternArgs_equals(const PatternArgs *self, const PatternArgs *rhs); +bool PatternArgs_notEquals(const PatternArgs *self, const PatternArgs *rhs); +uint8_t PatternArgs_get(const PatternArgs *self, int index); + +bool PatternArgs_serialize(const PatternArgs *self, ByteStream *buffer, ArgMap argmap); +ArgMap PatternArgs_unserialize(PatternArgs *self, ByteStream *buffer); #endif diff --git a/VortexEngine/src/Patterns/PatternBuilder.c b/VortexEngine/src/Patterns/PatternBuilder.c new file mode 100644 index 0000000000..5fb6c881e6 --- /dev/null +++ b/VortexEngine/src/Patterns/PatternBuilder.c @@ -0,0 +1,394 @@ +#include "PatternBuilder.h" + +#include "../Serial/ByteStream.h" +#include "../Time/TimeControl.h" +#include "../Time/Timings.h" +#include "../Log/Log.h" +#include "../Memory/Memory.h" + +#include "Pattern.h" + +#include "Single/SingleLedPattern.h" +#include "Single/BasicPattern.h" +#include "Single/BlendPattern.h" +#include "Single/SolidPattern.h" + +#if VORTEX_SLIM == 0 +#include "Multi/Sequencer/SequencedPattern.h" +#include "Multi/Sequencer/ChaserPattern.h" +#include "Multi/Sequencer/Sequence.h" + +#include "Multi/TheaterChasePattern.h" +#include "Multi/HueShiftPattern.h" +#include "Multi/ZigzagPattern.h" +#include "Multi/DripPattern.h" +#include "Multi/DripMorphPattern.h" +#include "Multi/CrossDopsPattern.h" +#include "Multi/DoubleStrobePattern.h" +#include "Multi/MeteorPattern.h" +#include "Multi/SparkleTracePattern.h" +#include "Multi/VortexWipePattern.h" +#include "Multi/WarpPattern.h" +#include "Multi/WarpWormPattern.h" +#include "Multi/FillPattern.h" +#include "Multi/SnowballPattern.h" +#include "Multi/LighthousePattern.h" +#include "Multi/PulsishPattern.h" +#include "Multi/BouncePattern.h" +#include "Multi/BackStrobePattern.h" +#include "Multi/VortexPattern.h" +#endif + +// Forward declarations +static Pattern *PatternBuilder_generate(PatternID id, const PatternArgs *args); +static Pattern *PatternBuilder_makeInternal(PatternID id, const PatternArgs *args); + +Pattern *PatternBuilder_make(PatternID id, const PatternArgs *args) +{ +#if VORTEX_SLIM == 0 + if (id > PATTERN_LAST) { +#else + if (id > PATTERN_SINGLE_LAST) { +#endif + DEBUG_LOGF("Invalid pattern id: %u", id); + id = PATTERN_FIRST; + } +#if VORTEX_SLIM == 0 + if (isMultiLedPatternID(id)) { + return PatternBuilder_makeMulti(id, args); + } +#endif + return PatternBuilder_makeSingle(id, args); +} + +Pattern *PatternBuilder_dupe(const Pattern *pat) +{ + if (!pat) { + return NULL; + } + PatternArgs args; + Pattern_getArgs(pat, &args); + Pattern *newPat = PatternBuilder_make(Pattern_getPatternID(pat), &args); + if (!newPat) { + return NULL; + } + Colorset _cs = Pattern_getColorset(pat); + Pattern_setColorset(newPat, &_cs); + Pattern_bind(newPat, Pattern_getLedPos(pat)); + return newPat; +} + +Pattern *PatternBuilder_makeSingle(PatternID id, const PatternArgs *args) +{ + if (!isSingleLedPatternID(id)) { + return NULL; + } + return PatternBuilder_makeInternal(id, args); +} + +Pattern *PatternBuilder_makeMulti(PatternID id, const PatternArgs *args) +{ + if (!isMultiLedPatternID(id)) { + return NULL; + } + return PatternBuilder_makeInternal(id, args); +} + +Pattern *PatternBuilder_unserialize(ByteStream *buffer) +{ + PatternID id = PATTERN_NONE; + if (!ByteStream_unserialize8(buffer, (uint8_t *)&id)) { + return NULL; + } + Pattern *pat = PatternBuilder_make(id, NULL); + if (!pat) { + return NULL; + } + if (!Pattern_unserialize(pat, buffer)) { + Pattern_destroy(pat); + vfree(pat); + return NULL; + } + return pat; +} + +Pattern *PatternBuilder_makeInternal(PatternID id, const PatternArgs *args) +{ + if (id == PATTERN_NONE) { + return NULL; + } + Pattern *pat = PatternBuilder_generate(id, args); + if (!pat) { + ERROR_OUT_OF_MEMORY(); + return NULL; + } + pat->patternID = id; + return pat; +} + +PatternArgs PatternBuilder_getDefaultArgs(PatternID id) +{ + PatternArgs args; + PatternArgs_init(&args); + switch (id) { + case PATTERN_STROBE: PatternArgs_init2(&args, STROBE_ON_DURATION, STROBE_OFF_DURATION); break; + case PATTERN_HYPERSTROBE: PatternArgs_init2(&args, HYPERSTROBE_ON_DURATION, HYPERSTROBE_OFF_DURATION); break; + case PATTERN_PICOSTROBE: PatternArgs_init2(&args, PICOSTROBE_ON_DURATION, PICOSTROBE_OFF_DURATION); break; + case PATTERN_STROBIE: PatternArgs_init2(&args, STROBIE_ON_DURATION, STROBIE_OFF_DURATION); break; + case PATTERN_DOPS: PatternArgs_init2(&args, DOPS_ON_DURATION, DOPS_OFF_DURATION); break; + case PATTERN_ULTRADOPS: PatternArgs_init2(&args, ULTRADOPS_ON_DURATION, ULTRADOPS_OFF_DURATION); break; + case PATTERN_STROBEGAP: PatternArgs_init3(&args, STROBE_ON_DURATION, STROBE_OFF_DURATION, 25); break; + case PATTERN_HYPERGAP: PatternArgs_init3(&args, HYPERSTROBE_ON_DURATION, HYPERSTROBE_OFF_DURATION, 218); break; + case PATTERN_PICOGAP: PatternArgs_init3(&args, PICOSTROBE_ON_DURATION, PICOSTROBE_OFF_DURATION, 90); break; + case PATTERN_STROBIEGAP: PatternArgs_init3(&args, STROBIE_ON_DURATION, STROBIE_OFF_DURATION, 100); break; + case PATTERN_DOPSGAP: PatternArgs_init3(&args, DOPS_ON_DURATION, DOPS_OFF_DURATION, 40); break; + case PATTERN_ULTRAGAP: PatternArgs_init3(&args, ULTRADOPS_ON_DURATION, ULTRADOPS_OFF_DURATION, 55); break; + case PATTERN_BLINKIE: PatternArgs_init3(&args, 3, 1, 65); break; + case PATTERN_GHOSTCRUSH: PatternArgs_init3(&args, 3, 1, 18); break; + case PATTERN_DOUBLEDOPS: PatternArgs_init5(&args, ULTRADOPS_ON_DURATION, ULTRADOPS_OFF_DURATION, DOPS_OFF_DURATION, 0, 2); break; + case PATTERN_CHOPPER: PatternArgs_init4(&args, ULTRADOPS_ON_DURATION, STROBIE_OFF_DURATION, 0, STROBE_ON_DURATION); break; + case PATTERN_DASHGAP: PatternArgs_init4(&args, ULTRADOPS_ON_DURATION, ULTRADOPS_OFF_DURATION, 20, 20); break; + case PATTERN_DASHDOPS: PatternArgs_init4(&args, DOPS_ON_DURATION, DOPS_OFF_DURATION, DOPS_OFF_DURATION, 18); break; + case PATTERN_DASHCRUSH: PatternArgs_init4(&args, 4, 1, 10, 18); break; + case PATTERN_ULTRADASH: PatternArgs_init4(&args, ULTRADOPS_ON_DURATION, ULTRADOPS_OFF_DURATION, ULTRADOPS_OFF_DURATION, 14); break; + case PATTERN_GAPCYCLE: PatternArgs_init5(&args, STROBIE_ON_DURATION, STROBE_OFF_DURATION, 12, 25, 2); break; + case PATTERN_DASHCYCLE: PatternArgs_init5(&args, ULTRADOPS_ON_DURATION, ULTRADOPS_OFF_DURATION, ULTRADOPS_OFF_DURATION, 30, 2); break; + case PATTERN_TRACER: PatternArgs_init5(&args, 3, 0, 0, 20, 1); break; + case PATTERN_RIBBON: PatternArgs_init1(&args, RIBBON_DURATION); break; + case PATTERN_MINIRIBBON: PatternArgs_init1(&args, 1); break; + case PATTERN_BLEND: PatternArgs_init7(&args, BLEND_ON_DURATION, BLEND_OFF_DURATION, 0, 0, 0, 5, 0); break; + case PATTERN_BLENDSTROBE: PatternArgs_init7(&args, STROBE_ON_DURATION, STROBE_OFF_DURATION, 0, 0, 0, 10, 0); break; + case PATTERN_BLENDSTROBEGAP: PatternArgs_init7(&args, STROBE_ON_DURATION, STROBE_OFF_DURATION, 25, 0, 0, 10, 0); break; + case PATTERN_COMPLEMENTARY_BLEND: PatternArgs_init7(&args, BLEND_ON_DURATION, BLEND_OFF_DURATION, 0, 0, 0, 5, 1); break; + case PATTERN_COMPLEMENTARY_BLENDSTROBE: PatternArgs_init7(&args, STROBE_ON_DURATION, STROBE_OFF_DURATION, 0, 0, 0, 10, 1); break; + case PATTERN_COMPLEMENTARY_BLENDSTROBEGAP: PatternArgs_init7(&args, STROBE_ON_DURATION, STROBE_OFF_DURATION, 25, 0, 0, 10, 1); break; + case PATTERN_SOLID: PatternArgs_init1(&args, 250); break; + +#if VORTEX_SLIM == 0 + case PATTERN_HUE_SCROLL: PatternArgs_init3(&args, 1, 1, 10); break; + case PATTERN_THEATER_CHASE: PatternArgs_init3(&args, DOPS_ON_DURATION, DOPS_OFF_DURATION, 28); break; + case PATTERN_CHASER: PatternArgs_init(&args); break; + case PATTERN_ZIGZAG: PatternArgs_init5(&args, DOPS_ON_DURATION, 3, 3, 5, 55); break; + case PATTERN_ZIPFADE: PatternArgs_init5(&args, DOPS_ON_DURATION, 2, 75, 9, 230); break; + case PATTERN_DRIP: PatternArgs_init3(&args, STROBE_ON_DURATION, STROBE_OFF_DURATION, 150); break; + case PATTERN_DRIPMORPH: PatternArgs_init3(&args, STROBE_ON_DURATION, STROBE_OFF_DURATION, 1); break; + case PATTERN_CROSSDOPS: PatternArgs_init3(&args, DOPS_ON_DURATION, 2, 25); break; + case PATTERN_DOUBLESTROBE: PatternArgs_init3(&args, DOPS_ON_DURATION, DOPS_OFF_DURATION, 115); break; + case PATTERN_METEOR: PatternArgs_init4(&args, 1, 1, 20, 130); break; + case PATTERN_SPARKLETRACE: PatternArgs_init3(&args, 1, 5, 3); break; + case PATTERN_VORTEXWIPE: PatternArgs_init3(&args, DOPS_ON_DURATION, 3, 80); break; + case PATTERN_WARP: PatternArgs_init3(&args, 3, DOPS_OFF_DURATION, 50); break; + case PATTERN_WARPWORM: PatternArgs_init3(&args, DOPS_ON_DURATION, 10, 100); break; + case PATTERN_SNOWBALL: PatternArgs_init3(&args, 3, 3, 33); break; + case PATTERN_LIGHTHOUSE: PatternArgs_init5(&args, DOPS_ON_DURATION, 5, 22, 3, 3); break; + case PATTERN_PULSISH: PatternArgs_init5(&args, DOPS_ON_DURATION, 6, 5, 1, 100); break; + case PATTERN_FILL: PatternArgs_init3(&args, DOPS_ON_DURATION, 5, 50); break; + case PATTERN_BOUNCE: PatternArgs_init4(&args, 10, 5, 50, 10); break; + case PATTERN_SPLITSTROBIE: PatternArgs_init8(&args, DOPS_ON_DURATION, DOPS_OFF_DURATION, 0, 16, 3, 10, PATTERN_DOPS, PATTERN_STROBIE); break; + case PATTERN_BACKSTROBE: PatternArgs_init8(&args, DOPS_ON_DURATION, DOPS_OFF_DURATION, 0, HYPERSTROBE_ON_DURATION, HYPERSTROBE_OFF_DURATION, 10, PATTERN_DOPS, PATTERN_HYPERSTROBE); break; + case PATTERN_VORTEX: PatternArgs_init3(&args, 1, 1, 130); break; +#else + default: PatternArgs_init2(&args, DOPS_ON_DURATION, DOPS_OFF_DURATION); break; +#endif + } + return args; +} + +uint8_t PatternBuilder_numDefaultArgs(PatternID id) +{ + Pattern *pat = PatternBuilder_make(id, NULL); + if (!pat) { + return 0; + } + uint8_t numArgs = Pattern_getNumArgs(pat); + Pattern_destroy(pat); + vfree(pat); + return numArgs; +} + +static Pattern *PatternBuilder_generate(PatternID id, const PatternArgs *userArgs) +{ + PatternArgs defaultArgs = PatternBuilder_getDefaultArgs(id); + const PatternArgs *args = userArgs ? userArgs : &defaultArgs; + + switch (id) { + default: + case PATTERN_STROBE: + case PATTERN_HYPERSTROBE: + case PATTERN_PICOSTROBE: + case PATTERN_STROBIE: + case PATTERN_DOPS: + case PATTERN_ULTRADOPS: + case PATTERN_STROBEGAP: + case PATTERN_HYPERGAP: + case PATTERN_PICOGAP: + case PATTERN_STROBIEGAP: + case PATTERN_DOPSGAP: + case PATTERN_ULTRAGAP: + case PATTERN_BLINKIE: + case PATTERN_GHOSTCRUSH: + case PATTERN_DOUBLEDOPS: + case PATTERN_CHOPPER: + case PATTERN_DASHGAP: + case PATTERN_DASHDOPS: + case PATTERN_DASHCRUSH: + case PATTERN_ULTRADASH: + case PATTERN_GAPCYCLE: + case PATTERN_DASHCYCLE: + case PATTERN_TRACER: + case PATTERN_RIBBON: + case PATTERN_MINIRIBBON: { + BasicPattern *bp = (BasicPattern *)vmalloc(sizeof(BasicPattern)); + if (!bp) return NULL; + BasicPattern_init(bp, args); + return &bp->base.base; + } + case PATTERN_BLEND: + case PATTERN_BLENDSTROBE: + case PATTERN_BLENDSTROBEGAP: + case PATTERN_COMPLEMENTARY_BLEND: + case PATTERN_COMPLEMENTARY_BLENDSTROBE: + case PATTERN_COMPLEMENTARY_BLENDSTROBEGAP: { + BlendPattern *bp = (BlendPattern *)vmalloc(sizeof(BlendPattern)); + if (!bp) return NULL; + BlendPattern_init(bp, args); + return &bp->base.base.base; + } + case PATTERN_SOLID: { + SolidPattern *sp = (SolidPattern *)vmalloc(sizeof(SolidPattern)); + if (!sp) return NULL; + SolidPattern_init(sp, args); + return &sp->base.base.base; + } + +#if VORTEX_SLIM == 0 + case PATTERN_HUE_SCROLL: { + HueShiftPattern *p = (HueShiftPattern *)vmalloc(sizeof(HueShiftPattern)); + if (!p) return NULL; + HueShiftPattern_init(p, args); + return &p->base; + } + case PATTERN_THEATER_CHASE: { + TheaterChasePattern *p = (TheaterChasePattern *)vmalloc(sizeof(TheaterChasePattern)); + if (!p) return NULL; + TheaterChasePattern_init(p, args); + return &p->base; + } + case PATTERN_CHASER: { + ChaserPattern *p = (ChaserPattern *)vmalloc(sizeof(ChaserPattern)); + if (!p) return NULL; + ChaserPattern_init(p, args); + return &p->base; + } + case PATTERN_ZIGZAG: + case PATTERN_ZIPFADE: { + ZigzagPattern *p = (ZigzagPattern *)vmalloc(sizeof(ZigzagPattern)); + if (!p) return NULL; + ZigzagPattern_init(p, args); + return &p->base; + } + case PATTERN_DRIP: { + DripPattern *p = (DripPattern *)vmalloc(sizeof(DripPattern)); + if (!p) return NULL; + DripPattern_init(p, args); + return &p->base; + } + case PATTERN_DRIPMORPH: { + DripMorphPattern *p = (DripMorphPattern *)vmalloc(sizeof(DripMorphPattern)); + if (!p) return NULL; + DripMorphPattern_init(p, args); + return &p->base; + } + case PATTERN_CROSSDOPS: { + CrossDopsPattern *p = (CrossDopsPattern *)vmalloc(sizeof(CrossDopsPattern)); + if (!p) return NULL; + CrossDopsPattern_init(p, args); + return &p->base; + } + case PATTERN_DOUBLESTROBE: { + DoubleStrobePattern *p = (DoubleStrobePattern *)vmalloc(sizeof(DoubleStrobePattern)); + if (!p) return NULL; + DoubleStrobePattern_init(p, args); + return &p->base; + } + case PATTERN_METEOR: { + MeteorPattern *p = (MeteorPattern *)vmalloc(sizeof(MeteorPattern)); + if (!p) return NULL; + MeteorPattern_init(p, args); + return &p->base; + } + case PATTERN_SPARKLETRACE: { + SparkleTracePattern *p = (SparkleTracePattern *)vmalloc(sizeof(SparkleTracePattern)); + if (!p) return NULL; + SparkleTracePattern_init(p, args); + return &p->base; + } + case PATTERN_VORTEXWIPE: { + VortexWipePattern *p = (VortexWipePattern *)vmalloc(sizeof(VortexWipePattern)); + if (!p) return NULL; + VortexWipePattern_init(p, args); + return &p->base; + } + case PATTERN_WARP: { + WarpPattern *p = (WarpPattern *)vmalloc(sizeof(WarpPattern)); + if (!p) return NULL; + WarpPattern_init(p, args); + return &p->base; + } + case PATTERN_WARPWORM: { + WarpWormPattern *p = (WarpWormPattern *)vmalloc(sizeof(WarpWormPattern)); + if (!p) return NULL; + WarpWormPattern_init(p, args); + return &p->base; + } + case PATTERN_SNOWBALL: { + SnowballPattern *p = (SnowballPattern *)vmalloc(sizeof(SnowballPattern)); + if (!p) return NULL; + SnowballPattern_init(p, args); + return &p->base; + } + case PATTERN_LIGHTHOUSE: { + LighthousePattern *p = (LighthousePattern *)vmalloc(sizeof(LighthousePattern)); + if (!p) return NULL; + LighthousePattern_init(p, args); + return &p->base; + } + case PATTERN_PULSISH: { + PulsishPattern *p = (PulsishPattern *)vmalloc(sizeof(PulsishPattern)); + if (!p) return NULL; + PulsishPattern_init(p, args); + return &p->base; + } + case PATTERN_FILL: { + FillPattern *p = (FillPattern *)vmalloc(sizeof(FillPattern)); + if (!p) return NULL; + FillPattern_init(p, args); + return &p->base; + } + case PATTERN_BOUNCE: { + BouncePattern *p = (BouncePattern *)vmalloc(sizeof(BouncePattern)); + if (!p) return NULL; + BouncePattern_init(p, args); + return &p->base; + } + case PATTERN_SPLITSTROBIE: + case PATTERN_BACKSTROBE: { + BackStrobePattern *p = (BackStrobePattern *)vmalloc(sizeof(BackStrobePattern)); + if (!p) return NULL; + BackStrobePattern_init(p, args); + return &p->base; + } + case PATTERN_VORTEX: { + VortexPattern *p = (VortexPattern *)vmalloc(sizeof(VortexPattern)); + if (!p) return NULL; + VortexPattern_init(p, args); + return &p->base; + } + case PATTERN_NONE: return NULL; +#else + case PATTERN_NONE: return NULL; +#endif + } + DEBUG_LOGF("Unknown pattern id: %u", id); + return NULL; +} diff --git a/VortexEngine/src/Patterns/PatternBuilder.h b/VortexEngine/src/Patterns/PatternBuilder.h index 7467f58888..a49d729e51 100644 --- a/VortexEngine/src/Patterns/PatternBuilder.h +++ b/VortexEngine/src/Patterns/PatternBuilder.h @@ -1,42 +1,21 @@ #ifndef PATTERN_BUILDER_H #define PATTERN_BUILDER_H -#include "Patterns.h" - -class Pattern; -class PatternArgs; -class ByteStream; -class MultiLedPattern; -class SingleLedPattern; - -class PatternBuilder -{ -public: - // generic make any pattern - static Pattern *make(PatternID id, const PatternArgs *args = nullptr); - - // make a copy of an existing pattern - static Pattern *dupe(const Pattern *pat); - - // generate a single LED pattern (nullptr if patternid is not single LED) - static SingleLedPattern *makeSingle(PatternID id, const PatternArgs *args = nullptr); +#include +#include - // generate a multi LED pattern (nullptr if patternid is not multi LED) - static MultiLedPattern *makeMulti(PatternID id, const PatternArgs *args = nullptr); - - // unserialize a buffer into a pattern - static Pattern *unserialize(ByteStream &buffer); - - // This is just the default arguments for any given pattern id - // it will *not* indicate the true amount of arguments a pattern has - static PatternArgs getDefaultArgs(PatternID id); - // this will give you actual amount of default args - static uint8_t numDefaultArgs(PatternID id); - -private: - // helper routines - static Pattern *makeInternal(PatternID id, const PatternArgs *args = nullptr); - static Pattern *generate(PatternID id, const PatternArgs *args = nullptr); -}; +#include "Patterns.h" +#include "PatternArgs.h" + +typedef struct ByteStream ByteStream; +typedef struct Pattern Pattern; + +Pattern *PatternBuilder_make(PatternID id, const PatternArgs *args); +Pattern *PatternBuilder_dupe(const Pattern *pat); +Pattern *PatternBuilder_makeSingle(PatternID id, const PatternArgs *args); +Pattern *PatternBuilder_makeMulti(PatternID id, const PatternArgs *args); +Pattern *PatternBuilder_unserialize(ByteStream *buffer); +PatternArgs PatternBuilder_getDefaultArgs(PatternID id); +uint8_t PatternBuilder_numDefaultArgs(PatternID id); #endif diff --git a/VortexEngine/src/Patterns/Patterns.h b/VortexEngine/src/Patterns/Patterns.h index 65f3b90bb7..e690eb4e00 100644 --- a/VortexEngine/src/Patterns/Patterns.h +++ b/VortexEngine/src/Patterns/Patterns.h @@ -2,26 +2,14 @@ #define PATTERNS_H #include +#include -// List of patterns that can be built, both single and multi-led patterns are found in this list. -// Within both single and multi LED pattern lists there are 'core' patterns which are associated -// with a class, and there are 'shell' patterns which are simply wrapperns around another pattern -// with different parameters passed to the constructor. There is no way to know which patterns -// are 'core' patterns, except by looking at PatternBuilder::generate to see which classes exist -enum PatternID : int8_t -{ - // no pattern at all, use this sparingly and default to - // PATTERN_FIRST when possible - PATTERN_NONE = (PatternID)-1, +typedef int8_t PatternID; - // first pattern of all +enum { + PATTERN_NONE = -1, PATTERN_FIRST = 0, - // first 'single led' pattern PATTERN_SINGLE_FIRST = PATTERN_FIRST, - // ===================================== - - // all 'single led' patterns below - PATTERN_STROBE = PATTERN_FIRST, PATTERN_HYPERSTROBE, PATTERN_PICOSTROBE, @@ -54,20 +42,9 @@ enum PatternID : int8_t PATTERN_COMPLEMENTARY_BLENDSTROBE, PATTERN_COMPLEMENTARY_BLENDSTROBEGAP, PATTERN_SOLID, - - // ADD NEW SINGLE LED PATTERNS HERE - // WARNING This will offset all multi-led patterns so sharing of patterns - // will be misaligned, for ex. IR and save files. - - // ===================================== - // Pattern Meta Constants: PATTERN_MULTI_FIRST, PATTERN_SINGLE_LAST = (PATTERN_MULTI_FIRST - 1), PATTERN_SINGLE_COUNT = (PATTERN_SINGLE_LAST - PATTERN_SINGLE_FIRST) + 1, - // ===================================== - - // all 'multi led' patterns below - PATTERN_HUE_SCROLL = PATTERN_MULTI_FIRST, PATTERN_THEATER_CHASE, PATTERN_CHASER, @@ -90,44 +67,22 @@ enum PatternID : int8_t PATTERN_SPLITSTROBIE, PATTERN_BACKSTROBE, PATTERN_VORTEX, - - // ADD NEW MULTI LED PATTERNS HERE - - // ===================================== - INTERNAL_PATTERNS_END, // <<< DON'T USE OR TOUCH THIS ONE + INTERNAL_PATTERNS_END, PATTERN_MULTI_LAST = (INTERNAL_PATTERNS_END - 1), PATTERN_MULTI_COUNT = (PATTERN_MULTI_LAST - PATTERN_MULTI_FIRST) + 1, PATTERN_LAST = PATTERN_MULTI_LAST, - PATTERN_COUNT = (PATTERN_LAST - PATTERN_FIRST) + 1, // total number of patterns + PATTERN_COUNT = (PATTERN_LAST - PATTERN_FIRST) + 1, }; -// some helper functions to improve readability -inline bool isMultiLedPatternID(PatternID id) { +static inline bool isMultiLedPatternID(PatternID id) { return id >= PATTERN_MULTI_FIRST && id <= PATTERN_MULTI_LAST; } -inline bool isSingleLedPatternID(PatternID id) { +static inline bool isSingleLedPatternID(PatternID id) { return id < PATTERN_MULTI_FIRST; } -// PatternID operators -inline PatternID &operator++(PatternID &c) -{ - c = PatternID(((uint16_t)c) + 1); - return c; -} -inline PatternID operator++(PatternID &c, int) -{ - PatternID temp = c; - ++c; - return temp; -} -inline PatternID operator+(PatternID &c, int b) -{ - return (PatternID)((uint16_t)c + b); -} -inline PatternID operator-(PatternID &c, int b) -{ - return (PatternID)((uint16_t)c - b); -} +static inline PatternID patternIDIncr(PatternID c) { return (PatternID)((uint16_t)c + 1); } +static inline PatternID patternIDAdd(PatternID c, int b) { return (PatternID)((uint16_t)c + (uint16_t)b); } +static inline PatternID patternIDSub(PatternID c, int b) { return (PatternID)((uint16_t)c - (uint16_t)b); } #endif diff --git a/VortexEngine/src/Patterns/Single/BasicPattern.c b/VortexEngine/src/Patterns/Single/BasicPattern.c new file mode 100644 index 0000000000..21276606b1 --- /dev/null +++ b/VortexEngine/src/Patterns/Single/BasicPattern.c @@ -0,0 +1,158 @@ +#include "BasicPattern.h" + +#include "../../Time/TimeControl.h" +#include "../../Colors/Colorset.h" +#include "../../Leds/Leds.h" + +const PatternVTable BasicPattern_vtable = { + NULL, + BasicPattern_play, + BasicPattern_initVirtual, + Pattern_bindBase, + BasicPattern_onBlinkOn, + BasicPattern_onBlinkOff, + BasicPattern_beginGap, + BasicPattern_beginDash +}; + +void BasicPattern_init(BasicPattern *self, const PatternArgs *args) +{ + SingleLedPattern_init(&self->base, args); + self->base.base.vtable = &BasicPattern_vtable; + self->onDuration = 0; + self->offDuration = 0; + self->gapDuration = 0; + self->dashDuration = 0; + self->groupSize = 0; + self->groupCounter = 0; + self->state = STATE_BLINK_ON; + Timer_init(&self->blinkTimer); + self->base.base.patternID = PATTERN_STROBE; + REGISTER_ARG(self, self->onDuration); + REGISTER_ARG(self, self->offDuration); + REGISTER_ARG(self, self->gapDuration); + REGISTER_ARG(self, self->dashDuration); + REGISTER_ARG(self, self->groupSize); + Pattern_setArgs(&self->base.base, args); +} + +void BasicPattern_initVirtual(Pattern *base) +{ + BasicPattern *self = (BasicPattern *)base; + + Pattern_initBase(base); + + self->state = STATE_BLINK_ON; + if (self->dashDuration > 0) { + self->state = STATE_BEGIN_DASH; + } + if ((!self->onDuration && !self->dashDuration) || !Colorset_numColors(&base->colorset)) { + self->state = STATE_DISABLED; + } + self->groupCounter = self->groupSize ? self->groupSize : (Colorset_numColors(&base->colorset) - (self->dashDuration != 0)); +} + +static void nextState(BasicPattern *self, uint8_t timing) +{ + Timer_initFlags(&self->blinkTimer, TIMER_1_ALARM | TIMER_START, timing, 0, 0, 0); + self->state = (uint8_t)(self->state + 1); +} + +void BasicPattern_play(Pattern *base) +{ + BasicPattern *self = (BasicPattern *)base; + +replay: + + switch (self->state) { + case STATE_DISABLED: + return; + case STATE_BLINK_ON: + if (self->onDuration > 0) { + base->vtable->onBlinkOn(base); + --self->groupCounter; + nextState(self, self->onDuration); + return; + } + self->state = STATE_BLINK_OFF; + case STATE_BLINK_OFF: + if (self->groupCounter > 0 || (!self->gapDuration && !self->dashDuration)) { + if (self->offDuration > 0) { + base->vtable->onBlinkOff(base); + nextState(self, self->offDuration); + return; + } + if (self->groupCounter > 0 && self->onDuration > 0) { + self->state = STATE_BLINK_ON; + goto replay; + } + } + self->state = STATE_BEGIN_GAP; + case STATE_BEGIN_GAP: + self->groupCounter = self->groupSize ? self->groupSize : (Colorset_numColors(&base->colorset) - (self->dashDuration != 0)); + if (self->gapDuration > 0) { + base->vtable->beginGap(base); + nextState(self, self->gapDuration); + return; + } + self->state = STATE_BEGIN_DASH; + case STATE_BEGIN_DASH: + if (self->dashDuration > 0) { + base->vtable->beginDash(base); + nextState(self, self->dashDuration); + return; + } + self->state = STATE_BEGIN_GAP2; + case STATE_BEGIN_GAP2: + if (self->dashDuration > 0 && self->gapDuration > 0) { + base->vtable->beginGap(base); + nextState(self, self->gapDuration); + return; + } + self->state = STATE_BLINK_ON; + goto replay; + default: + break; + } + + if (Timer_alarm(&self->blinkTimer) == ALARM_NONE) { + return; + } + + if (self->state == STATE_IN_GAP2 || (self->state == STATE_OFF && self->groupCounter > 0)) { + self->state = self->onDuration ? STATE_BLINK_ON : (self->dashDuration ? STATE_BEGIN_DASH : STATE_BEGIN_GAP); + } else if (self->state == STATE_OFF && (!self->groupCounter || Colorset_numColors(&base->colorset) == 1)) { + self->state = (self->groupCounter > 0) ? STATE_BLINK_ON : STATE_BEGIN_GAP; + } else { + self->state = (uint8_t)(self->state + 1); + } + goto replay; +} + +void BasicPattern_onBlinkOn(Pattern *base) +{ + BasicPattern *self = (BasicPattern *)base; + Leds_setIndex(base->ledPos, Colorset_getNext(&base->colorset)); + (void)self; +} + +void BasicPattern_onBlinkOff(Pattern *base) +{ + BasicPattern *self = (BasicPattern *)base; + Leds_clearIndex(base->ledPos); + (void)self; +} + +void BasicPattern_beginGap(Pattern *base) +{ + BasicPattern *self = (BasicPattern *)base; + Leds_clearIndex(base->ledPos); + (void)self; +} + +void BasicPattern_beginDash(Pattern *base) +{ + BasicPattern *self = (BasicPattern *)base; + Leds_setIndex(base->ledPos, Colorset_getNext(&base->colorset)); + (void)self; +} diff --git a/VortexEngine/src/Patterns/Single/BasicPattern.h b/VortexEngine/src/Patterns/Single/BasicPattern.h index 39cc660bd4..88b0b4c6dd 100644 --- a/VortexEngine/src/Patterns/Single/BasicPattern.h +++ b/VortexEngine/src/Patterns/Single/BasicPattern.h @@ -4,66 +4,38 @@ #include "SingleLedPattern.h" #include "../../Time/Timer.h" -class BasicPattern : public SingleLedPattern -{ -public: - BasicPattern(const PatternArgs &args); - virtual ~BasicPattern(); - - virtual void init() override; - virtual void play() override; - -protected: - // when blinking off - virtual void onBlinkOn(); - virtual void onBlinkOff(); - virtual void beginGap(); - virtual void beginDash(); - - // iterate to next state - void nextState(uint8_t timing); - - // the parameters of the pattern - uint8_t m_onDuration; - uint8_t m_offDuration; - uint8_t m_gapDuration; - uint8_t m_dashDuration; - uint8_t m_groupSize; - - uint8_t m_groupCounter; - - // the various different blinking states the pattern can be in - enum PatternState : uint8_t - { - // the led is disabled (there is no on or dash) - STATE_DISABLED, - - // the pattern is blinking on the next color in the set - STATE_BLINK_ON, - STATE_ON, - - // the pattern is blinking off - STATE_BLINK_OFF, - STATE_OFF, - - // the pattern is starting a gap after a colorset - STATE_BEGIN_GAP, - STATE_IN_GAP, - - // the pattern is beginning a dash after a colorset or gap - STATE_BEGIN_DASH, - STATE_IN_DASH, - - // the pattern is starting a gap after a dash - STATE_BEGIN_GAP2, - STATE_IN_GAP2, - }; - - // the state of the current pattern - PatternState m_state; - - // the blink timer used to measure blink timings - Timer m_blinkTimer; -}; +#define STATE_DISABLED 0 +#define STATE_BLINK_ON 1 +#define STATE_ON 2 +#define STATE_BLINK_OFF 3 +#define STATE_OFF 4 +#define STATE_BEGIN_GAP 5 +#define STATE_IN_GAP 6 +#define STATE_BEGIN_DASH 7 +#define STATE_IN_DASH 8 +#define STATE_BEGIN_GAP2 9 +#define STATE_IN_GAP2 10 + +typedef struct BasicPattern { + SingleLedPattern base; + uint8_t onDuration; + uint8_t offDuration; + uint8_t gapDuration; + uint8_t dashDuration; + uint8_t groupSize; + uint8_t groupCounter; + uint8_t state; + Timer blinkTimer; +} BasicPattern; + +extern const PatternVTable BasicPattern_vtable; + +void BasicPattern_init(BasicPattern *self, const PatternArgs *args); +void BasicPattern_play(Pattern *base); +void BasicPattern_initVirtual(Pattern *base); +void BasicPattern_onBlinkOn(Pattern *base); +void BasicPattern_onBlinkOff(Pattern *base); +void BasicPattern_beginGap(Pattern *base); +void BasicPattern_beginDash(Pattern *base); #endif diff --git a/VortexEngine/src/Patterns/Single/BlendPattern.c b/VortexEngine/src/Patterns/Single/BlendPattern.c new file mode 100644 index 0000000000..17aa7a6c2d --- /dev/null +++ b/VortexEngine/src/Patterns/Single/BlendPattern.c @@ -0,0 +1,78 @@ +#include "BlendPattern.h" + +#include "../../Time/TimeControl.h" +#include "../../Colors/Colorset.h" +#include "../../Leds/Leds.h" + +const PatternVTable BlendPattern_vtable = { + NULL, + BasicPattern_play, + BlendPattern_initVirtual, + Pattern_bindBase, + BlendPattern_onBlinkOn, + BasicPattern_onBlinkOff, + BasicPattern_beginGap, + BasicPattern_beginDash +}; + +void BlendPattern_init(BlendPattern *self, const PatternArgs *args) +{ + BasicPattern_init(&self->base, args); + self->base.base.base.vtable = &BlendPattern_vtable; + self->blendSpeed = 0; + self->numFlips = 0; + RGBColor_init(&self->cur); + RGBColor_init(&self->next); + self->flip = 0; + self->base.base.base.patternID = PATTERN_BLEND; + REGISTER_ARG(self, self->blendSpeed); + REGISTER_ARG(self, self->numFlips); + Pattern_setArgs(&self->base.base.base, args); +} + +void BlendPattern_initVirtual(Pattern *base) +{ + BlendPattern *self = (BlendPattern *)base; + + BasicPattern_initVirtual(base); + + self->cur = Colorset_getNext(&base->colorset); + self->next = Colorset_getNext(&base->colorset); + self->flip = 0; +} + +static void interpolate(uint8_t *current, const uint8_t next, uint8_t blendSpeed) +{ + if (*current < next) { + uint8_t step = (next - *current) > blendSpeed ? blendSpeed : (next - *current); + *current += step; + } else if (*current > next) { + uint8_t step = (*current - next) > blendSpeed ? blendSpeed : (*current - next); + *current -= step; + } +} + +void BlendPattern_onBlinkOn(Pattern *base) +{ + BlendPattern *self = (BlendPattern *)base; + + if (RGBColor_equals(&self->cur, &self->next)) { + self->next = Colorset_getNext(&base->colorset); + } + interpolate(&self->cur.red, self->next.red, self->blendSpeed); + interpolate(&self->cur.green, self->next.green, self->blendSpeed); + interpolate(&self->cur.blue, self->next.blue, self->blendSpeed); + RGBColor col = self->cur; + + if (self->flip && self->numFlips) { + HSVColor hsvCol; + HSVColor_initFromRGB(&hsvCol, &self->cur); + hsvCol.hue += (self->flip * (127 / self->numFlips)); + RGBColor_initFromHSV(&col, &hsvCol); + } + Leds_setIndex(base->ledPos, col); + self->flip++; + if (self->flip > self->numFlips) { + self->flip = 0; + } +} diff --git a/VortexEngine/src/Patterns/Single/BlendPattern.h b/VortexEngine/src/Patterns/Single/BlendPattern.h index bff3c1ff06..6407633880 100644 --- a/VortexEngine/src/Patterns/Single/BlendPattern.h +++ b/VortexEngine/src/Patterns/Single/BlendPattern.h @@ -2,33 +2,22 @@ #define BLEND_PATTERN_H #include "BasicPattern.h" - #include "../../Time/Timings.h" +#include "../../Colors/ColorTypes.h" -class BlendPattern : public BasicPattern -{ -public: - BlendPattern(const PatternArgs &args); - virtual ~BlendPattern(); - - virtual void init() override; - -protected: - // only override the onBlinkOn so we can control the color it blinks - virtual void onBlinkOn() override; - void interpolate(uint8_t ¤t, const uint8_t next); +typedef struct BlendPattern { + BasicPattern base; + uint8_t blendSpeed; + uint8_t numFlips; + RGBColor cur; + RGBColor next; + uint8_t flip; +} BlendPattern; - // offset of starting hue and number of flips - uint8_t m_blendSpeed; - uint8_t m_numFlips; +extern const PatternVTable BlendPattern_vtable; - // current color and target blend color - RGBColor m_cur; - RGBColor m_next; - - // the current flip counter - uint8_t m_flip; -}; +void BlendPattern_init(BlendPattern *self, const PatternArgs *args); +void BlendPattern_initVirtual(Pattern *base); +void BlendPattern_onBlinkOn(Pattern *base); #endif - diff --git a/VortexEngine/src/Patterns/Single/SingleLedPattern.c b/VortexEngine/src/Patterns/Single/SingleLedPattern.c new file mode 100644 index 0000000000..c0e35b1e84 --- /dev/null +++ b/VortexEngine/src/Patterns/Single/SingleLedPattern.c @@ -0,0 +1,6 @@ +#include "SingleLedPattern.h" + +void SingleLedPattern_init(SingleLedPattern *self, const PatternArgs *args) +{ + Pattern_init(&self->base, args); +} diff --git a/VortexEngine/src/Patterns/Single/SingleLedPattern.h b/VortexEngine/src/Patterns/Single/SingleLedPattern.h index 443045253c..a162175b1d 100644 --- a/VortexEngine/src/Patterns/Single/SingleLedPattern.h +++ b/VortexEngine/src/Patterns/Single/SingleLedPattern.h @@ -3,19 +3,10 @@ #include "../Pattern.h" -class SingleLedPattern : public Pattern -{ -protected: - // SingleLedPattern is an abstract class - SingleLedPattern(const PatternArgs &args); +typedef struct SingleLedPattern { + Pattern base; +} SingleLedPattern; -public: - virtual ~SingleLedPattern(); - - virtual void init() override; - -private: - -}; +void SingleLedPattern_init(SingleLedPattern *self, const PatternArgs *args); #endif diff --git a/VortexEngine/src/Patterns/Single/SolidPattern.c b/VortexEngine/src/Patterns/Single/SolidPattern.c new file mode 100644 index 0000000000..5603bb6bd1 --- /dev/null +++ b/VortexEngine/src/Patterns/Single/SolidPattern.c @@ -0,0 +1,33 @@ +#include "SolidPattern.h" + +const PatternVTable SolidPattern_vtable = { + NULL, + BasicPattern_play, + BasicPattern_initVirtual, + Pattern_bindBase, + SolidPattern_onBlinkOn, + BasicPattern_onBlinkOff, + BasicPattern_beginGap, + BasicPattern_beginDash +}; + +void SolidPattern_init(SolidPattern *self, const PatternArgs *args) +{ + BasicPattern_init(&self->base, args); + self->base.base.base.vtable = &SolidPattern_vtable; + self->colIndex = 0; + self->base.base.base.patternID = PATTERN_SOLID; + REGISTER_ARG(self, self->colIndex); + Pattern_setArgs(&self->base.base.base, args); +} + +void SolidPattern_onBlinkOn(Pattern *base) +{ + SolidPattern *self = (SolidPattern *)base; + if (self->colIndex == 0) { + Colorset_resetIndex(&base->colorset); + } else { + Colorset_setCurIndex(&base->colorset, self->colIndex - 1); + } + BasicPattern_onBlinkOn(base); +} diff --git a/VortexEngine/src/Patterns/Single/SolidPattern.h b/VortexEngine/src/Patterns/Single/SolidPattern.h index ac50b71da9..6fc94da6d1 100644 --- a/VortexEngine/src/Patterns/Single/SolidPattern.h +++ b/VortexEngine/src/Patterns/Single/SolidPattern.h @@ -2,19 +2,16 @@ #define SOLID_PATTERN_H #include "BasicPattern.h" - #include "../../Time/Timings.h" -class SolidPattern : public BasicPattern -{ -public: - SolidPattern(const PatternArgs &args); +typedef struct SolidPattern { + BasicPattern base; + uint8_t colIndex; +} SolidPattern; -protected: - // callbacks for blinking on/off, can be overridden by derived classes - virtual void onBlinkOn() override; +extern const PatternVTable SolidPattern_vtable; - uint8_t m_colIndex; -}; +void SolidPattern_init(SolidPattern *self, const PatternArgs *args); +void SolidPattern_onBlinkOn(Pattern *base); #endif diff --git a/VortexEngine/src/Random/Random.c b/VortexEngine/src/Random/Random.c new file mode 100644 index 0000000000..bb0fab9d38 --- /dev/null +++ b/VortexEngine/src/Random/Random.c @@ -0,0 +1,28 @@ +#include "Random.h" + +void Random_init(Random *self) { + self->seed = 0; +} + +void Random_initWithSeed(Random *self, uint32_t newseed) { + Random_init(self); + Random_seed(self, newseed); +} + +void Random_seed(Random *self, uint32_t newseed) { + self->seed = newseed ? newseed : 42; +} + +uint16_t Random_next16(Random *self, uint16_t minValue, uint16_t maxValue) { + self->seed = (self->seed * 1103515245 + 12345) & 0x7FFFFFFF; + uint32_t range = maxValue - minValue; + if (range != 0xFFFFFFFF) { + return ((self->seed >> 16) % (range + 1)) + minValue; + } + return (self->seed >> 16); +} + +uint8_t Random_next8(Random *self, uint8_t minValue, uint8_t maxValue) { + uint32_t result = Random_next16(self, minValue, maxValue); + return (uint8_t)result; +} diff --git a/VortexEngine/src/Random/Random.h b/VortexEngine/src/Random/Random.h index b413b7210f..11702de290 100644 --- a/VortexEngine/src/Random/Random.h +++ b/VortexEngine/src/Random/Random.h @@ -1,19 +1,17 @@ -#pragma once +#ifndef RANDOM_H +#define RANDOM_H #include +#include -class Random -{ -public: - Random(); - Random(uint32_t newseed); - ~Random(); +typedef struct Random_t { + uint32_t seed; +} Random; - void seed(uint32_t newseed); +void Random_init(Random *self); +void Random_initWithSeed(Random *self, uint32_t newseed); +void Random_seed(Random *self, uint32_t newseed); +uint8_t Random_next8(Random *self, uint8_t minValue, uint8_t maxValue); +uint16_t Random_next16(Random *self, uint16_t minValue, uint16_t maxValue); - uint8_t next8(uint8_t minValue = 0, uint8_t maxValue = 0xFF); - uint16_t next16(uint16_t minValue = 0, uint16_t maxValue = 0xFFFF); - -private: - uint32_t m_seed; -}; +#endif diff --git a/VortexEngine/src/Serial/BitStream.c b/VortexEngine/src/Serial/BitStream.c new file mode 100644 index 0000000000..d8a9c4dedf --- /dev/null +++ b/VortexEngine/src/Serial/BitStream.c @@ -0,0 +1,119 @@ +#include "BitStream.h" + +#include "../Memory/Memory.h" +#include "../Log/Log.h" + +#include + +void BitStream_init(BitStream *self) +{ + self->buf = NULL; + self->buf_size = 0; + self->bit_pos = 0; + self->buf_eof = false; + self->allocated = false; +} + +void BitStream_initBuf(BitStream *self, uint8_t *buf, uint32_t size) +{ + BitStream_init(self); + self->buf = buf; + self->buf_size = (uint16_t)size; + BitStream_resetPos(self); +} + +bool BitStream_initAlloc(BitStream *self, uint32_t size) +{ + if (self->buf) { + vfree(self->buf); + } + self->buf = (uint8_t *)vcalloc(1, size); + if (!self->buf) { + ERROR_OUT_OF_MEMORY(); + return false; + } + self->buf_size = (uint16_t)size; + self->allocated = true; + BitStream_resetPos(self); + return true; +} + +void BitStream_destroy(BitStream *self) +{ + if (self->allocated) { + vfree(self->buf); + } +} + +void BitStream_reset(BitStream *self) +{ + if (self->buf) { + memset(self->buf, 0, self->buf_size); + } + BitStream_resetPos(self); +} + +void BitStream_resetPos(BitStream *self) +{ + self->bit_pos = 0; + self->buf_eof = false; +} + +uint8_t BitStream_read1Bit(BitStream *self) +{ + if (self->buf_eof) { + return 0; + } + if (self->bit_pos >= (self->buf_size * 8)) { + self->buf_eof = true; + return 0; + } + uint32_t rv = (self->buf[self->bit_pos / 8] >> (7 - (self->bit_pos % 8))) & 1; + self->bit_pos++; + if (self->bit_pos >= (self->buf_size * 8)) { + self->buf_eof = true; + } + return (uint8_t)rv; +} + +void BitStream_write1Bit(BitStream *self, bool bit) +{ + if (self->buf_eof) { + return; + } + if (self->bit_pos >= (self->buf_size * 8)) { + self->buf_eof = true; + return; + } + uint8_t bitVal = (uint8_t)bit & 1; + self->buf[self->bit_pos / 8] |= bitVal << (7 - (self->bit_pos % 8)); + self->bit_pos++; + if (self->bit_pos >= (self->buf_size * 8)) { + self->buf_eof = true; + } +} + +uint8_t BitStream_readBits(BitStream *self, uint32_t numBits) +{ + uint32_t val = 0; + if (self->buf_eof) { + return 0; + } + for (uint32_t i = 0; i < numBits; ++i) { + val = (val << 1) | BitStream_read1Bit(self); + if (self->buf_eof) { + break; + } + } + if (self->bit_pos >= (self->buf_size * 8)) { + self->buf_eof = true; + } + return (uint8_t)val; +} + +void BitStream_writeBits(BitStream *self, uint32_t numBits, uint32_t val) +{ + for (uint32_t i = 0; i < numBits; ++i) { + BitStream_write1Bit(self, (val >> ((numBits - 1) - i)) & 1); + } +} diff --git a/VortexEngine/src/Serial/BitStream.h b/VortexEngine/src/Serial/BitStream.h index 87aba5d66e..8687b02220 100644 --- a/VortexEngine/src/Serial/BitStream.h +++ b/VortexEngine/src/Serial/BitStream.h @@ -2,50 +2,38 @@ #define BITSTREAM_H #include +#include -// A class to read/write a buffer of bits one bit at a time -class BitStream +typedef struct BitStream { -public: - BitStream(); - BitStream(uint32_t size); - BitStream(uint8_t *buf, uint32_t size); - ~BitStream(); - - // init the stream with a buffer - bool init(uint8_t *buf, uint32_t size); - // init the stream and allocate a buffer - bool init(uint32_t size); - - // clear the target buffer to 0 and reset position - void reset(); - // reset the reader/writer position - void resetPos(); - - // read/write a single bit in LSB - uint8_t read1Bit(); - void write1Bit(bool bit); - // read/write multiple bits from left to right at LSB - uint8_t readBits(uint32_t numBits); - void writeBits(uint32_t numBits, uint32_t val); - - // metainfo about the bit stream - bool eof() const { return m_buf_eof; } - bool allocated() const { return m_allocated; } - uint16_t size() const { return m_buf_size; } - const uint8_t *data() const { return m_buf; } - uint8_t peekData(uint8_t pos) const { return m_buf[pos]; } - const uint32_t *dwData() const { return (uint32_t *)m_buf; } - uint16_t dwordpos() const { return m_bit_pos / 32; } - uint16_t bytepos() const { return m_bit_pos / 8; } - uint16_t bitpos() const { return m_bit_pos; } - -private: - uint8_t *m_buf; - uint16_t m_buf_size; - uint16_t m_bit_pos; - bool m_buf_eof; - bool m_allocated; -}; + uint8_t *buf; + uint16_t buf_size; + uint16_t bit_pos; + bool buf_eof; + bool allocated; +} BitStream; + +void BitStream_init(BitStream *self); +void BitStream_initBuf(BitStream *self, uint8_t *buf, uint32_t size); +bool BitStream_initAlloc(BitStream *self, uint32_t size); +void BitStream_destroy(BitStream *self); + +void BitStream_reset(BitStream *self); +void BitStream_resetPos(BitStream *self); + +uint8_t BitStream_read1Bit(BitStream *self); +void BitStream_write1Bit(BitStream *self, bool bit); +uint8_t BitStream_readBits(BitStream *self, uint32_t numBits); +void BitStream_writeBits(BitStream *self, uint32_t numBits, uint32_t val); + +static inline bool BitStream_eof(const BitStream *self) { return self->buf_eof; } +static inline bool BitStream_allocated(const BitStream *self) { return self->allocated; } +static inline uint16_t BitStream_size(const BitStream *self) { return self->buf_size; } +static inline const uint8_t *BitStream_data(const BitStream *self) { return self->buf; } +static inline uint8_t BitStream_peekData(const BitStream *self, uint8_t pos) { return self->buf[pos]; } +static inline const uint32_t *BitStream_dwData(const BitStream *self) { return (const uint32_t *)self->buf; } +static inline uint16_t BitStream_dwordpos(const BitStream *self) { return self->bit_pos / 32; } +static inline uint16_t BitStream_bytepos(const BitStream *self) { return self->bit_pos / 8; } +static inline uint16_t BitStream_bitpos(const BitStream *self) { return self->bit_pos; } #endif diff --git a/VortexEngine/src/Serial/ByteStream.c b/VortexEngine/src/Serial/ByteStream.c new file mode 100644 index 0000000000..96d348f142 --- /dev/null +++ b/VortexEngine/src/Serial/ByteStream.c @@ -0,0 +1,556 @@ +#include "ByteStream.h" + +#include "../Serial/BitStream.h" +#include "../Memory/Memory.h" +#include "../Log/Log.h" + +#include + +#include "Compression.h" + +#define BUFFER_FLAG_COMRPESSED (1<<0) +#define BUFFER_FLAG_DIRTY (1<<1) + +// --- RawBuffer free functions --- + +uint32_t RawBuffer_hash(const RawBuffer *self) +{ + uint32_t hash = 5381; + for (uint32_t i = 0; i < self->size; ++i) { + hash = ((hash << 5) + hash) + self->buf[i]; + } + return hash; +} + +void RawBuffer_accumulate(RawBuffer *self, uint32_t val) +{ + self->crc32 = ((self->crc32 << 5) + self->crc32) + val; +} + +bool RawBuffer_verify(const RawBuffer *self) +{ + if (!self->size) { + return true; + } + if (RawBuffer_hash(self) != self->crc32) { + DEBUG_LOGF("CRC mismatch: %x should be %x", RawBuffer_hash(self), self->crc32); + return false; + } + return true; +} + +void RawBuffer_recalcCRC(RawBuffer *self) +{ + self->crc32 = RawBuffer_hash(self); +} + +// --- ByteStream functions --- + +void ByteStream_destroy(ByteStream *self) +{ + ByteStream_clear(self); +} + +void ByteStream_copy(ByteStream *self, const ByteStream *other) +{ + self->pData = NULL; + self->position = 0; + self->capacity = 0; + ByteStream_init(self, other->capacity, ByteStream_data(other)); + if (self->pData && other->pData) { + self->pData->flags = other->pData->flags; + self->pData->crc32 = other->pData->crc32; + self->pData->size = other->pData->size; + } +} + +void ByteStream_assign(ByteStream *self, const ByteStream *other) +{ + ByteStream_init(self, other->capacity, ByteStream_data(other)); + if (self->pData && other->pData) { + self->pData->flags = other->pData->flags; + self->pData->crc32 = other->pData->crc32; + self->pData->size = other->pData->size; + } +} + +void ByteStream_move(ByteStream *self, ByteStream *target) +{ + if (!target) { + return; + } + ByteStream_clear(target); + target->pData = self->pData; + target->position = self->position; + target->capacity = self->capacity; + self->pData = NULL; + self->position = 0; + self->capacity = 0; +} + +bool ByteStream_rawInit(ByteStream *self, const uint8_t *rawdata, uint32_t size) +{ + if (!rawdata || size < sizeof(RawBuffer)) { + DEBUG_LOGF("Cannot rawInit: %p %u", (const void *)rawdata, (unsigned)size); + return false; + } + self->capacity = (uint16_t)((size + 4) - (size % 4)); + self->pData = (RawBuffer *)vcalloc(1, (size_t)self->capacity + sizeof(RawBuffer)); + if (!self->pData) { + self->capacity = 0; + ERROR_OUT_OF_MEMORY(); + return false; + } + memcpy(self->pData, rawdata, size); + ByteStream_sanity(self); + return true; +} + +bool ByteStream_init(ByteStream *self, uint32_t size, const uint8_t *buf) +{ + ByteStream_clear(self); + if (size) { + self->capacity = (uint16_t)((size + 4) - (size % 4)); + self->pData = (RawBuffer *)vcalloc(1, (size_t)self->capacity + sizeof(RawBuffer)); + if (!self->pData) { + self->capacity = 0; + ERROR_OUT_OF_MEMORY(); + return false; + } + self->pData->size = 0; + self->pData->flags = 0; + self->pData->crc32 = 0; + memset(self->pData->buf, 0, self->capacity); + } + if (buf && self->pData) { + memcpy(self->pData->buf, buf, size); + self->pData->size = size; + RawBuffer_recalcCRC(self->pData); + } + return true; +} + +void ByteStream_clear(ByteStream *self) +{ + if (self->pData) { + vfree(self->pData); + self->pData = NULL; + } + self->capacity = 0; + self->position = 0; +} + +bool ByteStream_shrink(ByteStream *self) +{ + if (!self->pData) { + return false; + } + if (self->pData->size == self->capacity) { + return true; + } + self->capacity = (uint16_t)self->pData->size; + RawBuffer *temp = (RawBuffer *)vrealloc(self->pData, (size_t)self->pData->size + sizeof(RawBuffer)); + if (!temp) { + ERROR_OUT_OF_MEMORY(); + return false; + } + self->pData = temp; + return true; +} + +bool ByteStream_append(ByteStream *self, const ByteStream *other) +{ + if (!other->pData) { + return true; + } + if (!self->pData || other->pData->size > (self->capacity - ByteStream_size(self))) { + if (!ByteStream_extend(self, other->pData->size)) { + return false; + } + } + memcpy(ByteStream_frontSerializer(self), ByteStream_data(other), other->pData->size); + self->pData->size += other->pData->size; + return true; +} + +void ByteStream_trim(ByteStream *self, uint32_t bytes) +{ + if (!self->pData) { + return; + } + if (self->pData->size <= bytes) { + self->pData->size = 0; + } else { + self->pData->size -= bytes; + } + if (self->position >= self->pData->size) { + self->position = (uint16_t)self->pData->size; + } +} + +bool ByteStream_extend(ByteStream *self, uint32_t size) +{ + if (!size) { + return true; + } + if (!self->capacity) { + return ByteStream_init(self, size, NULL); + } + uint32_t buffer_size = (uint32_t)self->capacity + size + 3; + buffer_size -= buffer_size % 4; + uint32_t new_size = buffer_size + sizeof(RawBuffer); + RawBuffer *temp = (RawBuffer *)vrealloc(self->pData, new_size); + if (!temp) { + ERROR_OUT_OF_MEMORY(); + return false; + } + uint8_t *new_mem = ((uint8_t *)temp) + sizeof(RawBuffer) + self->capacity; + size_t new_mem_size = buffer_size - self->capacity; + memset(new_mem, 0, new_mem_size); + self->pData = temp; + self->capacity = (uint16_t)buffer_size; + return true; +} + +bool ByteStream_compress(ByteStream *self) +{ + if (!self->pData) { + DEBUG_LOG("No data to compress"); + return false; + } + ByteStream_recalcCRC(self, false); +#if VORTEX_SLIM == 0 + if (ByteStream_is_compressed(self)) { + return true; + } + ByteStream compressedBuffer; + ByteStream_init(&compressedBuffer, compress_size(self->pData->size), NULL); + int compressedSize = compress_buffer(self->pData->buf, compressedBuffer.pData->buf, + (int)self->pData->size, (int)compressedBuffer.capacity); + if (compressedSize < 0) { + DEBUG_LOGF("Failed to compress, error: %d", compressedSize); + ByteStream_destroy(&compressedBuffer); + return false; + } + if (!compressedSize || (uint32_t)compressedSize >= self->pData->size) { + ByteStream_destroy(&compressedBuffer); + return true; + } + compressedBuffer.pData->size = (uint32_t)compressedSize; + compressedBuffer.pData->flags = (self->pData->flags | BUFFER_FLAG_COMRPESSED); + RawBuffer_recalcCRC(compressedBuffer.pData); + ByteStream_shrink(&compressedBuffer); + ByteStream_move(&compressedBuffer, self); +#endif + return true; +} + +bool ByteStream_decompress(ByteStream *self) +{ + ByteStream_sanity(self); + if (!self->pData || !RawBuffer_verify(self->pData)) { + DEBUG_LOG("Cannot verify crc, not decompressing"); + return false; + } +#if VORTEX_SLIM == 0 + if (!ByteStream_is_compressed(self)) { + return true; + } + uint32_t multiple = 1; + int decompressedSize = 0; + ByteStream decompressedBuffer; + ByteStream_init(&decompressedBuffer, 0, NULL); + do { + multiple *= 2; + ByteStream_init(&decompressedBuffer, self->pData->size * multiple, NULL); + decompressedSize = decompress_buffer(self->pData->buf, decompressedBuffer.pData->buf, + (int)self->pData->size, (int)decompressedBuffer.capacity); + } while (decompressedSize < 0 && multiple < 512); + if (decompressedSize < 0) { + ERROR_LOGF("Failed to decompress: %d", decompressedSize); + ByteStream_destroy(&decompressedBuffer); + return false; + } + decompressedBuffer.pData->size = (uint32_t)decompressedSize; + decompressedBuffer.pData->flags = (self->pData->flags & ~BUFFER_FLAG_COMRPESSED); + RawBuffer_recalcCRC(decompressedBuffer.pData); + DEBUG_LOGF("Decompressed %u to %u bytes", self->pData->size, decompressedBuffer.pData->size); + ByteStream_shrink(&decompressedBuffer); + ByteStream_move(&decompressedBuffer, self); +#endif + return true; +} + +uint32_t ByteStream_recalcCRC(ByteStream *self, bool force) +{ + if (!self->pData || !self->pData->size) { + return 0; + } + if (!force && !(self->pData->flags & BUFFER_FLAG_DIRTY)) { + return 0; + } + RawBuffer_recalcCRC(self->pData); + self->pData->flags &= ~BUFFER_FLAG_DIRTY; + return self->pData->crc32; +} + +void ByteStream_sanity(ByteStream *self) +{ + if (self->pData && self->pData->size > self->capacity) { + self->pData->size = self->capacity; + } +} + +bool ByteStream_checkCRC(const ByteStream *self) +{ + if (!self->pData || ByteStream_isCRCDirty(self)) { + return false; + } + return RawBuffer_verify(self->pData); +} + +bool ByteStream_isCRCDirty(const ByteStream *self) +{ + return (self->pData && (self->pData->flags & BUFFER_FLAG_DIRTY) != 0); +} + +void ByteStream_setCRCDirty(ByteStream *self) +{ + if (!self->pData) { + return; + } + self->pData->flags |= BUFFER_FLAG_DIRTY; +} + +bool ByteStream_serialize8(ByteStream *self, uint8_t byte) +{ + if (!self->pData || (self->pData->size + sizeof(uint8_t)) > self->capacity) { + if (!ByteStream_extend(self, sizeof(uint32_t))) { + return false; + } + } + memcpy(self->pData->buf + self->pData->size, &byte, sizeof(uint8_t)); + self->pData->size += sizeof(uint8_t); + ByteStream_setCRCDirty(self); + return true; +} + +bool ByteStream_serialize16(ByteStream *self, uint16_t bytes) +{ + if (!self->pData || (self->pData->size + sizeof(uint16_t)) > self->capacity) { + if (!ByteStream_extend(self, sizeof(uint32_t))) { + return false; + } + } + memcpy(self->pData->buf + self->pData->size, &bytes, sizeof(uint16_t)); + self->pData->size += sizeof(uint16_t); + ByteStream_setCRCDirty(self); + return true; +} + +bool ByteStream_serialize32(ByteStream *self, uint32_t bytes) +{ + if (!self->pData || (self->pData->size + sizeof(uint32_t)) > self->capacity) { + if (!ByteStream_extend(self, sizeof(uint32_t))) { + return false; + } + } + memcpy(self->pData->buf + self->pData->size, &bytes, sizeof(uint32_t)); + self->pData->size += sizeof(uint32_t); + ByteStream_setCRCDirty(self); + return true; +} + +void ByteStream_resetUnserializer(ByteStream *self) +{ + ByteStream_moveUnserializer(self, 0); +} + +void ByteStream_moveUnserializer(ByteStream *self, uint32_t idx) +{ + if (!self->pData) { + return; + } + if (idx >= self->pData->size) { + idx = 0; + } + self->position = (uint16_t)idx; +} + +bool ByteStream_unserializerAtEnd(const ByteStream *self) +{ + return self->pData && self->position == self->pData->size; +} + +bool ByteStream_unserialize8(ByteStream *self, uint8_t *byte) +{ + if (!self->pData || self->position >= self->pData->size || (self->pData->size - self->position) < sizeof(uint8_t)) { + return false; + } + memcpy(byte, self->pData->buf + self->position, sizeof(uint8_t)); + self->position += sizeof(uint8_t); + return true; +} + +bool ByteStream_unserialize16(ByteStream *self, uint16_t *bytes) +{ + if (!self->pData || self->position >= self->pData->size || (self->pData->size - self->position) < sizeof(uint16_t)) { + return false; + } + memcpy(bytes, self->pData->buf + self->position, sizeof(uint16_t)); + self->position += sizeof(uint16_t); + return true; +} + +bool ByteStream_unserialize32(ByteStream *self, uint32_t *bytes) +{ + if (!self->pData || self->position >= self->pData->size || (self->pData->size - self->position) < sizeof(uint32_t)) { + return false; + } + memcpy(bytes, self->pData->buf + self->position, sizeof(uint32_t)); + self->position += sizeof(uint32_t); + return true; +} + +bool ByteStream_consume8(ByteStream *self, uint8_t *bytes) +{ + if (!self->pData || self->pData->size < sizeof(uint8_t)) { + return false; + } + if (bytes) { + memcpy(bytes, self->pData->buf, sizeof(uint8_t)); + } + self->pData->size -= sizeof(uint8_t); + memmove(self->pData->buf, self->pData->buf + sizeof(uint8_t), self->pData->size); + ByteStream_setCRCDirty(self); + return true; +} + +bool ByteStream_consume16(ByteStream *self, uint16_t *bytes) +{ + if (!self->pData || self->pData->size < sizeof(uint16_t)) { + return false; + } + if (bytes) { + memcpy(bytes, self->pData->buf, sizeof(uint16_t)); + } + self->pData->size -= sizeof(uint16_t); + memmove(self->pData->buf, self->pData->buf + sizeof(uint16_t), self->pData->size); + ByteStream_setCRCDirty(self); + return true; +} + +bool ByteStream_consume32(ByteStream *self, uint32_t *bytes) +{ + if (!self->pData || self->pData->size < sizeof(uint32_t)) { + return false; + } + if (bytes) { + memcpy(bytes, self->pData->buf, sizeof(uint32_t)); + } + self->pData->size -= sizeof(uint32_t); + memmove(self->pData->buf, self->pData->buf + sizeof(uint32_t), self->pData->size); + ByteStream_setCRCDirty(self); + return true; +} + +bool ByteStream_consume(ByteStream *self, uint32_t size, void *bytes) +{ + if (!self->pData || self->position >= self->pData->size || (self->pData->size - self->position) < size) { + return false; + } + if (bytes) { + memcpy(bytes, self->pData->buf, size); + } + self->pData->size -= size; + memmove(self->pData->buf, self->pData->buf + size, self->pData->size); + ByteStream_setCRCDirty(self); + return true; +} + +uint8_t ByteStream_peek8(const ByteStream *self) +{ + if (!self->pData) { + return 0; + } + return *(const uint8_t *)ByteStream_frontUnserializer(self); +} + +uint16_t ByteStream_peek16(const ByteStream *self) +{ + if (!self->pData) { + return 0; + } + return *(const uint16_t *)ByteStream_frontUnserializer(self); +} + +uint32_t ByteStream_peek32(const ByteStream *self) +{ + if (!self->pData) { + return 0; + } + return *(const uint32_t *)ByteStream_frontUnserializer(self); +} + +uint8_t *ByteStream_get(ByteStream *self, uint32_t index) +{ + return &self->pData->buf[index]; +} + +uint8_t *ByteStream_getPtr(ByteStream *self) +{ + return self->pData ? self->pData->buf : NULL; +} + +const uint8_t *ByteStream_getConstPtr(const ByteStream *self) +{ + return self->pData ? self->pData->buf : NULL; +} + +const uint8_t *ByteStream_data(const ByteStream *self) +{ + return self->pData ? self->pData->buf : NULL; +} + +void *ByteStream_rawData(const ByteStream *self) +{ + return (void *)self->pData; +} + +uint16_t ByteStream_rawSize(const ByteStream *self) +{ + return self->pData ? (uint16_t)(self->pData->size + sizeof(RawBuffer)) : 0; +} + +uint32_t ByteStream_size(const ByteStream *self) +{ + return self->pData ? self->pData->size : 0; +} + +uint32_t ByteStream_capacity(const ByteStream *self) +{ + return self->capacity; +} + +bool ByteStream_is_compressed(const ByteStream *self) +{ + if (!self->pData) { + return false; + } + return (self->pData->flags & BUFFER_FLAG_COMRPESSED) != 0; +} + +uint32_t ByteStream_CRC(const ByteStream *self) +{ + return self->pData ? self->pData->crc32 : 0; +} + +uint8_t *ByteStream_frontUnserializer(const ByteStream *self) +{ + return self->pData ? self->pData->buf + self->position : NULL; +} + +uint8_t *ByteStream_frontSerializer(const ByteStream *self) +{ + return self->pData ? self->pData->buf + self->pData->size : NULL; +} diff --git a/VortexEngine/src/Serial/ByteStream.h b/VortexEngine/src/Serial/ByteStream.h index 722aa81a08..99efa1c586 100644 --- a/VortexEngine/src/Serial/ByteStream.h +++ b/VortexEngine/src/Serial/ByteStream.h @@ -2,190 +2,101 @@ #define BYTE_STREAM_H #include +#include +#include #include "../Log/Log.h" -// disable warning about using flexible array at end of structure -// otherwise visual studio dumps a million warnings about the buf[] -// flexible array member at the end of the inner data structure. -// Yes I know it's scary Visual Studio, please shut up now. #ifdef _MSC_VER #pragma warning(disable : 4200) #endif -class FlashClass; - -class ByteStream +struct RawBuffer { - friend class Storage; - -public: - ByteStream(uint32_t size = 0, const uint8_t *buf = nullptr); - ~ByteStream(); - - // copy and assignment operators - ByteStream(const ByteStream &other); - void operator=(const ByteStream &other); - - // move operator since std::move not always available - void move(ByteStream *target); - - // used to initialize the full RawBuffer with a chunk of data, - // this includes the size, flags, crc, and data - bool rawInit(const uint8_t *rawdata, uint32_t size); - - // reset the buffer to a specific capcity - bool init(uint32_t capacity = 0, const uint8_t *buf = nullptr); - - // clear the buffer - void clear(); - - // shrink capacity down to size, to free unused space - bool shrink(); - - // append another buffer - bool append(const ByteStream &other); - - // extend the storage without changing the size of the data - bool extend(uint32_t size); - - // trim the storage and erase some bytes from the end - void trim(uint32_t bytes); - - // badly written in-place compression and decompression - // which just uses a table of values then compresses the - // data into indexes in the table packed into a buffer. - // Unfortunately sometimes compression does not yield a - // smaller buffer size, incases like this compress returns - // true but the data isn't compressed. Similarly, if that - // data is passed to decompress it will return true. - bool compress(); - bool decompress(); - - // re-calculate the built-in CRC on the bytestream if needed - // if the crc is up to date this will do nothing. Optionally - // force a re-calculation even if the dirty flag is missing. - // Will return the new CRC value. - uint32_t recalcCRC(bool force = false); - - // helper func to ensure the internal buffer is sane, use this - // after reading into the rawdata of the buffer - void sanity(); - // check the CRC without re-calculating, note, if the CRC is - // dirty then this will simply return false - bool checkCRC() const; - // check whether the data in the buffer has changed since the - // crc has been re-calculated, if it is dirty, call recalcCRC - bool isCRCDirty() const; - // mark the CRC as dirty (manually modified the buffer or something) - void setCRCDirty(); - - // serialize a byte into the buffer - bool serialize8(uint8_t byte); - bool serialize16(uint16_t bytes); - bool serialize32(uint32_t bytes); - - // reset the unserializer index - void resetUnserializer(); - // move the unserializer index manually - void moveUnserializer(uint32_t idx); - // check if the unserializer is at the end - bool unserializerAtEnd() const; - - // unserialize data out of the buffer - bool unserialize8(uint8_t *byte); - bool unserialize16(uint16_t *bytes); - bool unserialize32(uint32_t *bytes); - - // unserialize data and erase it from the buffer - bool consume8(uint8_t *byte = nullptr); - bool consume16(uint16_t *bytes = nullptr); - bool consume32(uint32_t *bytes = nullptr); - - // unserialize and consume a whole chunk - bool consume(uint32_t size, void *bytes = nullptr); - - uint8_t peek8() const; - uint16_t peek16() const; - uint32_t peek32() const; - - // overload [] for array access (no bounds check lol) - uint8_t &operator[](uint32_t index) { return m_pData->buf[index]; } - // overload (uint8_t *) for cast to buffer - operator uint8_t *() const { return m_pData ? m_pData->buf : nullptr; } - operator const uint8_t *() const { return m_pData ? m_pData->buf : nullptr; } - - // return the members - const uint8_t *data() const { return m_pData ? m_pData->buf : nullptr; } - void *rawData() const { return m_pData; } - uint16_t rawSize() const { return m_pData ? (uint16_t)m_pData->size + sizeof(RawBuffer) : 0; } - uint32_t size() const { return m_pData ? m_pData->size : 0; } - uint32_t capacity() const { return m_capacity; } - bool is_compressed() const; - uint32_t CRC() const { return m_pData ? m_pData->crc32 : 0; } - - uint8_t *frontUnserializer() const { return m_pData ? m_pData->buf + m_position : nullptr; } - -private: - // fetch pointers that walk the buffer to serialize/unserialize - //uint8_t *frontUnserializer() const { return m_pData ? m_pData->buf + m_position : nullptr; } - uint8_t *frontSerializer() const { return m_pData ? m_pData->buf + m_pData->size : nullptr; } - // check if the buffer is large enough for the amount - bool largeEnough(uint32_t amount) const; - // helper to get width of bits that makes up value - uint32_t getWidth(uint32_t value) const; - - // the structure of raw data that's written to storage - struct RawBuffer - { - RawBuffer() : size(0), flags(0), crc32(5381) {} - // hash the raw buffer into crc - uint32_t hash() const - { - uint32_t hash = 5381; - for (uint32_t i = 0; i < size; ++i) { - hash = ((hash << 5) + hash) + buf[i]; - } - return hash; - } - // accumulate the data into the hash - void accumulate(uint32_t val) - { - crc32 = ((crc32 << 5) + crc32) + val; - } - // veryify the crc - bool verify() const - { - // if the buffer is empty then 'verify' should just return true - if (!size) { - return true; - } - if (hash() != crc32) { - DEBUG_LOGF("CRC mismatch: %x should be %x", hash(), crc32); - return false; - } - return true; - } - // re-calculate the crc - void recalcCRC() - { - crc32 = hash(); - } - // the size of the buf of data - uint32_t size; - // any special flags about the data (compression etc) - uint32_t flags; - // the crc32 for the data - uint32_t crc32; - // the start of the buf of data - uint8_t buf[]; - }; - - // The raw buffer of data along with size and flags - RawBuffer *m_pData; - // the index in the raw buffer for unserialization - uint16_t m_position; - // the actual size of the buffer raw buffer - uint16_t m_capacity; + uint32_t size; + uint32_t flags; + uint32_t crc32; + uint8_t buf[]; }; +typedef struct RawBuffer RawBuffer; + +typedef struct ByteStream +{ + RawBuffer *pData; + uint16_t position; + uint16_t capacity; +} ByteStream; + +struct FlashClass; + +// init/destroy +bool ByteStream_init(ByteStream *self, uint32_t size, const uint8_t *buf); +void ByteStream_destroy(ByteStream *self); +void ByteStream_copy(ByteStream *self, const ByteStream *other); +void ByteStream_assign(ByteStream *self, const ByteStream *other); + +void ByteStream_move(ByteStream *self, ByteStream *target); + +bool ByteStream_rawInit(ByteStream *self, const uint8_t *rawdata, uint32_t size); +bool ByteStream_init(ByteStream *self, uint32_t capacity, const uint8_t *buf); + +void ByteStream_clear(ByteStream *self); +bool ByteStream_shrink(ByteStream *self); +bool ByteStream_append(ByteStream *self, const ByteStream *other); +bool ByteStream_extend(ByteStream *self, uint32_t size); +void ByteStream_trim(ByteStream *self, uint32_t bytes); + +bool ByteStream_compress(ByteStream *self); +bool ByteStream_decompress(ByteStream *self); + +uint32_t ByteStream_recalcCRC(ByteStream *self, bool force); +void ByteStream_sanity(ByteStream *self); +bool ByteStream_checkCRC(const ByteStream *self); +bool ByteStream_isCRCDirty(const ByteStream *self); +void ByteStream_setCRCDirty(ByteStream *self); + +bool ByteStream_serialize8(ByteStream *self, uint8_t byte); +bool ByteStream_serialize16(ByteStream *self, uint16_t bytes); +bool ByteStream_serialize32(ByteStream *self, uint32_t bytes); + +void ByteStream_resetUnserializer(ByteStream *self); +void ByteStream_moveUnserializer(ByteStream *self, uint32_t idx); +bool ByteStream_unserializerAtEnd(const ByteStream *self); + +bool ByteStream_unserialize8(ByteStream *self, uint8_t *byte); +bool ByteStream_unserialize16(ByteStream *self, uint16_t *bytes); +bool ByteStream_unserialize32(ByteStream *self, uint32_t *bytes); + +bool ByteStream_consume8(ByteStream *self, uint8_t *byte); +bool ByteStream_consume16(ByteStream *self, uint16_t *bytes); +bool ByteStream_consume32(ByteStream *self, uint32_t *bytes); +bool ByteStream_consume(ByteStream *self, uint32_t size, void *bytes); + +uint8_t ByteStream_peek8(const ByteStream *self); +uint16_t ByteStream_peek16(const ByteStream *self); +uint32_t ByteStream_peek32(const ByteStream *self); + +uint8_t *ByteStream_get(ByteStream *self, uint32_t index); +uint8_t *ByteStream_getPtr(ByteStream *self); +const uint8_t *ByteStream_getConstPtr(const ByteStream *self); + +const uint8_t *ByteStream_data(const ByteStream *self); +void *ByteStream_rawData(const ByteStream *self); +uint16_t ByteStream_rawSize(const ByteStream *self); +uint32_t ByteStream_size(const ByteStream *self); +uint32_t ByteStream_capacity(const ByteStream *self); +bool ByteStream_is_compressed(const ByteStream *self); +uint32_t ByteStream_CRC(const ByteStream *self); + +uint8_t *ByteStream_frontUnserializer(const ByteStream *self); + +// -- private helpers (exposed for internal use) -- +uint8_t *ByteStream_frontSerializer(const ByteStream *self); + +// RawBuffer free functions +uint32_t RawBuffer_hash(const RawBuffer *self); +void RawBuffer_accumulate(RawBuffer *self, uint32_t val); +bool RawBuffer_verify(const RawBuffer *self); +void RawBuffer_recalcCRC(RawBuffer *self); + #endif diff --git a/VortexEngine/src/Serial/Compression.c b/VortexEngine/src/Serial/Compression.c new file mode 100644 index 0000000000..529c45d1a2 --- /dev/null +++ b/VortexEngine/src/Serial/Compression.c @@ -0,0 +1,1468 @@ +#include "Compression.h" + +// This is a heavily stripped down version of lz4 + +#include "../Memory/Memory.h" +#include "../VortexConfig.h" + +#include +#include +#include + +#include +#include +#include + +#if VORTEX_SLIM == 0 + +#if (defined(__GNUC__) && (__GNUC__ >= 3)) || (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 800)) || defined(__clang__) +# define expect(expr,value) (__builtin_expect ((expr),(value)) ) +#else +# define expect(expr,value) (expr) +#endif + +#ifndef likely +#define likely(expr) expect((expr) != 0, 1) +#endif +#ifndef unlikely +#define unlikely(expr) expect((expr) != 0, 0) +#endif + +/* Should the alignment test prove unreliable, for some reason, + * it can be disabled by setting LZ4_ALIGN_TEST to 0 */ +#ifndef LZ4_ALIGN_TEST /* can be externally provided */ +# define LZ4_ALIGN_TEST 0 +#endif + +#define MINMATCH 4 + +#define WILDCOPYLENGTH 8 +#define LASTLITERALS 5 /* see ../doc/lz4_Block_format.md#parsing-restrictions */ +#define MFLIMIT 12 /* see ../doc/lz4_Block_format.md#parsing-restrictions */ +#define MATCH_SAFEGUARD_DISTANCE ((2*WILDCOPYLENGTH) - MINMATCH) /* ensure it's possible to write 2 x wildcopyLength without overflowing output buffer */ +#define FASTLOOP_SAFE_DISTANCE 64 +static const int LZ4_minLength = (MFLIMIT + 1); + +#define KB *(1 <<10) +#define MB *(1 <<20) +#define GB *(1U<<30) + +#define LZ4_DISTANCE_ABSOLUTE_MAX 65535 +#if (LZ4_DISTANCE_MAX > LZ4_DISTANCE_ABSOLUTE_MAX) /* max supported by LZ4 format */ +# error "LZ4_DISTANCE_MAX is too big : must be <= 65535" +#endif + +#define ML_BITS 4 +#define ML_MASK ((1U<=1) +# include +#else +# ifndef assert +# define assert(condition) ((void)0) +# endif +#endif + +#define LZ4_STATIC_ASSERT(c) { enum { LZ4_static_assert = 1/(int)(!!(c)) }; } /* use after variable declarations */ +#define DEBUGLOG(l, ...) {} /* disabled */ + +static int LZ4_isAligned(const void *ptr, size_t alignment) +{ + return ((size_t)ptr & (alignment - 1)) == 0; +} + +/*-************************************ +* Types +**************************************/ +typedef uint8_t BYTE; +typedef uint16_t U16; +typedef uint32_t U32; +typedef int32_t S32; +typedef uint64_t U64; +typedef uintptr_t uptrval; + +#if defined(__x86_64__) +typedef U64 reg_t; /* 64-bits in x32 mode */ +#else +typedef size_t reg_t; /* 32-bits in x32 mode */ +#endif + +typedef enum +{ + notLimited = 0, + limitedOutput = 1, + fillOutput = 2 +} limitedOutput_directive; + + +/*-************************************ +* Reading and writing into memory +**************************************/ + +/** + * LZ4 relies on memcpy with a constant size being inlined. In freestanding + * environments, the compiler can't assume the implementation of memcpy() is + * standard compliant, so it can't apply its specialized memcpy() inlining + * logic. When possible, use __builtin_memcpy() to tell the compiler to analyze + * memcpy() as if it were standard compliant, so it can inline it in freestanding + * environments. This is needed when decompressing the Linux Kernel, for example. + */ +#if defined(__GNUC__) && (__GNUC__ >= 4) +#define LZ4_memcpy(dst, src, size) __builtin_memcpy(dst, src, size) +#else +#define LZ4_memcpy(dst, src, size) memcpy(dst, src, size) +#endif + +static unsigned LZ4_isLittleEndian(void) +{ + const union { U32 u; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */ + return one.c[0]; +} + +static U16 LZ4_read16(const void *memPtr) +{ + U16 val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val; +} + +static U32 LZ4_read32(const void *memPtr) +{ + U32 val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val; +} + +static reg_t LZ4_read_ARCH(const void *memPtr) +{ + reg_t val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val; +} + +static void LZ4_write16(void *memPtr, U16 value) +{ + LZ4_memcpy(memPtr, &value, sizeof(value)); +} + +static void LZ4_write32(void *memPtr, U32 value) +{ + LZ4_memcpy(memPtr, &value, sizeof(value)); +} + +static U16 LZ4_readLE16(const void *memPtr) +{ + if (LZ4_isLittleEndian()) { + return LZ4_read16(memPtr); + } else { + const BYTE *p = (const BYTE *)memPtr; + return (U16)((U16)p[0] + (p[1] << 8)); + } +} + +static void LZ4_writeLE16(void *memPtr, U16 value) +{ + if (LZ4_isLittleEndian()) { + LZ4_write16(memPtr, value); + } else { + BYTE *p = (BYTE *)memPtr; + p[0] = (BYTE)value; + p[1] = (BYTE)(value >> 8); + } +} + +/* customized variant of memcpy, which can overwrite up to 8 bytes beyond dstEnd */ +void LZ4_wildCopy8(void *dstPtr, const void *srcPtr, void *dstEnd) +{ + BYTE *d = (BYTE *)dstPtr; + const BYTE *s = (const BYTE *)srcPtr; + BYTE *const e = (BYTE *)dstEnd; + + do { LZ4_memcpy(d, s, 8); d += 8; s += 8; } while (d < e); +} + +static const unsigned inc32table[8] = { 0, 1, 2, 1, 0, 4, 4, 4 }; +static const int dec64table[8] = { 0, 0, 0, -1, -4, 1, 2, 3 }; + +void LZ4_memcpy_using_offset_base(BYTE *dstPtr, const BYTE *srcPtr, BYTE *dstEnd, const size_t offset) +{ + assert(srcPtr + offset == dstPtr); + if (offset < 8) { + LZ4_write32(dstPtr, 0); /* silence an msan warning when offset==0 */ + dstPtr[0] = srcPtr[0]; + dstPtr[1] = srcPtr[1]; + dstPtr[2] = srcPtr[2]; + dstPtr[3] = srcPtr[3]; + srcPtr += inc32table[offset]; + LZ4_memcpy(dstPtr + 4, srcPtr, 4); + srcPtr -= dec64table[offset]; + dstPtr += 8; + } else { + LZ4_memcpy(dstPtr, srcPtr, 8); + dstPtr += 8; + srcPtr += 8; + } + + LZ4_wildCopy8(dstPtr, srcPtr, dstEnd); +} + +/* customized variant of memcpy, which can overwrite up to 32 bytes beyond dstEnd + * this version copies two times 16 bytes (instead of one time 32 bytes) + * because it must be compatible with offsets >= 16. */ +void LZ4_wildCopy32(void *dstPtr, const void *srcPtr, void *dstEnd) +{ + BYTE *d = (BYTE *)dstPtr; + const BYTE *s = (const BYTE *)srcPtr; + BYTE *const e = (BYTE *)dstEnd; + + do { LZ4_memcpy(d, s, 16); LZ4_memcpy(d + 16, s + 16, 16); d += 32; s += 32; } while (d < e); +} + +/* LZ4_memcpy_using_offset() presumes : + * - dstEnd >= dstPtr + MINMATCH + * - there is at least 8 bytes available to write after dstEnd */ +void LZ4_memcpy_using_offset(BYTE *dstPtr, const BYTE *srcPtr, BYTE *dstEnd, const size_t offset) +{ + BYTE v[8]; + + assert(dstEnd >= dstPtr + MINMATCH); + + switch (offset) { + case 1: + memset(v, *srcPtr, 8); + break; + case 2: + LZ4_memcpy(v, srcPtr, 2); + LZ4_memcpy(&v[2], srcPtr, 2); + LZ4_memcpy(&v[4], v, 4); + break; + case 4: + LZ4_memcpy(v, srcPtr, 4); + LZ4_memcpy(&v[4], srcPtr, 4); + break; + default: + LZ4_memcpy_using_offset_base(dstPtr, srcPtr, dstEnd, offset); + return; + } + + LZ4_memcpy(dstPtr, v, 8); + dstPtr += 8; + while (dstPtr < dstEnd) { + LZ4_memcpy(dstPtr, v, 8); + dstPtr += 8; + } +} + + +/*-************************************ +* Common functions +**************************************/ +static unsigned LZ4_NbCommonBytes(reg_t val) +{ + assert(val != 0); + if (LZ4_isLittleEndian()) { + if (sizeof(val) == 8) { + /*-************************************************************************************************* + * ARM64EC is a Microsoft-designed ARM64 ABI compatible with AMD64 applications on ARM64 Windows 11. + * The ARM64EC ABI does not support AVX/AVX2/AVX512 instructions, nor their relevant intrinsics + * including _tzcnt_u64. Therefore, we need to neuter the _tzcnt_u64 code path for ARM64EC. + ****************************************************************************************************/ + const U64 m = 0x0101010101010101ULL; + val ^= val - 1; + return (unsigned)(((U64)((val & (m - 1)) * m)) >> 56); + } else /* 32 bits */ { + const U32 m = 0x01010101; + return (unsigned)((((val - 1) ^ val) & (m - 1)) * m) >> 24; + } + } else /* Big Endian CPU */ { + if (sizeof(val) == 8) { + /* this method doesn't consume memory space like the previous one, + * but it contains several branches, + * that may end up slowing execution */ + static const U32 by32 = sizeof(val) * 4; /* 32 on 64 bits (goal), 16 on 32 bits. + Just to avoid some static analyzer complaining about shift by 32 on 32-bits target. + Note that this code path is never triggered in 32-bits mode. */ + unsigned r; + if (!(val >> by32)) { r = 4; } else { r = 0; val >>= by32; } + if (!(val >> 16)) { r += 2; val >>= 8; } else { val >>= 24; } + r += (!val); + return r; + } else /* 32 bits */ { + val >>= 8; + val = ((((val + 0x00FFFF00) | 0x00FFFFFF) + val) | + (val + 0x00FF0000)) >> 24; + return (unsigned)val ^ 3; + } + } +} + + +#define STEPSIZE sizeof(reg_t) +unsigned LZ4_count(const BYTE *pIn, const BYTE *pMatch, const BYTE *pInLimit) +{ + const BYTE *const pStart = pIn; + + if (likely(pIn < pInLimit - (STEPSIZE - 1))) { + reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn); + if (!diff) { + pIn += STEPSIZE; pMatch += STEPSIZE; + } else { + return LZ4_NbCommonBytes(diff); + } + } + + while (likely(pIn < pInLimit - (STEPSIZE - 1))) { + reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn); + if (!diff) { pIn += STEPSIZE; pMatch += STEPSIZE; continue; } + pIn += LZ4_NbCommonBytes(diff); + return (unsigned)(pIn - pStart); + } + + if ((STEPSIZE == 8) && (pIn < (pInLimit - 3)) && (LZ4_read32(pMatch) == LZ4_read32(pIn))) { pIn += 4; pMatch += 4; } + if ((pIn < (pInLimit - 1)) && (LZ4_read16(pMatch) == LZ4_read16(pIn))) { pIn += 2; pMatch += 2; } + if ((pIn < pInLimit) && (*pMatch == *pIn)) pIn++; + return (unsigned)(pIn - pStart); +} + + +/*-************************************ +* Local Constants +**************************************/ +static const int LZ4_64Klimit = ((64 KB) + (MFLIMIT - 1)); +static const U32 LZ4_skipTrigger = 6; /* Increase this value ==> compression run slower on incompressible data */ + + +/*-************************************ +* Local Structures and types +**************************************/ +typedef enum { clearedTable = 0, byPtr, byU32, byU16 } tableType_t; + +/** + * This enum distinguishes several different modes of accessing previous + * content in the stream. + * + * - noDict : There is no preceding content. + * - withPrefix64k : Table entries up to ctx->dictSize before the current blob + * blob being compressed are valid and refer to the preceding + * content (of length ctx->dictSize), which is available + * contiguously preceding in memory the content currently + * being compressed. + * - usingExtDict : Like withPrefix64k, but the preceding content is somewhere + * else in memory, starting at ctx->dictionary with length + * ctx->dictSize. + * - usingDictCtx : Everything concerning the preceding content is + * in a separate context, pointed to by ctx->dictCtx. + * ctx->dictionary, ctx->dictSize, and table entries + * in the current context that refer to positions + * preceding the beginning of the current compression are + * ignored. Instead, ctx->dictCtx->dictionary and ctx->dictCtx + * ->dictSize describe the location and size of the preceding + * content, and matches are found by looking in the ctx + * ->dictCtx->hashTable. + */ +typedef enum { noDict = 0, withPrefix64k, usingExtDict, usingDictCtx } dict_directive; +typedef enum { noDictIssue = 0, dictSmall } dictIssue_directive; + + +/*-************************************ +* Local Utils +**************************************/ +int LZ4_compressBound(int isize) { return LZ4_COMPRESSBOUND(isize); } + +/*-****************************** +* Compression functions +********************************/ +U32 LZ4_hash4(U32 sequence, tableType_t const tableType) +{ + if (tableType == byU16) + return ((sequence * 2654435761U) >> ((MINMATCH * 8) - (LZ4_HASHLOG + 1))); + else + return ((sequence * 2654435761U) >> ((MINMATCH * 8) - LZ4_HASHLOG)); +} + +U32 LZ4_hash5(U64 sequence, tableType_t const tableType) +{ + const U32 hashLog = (tableType == byU16) ? LZ4_HASHLOG + 1 : LZ4_HASHLOG; + if (LZ4_isLittleEndian()) { + const U64 prime5bytes = 889523592379ULL; + return (U32)(((sequence << 24) * prime5bytes) >> (64 - hashLog)); + } else { + const U64 prime8bytes = 11400714785074694791ULL; + return (U32)(((sequence >> 24) * prime8bytes) >> (64 - hashLog)); + } +} + +U32 LZ4_hashPosition(const void *const p, tableType_t const tableType) +{ + if ((sizeof(reg_t) == 8) && (tableType != byU16)) return LZ4_hash5(LZ4_read_ARCH(p), tableType); + return LZ4_hash4(LZ4_read32(p), tableType); +} + +void LZ4_clearHash(U32 h, void *tableBase, tableType_t const tableType) +{ + switch (tableType) { + default: /* fallthrough */ + case clearedTable: { /* illegal! */ assert(0); return; } + case byPtr: { const BYTE **hashTable = (const BYTE **)tableBase; hashTable[h] = NULL; return; } + case byU32: { U32 *hashTable = (U32 *)tableBase; hashTable[h] = 0; return; } + case byU16: { U16 *hashTable = (U16 *)tableBase; hashTable[h] = 0; return; } + } +} + +void LZ4_putIndexOnHash(U32 idx, U32 h, void *tableBase, tableType_t const tableType) +{ + switch (tableType) { + default: /* fallthrough */ + case clearedTable: /* fallthrough */ + case byPtr: { /* illegal! */ assert(0); return; } + case byU32: { U32 *hashTable = (U32 *)tableBase; hashTable[h] = idx; return; } + case byU16: { U16 *hashTable = (U16 *)tableBase; assert(idx < 65536); hashTable[h] = (U16)idx; return; } + } +} + +void LZ4_putPositionOnHash(const BYTE *p, U32 h, + void *tableBase, tableType_t const tableType, + const BYTE *srcBase) +{ + switch (tableType) { + case clearedTable: { /* illegal! */ assert(0); return; } + case byPtr: { const BYTE **hashTable = (const BYTE **)tableBase; hashTable[h] = p; return; } + case byU32: { U32 *hashTable = (U32 *)tableBase; hashTable[h] = (U32)(p - srcBase); return; } + case byU16: { U16 *hashTable = (U16 *)tableBase; hashTable[h] = (U16)(p - srcBase); return; } + } +} + +void LZ4_putPosition(const BYTE *p, void *tableBase, tableType_t tableType, const BYTE *srcBase) +{ + U32 const h = LZ4_hashPosition(p, tableType); + LZ4_putPositionOnHash(p, h, tableBase, tableType, srcBase); +} + +/* LZ4_getIndexOnHash() : + * Index of match position registered in hash table. + * hash position must be calculated by using base+index, or dictBase+index. + * Assumption 1 : only valid if tableType == byU32 or byU16. + * Assumption 2 : h is presumed valid (within limits of hash table) + */ +U32 LZ4_getIndexOnHash(U32 h, const void *tableBase, tableType_t tableType) +{ + LZ4_STATIC_ASSERT(LZ4_MEMORY_USAGE > 2); + if (tableType == byU32) { + const U32 *const hashTable = (const U32 *)tableBase; + assert(h < (1U << (LZ4_MEMORY_USAGE - 2))); + return hashTable[h]; + } + if (tableType == byU16) { + const U16 *const hashTable = (const U16 *)tableBase; + assert(h < (1U << (LZ4_MEMORY_USAGE - 1))); + return hashTable[h]; + } + assert(0); return 0; /* forbidden case */ +} + +static const BYTE *LZ4_getPositionOnHash(U32 h, const void *tableBase, tableType_t tableType, const BYTE *srcBase) +{ + if (tableType == byPtr) { const BYTE *const *hashTable = (const BYTE *const *)tableBase; return hashTable[h]; } + if (tableType == byU32) { const U32 *const hashTable = (const U32 *)tableBase; return hashTable[h] + srcBase; } + { const U16 *const hashTable = (const U16 *)tableBase; return hashTable[h] + srcBase; } /* default, to ensure a return */ +} + +const BYTE * LZ4_getPosition(const BYTE *p, + const void *tableBase, tableType_t tableType, + const BYTE *srcBase) +{ + U32 const h = LZ4_hashPosition(p, tableType); + return LZ4_getPositionOnHash(h, tableBase, tableType, srcBase); +} + +/** LZ4_compress_generic() : + * inlined, to ensure branches are decided at compilation time. + * Presumed already validated at this stage: + * - source != NULL + * - inputSize > 0 + */ +int LZ4_compress_generic_validated( + LZ4_stream_t_internal *const cctx, + const char *const source, + char *const dest, + const int inputSize, + int *inputConsumed, /* only written when outputDirective == fillOutput */ + const int maxOutputSize, + const limitedOutput_directive outputDirective, + const tableType_t tableType, + const dict_directive dictDirective, + const dictIssue_directive dictIssue, + const int acceleration) +{ + int result; + const BYTE *ip = (const BYTE *)source; + + U32 const startIndex = cctx->currentOffset; + const BYTE *base = (const BYTE *)source - startIndex; + const BYTE *lowLimit; + + const LZ4_stream_t_internal *dictCtx = (const LZ4_stream_t_internal *)cctx->dictCtx; + const BYTE *const dictionary = + dictDirective == usingDictCtx ? dictCtx->dictionary : cctx->dictionary; + const U32 dictSize = + dictDirective == usingDictCtx ? dictCtx->dictSize : cctx->dictSize; + const U32 dictDelta = (dictDirective == usingDictCtx) ? startIndex - dictCtx->currentOffset : 0; /* make indexes in dictCtx comparable with index in current context */ + + int const maybe_extMem = (dictDirective == usingExtDict) || (dictDirective == usingDictCtx); + U32 const prefixIdxLimit = startIndex - dictSize; /* used when dictDirective == dictSmall */ + const BYTE *const dictEnd = dictionary ? dictionary + dictSize : dictionary; + const BYTE *anchor = (const BYTE *)source; + const BYTE *const iend = ip + inputSize; + const BYTE *const mflimitPlusOne = iend - MFLIMIT + 1; + const BYTE *const matchlimit = iend - LASTLITERALS; + + /* the dictCtx currentOffset is indexed on the start of the dictionary, + * while a dictionary in the current context precedes the currentOffset */ + const BYTE *dictBase = (dictionary == NULL) ? NULL : + (dictDirective == usingDictCtx) ? + dictionary + dictSize - dictCtx->currentOffset : + dictionary + dictSize - startIndex; + + BYTE *op = (BYTE *)dest; + BYTE *const olimit = op + maxOutputSize; + + U32 offset = 0; + U32 forwardH; + + DEBUGLOG(5, "LZ4_compress_generic_validated: srcSize=%i, tableType=%u", inputSize, tableType); + assert(ip != NULL); + /* If init conditions are not met, we don't have to mark stream + * as having dirty context, since no action was taken yet */ + if (outputDirective == fillOutput && maxOutputSize < 1) { return 0; } /* Impossible to store anything */ + if ((tableType == byU16) && (inputSize >= LZ4_64Klimit)) { return 0; } /* Size too large (not within 64K limit) */ + if (tableType == byPtr) assert(dictDirective == noDict); /* only supported use case with byPtr */ + assert(acceleration >= 1); + + lowLimit = (const BYTE *)source - (dictDirective == withPrefix64k ? dictSize : 0); + + /* Update context state */ + if (dictDirective == usingDictCtx) { + /* Subsequent linked blocks can't use the dictionary. */ + /* Instead, they use the block we just compressed. */ + cctx->dictCtx = NULL; + cctx->dictSize = (U32)inputSize; + } else { + cctx->dictSize += (U32)inputSize; + } + cctx->currentOffset += (U32)inputSize; + cctx->tableType = (U32)tableType; + + if (inputSize < LZ4_minLength) goto _last_literals; /* Input too small, no compression (all literals) */ + + /* First Byte */ + LZ4_putPosition(ip, cctx->hashTable, tableType, base); + ip++; forwardH = LZ4_hashPosition(ip, tableType); + + /* Main Loop */ + for (; ; ) { + const BYTE *match; + BYTE *token; + const BYTE *filledIp; + + /* Find a match */ + if (tableType == byPtr) { + const BYTE *forwardIp = ip; + int step = 1; + int searchMatchNb = acceleration << LZ4_skipTrigger; + do { + U32 const h = forwardH; + ip = forwardIp; + forwardIp += step; + step = (searchMatchNb++ >> LZ4_skipTrigger); + + if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals; + assert(ip < mflimitPlusOne); + + match = LZ4_getPositionOnHash(h, cctx->hashTable, tableType, base); + forwardH = LZ4_hashPosition(forwardIp, tableType); + LZ4_putPositionOnHash(ip, h, cctx->hashTable, tableType, base); + + } while ((match + LZ4_DISTANCE_MAX < ip) + || (LZ4_read32(match) != LZ4_read32(ip))); + + } else { /* byU32, byU16 */ + + const BYTE *forwardIp = ip; + int step = 1; + int searchMatchNb = acceleration << LZ4_skipTrigger; + do { + U32 const h = forwardH; + U32 const current = (U32)(forwardIp - base); + U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType); + assert(matchIndex <= current); + assert(forwardIp - base < (ptrdiff_t)(2 GB - 1)); + ip = forwardIp; + forwardIp += step; + step = (searchMatchNb++ >> LZ4_skipTrigger); + + if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals; + assert(ip < mflimitPlusOne); + + if (dictDirective == usingDictCtx) { + if (matchIndex < startIndex) { + /* there was no match, try the dictionary */ + assert(tableType == byU32); + matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32); + match = dictBase + matchIndex; + matchIndex += dictDelta; /* make dictCtx index comparable with current context */ + lowLimit = dictionary; + } else { + match = base + matchIndex; + lowLimit = (const BYTE *)source; + } + } else if (dictDirective == usingExtDict) { + if (matchIndex < startIndex) { + DEBUGLOG(7, "extDict candidate: matchIndex=%5u < startIndex=%5u", matchIndex, startIndex); + assert(startIndex - matchIndex >= MINMATCH); + assert(dictBase); + match = dictBase + matchIndex; + lowLimit = dictionary; + } else { + match = base + matchIndex; + lowLimit = (const BYTE *)source; + } + } else { /* single continuous memory segment */ + match = base + matchIndex; + } + forwardH = LZ4_hashPosition(forwardIp, tableType); + LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType); + + DEBUGLOG(7, "candidate at pos=%u (offset=%u \n", matchIndex, current - matchIndex); + if ((dictIssue == dictSmall) && (matchIndex < prefixIdxLimit)) { continue; } /* match outside of valid area */ + assert(matchIndex < current); + if (((tableType != byU16) || (LZ4_DISTANCE_MAX < LZ4_DISTANCE_ABSOLUTE_MAX)) + && (matchIndex + LZ4_DISTANCE_MAX < current)) { + continue; + } /* too far */ + assert((current - matchIndex) <= LZ4_DISTANCE_MAX); /* match now expected within distance */ + + if (LZ4_read32(match) == LZ4_read32(ip)) { + if (maybe_extMem) offset = current - matchIndex; + break; /* match found */ + } + + } while (1); + } + + /* Catch up */ + filledIp = ip; + while (((ip > anchor) & (match > lowLimit)) && (unlikely(ip[-1] == match[-1]))) { ip--; match--; } + + /* Encode Literals */ + { + unsigned const litLength = (unsigned)(ip - anchor); + token = op++; + if ((outputDirective == limitedOutput) && /* Check output buffer overflow */ + (unlikely(op + litLength + (2 + 1 + LASTLITERALS) + (litLength / 255) > olimit))) { + return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */ + } + if ((outputDirective == fillOutput) && + (unlikely(op + (litLength + 240) / 255 /* litlen */ + litLength /* literals */ + 2 /* offset */ + 1 /* token */ + MFLIMIT - MINMATCH /* min last literals so last match is <= end - MFLIMIT */ > olimit))) { + op--; + goto _last_literals; + } + if (litLength >= RUN_MASK) { + int len = (int)(litLength - RUN_MASK); + *token = (RUN_MASK << ML_BITS); + for (; len >= 255; len -= 255) *op++ = 255; + *op++ = (BYTE)len; + } else *token = (BYTE)(litLength << ML_BITS); + + /* Copy Literals */ + LZ4_wildCopy8(op, anchor, op + litLength); + op += litLength; + DEBUGLOG(6, "seq.start:%i, literals=%u, match.start:%i", + (int)(anchor - (const BYTE *)source), litLength, (int)(ip - (const BYTE *)source)); + } + + _next_match: + /* at this stage, the following variables must be correctly set : + * - ip : at start of LZ operation + * - match : at start of previous pattern occurrence; can be within current prefix, or within extDict + * - offset : if maybe_ext_memSegment==1 (constant) + * - lowLimit : must be == dictionary to mean "match is within extDict"; must be == source otherwise + * - token and *token : position to write 4-bits for match length; higher 4-bits for literal length supposed already written + */ + + if ((outputDirective == fillOutput) && + (op + 2 /* offset */ + 1 /* token */ + MFLIMIT - MINMATCH /* min last literals so last match is <= end - MFLIMIT */ > olimit)) { + /* the match was too close to the end, rewind and go to last literals */ + op = token; + goto _last_literals; + } + + /* Encode Offset */ + if (maybe_extMem) { /* static test */ + DEBUGLOG(6, " with offset=%u (ext if > %i)", offset, (int)(ip - (const BYTE *)source)); + assert(offset <= LZ4_DISTANCE_MAX && offset > 0); + LZ4_writeLE16(op, (U16)offset); op += 2; + } else { + DEBUGLOG(6, " with offset=%u (same segment)", (U32)(ip - match)); + assert(ip - match <= LZ4_DISTANCE_MAX); + LZ4_writeLE16(op, (U16)(ip - match)); op += 2; + } + + /* Encode MatchLength */ + { + unsigned matchCode; + + if ((dictDirective == usingExtDict || dictDirective == usingDictCtx) + && (lowLimit == dictionary) /* match within extDict */) { + const BYTE *limit = ip + (dictEnd - match); + assert(dictEnd > match); + if (limit > matchlimit) limit = matchlimit; + matchCode = LZ4_count(ip + MINMATCH, match + MINMATCH, limit); + ip += (size_t)matchCode + MINMATCH; + if (ip == limit) { + unsigned const more = LZ4_count(limit, (const BYTE *)source, matchlimit); + matchCode += more; + ip += more; + } + DEBUGLOG(6, " with matchLength=%u starting in extDict", matchCode + MINMATCH); + } else { + matchCode = LZ4_count(ip + MINMATCH, match + MINMATCH, matchlimit); + ip += (size_t)matchCode + MINMATCH; + DEBUGLOG(6, " with matchLength=%u", matchCode + MINMATCH); + } + + if ((outputDirective) && /* Check output buffer overflow */ + (unlikely(op + (1 + LASTLITERALS) + (matchCode + 240) / 255 > olimit))) { + if (outputDirective == fillOutput) { + /* Match description too long : reduce it */ + U32 newMatchCode = 15 /* in token */ - 1 /* to avoid needing a zero byte */ + ((U32)(olimit - op) - 1 - LASTLITERALS) * 255; + ip -= matchCode - newMatchCode; + assert(newMatchCode < matchCode); + matchCode = newMatchCode; + if (unlikely(ip <= filledIp)) { + /* We have already filled up to filledIp so if ip ends up less than filledIp + * we have positions in the hash table beyond the current position. This is + * a problem if we reuse the hash table. So we have to remove these positions + * from the hash table. + */ + const BYTE *ptr; + DEBUGLOG(5, "Clearing %u positions", (U32)(filledIp - ip)); + for (ptr = ip; ptr <= filledIp; ++ptr) { + U32 const h = LZ4_hashPosition(ptr, tableType); + LZ4_clearHash(h, cctx->hashTable, tableType); + } + } + } else { + assert(outputDirective == limitedOutput); + return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */ + } + } + if (matchCode >= ML_MASK) { + *token += ML_MASK; + matchCode -= ML_MASK; + LZ4_write32(op, 0xFFFFFFFF); + while (matchCode >= 4 * 255) { + op += 4; + LZ4_write32(op, 0xFFFFFFFF); + matchCode -= 4 * 255; + } + op += matchCode / 255; + *op++ = (BYTE)(matchCode % 255); + } else + *token += (BYTE)(matchCode); + } + /* Ensure we have enough space for the last literals. */ + assert(!(outputDirective == fillOutput && op + 1 + LASTLITERALS > olimit)); + + anchor = ip; + + /* Test end of chunk */ + if (ip >= mflimitPlusOne) break; + + /* Fill table */ + LZ4_putPosition(ip - 2, cctx->hashTable, tableType, base); + + /* Test next position */ + if (tableType == byPtr) { + + match = LZ4_getPosition(ip, cctx->hashTable, tableType, base); + LZ4_putPosition(ip, cctx->hashTable, tableType, base); + if ((match + LZ4_DISTANCE_MAX >= ip) + && (LZ4_read32(match) == LZ4_read32(ip))) { + token = op++; *token = 0; goto _next_match; + } + + } else { /* byU32, byU16 */ + + U32 const h = LZ4_hashPosition(ip, tableType); + U32 const current = (U32)(ip - base); + U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType); + assert(matchIndex < current); + if (dictDirective == usingDictCtx) { + if (matchIndex < startIndex) { + /* there was no match, try the dictionary */ + matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32); + match = dictBase + matchIndex; + lowLimit = dictionary; /* required for match length counter */ + matchIndex += dictDelta; + } else { + match = base + matchIndex; + lowLimit = (const BYTE *)source; /* required for match length counter */ + } + } else if (dictDirective == usingExtDict) { + if (matchIndex < startIndex) { + assert(dictBase); + match = dictBase + matchIndex; + lowLimit = dictionary; /* required for match length counter */ + } else { + match = base + matchIndex; + lowLimit = (const BYTE *)source; /* required for match length counter */ + } + } else { /* single memory segment */ + match = base + matchIndex; + } + LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType); + assert(matchIndex < current); + if (((dictIssue == dictSmall) ? (matchIndex >= prefixIdxLimit) : 1) + && (((tableType == byU16) && (LZ4_DISTANCE_MAX == LZ4_DISTANCE_ABSOLUTE_MAX)) ? 1 : (matchIndex + LZ4_DISTANCE_MAX >= current)) + && (LZ4_read32(match) == LZ4_read32(ip))) { + token = op++; + *token = 0; + if (maybe_extMem) offset = current - matchIndex; + DEBUGLOG(6, "seq.start:%i, literals=%u, match.start:%i", + (int)(anchor - (const BYTE *)source), 0, (int)(ip - (const BYTE *)source)); + goto _next_match; + } + } + + /* Prepare next loop */ + forwardH = LZ4_hashPosition(++ip, tableType); + + } + +_last_literals: + /* Encode Last Literals */ + { + size_t lastRun = (size_t)(iend - anchor); + if ((outputDirective) && /* Check output buffer overflow */ + (op + lastRun + 1 + ((lastRun + 255 - RUN_MASK) / 255) > olimit)) { + if (outputDirective == fillOutput) { + /* adapt lastRun to fill 'dst' */ + assert(olimit >= op); + lastRun = (size_t)(olimit - op) - 1/*token*/; + lastRun -= (lastRun + 256 - RUN_MASK) / 256; /*additional length tokens*/ + } else { + assert(outputDirective == limitedOutput); + return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */ + } + } + DEBUGLOG(6, "Final literal run : %i literals", (int)lastRun); + if (lastRun >= RUN_MASK) { + size_t accumulator = lastRun - RUN_MASK; + *op++ = RUN_MASK << ML_BITS; + for (; accumulator >= 255; accumulator -= 255) *op++ = 255; + *op++ = (BYTE)accumulator; + } else { + *op++ = (BYTE)(lastRun << ML_BITS); + } + LZ4_memcpy(op, anchor, lastRun); + ip = anchor + lastRun; + op += lastRun; + } + + if (outputDirective == fillOutput) { + *inputConsumed = (int)(((const char *)ip) - source); + } + result = (int)(((char *)op) - dest); + assert(result > 0); + DEBUGLOG(5, "LZ4_compress_generic: compressed %i bytes into %i bytes", inputSize, result); + return result; +} + +/** LZ4_compress_generic() : + * inlined, to ensure branches are decided at compilation time; + * takes care of src == (NULL, 0) + * and forward the rest to LZ4_compress_generic_validated */ +int LZ4_compress_generic( + LZ4_stream_t_internal *const cctx, + const char *const src, + char *const dst, + const int srcSize, + int *inputConsumed, /* only written when outputDirective == fillOutput */ + const int dstCapacity, + const limitedOutput_directive outputDirective, + const tableType_t tableType, + const dict_directive dictDirective, + const dictIssue_directive dictIssue, + const int acceleration) +{ + DEBUGLOG(5, "LZ4_compress_generic: srcSize=%i, dstCapacity=%i", + srcSize, dstCapacity); + + if ((U32)srcSize > (U32)LZ4_MAX_INPUT_SIZE) { return 0; } /* Unsupported srcSize, too large (or negative) */ + if (srcSize == 0) { /* src == NULL supported if srcSize == 0 */ + if (outputDirective != notLimited && dstCapacity <= 0) return 0; /* no output, can't write anything */ + DEBUGLOG(5, "Generating an empty block"); + assert(outputDirective == notLimited || dstCapacity >= 1); + assert(dst != NULL); + dst[0] = 0; + if (outputDirective == fillOutput) { + assert(inputConsumed != NULL); + *inputConsumed = 0; + } + return 1; + } + assert(src != NULL); + + return LZ4_compress_generic_validated(cctx, src, dst, srcSize, + inputConsumed, /* only written into if outputDirective == fillOutput */ + dstCapacity, outputDirective, + tableType, dictDirective, dictIssue, acceleration); +} + +int LZ4_compress_fast_extState(void *state, const char *source, char *dest, int inputSize, int maxOutputSize, int acceleration) +{ + LZ4_stream_t_internal *const ctx = &LZ4_initStream(state, sizeof(LZ4_stream_t))->internal_donotuse; + assert(ctx != NULL); + if (acceleration < 1) acceleration = 1; + if (acceleration > 65537) acceleration = 65537; + if (maxOutputSize >= LZ4_compressBound(inputSize)) { + if (inputSize < LZ4_64Klimit) { + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, 0, notLimited, byU16, noDict, noDictIssue, acceleration); + } else { + const tableType_t tableType = ((sizeof(void *) == 4) && ((uptrval)source > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + if (inputSize < LZ4_64Klimit) { + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, byU16, noDict, noDictIssue, acceleration); + } else { + const tableType_t tableType = ((sizeof(void *) == 4) && ((uptrval)source > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } + } +} + +int LZ4_compress_fast(const char *source, char *dest, int inputSize, int maxOutputSize, int acceleration) +{ + int result; + LZ4_stream_t *ctxPtr = (LZ4_stream_t *)vmalloc(sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */ + if (ctxPtr == NULL) return 0; + result = LZ4_compress_fast_extState(ctxPtr, source, dest, inputSize, maxOutputSize, acceleration); + vfree(ctxPtr); + return result; +} + +int LZ4_compress_default(const char *src, char *dst, int srcSize, int maxOutputSize) +{ + return LZ4_compress_fast(src, dst, srcSize, maxOutputSize, 1); +} + +/*-****************************** +* Streaming functions +********************************/ + +static size_t LZ4_stream_t_alignment(void) +{ +#if LZ4_ALIGN_TEST + typedef struct { char c; LZ4_stream_t t; } t_a; + return sizeof(t_a) - sizeof(LZ4_stream_t); +#else + return 1; /* effectively disabled */ +#endif +} + +LZ4_stream_t *LZ4_initStream(void *buffer, size_t size) +{ + DEBUGLOG(5, "LZ4_initStream"); + if (buffer == NULL) { return NULL; } + if (size < sizeof(LZ4_stream_t)) { return NULL; } + if (!LZ4_isAligned(buffer, LZ4_stream_t_alignment())) return NULL; + memset(buffer, 0, sizeof(LZ4_stream_t_internal)); + return (LZ4_stream_t *)buffer; +} + + +/*-******************************* + * Decompression functions + ********************************/ + +typedef enum { endOnOutputSize = 0, endOnInputSize = 1 } endCondition_directive; +typedef enum { decode_full_block = 0, partial_decode = 1 } earlyEnd_directive; + +#undef MIN +#define MIN(a,b) ( (a) < (b) ? (a) : (b) ) + +/* Read the variable-length literal or match length. + * + * ip - pointer to use as input. + * lencheck - end ip. Return an error if ip advances >= lencheck. + * loop_check - check ip >= lencheck in body of loop. Returns loop_error if so. + * initial_check - check ip >= lencheck before start of loop. Returns initial_error if so. + * error (output) - error code. Should be set to 0 before call. + */ +typedef enum { loop_error = -2, initial_error = -1, ok = 0 } variable_length_error; +unsigned read_variable_length(const BYTE **ip, const BYTE *lencheck, + int loop_check, int initial_check, + variable_length_error *error) +{ + U32 length = 0; + U32 s; + if (initial_check && unlikely((*ip) >= lencheck)) { /* overflow detection */ + *error = initial_error; + return length; + } + do { + s = **ip; + (*ip)++; + length += s; + if (loop_check && unlikely((*ip) >= lencheck)) { /* overflow detection */ + *error = loop_error; + return length; + } + } while (s == 255); + + return length; +} + +/*! LZ4_decompress_generic() : + * This generic decompression function covers all use cases. + * It shall be instantiated several times, using different sets of directives. + * Note that it is important for performance that this function really get inlined, + * in order to remove useless branches during compilation optimization. + */ +int LZ4_decompress_generic( + const char *const src, + char *const dst, + int srcSize, + int outputSize, /* If endOnInput==endOnInputSize, this value is `dstCapacity` */ + + endCondition_directive endOnInput, /* endOnOutputSize, endOnInputSize */ + earlyEnd_directive partialDecoding, /* full, partial */ + dict_directive dict, /* noDict, withPrefix64k, usingExtDict */ + const BYTE *const lowPrefix, /* always <= dst, == dst when no prefix */ + const BYTE *const dictStart, /* only if dict==usingExtDict */ + const size_t dictSize /* note : = 0 if noDict */ +) +{ + if ((src == NULL) || (outputSize < 0)) { return -1; } + + { + const BYTE *ip = (const BYTE *)src; + const BYTE *const iend = ip + srcSize; + + BYTE *op = (BYTE *)dst; + BYTE *const oend = op + outputSize; + BYTE *cpy; + + const BYTE *const dictEnd = (dictStart == NULL) ? NULL : dictStart + dictSize; + + const int safeDecode = (endOnInput == endOnInputSize); + const int checkOffset = ((safeDecode) && (dictSize < (int)(64 KB))); + + + /* Set up the "end" pointers for the shortcut. */ + const BYTE *const shortiend = iend - (endOnInput ? 14 : 8) /*maxLL*/ - 2 /*offset*/; + const BYTE *const shortoend = oend - (endOnInput ? 14 : 8) /*maxLL*/ - 18 /*maxML*/; + + const BYTE *match; + size_t offset; + unsigned token; + size_t length; + + + DEBUGLOG(5, "LZ4_decompress_generic (srcSize:%i, dstSize:%i)", srcSize, outputSize); + + /* Special cases */ + assert(lowPrefix <= op); + if ((endOnInput) && (unlikely(outputSize == 0))) { + /* Empty output buffer */ + if (partialDecoding) return 0; + return ((srcSize == 1) && (*ip == 0)) ? 0 : -1; + } + if ((!endOnInput) && (unlikely(outputSize == 0))) { return (*ip == 0 ? 1 : -1); } + if ((endOnInput) && unlikely(srcSize == 0)) { return -1; } + + /* Currently the fast loop shows a regression on qualcomm arm chips. */ + if ((oend - op) < FASTLOOP_SAFE_DISTANCE) { + DEBUGLOG(6, "skip fast decode loop"); + goto safe_decode; + } + + /* Fast loop : decode sequences as long as output < iend-FASTLOOP_SAFE_DISTANCE */ + while (1) { + /* Main fastloop assertion: We can always wildcopy FASTLOOP_SAFE_DISTANCE */ + assert(oend - op >= FASTLOOP_SAFE_DISTANCE); + if (endOnInput) { assert(ip < iend); } + token = *ip++; + length = token >> ML_BITS; /* literal length */ + + assert(!endOnInput || ip <= iend); /* ip < iend before the increment */ + + /* decode literal length */ + if (length == RUN_MASK) { + variable_length_error error = ok; + length += read_variable_length(&ip, iend - RUN_MASK, (int)endOnInput, (int)endOnInput, &error); + if (error == initial_error) { goto _output_error; } + if ((safeDecode) && unlikely((uptrval)(op)+length < (uptrval)(op))) { goto _output_error; } /* overflow detection */ + if ((safeDecode) && unlikely((uptrval)(ip)+length < (uptrval)(ip))) { goto _output_error; } /* overflow detection */ + + /* copy literals */ + cpy = op + length; + LZ4_STATIC_ASSERT(MFLIMIT >= WILDCOPYLENGTH); + if (endOnInput) { /* LZ4_decompress_safe() */ + if ((cpy > oend - 32) || (ip + length > iend - 32)) { goto safe_literal_copy; } + LZ4_wildCopy32(op, ip, cpy); + } else { /* LZ4_decompress_fast() */ + if (cpy > oend - 8) { goto safe_literal_copy; } + LZ4_wildCopy8(op, ip, cpy); /* LZ4_decompress_fast() cannot copy more than 8 bytes at a time : + * it doesn't know input length, and only relies on end-of-block properties */ + } + ip += length; op = cpy; + } else { + cpy = op + length; + if (endOnInput) { /* LZ4_decompress_safe() */ + DEBUGLOG(7, "copy %u bytes in a 16-bytes stripe", (unsigned)length); + /* We don't need to check oend, since we check it once for each loop below */ + if (ip > iend - (16 + 1/*max lit + offset + nextToken*/)) { goto safe_literal_copy; } + /* Literals can only be 14, but hope compilers optimize if we copy by a register size */ + LZ4_memcpy(op, ip, 16); + } else { /* LZ4_decompress_fast() */ + /* LZ4_decompress_fast() cannot copy more than 8 bytes at a time : + * it doesn't know input length, and relies on end-of-block properties */ + LZ4_memcpy(op, ip, 8); + if (length > 8) { LZ4_memcpy(op + 8, ip + 8, 8); } + } + ip += length; op = cpy; + } + + /* get offset */ + offset = LZ4_readLE16(ip); ip += 2; + match = op - offset; + assert(match <= op); + + /* get matchlength */ + length = token & ML_MASK; + + if (length == ML_MASK) { + variable_length_error error = ok; + if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) { goto _output_error; } /* Error : offset outside buffers */ + length += read_variable_length(&ip, iend - LASTLITERALS + 1, (int)endOnInput, 0, &error); + if (error != ok) { goto _output_error; } + if ((safeDecode) && unlikely((uptrval)(op)+length < (uptrval)op)) { goto _output_error; } /* overflow detection */ + length += MINMATCH; + if (op + length >= oend - FASTLOOP_SAFE_DISTANCE) { + goto safe_match_copy; + } + } else { + length += MINMATCH; + if (op + length >= oend - FASTLOOP_SAFE_DISTANCE) { + goto safe_match_copy; + } + + /* Fastpath check: Avoids a branch in LZ4_wildCopy32 if true */ + if ((dict == withPrefix64k) || (match >= lowPrefix)) { + if (offset >= 8) { + assert(match >= lowPrefix); + assert(match <= op); + assert(op + 18 <= oend); + + LZ4_memcpy(op, match, 8); + LZ4_memcpy(op + 8, match + 8, 8); + LZ4_memcpy(op + 16, match + 16, 2); + op += length; + continue; + } + } + } + + if (checkOffset && (unlikely(match + dictSize < lowPrefix))) { goto _output_error; } /* Error : offset outside buffers */ + /* match starting within external dictionary */ + if ((dict == usingExtDict) && (match < lowPrefix)) { + if (unlikely(op + length > oend - LASTLITERALS)) { + if (partialDecoding) { + DEBUGLOG(7, "partialDecoding: dictionary match, close to dstEnd"); + length = MIN(length, (size_t)(oend - op)); + } else { + goto _output_error; /* end-of-block condition violated */ + } + } + + if (length <= (size_t)(lowPrefix - match)) { + /* match fits entirely within external dictionary : just copy */ + memmove(op, dictEnd - (lowPrefix - match), length); + op += length; + } else { + /* match stretches into both external dictionary and current block */ + size_t const copySize = (size_t)(lowPrefix - match); + size_t const restSize = length - copySize; + LZ4_memcpy(op, dictEnd - copySize, copySize); + op += copySize; + if (restSize > (size_t)(op - lowPrefix)) { /* overlap copy */ + BYTE *const endOfMatch = op + restSize; + const BYTE *copyFrom = lowPrefix; + while (op < endOfMatch) { *op++ = *copyFrom++; } + } else { + LZ4_memcpy(op, lowPrefix, restSize); + op += restSize; + } + } + continue; + } + + /* copy match within block */ + cpy = op + length; + + assert((op <= oend) && (oend - op >= 32)); + if (unlikely(offset < 16)) { + LZ4_memcpy_using_offset(op, match, cpy, offset); + } else { + LZ4_wildCopy32(op, match, cpy); + } + + op = cpy; /* wildcopy correction */ + } + safe_decode: + + /* Main Loop : decode remaining sequences where output < FASTLOOP_SAFE_DISTANCE */ + while (1) { + token = *ip++; + length = token >> ML_BITS; /* literal length */ + + assert(!endOnInput || ip <= iend); /* ip < iend before the increment */ + + /* A two-stage shortcut for the most common case: + * 1) If the literal length is 0..14, and there is enough space, + * enter the shortcut and copy 16 bytes on behalf of the literals + * (in the fast mode, only 8 bytes can be safely copied this way). + * 2) Further if the match length is 4..18, copy 18 bytes in a similar + * manner; but we ensure that there's enough space in the output for + * those 18 bytes earlier, upon entering the shortcut (in other words, + * there is a combined check for both stages). + */ + if ((endOnInput ? length != RUN_MASK : length <= 8) + /* strictly "less than" on input, to re-enter the loop with at least one byte */ + && likely((endOnInput ? ip < shortiend : 1) & (op <= shortoend))) { + /* Copy the literals */ + LZ4_memcpy(op, ip, endOnInput ? 16 : 8); + op += length; ip += length; + + /* The second stage: prepare for match copying, decode full info. + * If it doesn't work out, the info won't be wasted. */ + length = token & ML_MASK; /* match length */ + offset = LZ4_readLE16(ip); ip += 2; + match = op - offset; + assert(match <= op); /* check overflow */ + + /* Do not deal with overlapping matches. */ + if ((length != ML_MASK) + && (offset >= 8) + && (dict == withPrefix64k || match >= lowPrefix)) { + /* Copy the match. */ + LZ4_memcpy(op + 0, match + 0, 8); + LZ4_memcpy(op + 8, match + 8, 8); + LZ4_memcpy(op + 16, match + 16, 2); + op += length + MINMATCH; + /* Both stages worked, load the next token. */ + continue; + } + + /* The second stage didn't work out, but the info is ready. + * Propel it right to the point of match copying. */ + goto _copy_match; + } + + /* decode literal length */ + if (length == RUN_MASK) { + variable_length_error error = ok; + length += read_variable_length(&ip, iend - RUN_MASK, (int)endOnInput, (int)endOnInput, &error); + if (error == initial_error) { goto _output_error; } + if ((safeDecode) && unlikely((uptrval)(op)+length < (uptrval)(op))) { goto _output_error; } /* overflow detection */ + if ((safeDecode) && unlikely((uptrval)(ip)+length < (uptrval)(ip))) { goto _output_error; } /* overflow detection */ + } + + /* copy literals */ + cpy = op + length; + safe_literal_copy : + LZ4_STATIC_ASSERT(MFLIMIT >= WILDCOPYLENGTH); + if (((endOnInput) && ((cpy > oend - MFLIMIT) || (ip + length > iend - (2 + 1 + LASTLITERALS)))) + || ((!endOnInput) && (cpy > oend - WILDCOPYLENGTH))) { + /* We've either hit the input parsing restriction or the output parsing restriction. + * In the normal scenario, decoding a full block, it must be the last sequence, + * otherwise it's an error (invalid input or dimensions). + * In partialDecoding scenario, it's necessary to ensure there is no buffer overflow. + */ + if (partialDecoding) { + /* Since we are partial decoding we may be in this block because of the output parsing + * restriction, which is not valid since the output buffer is allowed to be undersized. + */ + assert(endOnInput); + DEBUGLOG(7, "partialDecoding: copying literals, close to input or output end") + DEBUGLOG(7, "partialDecoding: literal length = %u", (unsigned)length); + DEBUGLOG(7, "partialDecoding: remaining space in dstBuffer : %i", (int)(oend - op)); + DEBUGLOG(7, "partialDecoding: remaining space in srcBuffer : %i", (int)(iend - ip)); + /* Finishing in the middle of a literals segment, + * due to lack of input. + */ + if (ip + length > iend) { + length = (size_t)(iend - ip); + cpy = op + length; + } + /* Finishing in the middle of a literals segment, + * due to lack of output space. + */ + if (cpy > oend) { + cpy = oend; + assert(op <= oend); + length = (size_t)(oend - op); + } + } else { + /* We must be on the last sequence because of the parsing limitations so check + * that we exactly regenerate the original size (must be exact when !endOnInput). + */ + if ((!endOnInput) && (cpy != oend)) { goto _output_error; } + /* We must be on the last sequence (or invalid) because of the parsing limitations + * so check that we exactly consume the input and don't overrun the output buffer. + */ + if ((endOnInput) && ((ip + length != iend) || (cpy > oend))) { + DEBUGLOG(6, "should have been last run of literals") + DEBUGLOG(6, "ip(%p) + length(%i) = %p != iend (%p)", ip, (int)length, ip + length, iend); + DEBUGLOG(6, "or cpy(%p) > oend(%p)", cpy, oend); + goto _output_error; + } + } + memmove(op, ip, length); /* supports overlapping memory regions; only matters for in-place decompression scenarios */ + ip += length; + op += length; + /* Necessarily EOF when !partialDecoding. + * When partialDecoding, it is EOF if we've either + * filled the output buffer or + * can't proceed with reading an offset for following match. + */ + if (!partialDecoding || (cpy == oend) || (ip >= (iend - 2))) { + break; + } + } else { + LZ4_wildCopy8(op, ip, cpy); /* may overwrite up to WILDCOPYLENGTH beyond cpy */ + ip += length; op = cpy; + } + + /* get offset */ + offset = LZ4_readLE16(ip); ip += 2; + match = op - offset; + + /* get matchlength */ + length = token & ML_MASK; + + _copy_match: + if (length == ML_MASK) { + variable_length_error error = ok; + length += read_variable_length(&ip, iend - LASTLITERALS + 1, (int)endOnInput, 0, &error); + if (error != ok) goto _output_error; + if ((safeDecode) && unlikely((uptrval)(op)+length < (uptrval)op)) goto _output_error; /* overflow detection */ + } + length += MINMATCH; + + safe_match_copy : + if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) goto _output_error; /* Error : offset outside buffers */ + /* match starting within external dictionary */ + if ((dict == usingExtDict) && (match < lowPrefix)) { + if (unlikely(op + length > oend - LASTLITERALS)) { + if (partialDecoding) length = MIN(length, (size_t)(oend - op)); + else goto _output_error; /* doesn't respect parsing restriction */ + } + + if (length <= (size_t)(lowPrefix - match)) { + /* match fits entirely within external dictionary : just copy */ + memmove(op, dictEnd - (lowPrefix - match), length); + op += length; + } else { + /* match stretches into both external dictionary and current block */ + size_t const copySize = (size_t)(lowPrefix - match); + size_t const restSize = length - copySize; + LZ4_memcpy(op, dictEnd - copySize, copySize); + op += copySize; + if (restSize > (size_t)(op - lowPrefix)) { /* overlap copy */ + BYTE *const endOfMatch = op + restSize; + const BYTE *copyFrom = lowPrefix; + while (op < endOfMatch) *op++ = *copyFrom++; + } else { + LZ4_memcpy(op, lowPrefix, restSize); + op += restSize; + } + } + continue; + } + assert(match >= lowPrefix); + + /* copy match within block */ + cpy = op + length; + + /* partialDecoding : may end anywhere within the block */ + assert(op <= oend); + if (partialDecoding && (cpy > oend - MATCH_SAFEGUARD_DISTANCE)) { + size_t const mlen = MIN(length, (size_t)(oend - op)); + const BYTE *const matchEnd = match + mlen; + BYTE *const copyEnd = op + mlen; + if (matchEnd > op) { /* overlap copy */ + while (op < copyEnd) { *op++ = *match++; } + } else { + LZ4_memcpy(op, match, mlen); + } + op = copyEnd; + if (op == oend) { break; } + continue; + } + + if (unlikely(offset < 8)) { + LZ4_write32(op, 0); /* silence msan warning when offset==0 */ + op[0] = match[0]; + op[1] = match[1]; + op[2] = match[2]; + op[3] = match[3]; + match += inc32table[offset]; + LZ4_memcpy(op + 4, match, 4); + match -= dec64table[offset]; + } else { + LZ4_memcpy(op, match, 8); + match += 8; + } + op += 8; + + if (unlikely(cpy > oend - MATCH_SAFEGUARD_DISTANCE)) { + BYTE *const oCopyLimit = oend - (WILDCOPYLENGTH - 1); + if (cpy > oend - LASTLITERALS) { goto _output_error; } /* Error : last LASTLITERALS bytes must be literals (uncompressed) */ + if (op < oCopyLimit) { + LZ4_wildCopy8(op, match, oCopyLimit); + match += oCopyLimit - op; + op = oCopyLimit; + } + while (op < cpy) { *op++ = *match++; } + } else { + LZ4_memcpy(op, match, 8); + if (length > 16) { LZ4_wildCopy8(op + 8, match + 8, cpy); } + } + op = cpy; /* wildcopy correction */ + } + + /* end of decoding */ + if (endOnInput) { + DEBUGLOG(5, "decoded %i bytes", (int)(((char *)op) - dst)); + return (int)(((char *)op) - dst); /* Nb of output bytes decoded */ + } else { + return (int)(((const char *)ip) - src); /* Nb of input bytes read */ + } + + /* Overflow error detected */ + _output_error: + return (int)(-(((const char *)ip) - src)) - 1; + } +} + +int LZ4_decompress_safe(const char *source, char *dest, int compressedSize, int maxDecompressedSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxDecompressedSize, + endOnInputSize, decode_full_block, noDict, + (BYTE *)dest, NULL, 0); +} + +#endif // VORTEX_SLIM == 0 diff --git a/VortexEngine/src/Serial/Serial.c b/VortexEngine/src/Serial/Serial.c new file mode 100644 index 0000000000..3dff9d68d9 --- /dev/null +++ b/VortexEngine/src/Serial/Serial.c @@ -0,0 +1,140 @@ +#include "Serial.h" + +#include "../Serial/ByteStream.h" +#include "../Time/TimeControl.h" +#include "../Time/Timings.h" +#include "../Log/Log.h" + +#include "../VortexEngine.h" + +#ifdef VORTEX_LIB +#include "VortexLib.h" +#include +#endif + +static bool m_serialConnected = false; +static uint32_t m_lastCheck = 0; + +bool SerialComs_init() +{ + return true; +} + +void SerialComs_cleanup() +{ +} + +bool SerialComs_isConnected() +{ + return m_serialConnected; +} + +bool SerialComs_checkSerial() +{ +#if VORTEX_SLIM == 0 + if (SerialComs_isConnected()) { + return true; + } + uint32_t now = Time_getCurtime(); + if (m_lastCheck && (now - m_lastCheck) < MAX_SERIAL_CHECK_INTERVAL) { + return false; + } + m_lastCheck = now; +#ifdef VORTEX_LIB + if (!Vortex_vcallbacks()->serialCheck()) { + return false; + } + Vortex_vcallbacks()->serialBegin(SERIAL_BAUD_RATE); +#else + if (!Serial.available()) { + return false; + } + Serial.begin(SERIAL_BAUD_RATE); +#endif +#endif + m_serialConnected = true; + return true; +} + +void SerialComs_write(const char *msg, ...) +{ +#if VORTEX_SLIM == 0 + if (!SerialComs_isConnected()) { + return; + } + va_list list; + va_start(list, msg); + uint8_t buf[2048] = {0}; + int len = vsnprintf((char *)buf, sizeof(buf), msg, list); +#ifdef VORTEX_LIB + Vortex_vcallbacks()->serialWrite(buf, len); +#else + Serial.write(buf, len); + Serial.flush(); +#endif + va_end(list); +#endif +} + +void SerialComs_writeStream(ByteStream *byteStream) +{ +#if VORTEX_SLIM == 0 + if (!SerialComs_isConnected()) { + return; + } + ByteStream_recalcCRC(byteStream, false); + uint32_t size = ByteStream_rawSize(byteStream); +#ifdef VORTEX_LIB + Vortex_vcallbacks()->serialWrite((const uint8_t *)&size, sizeof(size)); + Vortex_vcallbacks()->serialWrite((const uint8_t *)ByteStream_rawData(byteStream), ByteStream_rawSize(byteStream)); +#else + Serial.write((const uint8_t *)&size, sizeof(size)); + Serial.write((const uint8_t *)ByteStream_rawData(byteStream), ByteStream_rawSize(byteStream)); + Serial.flush(); +#endif +#endif +} + +void SerialComs_read(ByteStream *byteStream) +{ +#if VORTEX_SLIM == 0 + if (!SerialComs_isConnected()) { + return; + } +#ifdef VORTEX_LIB + uint32_t amt = Vortex_vcallbacks()->serialAvail(); +#else + uint32_t amt = Serial.available(); +#endif + if (!amt) { + return; + } + do { + uint8_t byte = 0; +#ifdef VORTEX_LIB + if (!Vortex_vcallbacks()->serialRead((char *)&byte, 1)) { + return; + } +#else + byte = Serial.read(); +#endif + ByteStream_serialize8(byteStream, byte); + } while (--amt > 0); +#endif +} + +bool SerialComs_dataReady() +{ +#if VORTEX_SLIM == 0 + if (!SerialComs_isConnected()) { + return false; + } +#ifdef VORTEX_LIB + return (Vortex_vcallbacks()->serialAvail() > 0); +#else + return (Serial.available() > 0); +#endif +#else + return false; +#endif +} diff --git a/VortexEngine/src/Serial/Serial.h b/VortexEngine/src/Serial/Serial.h index 0121b5bbb0..f0bfd9b6c9 100644 --- a/VortexEngine/src/Serial/Serial.h +++ b/VortexEngine/src/Serial/Serial.h @@ -2,40 +2,17 @@ #define SERIAL_H #include - -class ByteStream; - -// Really wish I could name this Serial but arduino ruined that for me -class SerialComs -{ -public: - - // init serial - static bool init(); - static void cleanup(); - - // whether serial is initialized - static bool isConnected(); - - // check for any serial connection or messages - static bool checkSerial(); - - // write a message to serial - static void write(const char *msg, ...); - - // write raw data - static void write(ByteStream &byteStream); - - // read a message from serial - static void read(ByteStream &byteStream); - - // data in the socket ready to read - static bool dataReady(); - -private: - // whether serial communications are initialized - static bool m_serialConnected; - static uint32_t m_lastCheck; -}; +#include + +typedef struct ByteStream ByteStream; + +bool SerialComs_init(); +void SerialComs_cleanup(); +bool SerialComs_isConnected(); +bool SerialComs_checkSerial(); +void SerialComs_write(const char *msg, ...); +void SerialComs_writeStream(ByteStream *byteStream); +void SerialComs_read(ByteStream *byteStream); +bool SerialComs_dataReady(); #endif diff --git a/VortexEngine/src/Storage/Storage.c b/VortexEngine/src/Storage/Storage.c new file mode 100644 index 0000000000..75d7203b39 --- /dev/null +++ b/VortexEngine/src/Storage/Storage.c @@ -0,0 +1,263 @@ +#include "Storage.h" + +#include +#include + +#include "../VortexConfig.h" +#include "../Memory/Memory.h" +#include "../Serial/ByteStream.h" +#include "../Log/Log.h" + +#ifdef VORTEX_EMBEDDED +#include +#include +#include +#include +#endif + +#ifdef VORTEX_LIB +#include "../VortexLib/VortexLib.h" +#endif + +#ifdef VORTEX_EMBEDDED +static uint8_t eepromReadByte(uint16_t index); +static void eepromWriteByte(uint16_t index, uint8_t in); +#endif + +#ifndef VORTEX_EMBEDDED +#ifdef _WIN32 +#include +#else +#include +#include +#endif +#endif + +#define DEFAULT_STORAGE_FILENAME "FlashStorage.flash" + +#ifdef VORTEX_LIB +static char m_storageFilename[256]; +#define STORAGE_FILENAME m_storageFilename +#else +#define STORAGE_FILENAME DEFAULT_STORAGE_FILENAME +#endif + +#define FLASH_PAGE_SIZE 128 +#define FLASH_STORAGE_SIZE (STORAGE_SIZE) +#define FLASH_STORAGE_SPACE ((volatile uint8_t *)(0x10000 - FLASH_STORAGE_SIZE)) +#define HEADER_SIZE 15 +#define STORAGE_HEADER_SIZE (HEADER_SIZE + 12) + +static uint32_t m_lastSaveSize = 0; + +bool Storage_init() +{ +#ifdef VORTEX_LIB + if (strlen(m_storageFilename) == 0 && Vortex_storageEnabled()) { + strcpy(m_storageFilename, DEFAULT_STORAGE_FILENAME); + } +#endif + return true; +} + +void Storage_cleanup() +{ +} + +bool Storage_write(uint8_t slot, ByteStream *buffer) +{ +#ifdef VORTEX_LIB + if (!Vortex_storageEnabled()) { + return true; + } +#endif + uint16_t size = ByteStream_rawSize(buffer); + if (ByteStream_rawSize(buffer) > MAX_MODE_SIZE) { + ERROR_LOG("Buffer too big for storage space"); + return false; + } + if (slot >= NUM_MODE_SLOTS) { + return false; + } + ByteStream_recalcCRC(buffer, false); +#ifdef VORTEX_EMBEDDED + const uint8_t *buf = (const uint8_t *)ByteStream_rawData(buffer); + if (slot < 4) { + uint8_t eepSlot = 0; + if (slot > 0) { + eepSlot = STORAGE_HEADER_SIZE + (MAX_MODE_SIZE * (slot - 1)); + } + uint8_t slotSize = (slot == 0) ? STORAGE_HEADER_SIZE : MAX_MODE_SIZE; + for (uint8_t i = 0; i < slotSize; ++i) { + uint8_t b = (i < size) ? buf[i] : 0x00; + eepromWriteByte(eepSlot + i, b); + } + } else { + uint16_t slotAddr = (uint16_t)FLASH_STORAGE_SPACE + (MAX_MODE_SIZE * (slot - 4)); + while (size > 0) { + uint16_t pageStart = slotAddr & ~(FLASH_PAGE_SIZE - 1); + uint16_t offset = slotAddr % FLASH_PAGE_SIZE; + uint16_t space = FLASH_PAGE_SIZE - offset; + uint16_t writeSize = (size < space) ? size : space; + + for (uint8_t i = 0; i < FLASH_PAGE_SIZE; ++i) { + uint8_t value; + if (i >= offset && i < offset + writeSize) { + value = buf[i - offset]; + } else { + value = *(volatile uint8_t *)(pageStart + i); + } + *(volatile uint8_t *)(pageStart + i) = value; + } + + _PROTECTED_WRITE_SPM(NVMCTRL.CTRLA, NVMCTRL_CMD_PAGEERASEWRITE_gc); + while (NVMCTRL.STATUS & (NVMCTRL_FBUSY_bm | NVMCTRL_EEBUSY_bm)); + + slotAddr += writeSize; + buf += writeSize; + size -= writeSize; + } + } +#elif defined(_WIN32) + HANDLE hFile = CreateFile(STORAGE_FILENAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) { + return false; + } + DWORD written = 0; + DWORD offset = slot * MAX_MODE_SIZE; + SetFilePointer(hFile, offset, NULL, FILE_BEGIN); + uint8_t modeBuffer[MAX_MODE_SIZE] = {0}; + memcpy(modeBuffer, ByteStream_rawData(buffer), size); + if (!WriteFile(hFile, modeBuffer, MAX_MODE_SIZE, &written, NULL)) { + return false; + } + CloseHandle(hFile); +#else + FILE *f = fopen(STORAGE_FILENAME, "w"); + if (!f) { + return false; + } + long offset = slot * MAX_MODE_SIZE; + fseek(f, offset, SEEK_SET); + uint8_t modeBuffer[MAX_MODE_SIZE] = {0}; + memcpy(modeBuffer, ByteStream_rawData(buffer), size); + if (!fwrite(modeBuffer, sizeof(char), MAX_MODE_SIZE, f)) { + return false; + } + fclose(f); +#endif + DEBUG_LOGF("Wrote %u bytes to storage (max: %u)", m_lastSaveSize, STORAGE_SIZE); + return true; +} + +bool Storage_read(uint8_t slot, ByteStream *buffer) +{ +#ifdef VORTEX_LIB + if (!Vortex_storageEnabled()) { + return false; + } +#endif + uint32_t size = MAX_MODE_SIZE; + if (size > STORAGE_SIZE || size < sizeof(RawBuffer) + 4 || slot >= NUM_MODE_SLOTS) { + return false; + } + if (!ByteStream_init(buffer, size, NULL)) { + return false; + } +#ifdef VORTEX_EMBEDDED + uint8_t *buf = (uint8_t *)ByteStream_rawData(buffer); + volatile uint8_t *src; + if (slot == 0) { + src = (volatile uint8_t *)MAPPED_EEPROM_START; + size = STORAGE_HEADER_SIZE; + } else if (slot < 4) { + src = (volatile uint8_t *)MAPPED_EEPROM_START + STORAGE_HEADER_SIZE + (MAX_MODE_SIZE * (slot - 1)); + } else { + src = (volatile uint8_t *)FLASH_STORAGE_SPACE + (MAX_MODE_SIZE * (slot - 4)); + } + for (uint8_t i = 0; i < size; ++i) { + buf[i] = src[i]; + } +#elif defined(_WIN32) + HANDLE hFile = CreateFile(STORAGE_FILENAME, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) { + return false; + } + DWORD bytesRead = 0; + DWORD offset = slot * MAX_MODE_SIZE; + SetFilePointer(hFile, offset, NULL, FILE_BEGIN); + if (!ReadFile(hFile, ByteStream_rawData(buffer), MAX_MODE_SIZE, &bytesRead, NULL)) { + return false; + } + CloseHandle(hFile); +#else + FILE *f = fopen(STORAGE_FILENAME, "r"); + if (!f) { + return false; + } + long offset = slot * MAX_MODE_SIZE; + fseek(f, offset, SEEK_SET); + if (!fread(ByteStream_rawData(buffer), sizeof(char), MAX_MODE_SIZE, f)) { + return false; + } + fclose(f); +#endif + ByteStream_sanity(buffer); + if (!ByteStream_checkCRC(buffer)) { + ByteStream_clear(buffer); + ERROR_LOG("Could not verify buffer"); + return false; + } + m_lastSaveSize = (uint32_t)size; + DEBUG_LOGF("Loaded savedata (Size: %u)", ByteStream_size(buffer)); + return true; +} + +uint32_t Storage_lastSaveSize() +{ + return m_lastSaveSize; +} + +#ifdef VORTEX_EMBEDDED +static void eepromWriteByte(uint16_t index, uint8_t in) +{ + if (eepromReadByte(index) == in) { + return; + } + uint16_t adr = MAPPED_EEPROM_START + (index); + __asm__ __volatile__( + "ldi r30, 0x00" "\n\t" + "ldi r31, 0x10" "\n\t" + "ldd r18, Z+2" "\n\t" + "andi r18, 3" "\n\t" + "brne .-6" "\n\t" + "st X, %0" "\n\t" + "ldi %0, 0x9D" "\n\t" + "out 0x34, %0" "\n\t" + "ldi %0, 0x03" "\n\t" + "st Z, %0" "\n\t" + :"+d"(in) + : "x"(adr) + : "r30", "r31", "r18"); + + while (!(NVMCTRL.STATUS & NVMCTRL_EEBUSY_bm)); +} + +static uint8_t eepromReadByte(uint16_t index) +{ + return *(volatile uint8_t *)(MAPPED_EEPROM_START + index); +} +#endif + +#ifdef VORTEX_LIB +void Storage_setStorageFilename(const char *name) +{ + strncpy(m_storageFilename, name, sizeof(m_storageFilename) - 1); + m_storageFilename[sizeof(m_storageFilename) - 1] = '\0'; +} + +const char *Storage_getStorageFilename() +{ + return m_storageFilename; +} +#endif diff --git a/VortexEngine/src/Storage/Storage.h b/VortexEngine/src/Storage/Storage.h index 80da3edcb9..eecf43cb4d 100644 --- a/VortexEngine/src/Storage/Storage.h +++ b/VortexEngine/src/Storage/Storage.h @@ -2,46 +2,21 @@ #define STORAGE_H #include +#include -#ifdef VORTEX_LIB -#include -#endif - -class ByteStream; +typedef struct ByteStream ByteStream; -class Storage -{ - Storage(); -public: +bool Storage_init(); +void Storage_cleanup(); - // init storage - static bool init(); - static void cleanup(); +bool Storage_write(uint8_t slot, ByteStream *buffer); +bool Storage_read(uint8_t slot, ByteStream *buffer); - // store a serial buffer to storage - static bool write(uint8_t slot, ByteStream &buffer); - // read a serial buffer from storage - static bool read(uint8_t slot, ByteStream &buffer); - - // the last save size (use STORAGE_SIZE For total space) - static uint32_t lastSaveSize(); +uint32_t Storage_lastSaveSize(); #ifdef VORTEX_LIB - static void setStorageFilename(const std::string &name) { m_storageFilename = name; } - static std::string getStorageFilename() { return m_storageFilename; } +void Storage_setStorageFilename(const char *name); +const char *Storage_getStorageFilename(); #endif -private: - // write out a single byte to the eeprom - static void eepromWriteByte(uint16_t index, uint8_t in); - static uint8_t eepromReadByte(uint16_t index); - -#ifdef VORTEX_LIB - static std::string m_storageFilename; -#endif - - // the size of the last save - static uint32_t m_lastSaveSize; -}; - #endif diff --git a/VortexEngine/src/Time/TimeControl.c b/VortexEngine/src/Time/TimeControl.c new file mode 100644 index 0000000000..6c80b42d5a --- /dev/null +++ b/VortexEngine/src/Time/TimeControl.c @@ -0,0 +1,332 @@ +#include "TimeControl.h" + +#include + +#include "../Memory/Memory.h" +#include "../Log/Log.h" + +#include "Timings.h" + +#include "../Leds/Leds.h" + +#ifdef VORTEX_EMBEDDED +#include +#include +#endif + +#if !defined(_WIN32) || defined(WASM) +#include +#include +static uint64_t start_us = 0; +#define SEC_TO_US(sec) ((sec)*1000000) +#define NS_TO_US(ns) ((ns)/1000) +#else +#include +static LARGE_INTEGER tps; +static LARGE_INTEGER start_us; +#endif + +#if VARIABLE_TICKRATE == 1 +uint32_t Time_tickrate = DEFAULT_TICKRATE; +#endif +uint32_t Time_curTick = 0; +#ifdef VORTEX_LIB +uint32_t Time_prevTime = 0; +uint32_t Time_firstTime = 0; +uint32_t Time_simulationTick = 0; +bool Time_isSimulation_ = false; +bool Time_instantTimestep = false; +#endif + +#if VARIABLE_TICKRATE == 1 +#define TICKRATE Time_tickrate +#else +#define TICKRATE DEFAULT_TICKRATE +#endif + +bool Time_init(void) +{ +#ifdef VORTEX_EMBEDDED +#if (F_CPU == 20000000) + _PROTECTED_WRITE(CLKCTRL_MCLKCTRLB, 0x00); +#elif (F_CPU == 10000000) + _PROTECTED_WRITE(CLKCTRL_MCLKCTRLB, (CLKCTRL_PEN_bm | CLKCTRL_PDIV_2X_gc)); +#else + #error "F_CPU not supported" +#endif + _PROTECTED_WRITE(CPUINT_CTRLA, CPUINT_IVSEL_bm); +#endif + Time_curTick = 0; +#if VARIABLE_TICKRATE == 1 + Time_tickrate = DEFAULT_TICKRATE; +#endif +#ifdef VORTEX_LIB + Time_firstTime = Time_prevTime = Time_microseconds(); + Time_simulationTick = 0; + Time_isSimulation_ = false; + Time_instantTimestep = false; +#endif +#if !defined(_WIN32) || defined(WASM) + start_us = Time_microseconds(); +#else + QueryPerformanceFrequency(&tps); + QueryPerformanceCounter(&start_us); +#endif + return true; +} + +void Time_cleanup(void) +{ +} + +void Time_tickClock(void) +{ + Time_curTick++; + +#if DEBUG_ALLOCATIONS == 1 + if ((Time_curTick % MS_TO_TICKS(1000)) == 0) { + DEBUG_LOGF("Cur Memory: %u (%u)", cur_memory_usage(), cur_memory_usage_background()); + } +#endif + +#ifdef VORTEX_LIB + if (Time_instantTimestep) { + return; + } + + uint32_t elapsed_us; + uint32_t us; + do { + us = Time_microseconds(); + if (us < Time_prevTime) { + elapsed_us = (uint32_t)((UINT32_MAX - Time_prevTime) + us); + } else { + elapsed_us = (uint32_t)(us - Time_prevTime); + } +#if !defined(_WIN32) && defined(VORTEX_LIB) + uint32_t required = (1000000 / TICKRATE); + uint32_t sleepTime = 0; + if (required > elapsed_us) { + sleepTime = required - elapsed_us; + } + Time_delayMicroseconds(sleepTime); + break; +#endif + } while (elapsed_us < (1000000 / TICKRATE)); + + Time_prevTime = Time_microseconds(); +#endif +} + +uint32_t Time_getCurtime(void) +{ + return Time_curTick + SIMULATION_TICK; +} + +uint32_t Time_getRealCurtime(void) +{ + return Time_curTick; +} + +uint32_t Time_getTickrate(void) +{ + return TICKRATE; +} + +void Time_setTickrate(uint32_t tickrate) +{ +#if VARIABLE_TICKRATE == 1 + if (!tickrate) { + tickrate = DEFAULT_TICKRATE; + } else if (tickrate > 1000000) { + tickrate = 1000000; + } + Time_tickrate = tickrate; +#endif +} + +#if VARIABLE_TICKRATE == 1 +uint32_t Time_millisecondsToTicks(uint32_t ms) +{ + if (!ms) { + return 0; + } + uint32_t ticks = (ms * TICKRATE) / 1000; + if (!ticks) { + return 1; + } + return ticks; +} + +uint32_t Time_secondsToTicks(uint32_t sec) +{ + return Time_millisecondsToTicks(sec * 1000); +} +#endif + +uint32_t Time_microseconds(void) +{ +#ifndef VORTEX_LIB + uint32_t ticks; + ticks = (Time_curTick * DEFAULT_TICKRATE) + (TCB0.CNT / 1000); + return ticks; +#elif defined(_WIN32) + LARGE_INTEGER now; + QueryPerformanceCounter(&now); + if (!tps.QuadPart) { + return 0; + } + return (unsigned long)((now.QuadPart - start_us.QuadPart) * 1000000 / tps.QuadPart); +#else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC_RAW, &ts); + uint64_t us = SEC_TO_US((uint64_t)ts.tv_sec) + NS_TO_US((uint64_t)ts.tv_nsec); + return (unsigned long)us; +#endif +} + +#ifdef VORTEX_EMBEDDED +__attribute__ ((noinline)) +#endif +void Time_delayMicroseconds(uint32_t us) +{ +#ifdef VORTEX_EMBEDDED +#if F_CPU >= 20000000L + __asm__ __volatile__ ( + "rjmp .+0" "\n\t" + "nop" ); + if (us <= 1) return; + us = us << 1; + us -= 2; +#elif F_CPU >= 10000000L + if (us <= 2) return; + us -= 4; +#endif + __asm__ __volatile__( + "1: sbiw %0, 1" "\n\t" + "rjmp .+0" "\n\t" + "rjmp .+0" "\n\t" + "rjmp .+0" "\n\t" + "brne 1b" : "=w" (us) : "0" (us) + ); +#elif defined(_WIN32) + uint32_t newtime = Time_microseconds() + us; + while (Time_microseconds() < newtime) { + } +#else + usleep(us); +#endif +} + +void Time_delayMilliseconds(uint32_t ms) +{ +#ifdef VORTEX_EMBEDDED + for (uint16_t i = 0; i < ms; ++i) { + Time_delayMicroseconds(1000); + } +#elif defined(_WIN32) + Sleep(ms); +#else + usleep(ms * 1000); +#endif +} + +#ifdef VORTEX_LIB + +uint32_t Time_startSimulation(void) +{ + Time_simulationTick = 0; + Time_isSimulation_ = true; + return (uint32_t)Time_getCurtime(); +} + +uint32_t Time_tickSimulation(void) +{ + return ++Time_simulationTick; +} + +bool Time_isSimulation(void) +{ + return Time_isSimulation_; +} + +uint32_t Time_getSimulationTick(void) +{ + return Time_simulationTick; +} + +uint32_t Time_endSimulation(void) +{ + uint32_t endTick = (uint32_t)Time_getCurtime(); + Time_simulationTick = 0; + Time_isSimulation_ = false; + return endTick; +} + +void Time_setInstantTimestep(bool instant) +{ + Time_instantTimestep = instant; +} + +bool Time_isInstantStepping(void) +{ + return Time_instantTimestep; +} + +#endif + +#if TIMER_TEST == 1 +#include + +void Time_test(void) +{ + DEBUG_LOG("Starting Time class tests..."); + + Time_setInstantTimestep(true); + + assert(Time_init()); + DEBUG_LOG("Init test passed"); + Time_cleanup(); + + uint32_t initialTick = Time_curTick; + Time_tickClock(); + assert(Time_curTick == initialTick + 1); + DEBUG_LOG("tickClock test passed"); + + assert(Time_millisecondsToTicks(1000) == TICKRATE); + assert(Time_millisecondsToTicks(500) == TICKRATE / 2); + assert(Time_millisecondsToTicks(0) == 0); + DEBUG_LOG("msToTicks test passed"); + + assert(Time_getRealCurtime() == Time_curTick); + DEBUG_LOG("getRealCurtime test passed"); + + assert(Time_getTickrate() == TICKRATE); + DEBUG_LOG("getTickrate test passed"); + +#if VARIABLE_TICKRATE == 1 + uint32_t newTickrate = TICKRATE * 2; + Time_setTickrate(newTickrate); + assert(Time_getTickrate() == newTickrate); + DEBUG_LOG("setTickrate test passed"); +#endif + +#ifdef VORTEX_LIB + uint32_t simulationStartTick = Time_startSimulation(); + assert(Time_isSimulation()); + assert(Time_getSimulationTick() == 0); + DEBUG_LOG("startSimulation test passed"); + + uint32_t simulationTick = Time_tickSimulation(); + assert(Time_getSimulationTick() == 1); + DEBUG_LOGF("tickSimulation test passed, simulationTick: %u", simulationTick); + + uint32_t simulationEndTick = Time_endSimulation(); + assert(!Time_isSimulation()); + assert(simulationEndTick == simulationStartTick + 1); + DEBUG_LOGF("endSimulation test passed, simulationEndTick: %u", simulationEndTick); +#endif + + DEBUG_LOG("Time class tests completed successfully."); +} +#endif diff --git a/VortexEngine/src/Time/TimeControl.h b/VortexEngine/src/Time/TimeControl.h index 7c7a061984..1741d29188 100644 --- a/VortexEngine/src/Time/TimeControl.h +++ b/VortexEngine/src/Time/TimeControl.h @@ -2,140 +2,66 @@ #define TIME_CONTROL_H #include - +#include #include "../VortexConfig.h" #include "../Leds/LedTypes.h" #if VARIABLE_TICKRATE == 1 -#define MS_TO_TICKS(ms) Time::millisecondsToTicks(ms) -#define SEC_TO_TICKS(s) Time::secondsToTicks(s) +#define MS_TO_TICKS(ms) Time_millisecondsToTicks(ms) +#define SEC_TO_TICKS(s) Time_secondsToTicks(s) #else #define MS_TO_TICKS(ms) (uint32_t)(((uint32_t)(ms) * DEFAULT_TICKRATE) / 1000) #define SEC_TO_TICKS(s) (uint32_t)((uint32_t)(s) * DEFAULT_TICKRATE) #endif #ifdef VORTEX_LIB -#define SIMULATION_TICK getSimulationTick() +#define SIMULATION_TICK Time_getSimulationTick() #else #define SIMULATION_TICK 0 #endif -class Time -{ - // private unimplemented constructor - Time(); - -public: - // opting for static class here because there should only ever be one - // Settings control object and I don't like singletons - static bool init(); - static void cleanup(); - - // tick the clock forward to millis() - static void tickClock(); - - // get the current tick, offset by any active simulation (simulation only exists in vortexlib) - // Exposing this in the header seems to save on space a non negligible amount, it is used a lot - // and exposing in the header probably allows the compiler to optimize away repititive calls - static uint32_t getCurtime() { return m_curTick + SIMULATION_TICK; } - - // this ignore simulation time, it's used by timers to make simulations work - static uint32_t getRealCurtime(); - - // Set tickrate in Ticks Per Second (TPS) - // The valid range for this is 1 <= x <= 1000000 - // - // Setting a value of 0 will restore the default - // - // NOTE: Patterns will not change when this changes, you - // must re-create the pattern for the change to take - // effect. This is done by design to allow the test - // framework to control the speed of patterns. - // See PatternBuilder.cpp for more info. - static void setTickrate(uint32_t tickrate = 0); - - // The current tickrate - static uint32_t getTickrate(); - - // these apis are only present if the tickrate can change +bool Time_init(void); +void Time_cleanup(void); +void Time_tickClock(void); +uint32_t Time_getCurtime(void); +uint32_t Time_getRealCurtime(void); +void Time_setTickrate(uint32_t tickrate); +uint32_t Time_getTickrate(void); #if VARIABLE_TICKRATE == 1 - // convert milliseconds to a tickcount based on tickrate - static uint32_t millisecondsToTicks(uint32_t ms); - - // convert seconds to a tickcount based on tickrate - static uint32_t secondsToTicks(uint32_t sec) { return millisecondsToTicks(sec * 1000); } +uint32_t Time_millisecondsToTicks(uint32_t ms); +uint32_t Time_secondsToTicks(uint32_t sec); #endif - - // Current microseconds since startup, only use this for things like measuring rapid data transfer timings. - // If you just need to perform regular time checks for a pattern or some logic then use getCurtime() and measure - // time in ticks, use the SEC_TO_TICKS() or MS_TO_TICKS() macros to convert timings to measures of ticks for - // purpose of comparing against getCurtime() - static uint32_t microseconds(); - - // delay for some number of microseconds or milliseconds, these are bad - static void delayMicroseconds(uint32_t us); - static void delayMilliseconds(uint32_t ms); +uint32_t Time_microseconds(void); +void Time_delayMicroseconds(uint32_t us); +void Time_delayMilliseconds(uint32_t ms); #ifdef VORTEX_LIB - // Start a time simulation, while the simulation is active you can - // increment the 'current time' with tickSimulation() and all calls - // to Time::getCurtime will reflect the changes, then when you call - // endSimulation() the currentTime will be restored - static uint32_t startSimulation(); - - // Tick a time simulation forward, returning the next tick - static uint32_t tickSimulation(); - - // Whether running a time simulation - static bool isSimulation(); - - // get the current tick in the simulation - static uint32_t getSimulationTick(); - - // Finish a time simulation - static uint32_t endSimulation(); - - // Toggle instant step - static void setInstantTimestep(bool instant) { m_instantTimestep = instant; } - static bool isInstantStepping() { return m_instantTimestep; } +uint32_t Time_startSimulation(void); +uint32_t Time_tickSimulation(void); +bool Time_isSimulation(void); +uint32_t Time_getSimulationTick(void); +uint32_t Time_endSimulation(void); +void Time_setInstantTimestep(bool instant); +bool Time_isInstantStepping(void); #endif #if TIMER_TEST == 1 - static void test(); -#endif - -private: -#ifdef VORTEX_EMBEDDED - static void initMCUTime(); +void Time_test(void); #endif +extern uint32_t Time_curTick; #if VARIABLE_TICKRATE == 1 - // the number of ticks per second - static uint32_t m_tickrate; +extern uint32_t Time_tickrate; #endif - - // global tick counter - static uint32_t m_curTick; - #ifdef VORTEX_LIB - // the last frame timestamp - static uint32_t m_prevTime; - - // the first timestamp - static uint32_t m_firstTime; - - // the current simulation offset, simulations are - // used to fastforward patterns and colorsets by - // simulating tick changes and running pattern logic - // multiple times in a single frame - static uint32_t m_simulationTick; - - // whether the timer is running a simulation - static bool m_isSimulation; - - // whether to disable timestep and instantly tick - static bool m_instantTimestep; +extern uint32_t Time_prevTime; +extern uint32_t Time_firstTime; +extern uint32_t Time_simulationTick; +extern bool Time_isSimulation_; +extern bool Time_instantTimestep; +#endif +#ifdef VORTEX_EMBEDDED +void Time_initMCUTime(void); #endif -}; #endif diff --git a/VortexEngine/src/Time/Timer.c b/VortexEngine/src/Time/Timer.c new file mode 100644 index 0000000000..e2390aeada --- /dev/null +++ b/VortexEngine/src/Time/Timer.c @@ -0,0 +1,227 @@ +#include "Timer.h" + +#include "TimeControl.h" + +#include "../Memory/Memory.h" +#include "../Log/Log.h" + +static uint32_t Timer_getStartTime(const Timer *self) +{ +#ifdef VORTEX_LIB + if (Time_isSimulation() && self->simStartTime) { + return self->simStartTime; + } +#endif + return self->startTime; +} + +static void Timer_setStartTime(Timer *self, uint32_t tick) +{ +#ifdef VORTEX_LIB + if (Time_isSimulation()) { + self->simStartTime = tick; + return; + } +#endif + self->startTime = tick; +} + +void Timer_init(Timer *self) +{ + self->alarms = NULL; + self->numAlarms = 0; + self->curAlarm = 0; + self->startTime = 0; +#ifdef VORTEX_LIB + self->simStartTime = 0; +#endif +} + +void Timer_cleanup(Timer *self) +{ + if (self->alarms) { + vfree(self->alarms); + self->alarms = NULL; + } +} + +void Timer_initFlags(Timer *self, uint8_t flags, uint8_t alarm1, uint8_t alarm2, uint8_t alarm3, uint8_t alarm4) +{ + Timer_reset(self); + if ((flags & TIMER_ALARM_MASK) >= TIMER_1_ALARM) Timer_addAlarm(self, alarm1); + if ((flags & TIMER_ALARM_MASK) >= TIMER_2_ALARMS) Timer_addAlarm(self, alarm2); + if ((flags & TIMER_ALARM_MASK) >= TIMER_3_ALARMS) Timer_addAlarm(self, alarm3); + if ((flags & TIMER_ALARM_MASK) >= TIMER_4_ALARMS) Timer_addAlarm(self, alarm4); + if ((flags & TIMER_START) != 0) Timer_start(self, 0); +} + +AlarmID Timer_addAlarm(Timer *self, uint32_t interval) +{ + void *temp = vrealloc(self->alarms, sizeof(uint32_t) * (self->numAlarms + 1)); + if (!temp) { + ERROR_OUT_OF_MEMORY(); + return -1; + } + self->alarms = (uint32_t *)temp; + self->alarms[self->numAlarms] = interval; + return (AlarmID)(self->numAlarms++); +} + +void Timer_restart(Timer *self, uint32_t offset) +{ + Timer_start(self, offset); + self->curAlarm = 0; +} + +void Timer_start(Timer *self, uint32_t offset) +{ + self->startTime = Time_getCurtime() + offset; +#ifdef VORTEX_LIB + self->simStartTime = self->startTime; +#endif +} + +void Timer_reset(Timer *self) +{ + vfree(self->alarms); + self->alarms = NULL; + self->numAlarms = 0; + self->curAlarm = 0; + self->startTime = 0; +#ifdef VORTEX_LIB + self->simStartTime = 0; +#endif +} + +bool Timer_onStart(const Timer *self) +{ + if (Time_getCurtime() == Timer_getStartTime(self) && self->curAlarm == 0) { + return true; + } + return false; +} + +bool Timer_onEnd(const Timer *self) +{ + if (!self->alarms || !self->numAlarms) { + return false; + } + uint32_t alarmTime = self->alarms[self->curAlarm]; + if (alarmTime <= 1) { + return true; + } + uint32_t now = Time_getCurtime(); + uint32_t startTime = Timer_getStartTime(self); + int32_t timeDiff = (int32_t)(int64_t)(now - startTime); + if (!timeDiff) { + return false; + } + return ((timeDiff % (alarmTime - 1)) == 0); +} + +AlarmID Timer_alarm(Timer *self) +{ + if (!self->numAlarms || !self->alarms || self->curAlarm == ALARM_NONE) { + return ALARM_NONE; + } + uint32_t now = Time_getCurtime(); + int32_t timeDiff = (int32_t)(int64_t)(now - Timer_getStartTime(self)); + if (timeDiff < 0) { + return ALARM_NONE; + } + if (timeDiff == 0) { + return 0; + } + uint32_t alarmTime = self->alarms[self->curAlarm]; + if (alarmTime && (timeDiff % alarmTime) != 0) { + return ALARM_NONE; + } + Timer_setStartTime(self, now); + self->curAlarm = (AlarmID)((self->curAlarm + 1) % self->numAlarms); + return self->curAlarm; +} + +uint32_t Timer_numAlarms(const Timer *self) +{ + return (uint32_t)self->numAlarms; +} + +uint32_t Timer_curAlarm(const Timer *self) +{ + return (uint32_t)self->curAlarm; +} + +uint32_t Timer_startTime(const Timer *self) +{ + return self->startTime; +} + +#if TIMER_TEST == 1 +#include + +void Timer_test(void) +{ + DEBUG_LOG("Starting Timer class tests..."); + Timer newTimer; + Timer_init(&newTimer); + + AlarmID alarm0 = Timer_addAlarm(&newTimer, 5); + AlarmID alarm1 = Timer_addAlarm(&newTimer, 10); + AlarmID alarm2 = Timer_addAlarm(&newTimer, 20); + assert(alarm0 == 0); + assert(alarm1 == 1); + assert(alarm2 == 2); + DEBUG_LOG("Alarm addition tests passed."); + + Timer_start(&newTimer, 0); + assert(Timer_alarm(&newTimer) == alarm0); + DEBUG_LOG("Alarm start test passed."); + + for (uint32_t t = 0; t <= 35; ++t) { + AlarmID alarm = Timer_alarm(&newTimer); + if (t == 0) { + assert(alarm == alarm0); + } else if (t == 5) { + assert(alarm == alarm1); + } else if (t == 15) { + assert(alarm == alarm2); + } else if (t == 35) { + assert(alarm == alarm0); + } else { + assert(alarm == ALARM_NONE); + } + Time_tickClock(); + } + +#ifdef VORTEX_LIB + Timer_init(&newTimer); + + AlarmID simAlarm0 = Timer_addAlarm(&newTimer, 5); + AlarmID simAlarm1 = Timer_addAlarm(&newTimer, 10); + AlarmID simAlarm2 = Timer_addAlarm(&newTimer, 20); + + Timer_start(&newTimer, 0); + assert(Timer_alarm(&newTimer) == simAlarm0); + + Time_startSimulation(); + for (uint32_t t = 0; t <= 35; ++t) { + AlarmID alarm = Timer_alarm(&newTimer); + if (t == 0) { + assert(alarm == simAlarm0); + } else if (t == 5) { + assert(alarm == simAlarm1); + } else if (t == 15) { + assert(alarm == simAlarm2); + } else if (t == 35) { + assert(alarm == simAlarm0); + } else { + assert(alarm == ALARM_NONE); + } + Time_tickSimulation(); + } + Time_endSimulation(); +#endif + + DEBUG_LOG("Timer class tests completed successfully."); +} +#endif diff --git a/VortexEngine/src/Time/Timer.h b/VortexEngine/src/Time/Timer.h index bb69ab2530..63482286b1 100644 --- a/VortexEngine/src/Time/Timer.h +++ b/VortexEngine/src/Time/Timer.h @@ -2,92 +2,51 @@ #define TIMER_H #include +#include #include "../Leds/LedTypes.h" #define ALARM_NONE -1 -// an alarm id is just an int typedef int8_t AlarmID; #define TIMER_FLAGS_NONE 0 -// the number of alarms #define TIMER_1_ALARM 1 #define TIMER_2_ALARMS 2 #define TIMER_3_ALARMS 3 #define TIMER_4_ALARMS 4 -// bitmask for all alarms #define TIMER_ALARM_MASK ( 1 | 2 | 3 | 4 ) -// start the alarm #define TIMER_START (1 << 7) -class Timer -{ -public: - Timer(); - ~Timer(); - - // init a timer with a number of alarms and optionally start it - void init(uint8_t flags = TIMER_FLAGS_NONE, uint8_t alarm1 = 0, - uint8_t alarm2 = 0, uint8_t alarm3 = 0, uint8_t alarm4 = 0); - - // add alarms to the timer and returns it's ID - // Alarm IDs start at 0 and count upward - AlarmID addAlarm(uint32_t interval); - - // restart the timer entirely - void restart(uint32_t offset = 0); - - // start the timer but don't change current alarm, this shifts - // the timer startTime but does not reset it's alarm state - void start(uint32_t offset = 0); - - // delete all alarms from the timer and reset - void reset(); - - // returns true when the current timer is starting, used for synchronization - bool onStart() const; - // returns true when the current timer is ending, used for synchronization - bool onEnd() const; - - // Will return the alarmID of the alarm that triggered - // If no alarm hits it will return ALARM_NONE. - // Outputs an integer indicating the number of times the alarm has triggered - // since the start time or the last detection of that alarm ID - AlarmID alarm(); - - // the number of alarms - uint32_t numAlarms() const { return m_numAlarms; } - uint32_t curAlarm() const { return m_curAlarm; } - - // the start time of the timer - uint32_t startTime() const { return m_startTime; } +typedef struct Timer_s { + uint32_t *alarms; + uint8_t numAlarms; + AlarmID curAlarm; + uint32_t startTime; +#ifdef VORTEX_LIB + uint32_t simStartTime; +#endif +} Timer; + +void Timer_init(Timer *self); +void Timer_cleanup(Timer *self); +void Timer_initFlags(Timer *self, uint8_t flags, uint8_t alarm1, uint8_t alarm2, uint8_t alarm3, uint8_t alarm4); +AlarmID Timer_addAlarm(Timer *self, uint32_t interval); +void Timer_restart(Timer *self, uint32_t offset); +void Timer_start(Timer *self, uint32_t offset); +void Timer_reset(Timer *self); +bool Timer_onStart(const Timer *self); +bool Timer_onEnd(const Timer *self); +AlarmID Timer_alarm(Timer *self); +uint32_t Timer_numAlarms(const Timer *self); +uint32_t Timer_curAlarm(const Timer *self); +uint32_t Timer_startTime(const Timer *self); #if TIMER_TEST == 1 - static void test(); +void Timer_test(void); #endif -private: - // helpers to set/get start time - uint32_t getStartTime() const; - void setStartTime(uint32_t); - - // the list of alarms and number of alarms - uint32_t *m_alarms; - uint8_t m_numAlarms; - // the current alarm being checked - AlarmID m_curAlarm; - // start time in microseconds - uint32_t m_startTime; - - // only need time simulations for test framework pattern strip generation -#ifdef VORTEX_LIB - // the simulation start time - uint32_t m_simStartTime; #endif -}; - -#endif \ No newline at end of file diff --git a/VortexEngine/src/VortexEngine.c b/VortexEngine/src/VortexEngine.c new file mode 100644 index 0000000000..cab704080e --- /dev/null +++ b/VortexEngine/src/VortexEngine.c @@ -0,0 +1,464 @@ +#include "VortexEngine.h" + +#include "Wireless/IRReceiver.h" +#include "Wireless/IRSender.h" +#include "Wireless/VLReceiver.h" +#include "Wireless/VLSender.h" +#include "Wireless/IRConfig.h" +#include "Wireless/VLConfig.h" +#include "Storage/Storage.h" +#include "Buttons/Buttons.h" +#include "Time/TimeControl.h" +#include "Time/Timings.h" +#include "Serial/Serial.h" +#include "Modes/Modes.h" +#include "Menus/Menus.h" +#include "Modes/Mode.h" +#include "Leds/Leds.h" + +#ifdef VORTEX_EMBEDDED +#include +#include +#endif + +#ifdef VORTEX_LIB +#include "VortexLib.h" +#endif + +static volatile bool g_sleeping = false; +static bool g_forceSleepEnabled = true; +static bool g_autoCycle = false; + +bool VortexEngine_init(void) +{ +#ifdef VORTEX_EMBEDDED + VortexEngine_clearOutputPins(); +#endif + + if (!SerialComs_init()) { + DEBUG_LOG("Serial failed to initialize"); + return false; + } + if (!Time_init()) { + DEBUG_LOG("Time failed to initialize"); + return false; + } + if (!Storage_init()) { + DEBUG_LOG("Storage failed to initialize"); + return false; + } +#if IR_ENABLE_RECEIVER == 1 + if (!IRReceiver_init()) { + DEBUG_LOG("IRReceiver failed to initialize"); + return false; + } +#endif +#if IR_ENABLE_SENDER == 1 + if (!IRSender_init()) { + DEBUG_LOG("IRSender failed to initialize"); + return false; + } +#endif +#if VL_ENABLE_RECEIVER == 1 + if (!VLReceiver_init()) { + DEBUG_LOG("VLReceiver failed to initialize"); + return false; + } +#endif +#if VL_ENABLE_SENDER == 1 + if (!VLSender_init()) { + DEBUG_LOG("VLSender failed to initialize"); + return false; + } +#endif + if (!Leds_init()) { + DEBUG_LOG("Leds failed to initialize"); + return false; + } + if (!Buttons_init(0)) { + DEBUG_LOG("Buttons failed to initialize"); + return false; + } + if (!Menus_init()) { + DEBUG_LOG("Menus failed to initialize"); + return false; + } + if (!Modes_init()) { + DEBUG_LOG("Settings failed to initialize"); + return false; + } + +#if COMPRESSION_TEST == 1 + VortexEngine_compressionTest(); +#endif + +#if SERIALIZATION_TEST == 1 + VortexEngine_serializationTest(); +#endif + +#if TIMER_TEST == 1 + VortexEngine_timerTest(); +#endif + +#ifdef VORTEX_EMBEDDED + TCB0.CCMP = 10000; + TCB0.INTCTRL = TCB_CAPT_bm; + TCB0.CTRLA = TCB_CLKSEL_CLKDIV1_gc | TCB_RUNSTDBY_bm | TCB_ENABLE_bm; + VortexEngine_enableMOSFET(!Modes_locked()); + set_sleep_mode(SLEEP_MODE_STANDBY); + sei(); + while (!g_sleeping) { + sleep_mode(); + } +#endif + + return true; +} + +void VortexEngine_cleanup(void) +{ +#ifdef VORTEX_LIB + Modes_cleanup(); + Menus_cleanup(); + Buttons_cleanup(); + Leds_cleanup(); +#if VL_ENABLE_SENDER == 1 + VLSender_cleanup(); +#endif +#if VL_ENABLE_RECEIVER == 1 + VLReceiver_cleanup(); +#endif +#if IR_ENABLE_SENDER == 1 + IRSender_cleanup(); +#endif +#if IR_ENABLE_RECEIVER == 1 + IRReceiver_cleanup(); +#endif + Storage_cleanup(); + Time_cleanup(); + SerialComs_cleanup(); +#endif +} + +void VortexEngine_tick(void) +{ +#ifdef VORTEX_LIB + if (g_sleeping) { + Buttons_update(); + if (Modes_locked() && Button_onConsecutivePresses(g_pButton, DEVICE_LOCK_CLICKS - 1)) { + Modes_setLocked(false); + } + if (Button_check(g_pButton) || Button_onRelease(g_pButton) || !VortexEngine_isSleeping()) { + VortexEngine_wakeup(); + } + return; + } +#endif + + Time_tickClock(); + Buttons_update(); + VortexEngine_runMainLogic(); + Leds_update(); +} + +void VortexEngine_runMainLogic(void) +{ + uint32_t now = Time_getCurtime(); + + if (Modes_locked()) { + if (Button_onConsecutivePresses(g_pButton, DEVICE_LOCK_CLICKS - 1)) { + Modes_setLocked(false, true); +#ifdef VORTEX_EMBEDDED + VortexEngine_enableMOSFET(true); +#endif + } else if (now > (CONSECUTIVE_WINDOW_TICKS * DEVICE_LOCK_CLICKS)) { + VortexEngine_enterSleep(false); + } + Leds_clearAll(); + return; + } + + if (Button_releaseCount(g_pButton) == 0) { + if (!Modes_load()) { + return; + } + if (!Modes_keychainModeEnabled() && now == ONE_CLICK_THRESHOLD_TICKS && Button_isPressed(g_pButton)) { + bool isEnabledNow = !Modes_oneClickModeEnabled(); + Modes_setOneClickMode(isEnabledNow, true); + if (isEnabledNow) { + Modes_switchToStartupMode(); + } else { + Modes_setCurMode(0); + } + Leds_holdAll(isEnabledNow ? RGB_COLOR(RGB_WHITE0) : RGB_COLOR(RGB_WHITE5)); + } + return; + } + +#ifdef VORTEX_EMBEDDED + if (now == 1) { + if (Modes_getFlag(MODES_FLAG_NEW_FIRMWARE)) { + Modes_resetFlags(); + Modes_load(); + Modes_saveHeader(); + } + VortexEngine_enterSleep(false); + return; + } +#endif + + if (!Modes_load()) { + return; + } + + if (Modes_keychainModeEnabled() && !Menus_checkInMenu()) { + Modes_switchToStartupMode(); + Menus_openMenu(MENU_GLOBAL_BRIGHTNESS, true); + } + + const uint32_t holdTime = Button_holdDuration(g_pButton); + if (g_forceSleepEnabled && holdTime >= FORCE_SLEEP_THRESHOLD_TICKS) { + if (Button_isPressed(g_pButton)) { + Leds_clearAll(); + return; + } + if (Button_onRelease(g_pButton)) { + VortexEngine_enterSleep(false); + } + return; + } + + if (Menus_run()) { + return; + } + + if (Button_onRelease(g_pButton) && holdTime >= SLEEP_ENTER_THRESHOLD_TICKS) { + VortexEngine_enterSleep(true); + return; + } + + if (Button_isPressed(g_pButton) && holdTime >= SLEEP_ENTER_THRESHOLD_TICKS) { + Leds_clearAll(); + if (holdTime >= (SLEEP_ENTER_THRESHOLD_TICKS + SLEEP_WINDOW_THRESHOLD_TICKS)) { + DEBUG_LOG("Entering ring fill..."); + Menus_openMenuSelection(); + } + return; + } + + if (now < (CONSECUTIVE_WINDOW_TICKS * DEVICE_LOCK_CLICKS) && Button_onConsecutivePresses(g_pButton, DEVICE_LOCK_CLICKS - 1)) { +#ifdef VORTEX_LIB + if (!VortexEngine_isSleeping()) { + return; + } +#endif + Modes_setLocked(true, true); + VortexEngine_enterSleep(true); + return; + } + + if ((Button_onRelease(g_pButton) && g_autoCycle) || Button_onConsecutivePresses(g_pButton, AUTO_CYCLE_MODES_CLICKS)) { + g_autoCycle = !g_autoCycle; + Leds_holdAll(g_autoCycle ? RGB_COLOR(RGB_GREEN) : RGB_COLOR(RGB_RED)); + } + + if (g_autoCycle && (Modes_lastSwitchTime() + AUTO_RANDOM_DELAY < now)) { + Modes_nextMode(); + } + + Modes_play(); +} + +bool VortexEngine_serializeVersion(ByteStream *stream) +{ + if (!ByteStream_serialize8(stream, (uint8_t)VORTEX_VERSION_MAJOR)) { + return false; + } + if (!ByteStream_serialize8(stream, (uint8_t)VORTEX_VERSION_MINOR)) { + return false; + } + return true; +} + +bool VortexEngine_checkVersion(uint8_t major, uint8_t minor) +{ + if (major != VORTEX_VERSION_MAJOR) { + return false; + } + return true; +} + +Mode *VortexEngine_curMode(void) +{ +#ifdef VORTEX_LIB + return Modes_curMode(); +#else + return NULL; +#endif +} + +void VortexEngine_toggleForceSleep(bool enabled) +{ + g_forceSleepEnabled = enabled; +} + +void VortexEngine_setAutoCycle(bool enabled) +{ + g_autoCycle = enabled; +} + +#ifdef VORTEX_LIB +uint32_t VortexEngine_totalStorageSpace(void) +{ + return STORAGE_SIZE; +} + +uint32_t VortexEngine_savefileSize(void) +{ + return Storage_lastSaveSize(); +} + +bool VortexEngine_isSleeping(void) +{ + return g_sleeping; +} +#endif + +void VortexEngine_enterSleep(bool save) +{ + DEBUG_LOG("Sleeping"); + if (save) { + Modes_setStartupMode(Modes_curModeIndex()); + Modes_saveStorage(); + } + Leds_clearAll(); + Leds_update(); +#ifdef VORTEX_EMBEDDED + VortexEngine_clearOutputPins(); + VortexEngine_enableMOSFET(false); + Time_delayMicroseconds(250); + TCB0.INTCTRL = 0; + TCB0.CTRLA = 0; + Button_enableWake(g_pButton); + set_sleep_mode(SLEEP_MODE_PWR_DOWN); + g_sleeping = true; + sleep_mode(); +#else + g_sleeping = true; +#endif +} + +void VortexEngine_wakeup(bool reset) +{ + DEBUG_LOG("Waking up"); +#ifdef VORTEX_EMBEDDED + VortexEngine_enableMOSFET(true); + if (reset) { + _PROTECTED_WRITE(RSTCTRL.SWRR, 1); + } +#else + g_sleeping = false; + if (reset) { + VortexEngine_cleanup(); + VortexEngine_init(); + } +#endif +} + +#ifdef VORTEX_EMBEDDED +ISR(TCB0_INT_vect) +{ + TCB0.INTFLAGS = TCB_CAPT_bm; + VortexEngine_tick(); +} + +void VortexEngine_clearOutputPins(void) +{ + PORTA.DIRCLR = 0xFF; + PORTA.PIN0CTRL = PORT_ISC_INPUT_DISABLE_gc; + PORTA.PIN1CTRL = PORT_ISC_INPUT_DISABLE_gc; + PORTA.PIN2CTRL = PORT_ISC_INPUT_DISABLE_gc; + PORTA.PIN3CTRL = PORT_ISC_INPUT_DISABLE_gc; + PORTA.PIN4CTRL = PORT_ISC_INPUT_DISABLE_gc; + PORTA.PIN5CTRL = PORT_ISC_INPUT_DISABLE_gc; + PORTA.PIN6CTRL = PORT_ISC_INPUT_DISABLE_gc; + PORTA.PIN7CTRL = PORT_ISC_INPUT_DISABLE_gc; + PORTB.DIRCLR = 0xFF; + PORTB.PIN0CTRL = PORT_ISC_INPUT_DISABLE_gc; + PORTB.PIN1CTRL = PORT_ISC_INPUT_DISABLE_gc; + PORTB.PIN2CTRL = PORT_ISC_INPUT_DISABLE_gc; + PORTB.PIN3CTRL = PORT_ISC_INPUT_DISABLE_gc; + PORTB.PIN4CTRL = PORT_ISC_INPUT_DISABLE_gc; + PORTB.PIN5CTRL = PORT_ISC_INPUT_DISABLE_gc; + PORTB.PIN6CTRL = PORT_ISC_INPUT_DISABLE_gc; + PORTB.PIN7CTRL = PORT_ISC_INPUT_DISABLE_gc; + PORTC.DIRCLR = 0xFF; + PORTC.PIN0CTRL = PORT_ISC_INPUT_DISABLE_gc; + PORTC.PIN1CTRL = PORT_ISC_INPUT_DISABLE_gc; + PORTC.PIN2CTRL = PORT_ISC_INPUT_DISABLE_gc; + PORTC.PIN3CTRL = PORT_ISC_INPUT_DISABLE_gc; + PORTC.PIN4CTRL = PORT_ISC_INPUT_DISABLE_gc; + PORTC.PIN5CTRL = PORT_ISC_INPUT_DISABLE_gc; + PORTC.PIN6CTRL = PORT_ISC_INPUT_DISABLE_gc; + PORTC.PIN7CTRL = PORT_ISC_INPUT_DISABLE_gc; +} + +void VortexEngine_enableMOSFET(bool enabled) +{ + PORTC.DIRSET |= PIN4_bm; + if (enabled) { + PORTC.OUTSET |= PIN4_bm; + } else { + PORTC.OUTCLR |= PIN4_bm; + } +} +#endif + +#if COMPRESSION_TEST == 1 +#include +#include +#include "Colors/Colorset.h" +#include "Memory/Memory.h" +#include "Random/Random.h" +void VortexEngine_compressionTest(void) +{ + Random rand; + Random_initWithSeed(&rand, 0xdeadbeef); + ByteStream stream; + ByteStream_init(&stream, 0, NULL); + Modes_clearModes(); + for (uint32_t len = 1; len < 4096; ++len) { + uint8_t *buf = (uint8_t *)vcalloc(1, len + 1); + if (!buf) continue; + ByteStream modeStream; + ByteStream_init(&modeStream, 0, NULL); + Modes_serialize(&modeStream); + while (ByteStream_size(&modeStream) < len) { + Mode tmpMode; + Mode_init(&tmpMode); + Mode_setPatternID(&tmpMode, (PatternID)(len % PATTERN_COUNT)); + Colorset set; + Colorset_init(&set); + Colorset_randomizeColors(&set, &rand, 8, COLOR_MODE_EVENLY_SPACED); + Mode_setColorset(&tmpMode, &set); + Modes_addMode(&tmpMode); + ByteStream_clear(&modeStream); + Modes_serialize(&modeStream); + } + // ... copy stream etc + ByteStream_destroy(&modeStream); + free(buf); + } + Modes_clearModes(); + DEBUG_LOG("Success testing compression"); + ByteStream_destroy(&stream); +} +#endif + +#if SERIALIZATION_TEST == 1 +// ... serialization test +#endif + +#if TIMER_TEST == 1 +// ... timer test +#endif diff --git a/VortexEngine/src/VortexEngine.h b/VortexEngine/src/VortexEngine.h index ae1b054840..d36b0f3b95 100644 --- a/VortexEngine/src/VortexEngine.h +++ b/VortexEngine/src/VortexEngine.h @@ -2,77 +2,37 @@ #define VORTEX_ENGINE_H #include +#include #include "VortexConfig.h" -class ByteStream; -class Mode; +typedef struct ByteStream ByteStream; +typedef struct Mode Mode; -class VortexEngine -{ - // private unimplemented constructor - VortexEngine(); - -public: - // setup and initialization function - static bool init(); - // cleanup - static void cleanup(); - - // tick function for each loop - static void tick(); - - // run the main logic of the engine - static void runMainLogic(); - - // serialize the version number to a byte stream - static bool serializeVersion(ByteStream &stream); - static bool checkVersion(uint8_t major, uint8_t minor); - - // get the current mode - static Mode *curMode(); +bool VortexEngine_init(void); +void VortexEngine_cleanup(void); +void VortexEngine_tick(void); +void VortexEngine_runMainLogic(void); +bool VortexEngine_serializeVersion(ByteStream *stream); +bool VortexEngine_checkVersion(uint8_t major, uint8_t minor); +Mode *VortexEngine_curMode(void); #ifdef VORTEX_LIB - // the total available storage space - static uint32_t totalStorageSpace(); - // the size of the savefile - static uint32_t savefileSize(); - // whether sleeping or not - static bool isSleeping() { return m_sleeping; } +uint32_t VortexEngine_totalStorageSpace(void); +uint32_t VortexEngine_savefileSize(void); +bool VortexEngine_isSleeping(void); #endif - // enter/leave sleep mode - static void enterSleep(bool save = true); - static void wakeup(bool reset = true); - - // toggle force sleep from working - static void toggleForceSleep(bool enabled) { m_forceSleepEnabled = enabled; } - // toggle auto cycle enabled - static void setAutoCycle(bool enabled) { m_autoCycle = enabled; } +void VortexEngine_enterSleep(bool save); +void VortexEngine_wakeup(bool reset); +void VortexEngine_toggleForceSleep(bool enabled); +void VortexEngine_setAutoCycle(bool enabled); +void VortexEngine_compressionTest(void); +void VortexEngine_serializationTest(void); +void VortexEngine_timerTest(void); #ifdef VORTEX_EMBEDDED - // clear output pins - static void clearOutputPins(); - // enable/disable the mosfet - static void enableMOSFET(bool enabled); -#endif - -private: - // bool in vortexlib to simulate sleeping - static volatile bool m_sleeping; - static bool m_forceSleepEnabled; - - // whether auto cycle modes is turned on - static bool m_autoCycle; - -#if COMPRESSION_TEST == 1 - static void compressionTest(); -#endif -#if SERIALIZATION_TEST == 1 - static void serializationTest(); -#endif -#if TIMER_TEST == 1 - static void timerTest(); +void VortexEngine_clearOutputPins(void); +void VortexEngine_enableMOSFET(bool enabled); #endif -}; #endif diff --git a/VortexEngine/src/Wireless/IRReceiver.c b/VortexEngine/src/Wireless/IRReceiver.c new file mode 100644 index 0000000000..7b799b9435 --- /dev/null +++ b/VortexEngine/src/Wireless/IRReceiver.c @@ -0,0 +1,173 @@ +#include "IRReceiver.h" +#include "IRConfig.h" + +#if IR_ENABLE_RECEIVER == 1 + +#include "../Serial/ByteStream.h" +#include "../Serial/BitStream.h" +#include "../Time/TimeControl.h" +#include "../Modes/Mode.h" +#include "../Log/Log.h" + +BitStream IRReceiver_irData; +IRRecvState IRReceiver_recvState = IR_WAITING_HEADER_MARK; +uint32_t IRReceiver_prevTime = 0; +uint8_t IRReceiver_pinState = 0; +uint32_t IRReceiver_previousBytes = 0; + +bool IRReceiver_init() +{ + BitStream_initAlloc(&IRReceiver_irData, IR_RECV_BUF_SIZE); + return true; +} + +void IRReceiver_cleanup() +{ +} + +bool IRReceiver_dataReady() +{ + if (!IRReceiver_isReceiving()) { + return false; + } + uint8_t blocks = BitStream_data(&IRReceiver_irData)[0]; + uint8_t remainder = BitStream_data(&IRReceiver_irData)[1]; + uint32_t total = ((blocks - 1) * 32) + remainder; + if (!total || total > IR_MAX_DATA_TRANSFER) { + DEBUG_LOGF("Bad IR Data size: %u", total); + return false; + } + return (BitStream_bytepos(&IRReceiver_irData) >= (uint32_t)(total + 2)); +} + +bool IRReceiver_isReceiving() +{ + return (BitStream_bytepos(&IRReceiver_irData) > 2); +} + +uint8_t IRReceiver_percentReceived() +{ + if (!IRReceiver_isReceiving()) { + return 0; + } + uint8_t blocks = BitStream_data(&IRReceiver_irData)[0]; + uint8_t remainder = BitStream_data(&IRReceiver_irData)[1]; + uint16_t total = ((blocks - 1) * 32) + remainder; + return (uint8_t)((uint16_t)((BitStream_bytepos(&IRReceiver_irData) * 100 + (total / 2)) / total)); +} + +bool IRReceiver_receiveMode(Mode *pMode) +{ + ByteStream buf; + if (!IRReceiver_read(&buf)) { + DEBUG_LOG("No data available to read, or error reading"); + return false; + } + DEBUG_LOGF("Received %u bytes", ByteStream_rawSize(&buf)); + return Mode_loadFromBuffer(pMode, &buf); +} + +bool IRReceiver_beginReceiving() +{ + IRReceiver_resetIRState(); + return true; +} + +bool IRReceiver_endReceiving() +{ + IRReceiver_resetIRState(); + return true; +} + +bool IRReceiver_onNewData() +{ + if (IRReceiver_bytesReceived() == IRReceiver_previousBytes) { + return false; + } + IRReceiver_previousBytes = IRReceiver_bytesReceived(); + return true; +} + +bool IRReceiver_read(ByteStream *data) +{ + if (!BitStream_bytepos(&IRReceiver_irData) || BitStream_bytepos(&IRReceiver_irData) > IR_MAX_DATA_TRANSFER) { + DEBUG_LOG("Nothing to read, or read too much"); + return false; + } + uint8_t blocks = BitStream_data(&IRReceiver_irData)[0]; + uint8_t remainder = BitStream_data(&IRReceiver_irData)[1]; + uint32_t size = ((blocks - 1) * 32) + remainder; + if (!size || size > IR_MAX_DATA_TRANSFER) { + DEBUG_LOGF("Bad IR Data size: %u", size); + return false; + } + const uint8_t *actualData = BitStream_data(&IRReceiver_irData) + 2; + if (!ByteStream_rawInit(data, actualData, size)) { + DEBUG_LOG("Failed to init buffer for IR read"); + return false; + } + IRReceiver_resetIRState(); + return true; +} + +void IRReceiver_recvPCIHandler() +{ + IRReceiver_pinState = (uint8_t)!IRReceiver_pinState; + uint32_t now = Time_microseconds(); + if (!IRReceiver_prevTime || IRReceiver_prevTime > now) { + IRReceiver_prevTime = now; + DEBUG_LOG("Bad first time diff, resetting..."); + IRReceiver_resetIRState(); + return; + } + uint32_t diff = (uint32_t)(now - IRReceiver_prevTime); + IRReceiver_prevTime = now; + IRReceiver_handleIRTiming(diff); +} + +void IRReceiver_handleIRTiming(uint32_t diff) +{ + if ((diff > IR_HEADER_MARK_MAX && IRReceiver_recvState < IR_READING_DATA_MARK) || diff < IR_TIMING_MIN) { + DEBUG_LOGF("bad delay: %u, resetting...", diff); + IRReceiver_resetIRState(); + return; + } + switch (IRReceiver_recvState) { + case IR_WAITING_HEADER_MARK: + if (diff >= IR_HEADER_MARK_MIN && diff <= IR_HEADER_MARK_MAX) { + IRReceiver_recvState = IR_WAITING_HEADER_SPACE; + } else { + DEBUG_LOGF("Bad header mark %u, resetting...", diff); + IRReceiver_resetIRState(); + } + break; + case IR_WAITING_HEADER_SPACE: + if (diff >= IR_HEADER_SPACE_MIN && diff <= IR_HEADER_SPACE_MAX) { + IRReceiver_recvState = IR_READING_DATA_MARK; + } else { + DEBUG_LOGF("Bad header space %u, resetting...", diff); + IRReceiver_resetIRState(); + } + break; + case IR_READING_DATA_MARK: + BitStream_write1Bit(&IRReceiver_irData, (diff > (IR_TIMING * 2)) ? 1 : 0); + IRReceiver_recvState = IR_READING_DATA_SPACE; + break; + case IR_READING_DATA_SPACE: + IRReceiver_recvState = IR_READING_DATA_MARK; + break; + default: + DEBUG_LOGF("Bad receive state: %u", IRReceiver_recvState); + break; + } +} + +void IRReceiver_resetIRState() +{ + IRReceiver_previousBytes = 0; + IRReceiver_recvState = IR_WAITING_HEADER_MARK; + BitStream_reset(&IRReceiver_irData); + DEBUG_LOG("IR State Reset"); +} + +#endif diff --git a/VortexEngine/src/Wireless/IRReceiver.h b/VortexEngine/src/Wireless/IRReceiver.h index 14dff9069f..2c3ded705e 100644 --- a/VortexEngine/src/Wireless/IRReceiver.h +++ b/VortexEngine/src/Wireless/IRReceiver.h @@ -2,6 +2,7 @@ #define IR_RECEIVER_H #include +#include #include "../Serial/BitStream.h" @@ -9,73 +10,43 @@ #if IR_ENABLE_RECEIVER == 1 -class ByteStream; -class Mode; +typedef struct ByteStream ByteStream; +typedef struct Mode Mode; -class IRReceiver -{ - IRReceiver(); +typedef enum { + IR_WAITING_HEADER_MARK, + IR_WAITING_HEADER_SPACE, + IR_READING_DATA_MARK, + IR_READING_DATA_SPACE +} IRRecvState; -public: - // init and cleanup the receiver - static bool init(); - static void cleanup(); +extern BitStream IRReceiver_irData; +extern IRRecvState IRReceiver_recvState; +extern uint32_t IRReceiver_prevTime; +extern uint8_t IRReceiver_pinState; +extern uint32_t IRReceiver_previousBytes; - // check whether a full IR message is ready to read - static bool dataReady(); - // whether actively receiving - static bool isReceiving(); - // the percent of data received - static uint8_t percentReceived(); - static uint16_t bytesReceived() { return m_irData.bytepos(); } +bool IRReceiver_init(); +void IRReceiver_cleanup(); - // receive the IR message into a target mode - static bool receiveMode(Mode *pMode); +bool IRReceiver_dataReady(); +bool IRReceiver_isReceiving(); +uint8_t IRReceiver_percentReceived(); - // turn the receiver on/off - static bool beginReceiving(); - static bool endReceiving(); - // checks if there is new data since the last reset or check - static bool onNewData(); - // reset IR receiver buffer - static void resetIRState(); +static inline uint16_t IRReceiver_bytesReceived() { + return BitStream_bytepos(&IRReceiver_irData); +} -private: +bool IRReceiver_receiveMode(Mode *pMode); - // reading functions - // PCI handler for when IR receiver pin changes states - static bool read(ByteStream &data); - static void recvPCIHandler(); - static void handleIRTiming(uint32_t diff); +bool IRReceiver_beginReceiving(); +bool IRReceiver_endReceiving(); +bool IRReceiver_onNewData(); +void IRReceiver_resetIRState(); - // =================== - // private data: - - // BitStream object that IR data is fed to bit by bit - static BitStream m_irData; - - // Receive state used for state machine in PCIhandler - enum RecvState : uint8_t - { - WAITING_HEADER_MARK, - WAITING_HEADER_SPACE, - READING_DATA_MARK, - READING_DATA_SPACE - }; - - // state information used by the PCIHandler - static RecvState m_recvState; - // used to track pin changes - static uint32_t m_prevTime; - static uint8_t m_pinState; - - // used to compare if received data has changed since last checking - static uint32_t m_previousBytes; - -#ifdef VORTEX_LIB - friend class Vortex; -#endif -}; +bool IRReceiver_read(ByteStream *data); +void IRReceiver_recvPCIHandler(); +void IRReceiver_handleIRTiming(uint32_t diff); #endif diff --git a/VortexEngine/src/Wireless/IRSender.c b/VortexEngine/src/Wireless/IRSender.c new file mode 100644 index 0000000000..3fa754776c --- /dev/null +++ b/VortexEngine/src/Wireless/IRSender.c @@ -0,0 +1,179 @@ +#include "IRSender.h" +#include "IRConfig.h" + +#if IR_ENABLE_SENDER == 1 + +#include "../Time/TimeControl.h" +#include "../Modes/Mode.h" +#include "../Log/Log.h" + +#ifdef VORTEX_LIB +#include "VortexLib.h" +#endif + +IRSenderCallbacks *g_irSenderCallbacks; + +ByteStream IRSender_serialBuf; +BitStream IRSender_bitStream; +bool IRSender_isSending = false; +uint32_t IRSender_lastSendTime = 0; +uint32_t IRSender_size = 0; +uint8_t IRSender_numBlocks = 0; +uint8_t IRSender_remainder = 0; +uint32_t IRSender_blockSize = 0; +uint32_t IRSender_writeCounter = 0; + +#if defined(VORTEX_EMBEDDED) +Tcc *IR_TCCx; +#endif + +bool IRSender_init() +{ + IRSender_initPWM(); + return true; +} + +void IRSender_cleanup() +{ +} + +bool IRSender_loadMode(const Mode *targetMode) +{ + ByteStream_clear(&IRSender_serialBuf); + if (!Mode_saveToBuffer(targetMode, &IRSender_serialBuf, 0)) { + DEBUG_LOG("Failed to save mode to buffer"); + return false; + } + if (ByteStream_rawSize(&IRSender_serialBuf) > IR_MAX_DATA_TRANSFER) { + DEBUG_LOGF("Cannot transfer that much data: %u bytes", ByteStream_rawSize(&IRSender_serialBuf)); + return false; + } + BitStream_initBuf(&IRSender_bitStream, (uint8_t *)ByteStream_rawData(&IRSender_serialBuf), ByteStream_rawSize(&IRSender_serialBuf)); + IRSender_size = ByteStream_rawSize(&IRSender_serialBuf); + IRSender_numBlocks = (IRSender_size + (IR_DEFAULT_BLOCK_SIZE - 1)) / IR_DEFAULT_BLOCK_SIZE; + IRSender_remainder = IRSender_size % (IR_DEFAULT_BLOCK_SIZE + 0); + DEBUG_LOGF("Num blocks: %u", IRSender_numBlocks); + DEBUG_LOGF("Remainder: %u", IRSender_remainder); + DEBUG_LOGF("Size: %u", IRSender_size); + IRSender_writeCounter = 0; + return true; +} + +bool IRSender_send() +{ + if (IRSender_numBlocks < 1) { + IRSender_isSending = false; + return IRSender_isSending; + } + if (!IRSender_isSending) { + IRSender_beginSend(); + } + if (IRSender_lastSendTime > 0 && IRSender_lastSendTime + IR_DEFAULT_BLOCK_SPACING > Time_getCurtime()) { + return IRSender_isSending; + } + uint32_t blocksize = (IRSender_numBlocks == 1) ? IRSender_remainder : IR_DEFAULT_BLOCK_SIZE; + DEBUG_LOGF("IRSender Sending block #%u", IRSender_numBlocks); + const uint8_t *buf_ptr = BitStream_data(&IRSender_bitStream) + IRSender_writeCounter; + for (uint32_t i = 0; i < blocksize; ++i) { + IRSender_sendByte(*buf_ptr); + buf_ptr++; + IRSender_writeCounter++; + } + IRSender_numBlocks--; + IRSender_isSending = (IRSender_numBlocks > 0); + IRSender_lastSendTime = Time_getCurtime(); + return IRSender_isSending; +} + +void IRSender_beginSend() +{ + IRSender_isSending = true; + DEBUG_LOGF("[%zu] Beginning send size %u (blocks: %u remainder: %u blocksize: %u)", + Time_microseconds(), IRSender_size, IRSender_numBlocks, IRSender_remainder, IRSender_blockSize); + IRSender_initPWM(); + IRSender_sendMark(50); + IRSender_sendSpace(100); + IRSender_sendMark(IR_HEADER_MARK); + IRSender_sendSpace(IR_HEADER_SPACE); + IRSender_writeCounter = 0; + IRSender_sendByte(IRSender_numBlocks); + IRSender_sendByte(IRSender_remainder); +} + +void IRSender_sendByte(uint8_t data) +{ + for (int b = 0; b < 8; b++) { + uint32_t bit = (data >> (7 - b)) & 1; + IRSender_sendMark(IR_TIMING + (IR_TIMING * (2 * bit))); + IRSender_sendSpace(IR_TIMING); + } + DEBUG_LOGF("Sent byte[%u]: 0x%x", IRSender_writeCounter, data); + } + +void IRSender_sendMark(uint16_t time) +{ +#ifdef VORTEX_LIB + g_irSenderCallbacks->infraredWrite(true, time); +#else + IRSender_startPWM(); + Time_delayMicroseconds(time); +#endif +} + +void IRSender_sendSpace(uint16_t time) +{ +#ifdef VORTEX_LIB + g_irSenderCallbacks->infraredWrite(false, time); +#else + IRSender_stopPWM(); + Time_delayMicroseconds(time); +#endif +} + +void IRSender_initPWM() +{ +#if defined(VORTEX_EMBEDDED) + pinMode(IR_SEND_PWM_PIN, OUTPUT); + digitalWrite(IR_SEND_PWM_PIN, LOW); + uint8_t port = g_APinDescription[IR_SEND_PWM_PIN].ulPort; + uint8_t pin = g_APinDescription[IR_SEND_PWM_PIN].ulPin; + ETCChannel IR_TCC_Channel = TCC0_CH0; + int8_t IR_PER_EorF = PORT_PMUX_PMUXE_E; + PORT->Group[port].PINCFG[pin].bit.PMUXEN = 1; + PORT->Group[port].PMUX[pin >> 1].reg |= IR_PER_EorF; + REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | + GCLK_CLKCTRL_GEN_GCLK0 | + GCLK_CLKCTRL_ID_TCC0_TCC1; + while (GCLK->STATUS.bit.SYNCBUSY); + IR_TCCx = (Tcc*) GetTC(IR_TCC_Channel); + IR_TCCx->WAVE.reg |= TCC_WAVE_WAVEGEN_NPWM; + while (IR_TCCx->SYNCBUSY.bit.WAVE); + uint32_t cc = F_CPU/(38*1000) - 1; + IR_TCCx->PER.reg = cc; + while(IR_TCCx->SYNCBUSY.bit.PER); + IR_TCCx->CC[GetTCChannelNumber(IR_TCC_Channel)].reg = cc/3; + while (IR_TCCx->SYNCBUSY.reg & TCC_SYNCBUSY_MASK); + IR_TCCx->CTRLA.reg |= TCC_CTRLA_PRESCALER_DIV1; + while (IR_TCCx->SYNCBUSY.bit.ENABLE); + IR_TCCx->CTRLA.reg &= ~TCC_CTRLA_ENABLE; + while (IR_TCCx->SYNCBUSY.bit.ENABLE); +#endif +} + +void IRSender_startPWM() +{ +#if defined(VORTEX_EMBEDDED) + IR_TCCx->CTRLA.reg |= TCC_CTRLA_ENABLE; + while (IR_TCCx->SYNCBUSY.bit.ENABLE); +#endif +} + +void IRSender_stopPWM() +{ +#if defined(VORTEX_EMBEDDED) + IR_TCCx->CTRLA.reg &= ~TCC_CTRLA_ENABLE; + while (IR_TCCx->SYNCBUSY.bit.ENABLE); +#endif +} + +#endif diff --git a/VortexEngine/src/Wireless/IRSender.h b/VortexEngine/src/Wireless/IRSender.h index d8e9df30eb..ea6c5eee42 100644 --- a/VortexEngine/src/Wireless/IRSender.h +++ b/VortexEngine/src/Wireless/IRSender.h @@ -1,6 +1,9 @@ #ifndef IR_SENDER_H #define IR_SENDER_H +#include +#include + #include "../Serial/ByteStream.h" #include "../Serial/BitStream.h" @@ -8,58 +11,43 @@ #if IR_ENABLE_SENDER == 1 -class Mode; - -class IRSender -{ - IRSender(); - -public: - static bool init(); - static void cleanup(); +typedef struct Mode Mode; - // initialize the IR sender with a mode to send - static bool loadMode(const Mode *targetMode); - static bool send(); +typedef struct IRSenderCallbacks_s { + void (*infraredWrite)(bool mark, uint32_t amount); +} IRSenderCallbacks; - static bool isSending() { return m_isSending; } +extern IRSenderCallbacks *g_irSenderCallbacks; - static uint32_t percentDone() { return (uint32_t)(((float)m_writeCounter / (float)m_size) * 100.0); } +extern ByteStream IRSender_serialBuf; +extern BitStream IRSender_bitStream; +extern bool IRSender_isSending; +extern uint32_t IRSender_lastSendTime; +extern uint32_t IRSender_size; +extern uint8_t IRSender_numBlocks; +extern uint8_t IRSender_remainder; +extern uint32_t IRSender_blockSize; +extern uint32_t IRSender_writeCounter; -private: - // sender functions - static void beginSend(); - // send a full 8 bits in a tight loop - static void sendByte(uint8_t data); - // send a mark/space by turning PWM on/off - static void sendMark(uint16_t time); - static void sendSpace(uint16_t time); - // Pulse-Width Modulator (IR Transmitter) - static void initPWM(); - // turn the IR transmitter on/off in realtime - static void startPWM(); - static void stopPWM(); +bool IRSender_init(); +void IRSender_cleanup(); - // the serial buffer for the data - static ByteStream m_serialBuf; - // a bit walker for the serial data - static BitStream m_bitStream; - static bool m_isSending; - static uint32_t m_lastSendTime; +bool IRSender_loadMode(const Mode *targetMode); +bool IRSender_send(); - // some runtime meta info - static uint32_t m_size; - // the number of blocks that will be sent - static uint8_t m_numBlocks; - // the amount in the final block - static uint8_t m_remainder; +static inline bool IRSender_isSending_get() { return IRSender_isSending; } - // configuration options for the sender - static uint32_t m_blockSize; +static inline uint32_t IRSender_percentDone() { + return (uint32_t)(((float)IRSender_writeCounter / (float)IRSender_size) * 100.0); +} - // write total - static uint32_t m_writeCounter; -}; +void IRSender_beginSend(); +void IRSender_sendByte(uint8_t data); +void IRSender_sendMark(uint16_t time); +void IRSender_sendSpace(uint16_t time); +void IRSender_initPWM(); +void IRSender_startPWM(); +void IRSender_stopPWM(); #endif diff --git a/VortexEngine/src/Wireless/VLReceiver.c b/VortexEngine/src/Wireless/VLReceiver.c new file mode 100644 index 0000000000..9332393cac --- /dev/null +++ b/VortexEngine/src/Wireless/VLReceiver.c @@ -0,0 +1,276 @@ +#include "VLReceiver.h" +#include "IRConfig.h" + +#if VL_ENABLE_RECEIVER == 1 + +#include "../Serial/ByteStream.h" +#include "../Serial/BitStream.h" +#include "../Time/TimeControl.h" +#include "../Modes/Mode.h" +#include "../Leds/Leds.h" +#include "../Log/Log.h" + +#ifdef VORTEX_EMBEDDED +#include +#include +#endif + +BitStream VLReceiver_vlData; +VLRecvState VLReceiver_recvState = VL_WAITING_HEADER_MARK; +uint32_t VLReceiver_prevTime = 0; +uint8_t VLReceiver_pinState = 0; +uint16_t VLReceiver_previousBytes = 0; +uint16_t VLReceiver_vlMarkThreshold = 0; +uint16_t VLReceiver_vlSpaceThreshold = 0; +uint8_t VLReceiver_counter = 0; +uint8_t VLReceiver_parityBit = 0; +bool VLReceiver_legacy = false; + +#ifdef VORTEX_EMBEDDED +#define MIN_THRESHOLD 200 +#define BASE_OFFSET 100 +#define THRESHOLD_BEGIN (MIN_THRESHOLD + BASE_OFFSET) +#define SAMPLE_COUNT 5 +uint16_t threshold = THRESHOLD_BEGIN; +ISR(ADC0_WCOMP_vect) +{ + static bool wasAboveThreshold = false; + uint16_t val = (ADC0.RES >> SAMPLE_COUNT); + if (val > MIN_THRESHOLD && val < (threshold + BASE_OFFSET)) { + threshold = val + BASE_OFFSET; + } + bool isAboveThreshold = (val > threshold); + if (wasAboveThreshold != isAboveThreshold) { + VLReceiver_recvPCIHandler(); + wasAboveThreshold = isAboveThreshold; + } + ADC0.INTFLAGS = ADC_WCMP_bm; +} +#endif + +bool VLReceiver_init() +{ +#ifdef VORTEX_EMBEDDED + PORTB.PIN1CTRL &= ~PORT_ISC_gm; + PORTB.PIN1CTRL |= PORT_ISC_INPUT_DISABLE_gc; +#endif + return BitStream_initAlloc(&VLReceiver_vlData, VL_RECV_BUF_SIZE); +} + +void VLReceiver_cleanup() +{ +} + +bool VLReceiver_dataReady() +{ + if (!VLReceiver_isReceiving()) { + return false; + } + uint8_t size = BitStream_peekData(&VLReceiver_vlData, 1); + return (BitStream_bytepos(&VLReceiver_vlData) >= ((uint32_t)size + 2)); +} + +bool VLReceiver_isReceiving() +{ + return (BitStream_bytepos(&VLReceiver_vlData) > 2); +} + +uint8_t VLReceiver_percentReceived() +{ + if (!VLReceiver_isReceiving()) { + return 0; + } + uint8_t size = BitStream_peekData(&VLReceiver_vlData, 1); + return (uint8_t)((uint16_t)((BitStream_bytepos(&VLReceiver_vlData) * 100 + (size / 2)) / size)); +} + +bool VLReceiver_receiveMode(Mode *pMode) +{ + ByteStream buf; + if (!VLReceiver_read(&buf)) { + DEBUG_LOG("No data available to read, or error reading"); + return false; + } + DEBUG_LOGF("Received %u bytes", ByteStream_rawSize(&buf)); + return Mode_loadFromBuffer(pMode, &buf); +} + +bool VLReceiver_beginReceiving() +{ +#ifdef VORTEX_EMBEDDED + ADC0.CTRLC = ADC_SAMPCAP_bm | ADC_REFSEL_VDDREF_gc | ADC_PRESC_DIV2_gc; + ADC0.CTRLD = 0; + ADC0.SAMPCTRL = 1; + ADC0.MUXPOS = ADC_MUXPOS_AIN10_gc; + ADC0.CTRLE = ADC_WINCM_ABOVE_gc; + ADC0.WINHT = 0x1; + ADC0.WINLT = 0; + ADC0.CTRLB = SAMPLE_COUNT; + ADC0.INTCTRL = ADC_WCMP_bm; + ADC0.CTRLA = ADC_ENABLE_bm | ADC_FREERUN_bm; + ADC0.COMMAND = ADC_STCONV_bm; +#endif + VLReceiver_resetVLState(); + return true; +} + +bool VLReceiver_endReceiving() +{ +#ifdef VORTEX_EMBEDDED + ADC0.CTRLA &= ~(ADC_ENABLE_bm | ADC_FREERUN_bm); + ADC0.INTCTRL = 0; +#endif + return true; +} + +bool VLReceiver_onNewData() +{ + if (VLReceiver_bytesReceived() == VLReceiver_previousBytes) { + return false; + } + VLReceiver_previousBytes = VLReceiver_bytesReceived(); + return true; +} + +bool VLReceiver_read(ByteStream *data) +{ + if (!BitStream_bytepos(&VLReceiver_vlData) || BitStream_bytepos(&VLReceiver_vlData) > VL_MAX_DATA_TRANSFER) { + DEBUG_LOG("Nothing to read, or read too much"); + return false; + } + uint8_t size = BitStream_peekData(&VLReceiver_vlData, 1); + const uint8_t *actualData = BitStream_data(&VLReceiver_vlData) + 2; + if (!ByteStream_rawInit(data, actualData, size)) { + DEBUG_LOG("Failed to init buffer for VL read"); + return false; + } + VLReceiver_resetVLState(); + return true; +} + +void VLReceiver_recvPCIHandler() +{ + VLReceiver_pinState = (uint8_t)!VLReceiver_pinState; + uint32_t now = Time_microseconds(); + uint32_t diff = (uint32_t)(now - VLReceiver_prevTime); + VLReceiver_prevTime = now; + if (diff > UINT16_MAX) { + return; + } + if (VLReceiver_legacy) { + VLReceiver_handleVLTimingLegacy((uint16_t)diff); + } else { + VLReceiver_handleVLTiming((uint16_t)diff); + } +} + +void VLReceiver_handleVLTimingLegacy(uint16_t diff) +{ + switch (VLReceiver_recvState) { + case VL_WAITING_HEADER_MARK: + if (diff >= VL_HEADER_SPACE_MIN_LEGACY && diff <= VL_HEADER_MARK_MAX_LEGACY) { + VLReceiver_recvState = VL_WAITING_HEADER_SPACE; + } + break; + case VL_WAITING_HEADER_SPACE: + if (diff >= VL_HEADER_SPACE_MIN_LEGACY && diff <= VL_HEADER_MARK_MAX_LEGACY) { + VLReceiver_recvState = VL_READING_DATA_MARK; + VLReceiver_vlMarkThreshold = (diff / 4); + } + break; + case VL_READING_DATA_MARK: + BitStream_write1Bit(&VLReceiver_vlData, diff > VLReceiver_vlMarkThreshold); + VLReceiver_recvState = VL_READING_DATA_SPACE; + break; + case VL_READING_DATA_SPACE: + VLReceiver_recvState = VL_READING_DATA_MARK; + break; + default: + DEBUG_LOGF("Bad receive state: %u", VLReceiver_recvState); + break; + } +} + +void VLReceiver_handleVLTiming(uint16_t diff) +{ + uint8_t bit; + switch (VLReceiver_recvState) { + case VL_WAITING_HEADER_MARK: + case VL_WAITING_HEADER_SPACE: + if (diff >= VL_HEADER_SPACE_MIN && diff <= VL_HEADER_MARK_MAX) { + VLReceiver_recvState = (VLRecvState)(VLReceiver_recvState + 1); + } + break; + case VL_READING_BAUD_MARK: + VLReceiver_vlMarkThreshold += diff; + VLReceiver_recvState = VL_READING_BAUD_SPACE; + break; + case VL_READING_BAUD_SPACE: + VLReceiver_counter++; + VLReceiver_vlSpaceThreshold += diff; + if (VLReceiver_counter < 4) { + VLReceiver_recvState = VL_READING_BAUD_MARK; + break; + } + VLReceiver_vlMarkThreshold /= 4; + VLReceiver_vlSpaceThreshold /= 4; + VLReceiver_counter = 0; + VLReceiver_parityBit = 0; + VLReceiver_recvState = VL_READING_DATA_MARK; + break; + case VL_READING_DATA_MARK: + VLReceiver_counter++; + bit = (diff > VLReceiver_vlMarkThreshold) ? 1 : 0; + VLReceiver_parityBit = (VLReceiver_parityBit ^ bit) & 1; + BitStream_write1Bit(&VLReceiver_vlData, bit); + VLReceiver_recvState = VL_READING_DATA_SPACE; + break; + case VL_READING_DATA_SPACE: + bit = (diff > VLReceiver_vlSpaceThreshold) ? 1 : 0; + VLReceiver_parityBit = (VLReceiver_parityBit ^ bit) & 1; + BitStream_write1Bit(&VLReceiver_vlData, bit); + if ((VLReceiver_counter % 4) == 0) { + VLReceiver_recvState = VL_READING_DATA_PARITY_MARK; + } else { + VLReceiver_recvState = VL_READING_DATA_MARK; + } + break; + case VL_READING_DATA_PARITY_MARK: + bit = (diff > VLReceiver_vlMarkThreshold) ? 1 : 0; + if ((VLReceiver_parityBit & 1) != bit) { + VLReceiver_resetVLState(); + break; + } + VLReceiver_recvState = VL_READING_DATA_PARITY_SPACE; + break; + case VL_READING_DATA_PARITY_SPACE: + VLReceiver_recvState = VL_READING_DATA_MARK; + break; + default: + DEBUG_LOGF("Bad receive state: %u", VLReceiver_recvState); + break; + } +} + +void VLReceiver_resetVLState() +{ + VLReceiver_counter = 0; + VLReceiver_vlMarkThreshold = 0; + VLReceiver_vlSpaceThreshold = 0; + VLReceiver_previousBytes = 0; + VLReceiver_parityBit = 0; + VLReceiver_recvState = VL_WAITING_HEADER_MARK; + BitStream_reset(&VLReceiver_vlData); +#ifdef VORTEX_EMBEDDED + threshold = THRESHOLD_BEGIN; +#endif + DEBUG_LOG("VL State Reset"); +} + +#endif // VL_ENABLE_RECEIVER == 1 + +void VLReceiver_setLegacyReceiver(bool legacy) +{ + (void)legacy; +} + diff --git a/VortexEngine/src/Wireless/VLReceiver.h b/VortexEngine/src/Wireless/VLReceiver.h index 96482cea1f..ace7b89df1 100644 --- a/VortexEngine/src/Wireless/VLReceiver.h +++ b/VortexEngine/src/Wireless/VLReceiver.h @@ -2,6 +2,7 @@ #define VL_RECEIVER_H #include +#include #include "../Serial/BitStream.h" @@ -9,96 +10,56 @@ #if VL_ENABLE_RECEIVER == 1 -class ByteStream; -class Mode; - -class VLReceiver -{ - VLReceiver(); - -public: - // init and cleanup the receiver - static bool init(); - static void cleanup(); - - // check whether a full VL message is ready to read - static bool dataReady(); - // whether actively receiving - static bool isReceiving(); - // the percent of data received - static uint8_t percentReceived(); - static uint16_t bytesReceived() { return m_vlData.bytepos(); } - - // receive the VL message into a target mode - static bool receiveMode(Mode *pMode); - - // turn the receiver on/off - static bool beginReceiving(); - static bool endReceiving(); - // checks if there is new data since the last reset or check - static bool onNewData(); - // reset VL receiver buffer - static void resetVLState(); - - // turn on/off the legacy receiver - static void setLegacyReceiver(bool legacy) { m_legacy = legacy; } - - // The handler called when the VL receiver flips between on/off - // this has to be public because it's called from a global ISR - // but technically it's an internal function for VLReceiver - static void recvPCIHandler(); -private: - // reading functions - // PCI handler for when VL receiver pin changes states - static bool read(ByteStream &data); - static void handleVLTiming(uint16_t diff); - static void handleVLTimingLegacy(uint16_t diff); - - // =================== - // private data: - - // BitStream object that VL data is fed to bit by bit - static BitStream m_vlData; - - // Receive state used for state machine in PCIhandler - enum RecvState : uint8_t - { - WAITING_HEADER_MARK, - WAITING_HEADER_SPACE, - READING_BAUD_MARK, - READING_BAUD_SPACE, - READING_DATA_MARK, - READING_DATA_SPACE, - READING_DATA_PARITY_MARK, - READING_DATA_PARITY_SPACE - }; - - // state information used by the PCIHandler - static RecvState m_recvState; - // used to track pin changes - static uint32_t m_prevTime; - static uint8_t m_pinState; - - // used to compare if received data has changed since last checking - static uint16_t m_previousBytes; - - // the determined time based on sync - static uint16_t m_vlMarkThreshold; - // the determined time based on sync - static uint16_t m_vlSpaceThreshold; - - // count of the sync bits (similar length starter bits) - static uint8_t m_counter; - - static uint8_t m_parityBit; - - // legacy mode - static bool m_legacy; - -#ifdef VORTEX_LIB - friend class Vortex; -#endif -}; +typedef struct ByteStream ByteStream; +typedef struct Mode Mode; + +typedef enum { + VL_WAITING_HEADER_MARK, + VL_WAITING_HEADER_SPACE, + VL_READING_BAUD_MARK, + VL_READING_BAUD_SPACE, + VL_READING_DATA_MARK, + VL_READING_DATA_SPACE, + VL_READING_DATA_PARITY_MARK, + VL_READING_DATA_PARITY_SPACE +} VLRecvState; + +extern BitStream VLReceiver_vlData; +extern VLRecvState VLReceiver_recvState; +extern uint32_t VLReceiver_prevTime; +extern uint8_t VLReceiver_pinState; +extern uint16_t VLReceiver_previousBytes; +extern uint16_t VLReceiver_vlMarkThreshold; +extern uint16_t VLReceiver_vlSpaceThreshold; +extern uint8_t VLReceiver_counter; +extern uint8_t VLReceiver_parityBit; +extern bool VLReceiver_legacy; + +bool VLReceiver_init(); +void VLReceiver_cleanup(); + +bool VLReceiver_dataReady(); +bool VLReceiver_isReceiving(); +uint8_t VLReceiver_percentReceived(); + +static inline uint16_t VLReceiver_bytesReceived() { + return BitStream_bytepos(&VLReceiver_vlData); +} + +bool VLReceiver_receiveMode(Mode *pMode); + +bool VLReceiver_beginReceiving(); +bool VLReceiver_endReceiving(); +bool VLReceiver_onNewData(); +void VLReceiver_resetVLState(); + +void VLReceiver_setLegacyReceiver(bool legacy); + +void VLReceiver_recvPCIHandler(); + +bool VLReceiver_read(ByteStream *data); +void VLReceiver_handleVLTiming(uint16_t diff); +void VLReceiver_handleVLTimingLegacy(uint16_t diff); #endif diff --git a/VortexEngine/src/Wireless/VLSender.c b/VortexEngine/src/Wireless/VLSender.c new file mode 100644 index 0000000000..21ce06e4d9 --- /dev/null +++ b/VortexEngine/src/Wireless/VLSender.c @@ -0,0 +1,133 @@ +#include "VLSender.h" +#include "IRConfig.h" + +#if VL_ENABLE_SENDER == 1 + +#include "../Time/TimeControl.h" +#include "../Modes/Mode.h" +#include "../Leds/Leds.h" +#include "../Log/Log.h" + +#ifdef VORTEX_LIB +#include "VortexLib.h" +#endif + +VLSenderCallbacks *g_vlSenderCallbacks; + +ByteStream VLSender_serialBuf; +BitStream VLSender_bitStream; +uint8_t VLSender_size = 0; +uint8_t VLSender_parity = 0; + +bool VLSender_init() +{ + return true; +} + +void VLSender_cleanup() +{ +} + +bool VLSender_loadMode(const Mode *targetMode) +{ + ByteStream_clear(&VLSender_serialBuf); + if (!Mode_saveToBuffer(targetMode, &VLSender_serialBuf, 2)) { + DEBUG_LOG("Failed to save mode to buffer"); + return false; + } + if (!ByteStream_decompress(&VLSender_serialBuf)) { + DEBUG_LOG("Failed to decompress VL buf for sending"); + return false; + } + if (ByteStream_rawSize(&VLSender_serialBuf) > VL_MAX_DATA_TRANSFER) { + DEBUG_LOGF("Cannot transfer that much data: %u bytes", ByteStream_rawSize(&VLSender_serialBuf)); + return false; + } + BitStream_initBuf(&VLSender_bitStream, (uint8_t *)ByteStream_rawData(&VLSender_serialBuf), ByteStream_rawSize(&VLSender_serialBuf)); + VLSender_size = (uint8_t)ByteStream_rawSize(&VLSender_serialBuf); + VLSender_parity = 0; + DEBUG_LOGF("Size: %u", VLSender_size); + return true; +} + +void VLSender_send(const Mode *targetMode) +{ + VLSender_loadMode(targetMode); + VLSender_sendMarkSpace(VL_HEADER_MARK, VL_HEADER_SPACE); + for (uint8_t b = 0; b < 2; b++) { + VLSender_sendMarkSpace(VL_TIMING_BIT_ZERO, VL_TIMING_BIT_ZERO); + VLSender_sendMarkSpace(VL_TIMING_BIT_ONE, VL_TIMING_BIT_ONE); + } + VLSender_sendByte(1); + VLSender_sendByte(VLSender_size); + for (uint8_t i = 0; i < VLSender_size; ++i) { + VLSender_sendByte(BitStream_peekData(&VLSender_bitStream, i)); + } + VLSender_sendMarkSpace(VL_TIMING_BIT_ZERO, VL_TIMING_BIT_ZERO); +} + +void VLSender_sendLegacy(const Mode *targetMode) +{ + VLSender_loadMode(targetMode); + VLSender_sendMarkSpace(50, 50); + VLSender_sendMarkSpace(VL_HEADER_MARK_LEGACY, VL_HEADER_SPACE_LEGACY); + VLSender_sendByteLegacy(1); + VLSender_sendByteLegacy(VLSender_size); + for (uint8_t i = 0; i < VLSender_size; ++i) { + VLSender_sendByteLegacy(BitStream_peekData(&VLSender_bitStream, i)); + } +} + +void VLSender_sendByte(uint8_t data) +{ + for (uint8_t i = 0; i < 8; i += 2) { + uint8_t pair = (data >> (6 - i)) & 0x3; + uint8_t mark = (pair >> 1) & 1; + uint8_t space = pair & 1; + VLSender_sendMarkSpace(VL_TIMING_BIT(mark), VL_TIMING_BIT(space)); + VLSender_parity ^= mark ^ space; + } + VLSender_sendMarkSpace(VL_TIMING_BIT(VLSender_parity & 1), VL_TIMING_BIT(0)); +} + +void VLSender_sendByteLegacy(uint8_t data) +{ + for (uint8_t b = 0; b < 8; b++) { + uint8_t bit = (data >> (7 - b)) & 1; + VLSender_sendMarkSpace(VL_TIMING_BIT_LEGACY(bit), VL_TIMING); + } +} + +void VLSender_sendMarkSpace(uint16_t markTime, uint16_t spaceTime) +{ +#ifdef VORTEX_LIB + g_vlSenderCallbacks->infraredWrite(true, markTime); + g_vlSenderCallbacks->infraredWrite(false, spaceTime); +#else + VLSender_startPWM(); + Time_delayMicroseconds(markTime); + VLSender_stopPWM(); + Time_delayMicroseconds(spaceTime); +#endif +} + +void VLSender_startPWM() +{ +#if defined(VORTEX_EMBEDDED) + uint8_t oldBrightness = Leds_getBrightness(); + Leds_setBrightness(255); + Leds_setIndex(LED_0, RGB_COLOR(RGB_WHITE)); + Leds_update(); + Leds_setBrightness(oldBrightness); +#endif +} + +void VLSender_stopPWM() +{ +#if defined(VORTEX_EMBEDDED) + Leds_clearAll(); + Leds_update(); +#endif +} + +#endif diff --git a/VortexEngine/src/Wireless/VLSender.h b/VortexEngine/src/Wireless/VLSender.h index a368201d99..3ad2880583 100644 --- a/VortexEngine/src/Wireless/VLSender.h +++ b/VortexEngine/src/Wireless/VLSender.h @@ -1,6 +1,9 @@ #ifndef VL_SENDER_H #define VL_SENDER_H +#include +#include + #include "../Serial/ByteStream.h" #include "../Serial/BitStream.h" @@ -8,41 +11,31 @@ #if VL_ENABLE_SENDER == 1 -class Mode; - -class VLSender -{ - VLSender(); - -public: - static bool init(); - static void cleanup(); - - // send a mode - static void send(const Mode *targetMode); - static void sendLegacy(const Mode *targetMode); - -private: - static bool loadMode(const Mode *targetMode); - // send a full 8 bits in a tight loop - static void sendByte(uint8_t data); - // send full 8 bits legacy protocol - static void sendByteLegacy(uint8_t data); - // send a mark/space by turning PWM on/off - static void sendMarkSpace(uint16_t markTime, uint16_t spaceTime); - // turn the VL transmitter on/off in realtime - static void startPWM(); - static void stopPWM(); - - // the serial buffer for the data - static ByteStream m_serialBuf; - // a bit walker for the serial data - static BitStream m_bitStream; - - // some runtime meta info - static uint8_t m_size; - static uint8_t m_parity; -}; +typedef struct Mode Mode; + +typedef struct VLSenderCallbacks_s { + void (*infraredWrite)(bool mark, uint32_t amount); +} VLSenderCallbacks; + +extern VLSenderCallbacks *g_vlSenderCallbacks; + +extern ByteStream VLSender_serialBuf; +extern BitStream VLSender_bitStream; +extern uint8_t VLSender_size; +extern uint8_t VLSender_parity; + +bool VLSender_init(); +void VLSender_cleanup(); + +void VLSender_send(const Mode *targetMode); +void VLSender_sendLegacy(const Mode *targetMode); + +bool VLSender_loadMode(const Mode *targetMode); +void VLSender_sendByte(uint8_t data); +void VLSender_sendByteLegacy(uint8_t data); +void VLSender_sendMarkSpace(uint16_t markTime, uint16_t spaceTime); +void VLSender_startPWM(); +void VLSender_stopPWM(); #endif diff --git a/avrsize.sh b/avrsize.sh old mode 100644 new mode 100755 From 92414fa143739f6268c8e5b0c6fc090f9405e323 Mon Sep 17 00:00:00 2001 From: Unreal-Dan Date: Fri, 10 Jul 2026 00:56:43 -0700 Subject: [PATCH 02/10] fix CI: apt install gcc-avr instead of web download, test job uses make lib --- .github/workflows/duo_build.yml | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/.github/workflows/duo_build.yml b/.github/workflows/duo_build.yml index 0a5635170c..78969d1708 100644 --- a/.github/workflows/duo_build.yml +++ b/.github/workflows/duo_build.yml @@ -58,13 +58,13 @@ jobs: run: sudo apt-get update - name: Install Dependencies run: sudo apt-get install valgrind g++ make --fix-missing - - name: Build + - name: Build desktop library + CLI run: | export VORTEX_VERSION_MAJOR=${{ needs.setup.outputs.vortex_version_major }} export VORTEX_VERSION_MINOR=${{ needs.setup.outputs.vortex_version_minor }} export VORTEX_BUILD_NUMBER=${{ needs.setup.outputs.vortex_build_number }} export VORTEX_VERSION_NUMBER=${{ needs.setup.outputs.vortex_version_number }} - make -j + make lib working-directory: VortexEngine - name: Set execute permissions for test script run: chmod +x ./runtests.sh @@ -78,19 +78,15 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.x' - - name: Install Dependencies - run: make install - - name: Build Binary + - name: Install AVR toolchain + run: sudo apt-get install gcc-avr avr-libc make --fix-missing + - name: Build Embedded Firmware run: | export VORTEX_VERSION_MAJOR=${{ needs.setup.outputs.vortex_version_major }} export VORTEX_VERSION_MINOR=${{ needs.setup.outputs.vortex_version_minor }} export VORTEX_BUILD_NUMBER=${{ needs.setup.outputs.vortex_build_number }} export VORTEX_VERSION_NUMBER=${{ needs.setup.outputs.vortex_version_number }} - make build + make - name: Archive production artifacts uses: actions/upload-artifact@v4 with: From eefbc567227d41bcd653546b2e3b4c12ffc230d0 Mon Sep 17 00:00:00 2001 From: Unreal-Dan Date: Fri, 10 Jul 2026 00:57:20 -0700 Subject: [PATCH 03/10] add python setup back to embedded job --- .github/workflows/duo_build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/duo_build.yml b/.github/workflows/duo_build.yml index 78969d1708..f7f5bdbf75 100644 --- a/.github/workflows/duo_build.yml +++ b/.github/workflows/duo_build.yml @@ -78,6 +78,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' - name: Install AVR toolchain run: sudo apt-get install gcc-avr avr-libc make --fix-missing - name: Build Embedded Firmware From d5db7e7d7075c703799ae3372863862af3069e9a Mon Sep 17 00:00:00 2001 From: Unreal-Dan Date: Fri, 10 Jul 2026 00:59:34 -0700 Subject: [PATCH 04/10] revert VortexLib Makefile to find only .cpp for desktop C++ build --- VortexEngine/VortexLib/Makefile | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/VortexEngine/VortexLib/Makefile b/VortexEngine/VortexLib/Makefile index 317e08e356..1fe8c19c7e 100644 --- a/VortexEngine/VortexLib/Makefile +++ b/VortexEngine/VortexLib/Makefile @@ -9,11 +9,7 @@ ifdef WASM CC=em++ AR=emar cru else -ifdef USE_C -CC=gcc -else CC=g++ -endif AR=ar cru endif @@ -85,18 +81,18 @@ LIBS=\ # source files # local source files first, other sources after ifeq ($(OS),Windows_NT) - SRC = $(shell find ../src/ -type f -name \\*.c -o -type f -name \\*.cpp) VortexLib.cpp + SRC = $(shell find ../src/ -type f -name \\*.cpp) VortexLib.cpp else - SRC = $(shell find ../src/ -type f -name '*.c' -o -type f -name '*.cpp') VortexLib.cpp + SRC = $(shell find ../src/ -type f -name '*.cpp') VortexLib.cpp endif -# object files are source files with .c or .cpp replaced with .o +# object files are source files with .cpp replaced with .o OBJS=\ - $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(SRC))) \ + $(SRC:.cpp=.o) \ -# dependency files are source files with .c or .cpp replaced with .d +# dependency files are source files with .cpp replaced with .d DFILES=\ - $(patsubst %.c,%.d,$(patsubst %.cpp,%.d,$(SRC))) \ + $(SRC:.cpp=.d) \ # target dependencies # this includes any script generated c/h files, @@ -134,9 +130,6 @@ VortexLib.js: compute_version $(DEPS) $(CC) $(LDFLAGS) $(DEPS) -o $@ $(LLIBS) # catch-all make target to generate .o and .d files -%.o: %.c - $(CC) $(CFLAGS) -MMD -c $< -o $@ - %.o: %.cpp $(CC) $(CFLAGS) -MMD -c $< -o $@ From cd426ab9bd83615085f1f224c9fb6f0c4494ca9d Mon Sep 17 00:00:00 2001 From: Unreal-Dan Date: Fri, 10 Jul 2026 01:09:56 -0700 Subject: [PATCH 05/10] restore original C++ headers for desktop build --- VortexEngine/src/Buttons/Button.h | 129 +++++-- VortexEngine/src/Buttons/Buttons.h | 36 +- VortexEngine/src/Colors/ColorConstants.h | 219 ++++++------ VortexEngine/src/Colors/ColorTypes.h | 128 ++++--- VortexEngine/src/Colors/Colorset.h | 226 ++++++++---- VortexEngine/src/Leds/LedStash.h | 26 +- VortexEngine/src/Leds/LedTypes.h | 166 +++++++-- VortexEngine/src/Leds/Leds.h | 180 +++++++--- VortexEngine/src/Log/Log.h | 6 + VortexEngine/src/Memory/Memory.h | 24 +- VortexEngine/src/Menus/Menu.h | 98 +++--- VortexEngine/src/Menus/MenuList/ColorSelect.h | 89 +++-- .../src/Menus/MenuList/EditorConnection.h | 172 ++++++---- .../src/Menus/MenuList/FactoryReset.h | 24 +- .../src/Menus/MenuList/GlobalBrightness.h | 74 ++-- VortexEngine/src/Menus/MenuList/ModeSharing.h | 60 ++-- .../src/Menus/MenuList/PatternSelect.h | 39 ++- VortexEngine/src/Menus/MenuList/Randomizer.h | 102 ++++-- VortexEngine/src/Menus/Menus.h | 80 ++++- VortexEngine/src/Modes/DefaultModes.h | 6 +- VortexEngine/src/Modes/Mode.h | 195 +++++++---- VortexEngine/src/Modes/Modes.h | 321 ++++++++++++------ .../src/Patterns/Multi/MultiLedPattern.h | 23 +- VortexEngine/src/Patterns/Pattern.h | 168 +++++---- VortexEngine/src/Patterns/PatternArgs.h | 88 +++-- VortexEngine/src/Patterns/PatternBuilder.h | 51 ++- VortexEngine/src/Patterns/Patterns.h | 67 +++- .../src/Patterns/Single/BasicPattern.h | 94 +++-- .../src/Patterns/Single/BlendPattern.h | 37 +- .../src/Patterns/Single/SingleLedPattern.h | 17 +- .../src/Patterns/Single/SolidPattern.h | 17 +- VortexEngine/src/Random/Random.h | 26 +- VortexEngine/src/Serial/BitStream.h | 74 ++-- VortexEngine/src/Serial/ByteStream.h | 265 ++++++++++----- VortexEngine/src/Serial/Serial.h | 47 ++- VortexEngine/src/Storage/Storage.h | 43 ++- VortexEngine/src/Time/TimeControl.h | 142 ++++++-- VortexEngine/src/Time/Timer.h | 91 +++-- VortexEngine/src/VortexEngine.h | 84 +++-- VortexEngine/src/Wireless/IRReceiver.h | 89 +++-- VortexEngine/src/Wireless/IRSender.h | 76 +++-- VortexEngine/src/Wireless/VLReceiver.h | 141 +++++--- VortexEngine/src/Wireless/VLSender.h | 63 ++-- 43 files changed, 2776 insertions(+), 1327 deletions(-) diff --git a/VortexEngine/src/Buttons/Button.h b/VortexEngine/src/Buttons/Button.h index 35e6deb56e..8d0d98ab19 100644 --- a/VortexEngine/src/Buttons/Button.h +++ b/VortexEngine/src/Buttons/Button.h @@ -2,45 +2,106 @@ #define BUTTON_H #include -#include + #include "../VortexConfig.h" -typedef struct Button_s { - uint32_t pressTime; - uint32_t releaseTime; - uint32_t holdDuration; - uint32_t releaseDuration; - uint8_t consecutivePresses; - uint8_t releaseCount; - bool buttonState; - bool newPress; - bool newRelease; - bool isPressed; - bool shortClick; - bool longClick; -} Button; - -void Button_init(Button *self); -void Button_cleanup(Button *self); -bool Button_initPin(Button *self, uint8_t pin); -bool Button_check(Button *self); -void Button_update(Button *self); +// although there is only one button on the VortexFramework +// I am still opting for a non-static button class +class Button +{ +private: + // private unimplemented copy and assignment constructors to prevent copies + Button(Button const &); + void operator=(Button const &); + +public: + Button(); + ~Button(); + + // initialize a new button object with a pin number + bool init(uint8_t pin); + // directly poll the pin for whether it's pressed right now + bool check(); + // poll the button pin and update the state of the button object + void update(); + #ifdef VORTEX_EMBEDDED -void Button_enableWake(Button *self); + // enable the button-wake trigger to wake the device on press + void enableWake(); +#endif + + // whether the button was pressed this tick + bool onPress() const { return m_newPress; } + // whether the button was released this tick + bool onRelease() const { return m_newRelease; } + // whether the button is currently pressed + bool isPressed() const { return m_isPressed; } + + // whether the button was shortclicked this tick + bool onShortClick() const { return m_shortClick; } + // whether the button was long clicked this tick + bool onLongClick() const { return m_longClick; } + // fired when a certain number of presses is reached, the consecutive press + // counter is automatically reset when that happens + bool onConsecutivePresses(uint8_t numPresses); + + // when the button was last pressed + uint32_t pressTime() const { return m_pressTime; } + // when the button was last released + uint32_t releaseTime() const { return m_releaseTime; } + + // how long the button is currently or was last held down (in ticks) + uint32_t holdDuration() const { return m_holdDuration; } + // how long the button is currently or was last released for (in ticks) + uint32_t releaseDuration() const { return m_releaseDuration; } + + // the number of consecutive presses + uint8_t consecutivePresses() const { return m_consecutivePresses; } + // the number of releases + uint8_t releaseCount() const { return m_releaseCount; } + +private: + // ======================================== + // state data that is populated each check + + // the timestamp of when the button was pressed + uint32_t m_pressTime; + // the timestamp of when the button was released + uint32_t m_releaseTime; + + // the last hold duration + uint32_t m_holdDuration; + // the last release duration + uint32_t m_releaseDuration; + + // the number of repeated presses (automatically detects rapid presses) + uint8_t m_consecutivePresses; + // the number of times released, will overflow at 255 + uint8_t m_releaseCount; + + // the active state of the button + bool m_buttonState; + + // whether pressed this tick + bool m_newPress; + // whether released this tick + bool m_newRelease; + // whether currently pressed + bool m_isPressed; + // whether a short click occurred + bool m_shortClick; + // whether a long click occurred + bool m_longClick; + +#ifdef VORTEX_LIB + // allow the VortexLib class to manipulate the members of this + // class so that it can inject button events to make apis like + // Vortex::shortClick() and Vortex::longClick() possible + friend class Vortex; #endif -bool Button_onPress(const Button *self); -bool Button_onRelease(const Button *self); -bool Button_isPressed(const Button *self); -bool Button_onShortClick(const Button *self); -bool Button_onLongClick(const Button *self); -bool Button_onConsecutivePresses(Button *self, uint8_t numPresses); -uint32_t Button_pressTime(const Button *self); -uint32_t Button_releaseTime(const Button *self); -uint32_t Button_holdDuration(const Button *self); -uint32_t Button_releaseDuration(const Button *self); -uint8_t Button_consecutivePresses(const Button *self); -uint8_t Button_releaseCount(const Button *self); +}; +// See Button.cpp for info about this extern Button *g_pButton; #endif diff --git a/VortexEngine/src/Buttons/Buttons.h b/VortexEngine/src/Buttons/Buttons.h index 2bc248a97c..ad9ed95146 100644 --- a/VortexEngine/src/Buttons/Buttons.h +++ b/VortexEngine/src/Buttons/Buttons.h @@ -1,19 +1,33 @@ #ifndef BUTTONS_H #define BUTTONS_H -#include -#include #include "Button.h" -#include "../VortexConfig.h" -#ifdef VORTEX_EMBEDDED -#define REGISTER_BUTTON(pin) Buttons_init(pin) -#else -#define REGISTER_BUTTON(pin) Buttons_init() -#endif +// the number of buttons, this isn't in VortexConfig.h because +// changing it won't really work without updating other things +// like which pins the buttons are attached to. So this is more +// of a hardcoded constant than a configuration setting +#define NUM_BUTTONS 1 + +class Buttons +{ + // private unimplemented constructor + Buttons(); + +public: + // initialize all buttons + static bool init(); + static void cleanup(); + + // poll the buttons + static void update(); + + static uint8_t numButtons() { return NUM_BUTTONS; } + +private: +}; -bool Buttons_init(uint8_t pin); -void Buttons_cleanup(void); -void Buttons_update(void); +// best way I think +extern Button *g_pButton; #endif diff --git a/VortexEngine/src/Colors/ColorConstants.h b/VortexEngine/src/Colors/ColorConstants.h index f379236c45..a33f6c3a80 100644 --- a/VortexEngine/src/Colors/ColorConstants.h +++ b/VortexEngine/src/Colors/ColorConstants.h @@ -1,6 +1,12 @@ -#ifndef COLOR_CONSTANTS_H -#define COLOR_CONSTANTS_H +#pragma once +// ==================================================================================================== +// Color Constants +// +// This file defines constants for colors as we best see them for the Vortex Engine + +// Some Pre-defined hue values +// TODO: remove HSV_ underscore once FastLED is gone #define HSV_HUE_RED 0 #define HSV_HUE_ORANGE 32 #define HSV_HUE_YELLOW 64 @@ -10,118 +16,125 @@ #define HSV_HUE_PURPLE 192 #define HSV_HUE_PINK 224 +// if this bit is present it's an HSV constant #define HSV_BIT ((uint32_t)1 << 31) +// produce a DWORD HSV constant #define HSV(h, s, v) (HSV_BIT | ((uint32_t)h << 16) | ((uint32_t)s << 8) | (uint32_t)v) -#define HSV_WHITE HSV_BIT | (uint32_t)0x00006E -#define HSV_BLUE HSV_BIT | (uint32_t)0xA0FF6E -#define HSV_YELLOW HSV_BIT | (uint32_t)0x3CFF6E -#define HSV_RED HSV_BIT | (uint32_t)0x00FF6E -#define HSV_GREEN HSV_BIT | (uint32_t)0x55FF6E -#define HSV_CYAN HSV_BIT | (uint32_t)0x78FF6E -#define HSV_PURPLE HSV_BIT | (uint32_t)0xD4FF6E -#define HSV_ORANGE HSV_BIT | (uint32_t)0x14FF6E -#define HSV_OFF HSV_BIT | (uint32_t)0x000000 +// Pre defined hex HSV values +#define HSV_WHITE HSV_BIT | (uint32_t)0x00006E // 0 0 110 +#define HSV_BLUE HSV_BIT | (uint32_t)0xA0FF6E // 160 255 110 +#define HSV_YELLOW HSV_BIT | (uint32_t)0x3CFF6E // 60 255 110 +#define HSV_RED HSV_BIT | (uint32_t)0x00FF6E // 0 255 110 +#define HSV_GREEN HSV_BIT | (uint32_t)0x55FF6E // 85 255 110 +#define HSV_CYAN HSV_BIT | (uint32_t)0x78FF6E // 120 255 110 +#define HSV_PURPLE HSV_BIT | (uint32_t)0xD4FF6E // 212 255 110 +#define HSV_ORANGE HSV_BIT | (uint32_t)0x14FF6E // 20 255 110 +#define HSV_OFF HSV_BIT | (uint32_t)0x000000 // 0 0 0 -#define RGB_WHITE (uint32_t)0xFFFFFF -#define RGB_BLUE (uint32_t)0x0000FF -#define RGB_YELLOW (uint32_t)0xFFFF00 -#define RGB_RED (uint32_t)0xFF0000 -#define RGB_GREEN (uint32_t)0x00FF00 -#define RGB_CYAN (uint32_t)0x00FFFF -#define RGB_PURPLE (uint32_t)0x9933FF -#define RGB_ORANGE (uint32_t)0xFF8300 -#define RGB_PINK (uint32_t)0xFF0099 -#define RGB_MAGENTA (uint32_t)0xFF00FF -#define RGB_OFF (uint32_t)0x000000 +// Pure RGB colors (brightest) +#define RGB_WHITE (uint32_t)0xFFFFFF // 255 255 255 +#define RGB_BLUE (uint32_t)0x0000FF // 0 0 255 +#define RGB_YELLOW (uint32_t)0xFFFF00 // 255 255 0 +#define RGB_RED (uint32_t)0xFF0000 // 255 0 0 +#define RGB_GREEN (uint32_t)0x00FF00 // 0 255 0 +#define RGB_CYAN (uint32_t)0x00FFFF // 0 255 255 +#define RGB_PURPLE (uint32_t)0x9933FF // 153 51 255 +#define RGB_ORANGE (uint32_t)0xFF8300 // 255 131 0 +#define RGB_PINK (uint32_t)0xFF0099 // 255 0 153 +#define RGB_MAGENTA (uint32_t)0xFF00FF // 255 0 255 +#define RGB_OFF (uint32_t)0x000000 // 0 0 0 -#define RGB_WHITE0 (uint32_t)0x101010 -#define RGB_WHITE1 (uint32_t)0x1C1C1C -#define RGB_WHITE2 (uint32_t)0x383838 -#define RGB_WHITE3 (uint32_t)0x545454 -#define RGB_WHITE4 (uint32_t)0x707070 -#define RGB_WHITE5 (uint32_t)0x8C8C8C -#define RGB_WHITE6 (uint32_t)0xA8A8A8 -#define RGB_WHITE7 (uint32_t)0xC4C4C4 -#define RGB_WHITE8 (uint32_t)0xE0E0E0 -#define RGB_WHITE9 (uint32_t)0xFCFCFC +// =========================================================== +// Steps of colors in increasing brightness from 0 to 9, +// Where O is the lowest brightness and 9 is the highest +// brightness before the pure colors above -#define RGB_BLUE0 (uint32_t)0x000010 -#define RGB_BLUE1 (uint32_t)0x00001C -#define RGB_BLUE2 (uint32_t)0x000038 -#define RGB_BLUE3 (uint32_t)0x000054 -#define RGB_BLUE4 (uint32_t)0x000070 -#define RGB_BLUE5 (uint32_t)0x00008C -#define RGB_BLUE6 (uint32_t)0x0000A8 -#define RGB_BLUE7 (uint32_t)0x0000C4 -#define RGB_BLUE8 (uint32_t)0x0000E0 -#define RGB_BLUE9 (uint32_t)0x0000FC +#define RGB_WHITE0 (uint32_t)0x101010 // 16 16 16 +#define RGB_WHITE1 (uint32_t)0x1C1C1C // 28 28 28 +#define RGB_WHITE2 (uint32_t)0x383838 // 56 56 56 +#define RGB_WHITE3 (uint32_t)0x545454 // 84 84 84 +#define RGB_WHITE4 (uint32_t)0x707070 // 112 112 112 +#define RGB_WHITE5 (uint32_t)0x8C8C8C // 140 140 140 +#define RGB_WHITE6 (uint32_t)0xA8A8A8 // 168 168 168 +#define RGB_WHITE7 (uint32_t)0xC4C4C4 // 196 196 196 +#define RGB_WHITE8 (uint32_t)0xE0E0E0 // 224 224 224 +#define RGB_WHITE9 (uint32_t)0xFCFCFC // 252 252 252 -#define RGB_YELLOW0 (uint32_t)0x101000 -#define RGB_YELLOW1 (uint32_t)0x1C1C00 -#define RGB_YELLOW2 (uint32_t)0x383800 -#define RGB_YELLOW3 (uint32_t)0x545400 -#define RGB_YELLOW4 (uint32_t)0x707000 -#define RGB_YELLOW5 (uint32_t)0x8C8C00 -#define RGB_YELLOW6 (uint32_t)0xA8A800 -#define RGB_YELLOW7 (uint32_t)0xC4C400 -#define RGB_YELLOW8 (uint32_t)0xE0E000 -#define RGB_YELLOW9 (uint32_t)0xFCFC00 +#define RGB_BLUE0 (uint32_t)0x000010 // 0 0 16 +#define RGB_BLUE1 (uint32_t)0x00001C // 0 0 28 +#define RGB_BLUE2 (uint32_t)0x000038 // 0 0 56 +#define RGB_BLUE3 (uint32_t)0x000054 // 0 0 84 +#define RGB_BLUE4 (uint32_t)0x000070 // 0 0 112 +#define RGB_BLUE5 (uint32_t)0x00008C // 0 0 140 +#define RGB_BLUE6 (uint32_t)0x0000A8 // 0 0 168 +#define RGB_BLUE7 (uint32_t)0x0000C4 // 0 0 196 +#define RGB_BLUE8 (uint32_t)0x0000E0 // 0 0 224 +#define RGB_BLUE9 (uint32_t)0x0000FC // 0 0 252 -#define RGB_RED0 (uint32_t)0x100000 -#define RGB_RED1 (uint32_t)0x1C0000 -#define RGB_RED2 (uint32_t)0x380000 -#define RGB_RED3 (uint32_t)0x540000 -#define RGB_RED4 (uint32_t)0x700000 -#define RGB_RED5 (uint32_t)0x8C0000 -#define RGB_RED6 (uint32_t)0xA80000 -#define RGB_RED7 (uint32_t)0xC40000 -#define RGB_RED8 (uint32_t)0xE00000 -#define RGB_RED9 (uint32_t)0xFC0000 +#define RGB_YELLOW0 (uint32_t)0x101000 // 16 16 0 +#define RGB_YELLOW1 (uint32_t)0x1C1C00 // 28 28 0 +#define RGB_YELLOW2 (uint32_t)0x383800 // 56 56 0 +#define RGB_YELLOW3 (uint32_t)0x545400 // 84 84 0 +#define RGB_YELLOW4 (uint32_t)0x707000 // 112 112 0 +#define RGB_YELLOW5 (uint32_t)0x8C8C00 // 140 140 0 +#define RGB_YELLOW6 (uint32_t)0xA8A800 // 168 168 0 +#define RGB_YELLOW7 (uint32_t)0xC4C400 // 196 196 0 +#define RGB_YELLOW8 (uint32_t)0xE0E000 // 224 224 0 +#define RGB_YELLOW9 (uint32_t)0xFCFC00 // 252 252 0 -#define RGB_GREEN0 (uint32_t)0x001000 -#define RGB_GREEN1 (uint32_t)0x001C00 -#define RGB_GREEN2 (uint32_t)0x003800 -#define RGB_GREEN3 (uint32_t)0x005400 -#define RGB_GREEN4 (uint32_t)0x007000 -#define RGB_GREEN5 (uint32_t)0x008C00 -#define RGB_GREEN6 (uint32_t)0x00A800 -#define RGB_GREEN7 (uint32_t)0x00C400 -#define RGB_GREEN8 (uint32_t)0x00E000 -#define RGB_GREEN9 (uint32_t)0x00FC00 +#define RGB_RED0 (uint32_t)0x100000 // 16 0 0 +#define RGB_RED1 (uint32_t)0x1C0000 // 28 0 0 +#define RGB_RED2 (uint32_t)0x380000 // 56 0 0 +#define RGB_RED3 (uint32_t)0x540000 // 84 0 0 +#define RGB_RED4 (uint32_t)0x700000 // 112 0 0 +#define RGB_RED5 (uint32_t)0x8C0000 // 140 0 0 +#define RGB_RED6 (uint32_t)0xA80000 // 168 0 0 +#define RGB_RED7 (uint32_t)0xC40000 // 196 0 0 +#define RGB_RED8 (uint32_t)0xE00000 // 224 0 0 +#define RGB_RED9 (uint32_t)0xFC0000 // 252 0 0 -#define RGB_CYAN0 (uint32_t)0x001010 -#define RGB_CYAN1 (uint32_t)0x001C1C -#define RGB_CYAN2 (uint32_t)0x003838 -#define RGB_CYAN3 (uint32_t)0x005454 -#define RGB_CYAN4 (uint32_t)0x007070 -#define RGB_CYAN5 (uint32_t)0x008C8C -#define RGB_CYAN6 (uint32_t)0x00A8A8 -#define RGB_CYAN7 (uint32_t)0x00C4C4 -#define RGB_CYAN8 (uint32_t)0x00E0E0 -#define RGB_CYAN9 (uint32_t)0x00FCFC +#define RGB_GREEN0 (uint32_t)0x001000 // 0 16 0 +#define RGB_GREEN1 (uint32_t)0x001C00 // 0 28 0 +#define RGB_GREEN2 (uint32_t)0x003800 // 0 56 0 +#define RGB_GREEN3 (uint32_t)0x005400 // 0 84 0 +#define RGB_GREEN4 (uint32_t)0x007000 // 0 112 0 +#define RGB_GREEN5 (uint32_t)0x008C00 // 0 140 0 +#define RGB_GREEN6 (uint32_t)0x00A800 // 0 168 0 +#define RGB_GREEN7 (uint32_t)0x00C400 // 0 196 0 +#define RGB_GREEN8 (uint32_t)0x00E000 // 0 224 0 +#define RGB_GREEN9 (uint32_t)0x00FC00 // 0 252 0 -#define RGB_MAGENTA0 (uint32_t)0x100010 -#define RGB_MAGENTA1 (uint32_t)0x1C001C -#define RGB_MAGENTA2 (uint32_t)0x380038 -#define RGB_MAGENTA3 (uint32_t)0x540054 -#define RGB_MAGENTA4 (uint32_t)0x700070 -#define RGB_MAGENTA5 (uint32_t)0x8C008C -#define RGB_MAGENTA6 (uint32_t)0xA800A8 -#define RGB_MAGENTA7 (uint32_t)0xC400C4 -#define RGB_MAGENTA8 (uint32_t)0xE000E0 -#define RGB_MAGENTA9 (uint32_t)0xFC00FC +#define RGB_CYAN0 (uint32_t)0x001010 // 0 16 16 +#define RGB_CYAN1 (uint32_t)0x001C1C // 0 28 28 +#define RGB_CYAN2 (uint32_t)0x003838 // 0 56 56 +#define RGB_CYAN3 (uint32_t)0x005454 // 0 84 84 +#define RGB_CYAN4 (uint32_t)0x007070 // 0 112 112 +#define RGB_CYAN5 (uint32_t)0x008C8C // 0 140 140 +#define RGB_CYAN6 (uint32_t)0x00A8A8 // 0 168 168 +#define RGB_CYAN7 (uint32_t)0x00C4C4 // 0 196 196 +#define RGB_CYAN8 (uint32_t)0x00E0E0 // 0 224 224 +#define RGB_CYAN9 (uint32_t)0x00FCFC // 0 252 252 -#define RGB_ORANGE0 (uint32_t)0x100800 -#define RGB_ORANGE1 (uint32_t)0x1C0E00 -#define RGB_ORANGE2 (uint32_t)0x381C00 -#define RGB_ORANGE3 (uint32_t)0x542B00 -#define RGB_ORANGE4 (uint32_t)0x703900 -#define RGB_ORANGE5 (uint32_t)0x8C4800 -#define RGB_ORANGE6 (uint32_t)0xA85600 -#define RGB_ORANGE7 (uint32_t)0xC46500 -#define RGB_ORANGE8 (uint32_t)0xE07300 -#define RGB_ORANGE9 (uint32_t)0xFC8200 +#define RGB_MAGENTA0 (uint32_t)0x100010 // 16 0 16 +#define RGB_MAGENTA1 (uint32_t)0x1C001C // 28 0 28 +#define RGB_MAGENTA2 (uint32_t)0x380038 // 56 0 56 +#define RGB_MAGENTA3 (uint32_t)0x540054 // 84 0 84 +#define RGB_MAGENTA4 (uint32_t)0x700070 // 112 0 112 +#define RGB_MAGENTA5 (uint32_t)0x8C008C // 140 0 140 +#define RGB_MAGENTA6 (uint32_t)0xA800A8 // 168 0 168 +#define RGB_MAGENTA7 (uint32_t)0xC400C4 // 196 0 196 +#define RGB_MAGENTA8 (uint32_t)0xE000E0 // 224 0 224 +#define RGB_MAGENTA9 (uint32_t)0xFC00FC // 252 0 252 -#endif +#define RGB_ORANGE0 (uint32_t)0x100800 // 16 8 0 +#define RGB_ORANGE1 (uint32_t)0x1C0E00 // 28 14 0 +#define RGB_ORANGE2 (uint32_t)0x381C00 // 56 28 0 +#define RGB_ORANGE3 (uint32_t)0x542B00 // 84 43 0 +#define RGB_ORANGE4 (uint32_t)0x703900 // 112 57 0 +#define RGB_ORANGE5 (uint32_t)0x8C4800 // 140 72 0 +#define RGB_ORANGE6 (uint32_t)0xA85600 // 168 86 0 +#define RGB_ORANGE7 (uint32_t)0xC46500 // 196 101 0 +#define RGB_ORANGE8 (uint32_t)0xE07300 // 224 115 0 +#define RGB_ORANGE9 (uint32_t)0xFC8200 // 252 130 0 diff --git a/VortexEngine/src/Colors/ColorTypes.h b/VortexEngine/src/Colors/ColorTypes.h index 5fa212ca9d..c4e92b2936 100644 --- a/VortexEngine/src/Colors/ColorTypes.h +++ b/VortexEngine/src/Colors/ColorTypes.h @@ -2,76 +2,108 @@ #define COLOR_H #include -#include #include "../VortexConfig.h" #include "ColorConstants.h" -typedef uint8_t hsv_to_rgb_algorithm; - -enum { +enum hsv_to_rgb_algorithm : uint8_t +{ HSV_TO_RGB_GENERIC, HSV_TO_RGB_RAW, HSV_TO_RGB_RAINBOW }; +// global hsv to rgb algorithm selector, switch this to control +// all hsv to rgb conversions extern hsv_to_rgb_algorithm g_hsv_rgb_alg; -typedef struct ByteStream ByteStream; +class ByteStream; +class RGBColor; + +class HSVColor +{ +public: + HSVColor(); + HSVColor(uint8_t hue, uint8_t sat, uint8_t val); + + // assignment from uint32_t + HSVColor(uint32_t dwVal); + HSVColor &operator=(const uint32_t &rhs); + + // copy/assignment construction + HSVColor(const HSVColor &rhs); + HSVColor &operator=(const HSVColor &rhs); -typedef struct RGBColor_t RGBColor; -typedef struct HSVColor_t HSVColor; + // construction/assignment from RGB + HSVColor(const RGBColor &rhs); + HSVColor &operator=(const RGBColor &rhs); -struct HSVColor_t { + // equality operators + bool operator==(const HSVColor &other) const; + bool operator!=(const HSVColor &other) const; + + bool empty() const; + void clear(); + + uint32_t raw() const { return HSV_BIT | ((uint32_t)hue << 16) | ((uint32_t)sat << 8) | (uint32_t)val; } + + // public members uint8_t hue; uint8_t sat; uint8_t val; }; -struct RGBColor_t { +class RGBColor +{ +public: + RGBColor(); + RGBColor(uint8_t red, uint8_t green, uint8_t blue); + + // assignment from uint32_t + RGBColor(uint32_t dwVal); + RGBColor &operator=(const uint32_t &rhs); + + // copy/assignment construction + RGBColor(const RGBColor &rhs); + RGBColor &operator=(const RGBColor &rhs); + + // construction/assignment from HSV + RGBColor(const HSVColor &rhs); + RGBColor &operator=(const HSVColor &rhs); + + // equality operators + bool operator==(const RGBColor &other) const; + bool operator!=(const RGBColor &other) const; + + bool empty() const; + void clear(); + + RGBColor adjustBrightness(uint8_t fadeBy); + bool serialize(ByteStream &buffer) const; + bool unserialize(ByteStream &buffer); + + uint32_t raw() const { return ((uint32_t)red << 16) | ((uint32_t)green << 8) | (uint32_t)blue; } + + // public members uint8_t red; uint8_t green; uint8_t blue; }; -void HSVColor_init(HSVColor *self); -void HSVColor_initHSV(HSVColor *self, uint8_t hue, uint8_t sat, uint8_t val); -void HSVColor_initFromU32(HSVColor *self, uint32_t dwVal); -void HSVColor_copy(HSVColor *self, const HSVColor *rhs); -void HSVColor_initFromRGB(HSVColor *self, const RGBColor *rhs); -void HSVColor_assignRGB(HSVColor *self, const RGBColor *rhs); -void HSVColor_assignU32(HSVColor *self, const uint32_t *rhs); -bool HSVColor_equals(const HSVColor *self, const HSVColor *other); -bool HSVColor_notEquals(const HSVColor *self, const HSVColor *other); -bool HSVColor_empty(const HSVColor *self); -void HSVColor_clear(HSVColor *self); -uint32_t HSVColor_raw(const HSVColor *self); - -void RGBColor_init(RGBColor *self); -void RGBColor_initRGB(RGBColor *self, uint8_t red, uint8_t green, uint8_t blue); -void RGBColor_initFromU32(RGBColor *self, uint32_t dwVal); -void RGBColor_copy(RGBColor *self, const RGBColor *rhs); -void RGBColor_initFromHSV(RGBColor *self, const HSVColor *rhs); -void RGBColor_assignU32(RGBColor *self, const uint32_t *rhs); -void RGBColor_assignHSV(RGBColor *self, const HSVColor *rhs); -bool RGBColor_equals(const RGBColor *self, const RGBColor *other); -bool RGBColor_notEquals(const RGBColor *self, const RGBColor *other); -bool RGBColor_empty(const RGBColor *self); -void RGBColor_clear(RGBColor *self); -void RGBColor_adjustBrightness(RGBColor *self, uint8_t fadeBy); -uint32_t RGBColor_raw(const RGBColor *self); - -bool RGBColor_serialize(const RGBColor *self, ByteStream *buffer); -bool RGBColor_unserialize(RGBColor *self, ByteStream *buffer); - -RGBColor hsv_to_rgb_rainbow(const HSVColor *rhs); -RGBColor hsv_to_rgb_raw_C(const HSVColor *rhs); -RGBColor hsv_to_rgb_generic(const HSVColor *rhs); -HSVColor rgb_to_hsv_approx(const RGBColor *rhs); -HSVColor rgb_to_hsv_generic(const RGBColor *rhs); - -// Convert uint32_t color value to RGBColor struct (C has no implicit uint32_t→RGBColor) -#define RGB_COLOR(v) ((RGBColor){ .red = (uint8_t)((v) >> 16), .green = (uint8_t)((v) >> 8), .blue = (uint8_t)(v) }) -#define HSV_COLOR(v) ((HSVColor){ .hue = (uint8_t)((v) >> 16), .sat = (uint8_t)((v) >> 8), .val = (uint8_t)(v) }) +// Stolen from FastLED hsv to rgb full rainbox where all colours +// are given equal weight, this makes for-example yellow larger +// best to use this function as it is the legacy choice +RGBColor hsv_to_rgb_rainbow(const HSVColor &rhs); +// stolen from FastLED raw hsv to rgb conversion, idk what is special +// about this one but I think the hue stops at 190 maybe? +RGBColor hsv_to_rgb_raw_C(const HSVColor &rhs); +// generic hsv to rgb conversion nothing special +RGBColor hsv_to_rgb_generic(const HSVColor &rhs); + +// Convert rgb to hsv with slower approximation that matches +// the fastled hsv to rgb 'rainbow' +HSVColor rgb_to_hsv_approx(const RGBColor &rhs); +// Convert rgb to hsv with generic fast method +HSVColor rgb_to_hsv_generic(const RGBColor &rhs); #endif diff --git a/VortexEngine/src/Colors/Colorset.h b/VortexEngine/src/Colors/Colorset.h index d210b6f0e1..2c34f32c0d 100644 --- a/VortexEngine/src/Colors/Colorset.h +++ b/VortexEngine/src/Colors/Colorset.h @@ -2,83 +2,161 @@ #define COLORSET_H #include "ColorTypes.h" + #include "../VortexConfig.h" -#include - -typedef struct ByteStream ByteStream; -#include "../Random/Random.h" - -typedef uint8_t ValueStyle; -enum { - VAL_STYLE_RANDOM = 0, - VAL_STYLE_LOW_FIRST_COLOR, - VAL_STYLE_HIGH_FIRST_COLOR, - VAL_STYLE_ALTERNATING, - VAL_STYLE_ASCENDING, - VAL_STYLE_DESCENDING, - VAL_STYLE_CONSTANT, - VAL_STYLE_COUNT -}; -typedef uint8_t ColorMode; -enum { - COLOR_MODE_THEORY, - COLOR_MODE_MONOCHROMATIC, - COLOR_MODE_EVENLY_SPACED -}; +class ByteStream; +class Random; -typedef uint8_t ColorMode2; -enum { - COLOR_MODE2_DOUBLE_SPLIT_COMPLIMENTARY, - COLOR_MODE2_TETRADIC -}; +class Colorset +{ +public: + // empty colorset + Colorset(); + // constructor for 1-8 color slots + Colorset(RGBColor c1, RGBColor c2 = RGB_OFF, RGBColor c3 = RGB_OFF, + RGBColor c4 = RGB_OFF, RGBColor c5 = RGB_OFF, RGBColor c6 = RGB_OFF, + RGBColor c7 = RGB_OFF, RGBColor c8 = RGB_OFF); + Colorset(uint8_t numCols, const uint32_t *cols); + ~Colorset(); + + // move constructor + Colorset(Colorset &&other) noexcept; + + // copy and assignment operators + Colorset(const Colorset &other); + void operator=(const Colorset &other); + + // equality operators + bool operator==(const Colorset &other) const; + bool operator!=(const Colorset &other) const; + + // initialize the colorset + void init(RGBColor c1 = RGB_OFF, RGBColor c2 = RGB_OFF, RGBColor c3 = RGB_OFF, + RGBColor c4 = RGB_OFF, RGBColor c5 = RGB_OFF, RGBColor c6 = RGB_OFF, + RGBColor c7 = RGB_OFF, RGBColor c8 = RGB_OFF); + + // clear the colorset + void clear(); + + // pointer comparison + bool equals(const Colorset &set) const; + bool equals(const Colorset *set) const; + + // index operator to access color index + RGBColor operator[](int index) const; + + enum ValueStyle : uint8_t + { + // Random values + VAL_STYLE_RANDOM = 0, + // First color low value, the rest are random + VAL_STYLE_LOW_FIRST_COLOR, + // First color high value, the rest are low + VAL_STYLE_HIGH_FIRST_COLOR, + // Alternat between high and low value + VAL_STYLE_ALTERNATING, + // Ascending values from low to high + VAL_STYLE_ASCENDING, + // Descending values from high to low + VAL_STYLE_DESCENDING, + // Constant value + VAL_STYLE_CONSTANT, + // Total number of value styles + VAL_STYLE_COUNT + }; + + // add a single color + bool addColor(RGBColor col); + bool addColorHSV(uint8_t hue, uint8_t sat, uint8_t val); + void addColorWithValueStyle(Random &ctx, uint8_t hue, uint8_t sat, + ValueStyle valStyle, uint8_t numColors, uint8_t colorPos); + void removeColor(uint8_t index); + + // randomize a colorset with a specific number of colors with + // various different randomization techniques + void randomize(Random &ctx, uint8_t numColors = 0); + + // function to randomize the colors with various different modes of randomization + enum ColorMode { + THEORY, + MONOCHROMATIC, + EVENLY_SPACED + }; + void randomizeColors(Random &ctx, uint8_t numColors, ColorMode mode); + + // similar function but with some different modes + enum ColorMode2 { + DOUBLE_SPLIT_COMPLIMENTARY, + TETRADIC + }; + void randomizeColors2(Random &ctx, ColorMode2 mode); + + // wrappers for various spacings + void randomizeSolid(Random &ctx) { randomizeColors(ctx, 1, Colorset::ColorMode::EVENLY_SPACED); } + void randomizeComplimentary(Random &ctx) { randomizeColors(ctx, 2, Colorset::ColorMode::EVENLY_SPACED); } + void randomizeTriadic(Random &ctx) { randomizeColors(ctx, 3, Colorset::ColorMode::EVENLY_SPACED); } + void randomizeSquare(Random &ctx) { randomizeColors(ctx, 4, Colorset::ColorMode::EVENLY_SPACED); } + void randomizePentadic(Random &ctx) { randomizeColors(ctx, 5, Colorset::ColorMode::EVENLY_SPACED); } + void randomizeRainbow(Random &ctx) { randomizeColors(ctx, 8, Colorset::ColorMode::EVENLY_SPACED); } -typedef struct Colorset_s { - RGBColor palette[MAX_COLOR_SLOTS]; - uint8_t curIndex; - uint8_t numColors; -} Colorset; - -void Colorset_init(Colorset *self); -void Colorset_initColors(Colorset *self, RGBColor c1, RGBColor c2, RGBColor c3, RGBColor c4, RGBColor c5, RGBColor c6, RGBColor c7, RGBColor c8); -void Colorset_initFromU32s(Colorset *self, uint8_t numCols, const uint32_t *cols); -void Colorset_copy(Colorset *self, const Colorset *other); -void Colorset_destroy(Colorset *self); -bool Colorset_equals(const Colorset *self, const Colorset *other); -bool Colorset_notEquals(const Colorset *self, const Colorset *other); -bool Colorset_equalsPtr(const Colorset *self, const Colorset *set); -void Colorset_initPalette(Colorset *self, RGBColor c1, RGBColor c2, RGBColor c3, RGBColor c4, RGBColor c5, RGBColor c6, RGBColor c7, RGBColor c8); -void Colorset_clear(Colorset *self); -bool Colorset_addColor(Colorset *self, RGBColor col); -bool Colorset_addColorHSV(Colorset *self, uint8_t hue, uint8_t sat, uint8_t val); -void Colorset_addColorWithValueStyle(Colorset *self, Random *ctx, uint8_t hue, uint8_t sat, ValueStyle valStyle, uint8_t numColors, uint8_t colorPos); -void Colorset_removeColor(Colorset *self, uint8_t index); -void Colorset_randomize(Colorset *self, Random *ctx, uint8_t numColors); -void Colorset_randomizeColors(Colorset *self, Random *ctx, uint8_t numColors, ColorMode mode); -void Colorset_randomizeColors2(Colorset *self, Random *ctx, ColorMode2 mode); -void Colorset_randomizeSolid(Colorset *self, Random *ctx); -void Colorset_randomizeComplimentary(Colorset *self, Random *ctx); -void Colorset_randomizeTriadic(Colorset *self, Random *ctx); -void Colorset_randomizeSquare(Colorset *self, Random *ctx); -void Colorset_randomizePentadic(Colorset *self, Random *ctx); -void Colorset_randomizeRainbow(Colorset *self, Random *ctx); -void Colorset_adjustBrightness(Colorset *self, uint8_t fadeby); -RGBColor Colorset_get(const Colorset *self, uint8_t index); -void Colorset_set(Colorset *self, uint8_t index, RGBColor col); -void Colorset_skip(Colorset *self, int32_t amount); -RGBColor Colorset_cur(Colorset *self); -void Colorset_setCurIndex(Colorset *self, uint8_t index); -void Colorset_resetIndex(Colorset *self); -uint8_t Colorset_curIndex(const Colorset *self); -RGBColor Colorset_getPrev(Colorset *self); -RGBColor Colorset_getNext(Colorset *self); -RGBColor Colorset_peek(const Colorset *self, int32_t offset); -RGBColor Colorset_peekNext(const Colorset *self); -uint8_t Colorset_numColors(const Colorset *self); -bool Colorset_onStart(const Colorset *self); -bool Colorset_onEnd(const Colorset *self); -bool Colorset_serialize(const Colorset *self, ByteStream *buffer); -bool Colorset_unserialize(Colorset *self, ByteStream *buffer); -bool Colorset_initPaletteInternal(Colorset *self, uint8_t numColors); + // fade all of the colors in the set + void adjustBrightness(uint8_t fadeby); + + // get a color from the colorset + RGBColor get(uint8_t index = 0) const; + + // set an rgb color in a slot, or add a new color if you specify + // a slot higher than the number of colors in the colorset + void set(uint8_t index, RGBColor col); + + // skip some amount of colors + void skip(int32_t amount = 1); + + // get current color in cycle + RGBColor cur(); + + // set the current index of the colorset + void setCurIndex(uint8_t index); + void resetIndex(); + + // the current index + uint8_t curIndex() const { return m_curIndex; } + + // get the prev color in cycle + RGBColor getPrev(); + + // get the next color in cycle + RGBColor getNext(); + + // peek at the color indexes from current but don't iterate + RGBColor peek(int32_t offset) const; + + // better wording for peek 1 ahead + RGBColor peekNext() const { return peek(1); } + + // the number of colors in the palette + uint8_t numColors() const { return m_numColors; } + + // whether the colorset is currently on the first color or last color + bool onStart() const; + bool onEnd() const; + + // serialize the colorset to save/load + bool serialize(ByteStream &buffer) const; + bool unserialize(ByteStream &buffer); + +private: + // pre-allocate the palette + bool initPalette(uint8_t numColors); + + // palette of colors + RGBColor m_palette[MAX_COLOR_SLOTS]; + // the current index, starts at UINT8_MAX so that + // the very first call to getNext will iterate to 0 + uint8_t m_curIndex; + // the actual number of colors in the set + uint8_t m_numColors; +}; #endif diff --git a/VortexEngine/src/Leds/LedStash.h b/VortexEngine/src/Leds/LedStash.h index 3ad38585f2..1089ca40c9 100644 --- a/VortexEngine/src/Leds/LedStash.h +++ b/VortexEngine/src/Leds/LedStash.h @@ -4,14 +4,22 @@ #include "../Colors/ColorTypes.h" #include "LedTypes.h" -typedef struct LedStash_s { - RGBColor ledColorsStash[LED_COUNT]; -} LedStash; +class LedStash +{ + friend class Leds; +public: + LedStash(); + ~LedStash(); -void LedStash_init(LedStash *self); -void LedStash_setIndex(LedStash *self, LedPos pos, RGBColor col); -void LedStash_clear(LedStash *self); -RGBColor* LedStash_get(LedStash *self, int index); -void LedStash_cleanup(LedStash *self); + void setIndex(LedPos pos, RGBColor col); -#endif + void clear(); + + // index operator to access color index + RGBColor &operator[](int index); + +private: + RGBColor m_ledColorsStash[LED_COUNT]; +}; + +#endif \ No newline at end of file diff --git a/VortexEngine/src/Leds/LedTypes.h b/VortexEngine/src/Leds/LedTypes.h index 8ae429dffc..953ffa4c99 100644 --- a/VortexEngine/src/Leds/LedTypes.h +++ b/VortexEngine/src/Leds/LedTypes.h @@ -3,36 +3,77 @@ #include #include -#include #include "../VortexConfig.h" -typedef uint8_t LedPos; - -enum { +// Defined the LED positions, their order, and index +enum LedPos : uint8_t +{ + // this should always be first LED_FIRST = 0, + + // LED constants for each led LED_0 = LED_FIRST, LED_1, + + // the number of entries above LED_COUNT, + + // the last LED index LED_LAST = (LED_COUNT - 1), + + // target all leds (multi and single) + // When fetching this the same as LED_ANY + // When setting this will set all of the leds LED_ALL = LED_COUNT, + + // target the multi led slot + // + // When fetching this will return the multi led slot + // When setting this will set the multi led slot LED_MULTI = (LED_COUNT + 1), + + // target all single led slots + // + // When fetching this will return the first single led slot + // When setting this will set all single led slots LED_ALL_SINGLE = (LED_COUNT + 2), + + // Target the 'effective' led slot (any slot) + // + // When fetching this will: + // 1. return the multi led slot if it exists + // 2. otherwise the first single led slot + // + // When setting this will: + // 1. if setting single led pattern will set all + // 2. if setting multi led pattern will set multi LED_ANY = (LED_COUNT + 3), + + // other customs? + // LED_EVENS = (LED_COUNT + 2), + // LED_ODDS = (LED_COUNT + 3), }; +// some helpers for microlight code #define LED_TIP LED_0 #define LED_TOP LED_1 -typedef uint8_t Pair; - -enum { +enum Pair : uint8_t +{ PAIR_FIRST = 0, + + // one pair for each pair of leds, adjust this to be 2x the LED_COUNT PAIR_0 = PAIR_FIRST, + PAIR_COUNT, PAIR_LAST = (PAIR_COUNT - 1), }; +// Compile-time check on the number of pairs and leds +static_assert(LED_COUNT == (PAIR_COUNT * 2), "Incorrect number of Pairs for Leds! Adjust the Led enum or Pair enum to match"); + +// backwards compat with bigger patterns #define LED_2 LED_0 #define LED_3 LED_1 #define LED_4 LED_0 @@ -47,26 +88,36 @@ enum { #define PAIR_3 PAIR_0 #define PAIR_4 PAIR_0 +// check if an led is even or odd #define isEven(pos) ((pos % 2) == 0) #define isOdd(pos) ((pos % 2) != 0) -#define pairEven(pair) ((LedPos)((uint32_t)pair * 2)) -#define pairOdd(pair) ((LedPos)(((uint32_t)pair * 2) + 1)) +// convert a pair to even or odd led position +#define pairEven(pair) (LedPos)((uint32_t)pair * 2) +#define pairOdd(pair) (LedPos)(((uint32_t)pair * 2) + 1) -#define ledToPair(pos) ((Pair)((uint32_t)pos / 2)) +// convert an led position to a pair +#define ledToPair(pos) (Pair)((uint32_t)pos / 2) +// LedMap is a bitmap of leds, used for expressing whether to turn certain leds on +// or off with a single integer typedef uint64_t LedMap; -#define MAP_LED(led) ((LedMap)((uint64_t)1 << led)) +// various macros for mapping leds to an LedMap +#define MAP_LED(led) (LedMap)((uint64_t)1 << led) #define MAP_PAIR_EVEN(pair) MAP_LED(pairEven(pair)) #define MAP_PAIR_ODD(pair) MAP_LED(pairOdd(pair)) #define MAP_PAIR(pair) (MAP_PAIR_EVEN(pair) | MAP_PAIR_ODD(pair)) +// check if a map is purely just 1 led or not #define MAP_IS_ONE_LED(map) (map && !(map & (map-1))) +// foreach led macro (only iterates singles) #define MAP_FOREACH_LED(map) for (LedPos pos = ledmapGetFirstLed(map); pos != LED_COUNT; pos = ledmapGetNextLed(map, pos)) -static inline LedPos ledmapGetFirstLed(LedMap map) { +// convert a map to the first Led position in the map +inline LedPos ledmapGetFirstLed(LedMap map) +{ if (map == MAP_LED(LED_MULTI)) { return LED_MULTI; } @@ -81,7 +132,9 @@ static inline LedPos ledmapGetFirstLed(LedMap map) { return LED_COUNT; } -static inline LedPos ledmapGetNextLed(LedMap map, LedPos pos) { +// given an led map and a position, find the next position in the map +inline LedPos ledmapGetNextLed(LedMap map, LedPos pos) +{ pos = (LedPos)(pos + 1); map >>= pos; while (map && pos < LED_COUNT) { @@ -94,41 +147,98 @@ static inline LedPos ledmapGetNextLed(LedMap map, LedPos pos) { return LED_COUNT; } +// bitmap of all pairs (basically LED_COUNT bits) #define MAP_LED_ALL ((2 << (LED_COUNT - 1)) - 1) + +// blank map #define MAP_LED_NONE 0 + #define MAP_INVERSE(map) ((~map) & MAP_LED_ALL) + +// macro for all evens and odds #define MAP_PAIR_EVENS (((1 << LED_COUNT) - 1) & 0x55555555) #define MAP_PAIR_ODDS (((1 << LED_COUNT) - 1) & 0xAAAAAAAA) + +// Some preset bitmaps for pair groupings #define MAP_PAIR_ODD_EVENS (MAP_PAIR_EVEN(PAIR_0) | MAP_PAIR_EVEN(PAIR_2) | MAP_PAIR_EVEN(PAIR_4)) #define MAP_PAIR_ODD_ODDS (MAP_PAIR_ODD(PAIR_0) | MAP_PAIR_ODD(PAIR_2) | MAP_PAIR_ODD(PAIR_4)) + #define MAP_PAIR_EVEN_EVENS (MAP_PAIR_EVEN(PAIR_3) | MAP_PAIR_EVEN(PAIR_1)) #define MAP_PAIR_EVEN_ODDS (MAP_PAIR_ODD(PAIR_3) | MAP_PAIR_ODD(PAIR_1)) -static inline void ledmapSetLed(LedMap *map, LedPos pos) { - if (pos < LED_COUNT) *map |= (1ull << pos); +// set a single led +inline void ledmapSetLed(LedMap &map, LedPos pos) +{ + if (pos < LED_COUNT) map |= (1ull << pos); } - -static inline void ledmapSetPair(LedMap *map, Pair pair) { +// set a single pair +inline void ledmapSetPair(LedMap &map, Pair pair) +{ ledmapSetLed(map, pairEven(pair)); ledmapSetLed(map, pairOdd(pair)); } -static inline bool ledmapCheckLed(LedMap map, LedPos pos) { +// check if an led is set in the map +inline bool ledmapCheckLed(LedMap map, LedPos pos) +{ return ((map & (1ull << pos)) != 0); } - -static inline bool ledmapCheckPair(LedMap map, Pair pair) { +// check if a pair is set in the map (both leds) +inline bool ledmapCheckPair(LedMap map, Pair pair) +{ return ledmapCheckLed(map, pairEven(pair)) && ledmapCheckLed(map, pairOdd(pair)); } -static inline LedPos ledPosIncr(LedPos c) { return (LedPos)((uint32_t)c + 1); } -static inline LedPos ledPosAdd(LedPos c, int b) { return (LedPos)((uint32_t)c + (uint32_t)b); } -static inline LedPos ledPosAddEq(LedPos *c, int b) { *c = (LedPos)((uint32_t)(*c) + (uint32_t)b); return *c; } -static inline LedPos ledPosSub(LedPos c, int b) { return (LedPos)((uint32_t)c - (uint32_t)b); } -static inline LedPos ledPosSubEq(LedPos *c, int b) { *c = (LedPos)((uint32_t)(*c) - (uint32_t)b); return *c; } +// LedPos operators +inline LedPos &operator++(LedPos &c) +{ + c = LedPos(((uint32_t)c) + 1); + return c; +} +inline LedPos operator++(LedPos &c, int) +{ + LedPos temp = c; + ++c; + return temp; +} +inline LedPos operator+(LedPos &c, int b) +{ + return (LedPos)((uint32_t)c + b); +} +inline LedPos &operator+=(LedPos &c, int b) +{ + c = LedPos(((uint32_t)c) + b); + return c; +} +inline LedPos operator-(LedPos &c, int b) +{ + return (LedPos)((uint32_t)c - b); +} +inline LedPos &operator-=(LedPos &c, int b) +{ + c = LedPos(((uint32_t)c) - b); + return c; +} -static inline Pair pairIncr(Pair c) { return (Pair)((uint32_t)c + 1); } -static inline Pair pairAdd(Pair c, int b) { return (Pair)((uint32_t)c + (uint32_t)b); } -static inline Pair pairSub(Pair c, int b) { return (Pair)((uint32_t)c - (uint32_t)b); } +// pair operators +inline Pair &operator++(Pair &c) +{ + c = Pair(((uint32_t)c) + 1); + return c; +} +inline Pair operator++(Pair &c, int) +{ + Pair temp = c; + ++c; + return temp; +} +inline Pair operator+(Pair &c, int b) +{ + return (Pair)((uint32_t)c + b); +} +inline Pair operator-(Pair &c, int b) +{ + return (Pair)((uint32_t)c - b); +} #endif diff --git a/VortexEngine/src/Leds/Leds.h b/VortexEngine/src/Leds/Leds.h index 00348ab582..bdcd94af9b 100644 --- a/VortexEngine/src/Leds/Leds.h +++ b/VortexEngine/src/Leds/Leds.h @@ -2,62 +2,134 @@ #define LED_CONTROL_H #include -#include + #include "../Colors/ColorTypes.h" #include "LedTypes.h" -typedef struct LedStash_s LedStash; - -bool Leds_init(void); -void Leds_cleanup(void); -void Leds_setIndex(LedPos target, RGBColor col); -void Leds_setRange(LedPos first, LedPos last, RGBColor col); -void Leds_setAll(RGBColor col); -void Leds_clearIndex(LedPos target); -void Leds_clearRange(LedPos first, LedPos last); -void Leds_clearAll(void); -void Leds_setPair(Pair pair, RGBColor col); -void Leds_setPairs(Pair first, Pair last, RGBColor col); -void Leds_clearPair(Pair pair); -void Leds_clearPairs(Pair first, Pair last); -void Leds_setRangeEvens(Pair first, Pair last, RGBColor col); -void Leds_setAllEvens(RGBColor col); -void Leds_setRangeOdds(Pair first, Pair last, RGBColor col); -void Leds_setAllOdds(RGBColor col); -void Leds_clearRangeEvens(Pair first, Pair last); -void Leds_clearAllEvens(void); -void Leds_clearRangeOdds(Pair first, Pair last); -void Leds_clearAllOdds(void); -void Leds_setMap(LedMap map, RGBColor col); -void Leds_clearMap(LedMap map); -void Leds_stashAll(LedStash *stash); -void Leds_restoreAll(const LedStash *stash); -void Leds_adjustBrightnessIndex(LedPos target, uint8_t fadeBy); -void Leds_adjustBrightnessRange(LedPos first, LedPos last, uint8_t fadeBy); -void Leds_adjustBrightnessAll(uint8_t fadeBy); -void Leds_blinkIndexOffset(LedPos target, uint32_t time, uint16_t offMs, uint16_t onMs, RGBColor col); -void Leds_blinkRangeOffset(LedPos first, LedPos last, uint32_t time, uint16_t offMs, uint16_t onMs, RGBColor col); -void Leds_blinkIndex(LedPos target, uint16_t offMs, uint16_t onMs, RGBColor col); -void Leds_blinkRange(LedPos first, LedPos last, uint16_t offMs, uint16_t onMs, RGBColor col); -void Leds_blinkMap(LedMap targets, uint16_t offMs, uint16_t onMs, RGBColor col); -void Leds_blinkAll(uint16_t offMs, uint16_t onMs, RGBColor col); -void Leds_blinkPair(Pair pair, uint16_t offMs, uint16_t onMs, RGBColor col); -void Leds_blinkPairs(Pair first, Pair last, uint16_t offMs, uint16_t onMs, RGBColor col); -void Leds_breatheIndex(LedPos target, uint8_t hue, uint32_t variance, uint32_t magnitude, uint8_t sat, uint8_t val); -void Leds_breatheRange(LedPos first, LedPos last, uint8_t hue, uint32_t variance, uint32_t magnitude, uint8_t sat, uint8_t val); -void Leds_breatheIndexSat(LedPos target, uint8_t hue, uint32_t variance, uint32_t magnitude, uint8_t sat, uint8_t val); -void Leds_breatheIndexVal(LedPos target, uint8_t hue, uint32_t variance, uint32_t magnitude, uint8_t sat, uint8_t val); -void Leds_holdAll(RGBColor col); -RGBColor Leds_getLed(LedPos pos); -uint8_t Leds_getBrightness(void); -void Leds_setBrightness(uint8_t brightness); -void Leds_update(void); - -extern uint8_t Leds_brightness; -extern RGBColor Leds_ledColors[LED_COUNT]; -extern volatile uint8_t *Leds_port; -extern uint8_t Leds_pinMask; - -RGBColor* Leds_led(LedPos pos); +class LedStash; + +class Leds +{ + // private unimplemented constructor + Leds(); + +public: + // opting for static class here because there should only ever be one + // Led control object and I don't like singletons + static bool init(); + static void cleanup(); + + // control individual LED, these are appropriate to use in internal pattern logic + static void setIndex(LedPos target, RGBColor col); + static void setRange(LedPos first, LedPos last, RGBColor col); + static void setAll(RGBColor col); + + // Turn off individual LEDs, these are appropriate to use in internal pattern logic + static void clearIndex(LedPos target) { setIndex(target, RGB_OFF); } + static void clearRange(LedPos first, LedPos last) { setRange(first, last, RGB_OFF); } + static void clearAll() { setAll(RGB_OFF); } + + // control two LEDs on a pair, these are appropriate for use in internal pattern logic + static void setPair(Pair pair, RGBColor col); + static void setPairs(Pair first, Pair last, RGBColor col); + + // Turn off both LEDs on a pair, these are appropriate for use in internal pattern logic + static void clearPair(Pair pair) { setPair(pair, RGB_OFF); } + static void clearPairs(Pair first, Pair last) { setPairs(first, last, RGB_OFF); } + + // Controll pair evens + static void setRangeEvens(Pair first, Pair last, RGBColor); + static void setAllEvens(RGBColor col); + // Controll pair odds + static void setRangeOdds(Pair first, Pair last, RGBColor); + static void setAllOdds(RGBColor col); + + // Turn off tips + static void clearRangeEvens(Pair first, Pair last); + static void clearAllEvens(); + // Turn off tops + static void clearRangeOdds(Pair first, Pair last); + static void clearAllOdds(); + + // Turn on/off a mapping of leds with a color + static void setMap(LedMap map, RGBColor col); + static void clearMap(LedMap map); + + // stores Led for later use + static void stashAll(LedStash &stash); + + // restores Leds from stash + static void restoreAll(const LedStash &stash); + + // Dim individual LEDs, these are appropriate to use in internal pattern logic + static void adjustBrightnessIndex(LedPos target, uint8_t fadeBy); + static void adjustBrightnessRange(LedPos first, LedPos last, uint8_t fadeBy); + static void adjustBrightnessAll(uint8_t fadeBy); + + // Blink an led to blank or a color + // + // These APIs work by checking if the current time is within the + // 'on duration' then they apply the color to the target, otherwise they do + // nothing. The goal of this behaviour is to allow them to be used in any + // situation to 'blink' an led to either off or some color. + // + // However since these APIs modulate current time to check if within the 'on' + // threshold that makes them unsuitable for internal pattern usage because it + // is unpredictable whether they will blink on or off first + static void blinkIndexOffset(LedPos target, uint32_t time, uint16_t offMs = 250, uint16_t onMs = 500, RGBColor col = RGB_OFF); + static void blinkRangeOffset(LedPos first, LedPos last, uint32_t time, uint16_t offMs = 250, uint16_t onMs = 500, RGBColor col = RGB_OFF); + static void blinkIndex(LedPos target, uint16_t offMs = 250, uint16_t onMs = 500, RGBColor col = RGB_OFF); + static void blinkRange(LedPos first, LedPos last, uint16_t offMs = 250, uint16_t onMs = 500, RGBColor col = RGB_OFF); + static void blinkMap(LedMap targets, uint16_t offMs = 250, uint16_t onMs = 500, RGBColor col = RGB_OFF); + static void blinkAll(uint16_t offMs = 250, uint16_t onMs = 500, RGBColor col = RGB_OFF); + // Blink both LEDs on a pair + static void blinkPair(Pair pair, uint16_t offMs = 250, uint16_t onMs = 500, RGBColor col = RGB_OFF); + static void blinkPairs(Pair first, Pair last, uint16_t offMs = 250, uint16_t onMs = 500, RGBColor col = RGB_OFF); + + // breath the hue on an index + // warning: these use hsv to rgb in realtime! + static void breatheIndex(LedPos target, uint8_t hue, uint32_t variance, + uint32_t magnitude = 15, uint8_t sat = 255, uint8_t val = 210); + static void breatheRange(LedPos first, LedPos last, uint8_t hue, uint32_t variance, + uint32_t magnitude = 15, uint8_t sat = 255, uint8_t val = 210); + static void breatheIndexSat(LedPos target, uint8_t hue, uint32_t variance, + uint32_t magnitude = 15, uint8_t sat = 255, uint8_t val = 210); + static void breatheIndexVal(LedPos target, uint8_t hue, uint32_t variance, + uint32_t magnitude = 15, uint8_t sat = 255, uint8_t val = 210); + + // a very specialized api to hold all leds on a color for 250ms + static void holdAll(RGBColor col); + + // get the RGBColor of an Led index + static RGBColor getLed(LedPos pos) { return led(pos); } + + // global brightness + static uint8_t getBrightness() { return m_brightness; } + static void setBrightness(uint8_t brightness) { m_brightness = brightness; } + + // actually update the LEDs and show the changes + static void update(); + +private: + // accessor for led colors, use this for all access to allow for mapping + static inline RGBColor &led(LedPos pos) + { + if (pos > LED_LAST) { + pos = LED_LAST; + } + return m_ledColors[pos]; + } + + // the global brightness + static uint8_t m_brightness; + + // array of led color values + static RGBColor m_ledColors[LED_COUNT]; + + // Output PORT register + static volatile uint8_t *m_port; + // Output PORT bitmask + static uint8_t m_pinMask; +}; #endif diff --git a/VortexEngine/src/Log/Log.h b/VortexEngine/src/Log/Log.h index fc09332be4..52bb5f7786 100644 --- a/VortexEngine/src/Log/Log.h +++ b/VortexEngine/src/Log/Log.h @@ -8,6 +8,7 @@ #if LOGGING_LEVEL > 0 #define INFO_LOG(msg) InfoMsg(msg) #define INFO_LOGF(msg, ...) InfoMsg(msg, __VA_ARGS__) +// infos are in final builds and they are meant to be standalone messages void InfoMsg(const char *msg, ...); #else #define INFO_LOG(msg) @@ -15,8 +16,10 @@ void InfoMsg(const char *msg, ...); #endif #if LOGGING_LEVEL > 1 +// in test build errors are just debug messages #define ERROR_LOG(msg) ErrorMsg(__FUNCTION__, msg) #define ERROR_LOGF(msg, ...) ErrorMsg(__FUNCTION__, msg, __VA_ARGS__) +// errors are in final builds so they only have the function name void ErrorMsg(const char *func, const char *msg, ...); #else #define ERROR_LOG(msg) @@ -24,14 +27,17 @@ void ErrorMsg(const char *func, const char *msg, ...); #endif #if LOGGING_LEVEL > 2 +// some compilers won't allow for ellipsis macro that's passed no args... #define DEBUG_LOG(msg) DebugMsg(__FILE__, __FUNCTION__, __LINE__, msg) #define DEBUG_LOGF(msg, ...) DebugMsg(__FILE__, __FUNCTION__, __LINE__, msg, __VA_ARGS__) +// debug messages are only in debug builds so they have full file + func + line info void DebugMsg(const char *file, const char *func, int line, const char *msg, ...); #else #define DEBUG_LOG(msg) #define DEBUG_LOGF(msg, ...) #endif +// report OOM #define ERROR_OUT_OF_MEMORY() ERROR_LOG("Out of memory") #endif diff --git a/VortexEngine/src/Memory/Memory.h b/VortexEngine/src/Memory/Memory.h index f0646838c2..b851d781ec 100644 --- a/VortexEngine/src/Memory/Memory.h +++ b/VortexEngine/src/Memory/Memory.h @@ -8,21 +8,26 @@ #if DEBUG_ALLOCATIONS == 1 +// monitored allocation routines #define vmalloc(size) _vmalloc(size) #define vcalloc(size, amount) _vcalloc(size, amount) #define vrealloc(ptr, size) _vrealloc(ptr, size) #define vfree(ptr) _vfree(ptr) +// Vortex allocation functions void *_vmalloc(uint32_t size); void *_vcalloc(uint32_t size, uint32_t amount); void *_vrealloc(void *ptr, uint32_t size); void _vfree(void *ptr); +// memory used by regular code uint32_t cur_memory_usage(); +// memory used by the memory tracker itself uint32_t cur_memory_usage_background(); +// memory used by everything uint32_t cur_memory_usage_total(); -#else +#else // if DEBUG_ALLOCATIONS #define vmalloc(size) malloc(size) #define vcalloc(size, amount) calloc(size, amount) @@ -31,4 +36,21 @@ uint32_t cur_memory_usage_total(); #endif +#ifndef VORTEX_LIB +void *operator new (size_t size); +void *operator new[](size_t size); +void operator delete (void *ptr); +void operator delete[](void *ptr); +void *operator new (size_t size, void *ptr) noexcept; +void *operator new[](size_t size, void *ptr) noexcept; +void operator delete (void *ptr, size_t size) noexcept; +void operator delete[](void *ptr, size_t size) noexcept; +//void *operator new (size_t size, std::align_val_t al); +//void *operator new[](size_t size, std::align_val_t al); +//void operator delete (void *ptr, std::align_val_t al) noexcept; +//void operator delete[](void *ptr, std::align_val_t al) noexcept; +//void operator delete (void *ptr, size_t size, std::align_val_t al) noexcept; +//void operator delete[](void *ptr, size_t size, std::align_val_t al) noexcept; +#endif + #endif diff --git a/VortexEngine/src/Menus/Menu.h b/VortexEngine/src/Menus/Menu.h index 374361cc28..997a46ee9d 100644 --- a/VortexEngine/src/Menus/Menu.h +++ b/VortexEngine/src/Menus/Menu.h @@ -2,56 +2,72 @@ #define MENU_H #include -#include #include "../Colors/ColorTypes.h" #include "../Leds/LedTypes.h" #include "../Modes/Mode.h" -typedef uint8_t MenuAction; -enum { - MENU_QUIT, - MENU_CONTINUE, - MENU_SKIP -}; +class Menu +{ +public: + Menu(const RGBColor &col, bool advanced); + virtual ~Menu(); -typedef struct Menu Menu; -typedef struct MenuVTable { - void (*destroy)(Menu *self); - bool (*init)(Menu *self); - MenuAction (*run)(Menu *self); - void (*onLedSelected)(Menu *self); - void (*onShortClick)(Menu *self); - void (*onLongClick)(Menu *self); - void (*leaveMenu)(Menu *self, bool doSave); -} MenuVTable; - -struct Menu { - const MenuVTable *vtable; - Mode previewMode; - RGBColor menuColor; - LedMap targetLeds; - uint8_t curSelection; - bool ledSelected; - bool advanced; - bool shouldClose; -}; + virtual bool init(); + + // the action for the menu to execute + enum MenuAction : uint8_t { + // quit the menus + MENU_QUIT, + // continue running the menu + MENU_CONTINUE, + // don't run derived menu code, this is used internally + // by the menu class itself + MENU_SKIP + }; + virtual MenuAction run(); + + // optional handler for initializing data after user selected led + virtual void onLedSelected(); + + // optional handlers for clicks + virtual void onShortClick(); + virtual void onLongClick(); -void Menu_construct(Menu *self, RGBColor col, bool advanced); + // close the current menu + virtual void leaveMenu(bool doSave = false); -void Menu_destroy(Menu *self); -bool Menu_init(Menu *self); -MenuAction Menu_run(Menu *self); -void Menu_onLedSelected(Menu *self); -void Menu_onShortClick(Menu *self); -void Menu_onLongClick(Menu *self); -void Menu_leaveMenu(Menu *self, bool doSave); +protected: + void showBulbSelection(); + void showExit(); -void Menu_showBulbSelection(Menu *self); -void Menu_showExit(Menu *self); -void Menu_nextBulbSelection(Menu *self); -void Menu_bypassLedSelection(Menu *self, LedMap map); + // iterate to next bulb selection + void nextBulbSelection(); -extern const MenuVTable g_menuVTable; + // skip led selection + void bypassLedSelection(LedMap map); + + // the mode copied from the current mode used to preview changes + Mode m_previewMode; + // the color of this menu + RGBColor m_menuColor; + // tracks the targetted leds for this menu + // note this is an led map + LedMap m_targetLeds; + // all menus have a 'current selection' + uint8_t m_curSelection; + // true once a an led is selected + bool m_ledSelected; + // whether advanced menu was activated + bool m_advanced; + +private: + // internal flag to close the menu + bool m_shouldClose; + +#ifdef VORTEX_LIB + friend class Vortex; +#endif +}; #endif diff --git a/VortexEngine/src/Menus/MenuList/ColorSelect.h b/VortexEngine/src/Menus/MenuList/ColorSelect.h index 7ecae65bf9..e0f0ed496a 100644 --- a/VortexEngine/src/Menus/MenuList/ColorSelect.h +++ b/VortexEngine/src/Menus/MenuList/ColorSelect.h @@ -5,32 +5,67 @@ #include "../../Colors/Colorset.h" -typedef enum { - COLOR_SELECT_STATE_INIT, - COLOR_SELECT_STATE_PICK_SLOT, - COLOR_SELECT_STATE_PICK_HUE1, - COLOR_SELECT_STATE_PICK_HUE2, - COLOR_SELECT_STATE_PICK_SAT, - COLOR_SELECT_STATE_PICK_VAL -} ColorSelectState; - -typedef struct ColorSelectMenu_s { - Menu base; - ColorSelectState state; - HSVColor newColor; - Colorset colorset; - uint8_t targetSlot; - uint8_t targetHue1; -} ColorSelectMenu; - -Menu *ColorSelectMenu_Create(RGBColor col, bool advanced); -void ColorSelectMenu_destroy(Menu *self); -bool ColorSelectMenu_init(Menu *self); -MenuAction ColorSelectMenu_run(Menu *self); -void ColorSelectMenu_onLedSelected(Menu *self); -void ColorSelectMenu_onShortClick(Menu *self); -void ColorSelectMenu_onLongClick(Menu *self); - -extern const MenuVTable g_colorSelectMenuVTable; +class ColorSelect : public Menu +{ +public: + ColorSelect(const RGBColor &col, bool advanced); + ~ColorSelect(); + + bool init() override; + MenuAction run() override; + + // callback after the user selects the target led + void onLedSelected() override; + + // handlers for clicks + void onShortClick() override; + void onLongClick() override; + +private: + // private enumeration for internal state of color selection + enum ColorSelectState : uint8_t + { + STATE_INIT, + STATE_PICK_SLOT, + STATE_PICK_HUE1, + STATE_PICK_HUE2, + STATE_PICK_SAT, + STATE_PICK_VAL + }; + + void showSlotSelection(); + void showSelection(ColorSelectState mode); + void showFullSet(uint8_t offMs, uint8_t onMs); + + // the options for saturations + const uint8_t sats[4] = { + SAT_OPTION_4, + SAT_OPTION_3, + SAT_OPTION_2, + SAT_OPTION_1 + }; + + // the options for values + const uint8_t vals[4] = { + VAL_OPTION_4, + VAL_OPTION_3, + VAL_OPTION_2, + VAL_OPTION_1 + }; + + // the state of the color select menu + ColorSelectState m_state; + // the new color being built via hue, sat then val + HSVColor m_newColor; + // A copy of the colorset being changed + Colorset m_colorset; + + // below are the selection indexes for each level + + // the target values selected at each level to build the color, the value + // selected at the last level isn't stored because you can't go back after + uint8_t m_targetSlot; + uint8_t m_targetHue1; +}; #endif diff --git a/VortexEngine/src/Menus/MenuList/EditorConnection.h b/VortexEngine/src/Menus/MenuList/EditorConnection.h index 87382d819c..fa499db7fe 100644 --- a/VortexEngine/src/Menus/MenuList/EditorConnection.h +++ b/VortexEngine/src/Menus/MenuList/EditorConnection.h @@ -6,66 +6,122 @@ #include "../../Serial/ByteStream.h" #include "../../Modes/Mode.h" -typedef enum { - STATE_DISCONNECTED, - STATE_GREETING, - STATE_IDLE, - STATE_PULL_MODES, - STATE_PULL_MODES_SEND, - STATE_PULL_MODES_DONE, - STATE_PUSH_MODES, - STATE_PUSH_MODES_RECEIVE, - STATE_PUSH_MODES_DONE, - STATE_DEMO_MODE, - STATE_DEMO_MODE_RECEIVE, - STATE_DEMO_MODE_DONE, - STATE_CLEAR_DEMO, - STATE_TRANSMIT_MODE_VL, - STATE_TRANSMIT_MODE_VL_DONE, - STATE_PULL_EACH_MODE, - STATE_PULL_EACH_MODE_COUNT, - STATE_PULL_EACH_MODE_SEND, - STATE_PULL_EACH_MODE_WAIT, - STATE_PULL_EACH_MODE_DONE, - STATE_PUSH_EACH_MODE, - STATE_PUSH_EACH_MODE_COUNT, - STATE_PUSH_EACH_MODE_RECEIVE, - STATE_PUSH_EACH_MODE_WAIT, - STATE_PUSH_EACH_MODE_DONE, - STATE_SET_GLOBAL_BRIGHTNESS, - STATE_SET_GLOBAL_BRIGHTNESS_RECEIVE, - STATE_GET_GLOBAL_BRIGHTNESS, -} EditorConnectionState; - -typedef struct EditorConnectionCommandState_s { - const char *cmd; - EditorConnectionState cmdState; -} EditorConnectionCommandState; - -typedef enum { +// various return codes that internal functions use +enum ReturnCode +{ RV_FAIL = 0, RV_OK, RV_WAIT, -} ReturnCode; - -typedef struct EditorConnectionMenu_s { - Menu base; - EditorConnectionState state; - ByteStream receiveBuffer; - bool allowReset; - uint8_t previousModeIndex; - uint8_t numModesToReceive; - ReturnCode rv; -} EditorConnectionMenu; - -Menu *EditorConnectionMenu_Create(RGBColor col, bool advanced); -void EditorConnectionMenu_destroy(Menu *self); -bool EditorConnectionMenu_init(Menu *self); -MenuAction EditorConnectionMenu_run(Menu *self); -void EditorConnectionMenu_onShortClick(Menu *self); -void EditorConnectionMenu_onLongClick(Menu *self); -void EditorConnectionMenu_leaveMenu(Menu *self, bool doSave); - -extern const MenuVTable g_editorConnectionMenuVTable; +}; + +class EditorConnection : public Menu +{ +public: + EditorConnection(const RGBColor &col, bool advanced); + ~EditorConnection(); + + bool init() override; + MenuAction run() override; + + // handlers for clicks + void onShortClick() override; + void onLongClick() override; + + void leaveMenu(bool doSave = false) override; + +private: + void clearDemo(); + void handleErrors(); + void handleCommand(); + void handleState(); + void showEditor(); + void receiveData(); + void sendModes(); + void sendModeCount(); + void sendCurMode(); + void sendCurModeVL(); + ReturnCode sendBrightness(); + ReturnCode receiveBuffer(ByteStream &buffer); + ReturnCode receiveModes(); + ReturnCode receiveModeCount(); + ReturnCode receiveMode(); + ReturnCode receiveDemoMode(); + ReturnCode receiveMessage(const char *message); + ReturnCode receiveBrightness(); + + enum EditorConnectionState { + // the editor is not connected + STATE_DISCONNECTED, + + // Sending the greeting message + STATE_GREETING, + + // entirely idle, waiting for commands + STATE_IDLE, + + // editor pulls the modes from device, then wait for the sent modes ack + STATE_PULL_MODES, + STATE_PULL_MODES_SEND, + STATE_PULL_MODES_DONE, + + // editor pushes modes to device, then waits for done + STATE_PUSH_MODES, + STATE_PUSH_MODES_RECEIVE, + STATE_PUSH_MODES_DONE, + + // editor pushes mode to device for demo while idle + STATE_DEMO_MODE, + STATE_DEMO_MODE_RECEIVE, + STATE_DEMO_MODE_DONE, + + // editor tells device to clear the demo preview, device acknowledge + STATE_CLEAR_DEMO, + + // transmit the mode over visible light + STATE_TRANSMIT_MODE_VL, + STATE_TRANSMIT_MODE_VL_DONE, + + // editor pulls the modes from device (safer version) + STATE_PULL_EACH_MODE, + STATE_PULL_EACH_MODE_COUNT, + STATE_PULL_EACH_MODE_SEND, + STATE_PULL_EACH_MODE_WAIT, + STATE_PULL_EACH_MODE_DONE, + + // editor pushes modes to device (safer version) + STATE_PUSH_EACH_MODE, + STATE_PUSH_EACH_MODE_COUNT, + STATE_PUSH_EACH_MODE_RECEIVE, + STATE_PUSH_EACH_MODE_WAIT, + STATE_PUSH_EACH_MODE_DONE, + + // set global brightness + STATE_SET_GLOBAL_BRIGHTNESS, + STATE_SET_GLOBAL_BRIGHTNESS_RECEIVE, + + // get global brightness + STATE_GET_GLOBAL_BRIGHTNESS, + }; + + struct CommandState + { + const char *cmd; + EditorConnection::EditorConnectionState cmdState; + }; + static const CommandState commands[]; + + // state of the editor + EditorConnectionState m_state; + // the data that is received + ByteStream m_receiveBuffer; + // Whether at least one command has been received yet + bool m_allowReset; + // the mode index to return to after iterating the modes to send them + uint8_t m_previousModeIndex; + // the number of modes that should be received + uint8_t m_numModesToReceive; + // internal return value tracker + ReturnCode m_rv; +}; #endif diff --git a/VortexEngine/src/Menus/MenuList/FactoryReset.h b/VortexEngine/src/Menus/MenuList/FactoryReset.h index 31d4e6caea..e1fd64414e 100644 --- a/VortexEngine/src/Menus/MenuList/FactoryReset.h +++ b/VortexEngine/src/Menus/MenuList/FactoryReset.h @@ -3,17 +3,21 @@ #include "../Menu.h" -typedef struct FactoryResetMenu_s { - Menu base; -} FactoryResetMenu; +class FactoryReset : public Menu +{ +public: + FactoryReset(const RGBColor &col, bool advanced); + ~FactoryReset(); -Menu *FactoryResetMenu_Create(RGBColor col, bool advanced); -void FactoryResetMenu_destroy(Menu *self); -bool FactoryResetMenu_init(Menu *self); -MenuAction FactoryResetMenu_run(Menu *self); -void FactoryResetMenu_onShortClick(Menu *self); -void FactoryResetMenu_onLongClick(Menu *self); + bool init() override; + MenuAction run() override; -extern const MenuVTable g_factoryResetMenuVTable; + // handlers for clicks + void onShortClick() override; + void onLongClick() override; + +private: + void showReset(); +}; #endif diff --git a/VortexEngine/src/Menus/MenuList/GlobalBrightness.h b/VortexEngine/src/Menus/MenuList/GlobalBrightness.h index 7954efef5b..5efabf408a 100644 --- a/VortexEngine/src/Menus/MenuList/GlobalBrightness.h +++ b/VortexEngine/src/Menus/MenuList/GlobalBrightness.h @@ -3,28 +3,56 @@ #include "../Menu.h" -typedef enum { - KEYCHAIN_MODE_STATE_OFF = 0, - KEYCHAIN_MODE_STATE_SOLID, - KEYCHAIN_MODE_STATE_DOPS, - KEYCHAIN_MODE_STATE_SIGNAL, - KEYCHAIN_MODE_STATE_COUNT -} KeychainModeState; - -typedef struct GlobalBrightnessMenu_s { - Menu base; - KeychainModeState keychainModeState; - uint32_t lastStateChange; - uint8_t colorIndex; -} GlobalBrightnessMenu; - -Menu *GlobalBrightnessMenu_Create(RGBColor col, bool advanced); -void GlobalBrightnessMenu_destroy(Menu *self); -bool GlobalBrightnessMenu_init(Menu *self); -MenuAction GlobalBrightnessMenu_run(Menu *self); -void GlobalBrightnessMenu_onShortClick(Menu *self); -void GlobalBrightnessMenu_onLongClick(Menu *self); - -extern const MenuVTable g_globalBrightnessMenuVTable; +class GlobalBrightness : public Menu +{ +public: + GlobalBrightness(const RGBColor &col, bool advanced); + ~GlobalBrightness(); + + bool init() override; + MenuAction run() override; + + // handlers for clicks + void onShortClick() override; + void onLongClick() override; + +private: + void showBrightnessSelection(); + + // the list of brightness options + const uint8_t m_brightnessOptions[4] = { + BRIGHTNESS_OPTION_1, + BRIGHTNESS_OPTION_2, + BRIGHTNESS_OPTION_3, + BRIGHTNESS_OPTION_4 + }; + +private: + // don't worry about this stuff + enum keychain_mode_state : uint8_t + { + // sleeping / fake off + KEYCHAIN_MODE_STATE_OFF = 0, + + // solid/tracer + KEYCHAIN_MODE_STATE_SOLID, + + // dops blink 4 / 16 + KEYCHAIN_MODE_STATE_DOPS, + + // signal blink 16 / 120 + KEYCHAIN_MODE_STATE_SIGNAL, + + // total states + KEYCHAIN_MODE_STATE_COUNT + }; + + keychain_mode_state m_keychain_modeState; + uint32_t m_lastStateChange; + uint8_t m_colorIndex; + + void setKeychainModeState(keychain_mode_state newState); + Menu::MenuAction runKeychainMode(); +}; #endif diff --git a/VortexEngine/src/Menus/MenuList/ModeSharing.h b/VortexEngine/src/Menus/MenuList/ModeSharing.h index b672ff0b58..826b08fa06 100644 --- a/VortexEngine/src/Menus/MenuList/ModeSharing.h +++ b/VortexEngine/src/Menus/MenuList/ModeSharing.h @@ -3,28 +3,42 @@ #include "../Menu.h" -typedef enum { - SHARE_SEND_RECEIVE, - SHARE_SEND_RECEIVE_LEGACY, - SHARE_EXIT, -} ModeShareState; - -typedef struct ModeSharingMenu_s { - Menu base; - ModeShareState sharingMode; - uint32_t timeOutStartTime; - uint32_t lastPercentChange; - uint8_t lastPercent; -} ModeSharingMenu; - -Menu *ModeSharingMenu_Create(RGBColor col, bool advanced); -void ModeSharingMenu_destroy(Menu *self); -bool ModeSharingMenu_init(Menu *self); -MenuAction ModeSharingMenu_run(Menu *self); -void ModeSharingMenu_onLedSelected(Menu *self); -void ModeSharingMenu_onShortClick(Menu *self); -void ModeSharingMenu_onLongClick(Menu *self); - -extern const MenuVTable g_modeSharingMenuVTable; +class ModeSharing : public Menu +{ +public: + ModeSharing(const RGBColor &col, bool advanced); + ~ModeSharing(); + + bool init() override; + MenuAction run() override; + + // callback after the user selects the target led + void onLedSelected() override; + + // handlers for clicks + void onShortClick() override; + void onLongClick() override; + +private: + void receiveMode(); + void showReceiveMode(); + + enum class ModeShareState { + // these three are the main options that can be iterated through + // and if the button is held it will send, otherwise receive + SHARE_SEND_RECEIVE, // send/receive mode + SHARE_SEND_RECEIVE_LEGACY, // send/receive mode legacy + SHARE_EXIT, // exit mode sharing + }; + + ModeShareState m_sharingMode; + + // the start time when checking for timing out + uint32_t m_timeOutStartTime; + + // used to track when the receive percentage changes for timeout purposes + uint32_t m_lastPercentChange; + uint8_t m_lastPercent; +}; #endif diff --git a/VortexEngine/src/Menus/MenuList/PatternSelect.h b/VortexEngine/src/Menus/MenuList/PatternSelect.h index d1931a1f3d..e825f56b54 100644 --- a/VortexEngine/src/Menus/MenuList/PatternSelect.h +++ b/VortexEngine/src/Menus/MenuList/PatternSelect.h @@ -6,20 +6,29 @@ #include "../../Colors/Colorset.h" #include "../../Modes/Mode.h" -typedef struct PatternSelectMenu_s { - Menu base; - LedPos srcLed; - bool started; -} PatternSelectMenu; - -Menu *PatternSelectMenu_Create(RGBColor col, bool advanced); -void PatternSelectMenu_destroy(Menu *self); -bool PatternSelectMenu_init(Menu *self); -MenuAction PatternSelectMenu_run(Menu *self); -void PatternSelectMenu_onLedSelected(Menu *self); -void PatternSelectMenu_onShortClick(Menu *self); -void PatternSelectMenu_onLongClick(Menu *self); - -extern const MenuVTable g_patternSelectMenuVTable; +class PatternSelect : public Menu +{ +public: + PatternSelect(const RGBColor &col, bool advanced); + ~PatternSelect(); + + bool init() override; + MenuAction run() override; + + // callback after the user selects the target led + void onLedSelected() override; + + // handlers for clicks + void onShortClick() override; + void onLongClick() override; + +private: + // helpful member + LedPos m_srcLed; + + // the pat select starts by showing the current pattern + // then the first click begin cycling the list of pats + bool m_started; +}; #endif diff --git a/VortexEngine/src/Menus/MenuList/Randomizer.h b/VortexEngine/src/Menus/MenuList/Randomizer.h index 61efa2a04b..0575ab132f 100644 --- a/VortexEngine/src/Menus/MenuList/Randomizer.h +++ b/VortexEngine/src/Menus/MenuList/Randomizer.h @@ -6,35 +6,81 @@ #include "../../Random/Random.h" #include "../../Modes/Mode.h" -typedef struct Mode Mode; - -typedef enum { - RANDOMIZE_NONE = 0, - RANDOMIZE_COLORSET = (1 << 0), - RANDOMIZE_PATTERN = (1 << 1), - RANDOMIZE_BOTH = (RANDOMIZE_COLORSET | RANDOMIZE_PATTERN) -} RandomizeFlags; - -typedef struct RandomizerMenu_s { - Menu base; - Random singlesRandCtx[LED_COUNT]; +class Mode; + +class Randomizer : public Menu +{ +public: + Randomizer(const RGBColor &col, bool advanced); + ~Randomizer(); + + bool init() override; + MenuAction run() override; + + // handlers for clicks + void onShortClick() override; + void onLongClick() override; + + // re-roll a new randomization with a given context on an led + bool reRoll(); + +private: + // random context for each single led + Random m_singlesRandCtx[LED_COUNT]; #if VORTEX_SLIM == 0 - Random multiRandCtx; + // random context for the multi led position + Random m_multiRandCtx; #endif - uint32_t lastRandomization; - uint8_t flags; - uint8_t displayHue; - bool needToSelect; - bool autoCycle; -} RandomizerMenu; - -Menu *RandomizerMenu_Create(RGBColor col, bool advanced); -void RandomizerMenu_destroy(Menu *self); -bool RandomizerMenu_init(Menu *self); -MenuAction RandomizerMenu_run(Menu *self); -void RandomizerMenu_onShortClick(Menu *self); -void RandomizerMenu_onLongClick(Menu *self); - -extern const MenuVTable g_randomizerMenuVTable; + + // the time of the last randomization + uint32_t m_lastRandomization; + + enum RandomizeFlags : uint8_t { + // this isn't a valid randomization state, if the flags are + // on this state then the user will be prompted to pick + RANDOMIZE_NONE = 0, + + // the two main kinds of randomization + RANDOMIZE_COLORSET = (1 << 0), + RANDOMIZE_PATTERN = (1 << 1), + + // compound flags both colorset and pattern randomization + RANDOMIZE_BOTH = (RANDOMIZE_COLORSET | RANDOMIZE_PATTERN) + }; + + // the randomization flags above + uint8_t m_flags; + + // auxilliary variable to display cycling hue at menus + uint8_t m_displayHue; + + // whether still need to select a randomization type + bool m_needToSelect; + // whether auto cycling + bool m_autoCycle; + + // show the randomization type selection + void showRandomizationSelect(); + + // main reRoll functions +#if VORTEX_SLIM == 0 + bool reRollMulti(); + PatternID rollMultiLedPatternID(Random &ctx); +#endif + bool reRollSingles(); + PatternID rollSingleLedPatternID(Random &ctx); + + // generate a random colorset with a random context + Colorset rollColorset(Random &ctx); + + // roll a custom pattern by generating random arguments + bool rollCustomPattern(Random &ctx, Mode *pMode, LedPos pos); + + // more specific random pattern generators that just generate patternargs + void traditionalPattern(Random &ctx, PatternArgs &outArgs); + void gapPattern(Random &ctx, PatternArgs &outArgs); + void dashPattern(Random &ctx, PatternArgs &outArgs); + void crushPattern(Random &ctx, PatternArgs &outArgs); +}; #endif diff --git a/VortexEngine/src/Menus/Menus.h b/VortexEngine/src/Menus/Menus.h index 413cd561a0..d352a69cf9 100644 --- a/VortexEngine/src/Menus/Menus.h +++ b/VortexEngine/src/Menus/Menus.h @@ -1,8 +1,7 @@ -#ifndef MENUS_H -#define MENUS_H +#ifndef RING_MENU_H +#define RING_MENU_H #include -#include #include "../Colors/ColorTypes.h" #include "../Leds/LedTypes.h" @@ -22,26 +21,79 @@ enum MenuEntryID MENU_PATTERN_SELECT, MENU_GLOBAL_BRIGHTNESS, MENU_FACTORY_RESET, + // add new menus here + // the total number of menus MENU_COUNT }; -typedef struct Menu Menu; +class Menu; -bool Menus_init(void); -void Menus_cleanup(void); +class Menus +{ + // private unimplemented constructor + Menus(); + +public: + // opting for static class here because there should only ever be one + // Menu control object and I don't like singletons + static bool init(); + static void cleanup(); + + // Run the menus returns true if the menu remains open, false if closed + static bool run(); + + // open the ring menu + static bool openMenuSelection(); + + // open a menu by index in the menu table, optionally specify to + // open the advanced version of the menu (default basic version) + static bool openMenu(uint32_t index, bool advanced = false); -bool Menus_run(void); + // if you call this then a blink will be shown when the user has + // held the button for at least the long click duration + static void showSelection(RGBColor colval = RGB_WHITE5); -bool Menus_openMenuSelection(void); + // whether the menus are actually open (The user has let go after opening) + static bool checkOpen(); + // whether the user is actually in a menu + static bool checkInMenu(); + static Menu *curMenu(); + static MenuEntryID curMenuID(); -bool Menus_openMenu(uint32_t index, bool advanced); +private: + // run the currently open menu + static bool runCurMenu(); + // run the ring filling logic + static bool runMenuSelection(); + // helper to calculate the relative hold time for the current menu + static LedPos calcLedPos(); + // close the currently open menu + static void closeCurMenu(); -void Menus_showSelection(RGBColor colval); + // ===================== + // private members -bool Menus_checkOpen(void); -bool Menus_checkInMenu(void); -Menu *Menus_curMenu(void); -enum MenuEntryID Menus_curMenuID(void); + enum MenuState { + // menus aren't open at all + MENU_STATE_NOT_OPEN = 0, + + // menus are open and the user is choosing a menu + MENU_STATE_MENU_SELECTION, + + // the menus are open and we opened a menu + MENU_STATE_IN_MENU + }; + + // the current menus states + static MenuState m_menuState; + + // the ring menu section + static uint8_t m_selection; + // the time when we first opened the ringmenu + static uint32_t m_openTime; + // the current sub menu that is open + static Menu *m_pCurMenu; +}; #endif diff --git a/VortexEngine/src/Modes/DefaultModes.h b/VortexEngine/src/Modes/DefaultModes.h index 581cb36ee2..6c685a0d81 100644 --- a/VortexEngine/src/Modes/DefaultModes.h +++ b/VortexEngine/src/Modes/DefaultModes.h @@ -5,6 +5,7 @@ #include "../Leds/LedTypes.h" #include "../VortexConfig.h" +// structure of the entries in the default modes array struct DefaultLedEntry { PatternID patternID; @@ -14,9 +15,10 @@ struct DefaultLedEntry struct DefaultModeEntry { - struct DefaultLedEntry leds[LED_COUNT]; + DefaultLedEntry leds[LED_COUNT]; }; -extern const struct DefaultModeEntry defaultModes[MAX_MODES]; +// exposed global array of default modes +extern const DefaultModeEntry defaultModes[MAX_MODES]; #endif diff --git a/VortexEngine/src/Modes/Mode.h b/VortexEngine/src/Modes/Mode.h index 06779c4c97..3c0c574b6d 100644 --- a/VortexEngine/src/Modes/Mode.h +++ b/VortexEngine/src/Modes/Mode.h @@ -1,99 +1,164 @@ #ifndef MODE_H #define MODE_H -#include -#include #include "../Leds/LedTypes.h" #include "../Patterns/Patterns.h" #include "../VortexConfig.h" #include "DefaultModes.h" -typedef struct Pattern Pattern; -typedef struct PatternArgs_s PatternArgs; -typedef struct ByteStream ByteStream; -typedef struct Colorset_s Colorset; -typedef struct SingleLedPattern SingleLedPattern; -typedef struct MultiLedPattern MultiLedPattern; +class SingleLedPattern; +class MultiLedPattern; +class PatternArgs; +class ByteStream; +class Colorset; +class Pattern; typedef uint8_t ModeFlags; +// Bitflags for saving modes #define MODE_FLAG_NONE 0 +// the mode is utilizing a multi-led pattern #define MODE_FLAG_MULTI_LED (1 << 0) +// the mode is utilizing at least one single-led pattern #define MODE_FLAG_SINGLE_LED (1 << 1) +// the mode is utilizing the same single-led pattern on each finger +// optimization will be done when saving and only one led is saved #define MODE_FLAG_ALL_SAME_SINGLE (1 << 2) +// the mode is missing leds on some single slots, an led map is +// present in savefile in order to convey which leds are missing #define MODE_FLAG_SPARSE_SINGLES (1 << 3) -typedef struct Mode { -#if VORTEX_SLIM == 0 - Pattern *m_multiPat; -#endif +// A mode is the container for instances of patterns. A pattern +// must be bound to a colorset and led position with bind() and +// the Mode class allows that to be done in one step. Other things +// can be done in the Mode class such as accelerometer reaction, +// microphone reaction, timed colorset or pattern switches, or any +// other kind of logic that would involve manipulating the pattern +// or colorset as a whole +class Mode +{ +public: #if FIXED_LED_COUNT == 0 - uint8_t m_numLeds; - Pattern **m_singlePats; -#else - Pattern *m_singlePats[LED_COUNT]; + Mode(uint8_t numLeds); #endif -} Mode; - -void Mode_init(Mode *self); -void Mode_initFromEntry(Mode *self, const struct DefaultModeEntry *entry); -void Mode_initFromID(Mode *self, PatternID id, const Colorset *set); -void Mode_initFromIDArgsSet(Mode *self, PatternID id, const PatternArgs *args, const Colorset *set); -void Mode_initFromIDArgsSetPtr(Mode *self, PatternID id, const PatternArgs *args, const Colorset *set); -void Mode_initFromOther(Mode *self, const Mode *other); -void Mode_initCopy(Mode *self, const Mode *other); -void Mode_cleanup(Mode *self); - -void Mode_assign(Mode *self, const Mode *other); -bool Mode_equals(const Mode *self, const Mode *other); -bool Mode_notEquals(const Mode *self, const Mode *other); - -void Mode_initMode(Mode *self); -void Mode_play(Mode *self); -bool Mode_saveToBuffer(const Mode *self, ByteStream *saveBuffer, uint8_t numLeds); -bool Mode_loadFromBuffer(Mode *self, ByteStream *saveBuffer); -bool Mode_serialize(const Mode *self, ByteStream *buffer, uint8_t numLeds); -bool Mode_unserialize(Mode *self, ByteStream *buffer); + Mode(); + + Mode(const DefaultModeEntry &entry); + Mode(PatternID id, const Colorset &set); + Mode(PatternID id, const PatternArgs &args, const Colorset &set); + Mode(PatternID id, const PatternArgs *args, const Colorset *set); + Mode(const Mode *other); + ~Mode(); + + // copy and assignment operators + Mode(const Mode &other); + void operator=(const Mode &other); + + // equality operators + bool operator==(const Mode &other) const; + bool operator!=(const Mode &other) const; + + // initialize the mode to initial state + void init(); + + // Play the mode + void play(); + + // save/load from a buffer for individual mode sharing + bool saveToBuffer(ByteStream &saveBuffer, uint8_t numLeds = 0) const; + bool loadFromBuffer(ByteStream &saveBuffer); + + // save the mode to serial + bool serialize(ByteStream &buffer, uint8_t numLeds = 0) const; + // load the mode from serial (optional led count) + bool unserialize(ByteStream &buffer); + + // mode comparison + bool equals(const Mode *other) const; + #if FIXED_LED_COUNT == 0 -bool Mode_setLedCount(Mode *self, uint8_t numLeds); + // change the internal pattern count in the mode object + bool setLedCount(uint8_t numLeds); #endif -uint8_t Mode_getLedCount(const Mode *self); + uint8_t getLedCount() const; + + // get the pattern at a position + const Pattern *getPattern(LedPos pos = LED_ANY) const; + Pattern *getPattern(LedPos pos = LED_ANY); -const Pattern *Mode_getPatternConst(const Mode *self, LedPos pos); -Pattern *Mode_getPattern(Mode *self, LedPos pos); + // get the colorset at a position + const Colorset getColorset(LedPos pos = LED_ANY) const; + Colorset getColorset(LedPos pos = LED_ANY); -Colorset Mode_getColorset(const Mode *self, LedPos pos); -Colorset Mode_getColorsetMut(Mode *self, LedPos pos); + // get the pattern ID of the given pattern + PatternID getPatternID(LedPos pos = LED_ANY) const; -PatternID Mode_getPatternID(const Mode *self, LedPos pos); + // change the pattern on a mode (NOTE: you may need to call init() after!) + bool setPattern(PatternID pat, LedPos pos = LED_ANY, const PatternArgs *args = nullptr, const Colorset *set = nullptr); + bool setPatternMap(LedMap pos, PatternID pat, const PatternArgs *args = nullptr, const Colorset *set = nullptr); -bool Mode_setPattern(Mode *self, PatternID pat, LedPos pos, const PatternArgs *args, const Colorset *set); -bool Mode_setPatternMap(Mode *self, LedMap map, PatternID pat, const PatternArgs *args, const Colorset *set); + // copy a pattern from another mode ledpos into this mode + void copyPatternFrom(const Mode *other, LedPos to, LedPos from); -void Mode_copyPatternFrom(Mode *self, const Mode *other, LedPos to, LedPos from); -void Mode_swapPatterns(Mode *self, LedPos a, LedPos b); -bool Mode_setColorset(Mode *self, const Colorset *set, LedPos pos); -bool Mode_setColorsetMap(Mode *self, LedMap map, const Colorset *set); + // swap two patterns + void swapPatterns(LedPos a, LedPos b); -void Mode_clearPattern(Mode *self, LedPos pos); -void Mode_clearPatternMap(Mode *self, LedMap map); -void Mode_clearColorset(Mode *self, LedPos pos); -void Mode_clearColorsetMap(Mode *self, LedMap map); + // set colorset at a specific position + bool setColorset(const Colorset &set, LedPos pos = LED_ANY); + // set colorset at each position in a map + bool setColorsetMap(LedMap map, const Colorset &set); -void Mode_setArg(Mode *self, uint8_t index, uint8_t value, LedMap map); -uint8_t Mode_getArg(Mode *self, uint8_t index, LedPos pos); + // clear stored patterns in various ways + void clearPattern(LedPos pos = LED_ALL); + void clearPatternMap(LedMap map); -ModeFlags Mode_getFlags(const Mode *self); -bool Mode_hasMultiLed(const Mode *self); -bool Mode_hasSingleLed(const Mode *self); -bool Mode_hasSameSingleLed(const Mode *self); -bool Mode_hasSparseSingleLed(const Mode *self); -bool Mode_isEmpty(const Mode *self); -LedMap Mode_getSingleLedMap(const Mode *self); -bool Mode_isMultiLed(const Mode *self); + // clear colorset in various ways + void clearColorset(LedPos pos = LED_ALL); + void clearColorsetMap(LedMap map); + + // set/get a single argument on various positions + void setArg(uint8_t index, uint8_t value, LedMap map = MAP_LED_ALL); + uint8_t getArg(uint8_t index, LedPos pos = LED_ANY); + + // get the flags associated with this mode + ModeFlags getFlags() const; + + // whether a multi-led pattern is present in the mode + bool hasMultiLed() const; + // whether at least one single-led pattern is present in the mode + bool hasSingleLed() const; + // whether this mode has all same single-led patterns + bool hasSameSingleLed() const; + // whether this mode has sparse single-led patterns (missing some slots) + bool hasSparseSingleLed() const; + + // whether the mode contains no patterns, or all patterns are PATTERN_NONE + bool isEmpty() const; + + // get the led map of which singles are set + LedMap getSingleLedMap() const; + + // whether this mode purely a multi-led pattern with no singles + bool isMultiLed() const; #if MODES_TEST == 1 -void Mode_test(void); + static void test(); +#endif + +private: +#if VORTEX_SLIM == 0 + // the multi-led pattern slot is only present in non-slim builds + Pattern *m_multiPat; +#endif + // a list of slots for each single led pattern +#if FIXED_LED_COUNT == 0 + // the number of leds the mode is targetting + uint8_t m_numLeds; + // list of pointers to Patterns, one for each led + Pattern **m_singlePats; +#else + Pattern *m_singlePats[LED_COUNT]; #endif +}; #endif diff --git a/VortexEngine/src/Modes/Modes.h b/VortexEngine/src/Modes/Modes.h index 81c706c6fc..6c7e4d384c 100644 --- a/VortexEngine/src/Modes/Modes.h +++ b/VortexEngine/src/Modes/Modes.h @@ -1,125 +1,248 @@ #ifndef MODES_H #define MODES_H -#include -#include #include "../Serial/ByteStream.h" #include "../Colors/ColorTypes.h" #include "../Patterns/Patterns.h" #include "../Leds/Leds.h" + #include "../VortexConfig.h" -typedef struct PatternArgs_s PatternArgs; -typedef struct Colorset_s Colorset; -typedef struct Mode Mode; +#include +class PatternArgs; +class Colorset; +class Mode; + +// the device is locked and needs to be unlocked to turn on #define MODES_FLAG_LOCKED (1 << 0) +// the device will turn on and off with a single click #define MODES_FLAG_ONE_CLICK (1 << 1) +// toggles the advanced menus being accessible #define MODES_FLAG_ADV_MENUS (1 << 2) +// keychain light mode (behave like a keychain) #define MODES_FLAG_KEYCHAIN (1 << 3) +// WARNING!! The upper 4 bits of the flags are taken by the startup mode id, +// you can only use 4 global flags! + +// This is a new special feature used by the Chromadeck when it flashes a new +// firmware to the Duo it will set the global flags to 0xF0 like this: +// +// 0 = button lock enabled +// 0 = one click mode enabled +// 0 = advanced menus enabled +// 0 = keychain mode enabled +// 1111 = Startup Mode Index 15 (impossible) +// +// If this flag is present then the Duo needs to turn on and write out it's +// save header because a new firmware has been flashed and the save header +// will still have the old version number saved inside #define MODES_FLAG_NEW_FIRMWARE 0xF0 -typedef struct ModeLink { - Mode *m_pInstantiatedMode; - ByteStream m_storedMode; - struct ModeLink *m_next; - struct ModeLink *m_prev; -} ModeLink; - -void ModeLink_init(ModeLink *self, const Mode *src, bool inst); -void ModeLink_initFromStream(ModeLink *self, const ByteStream *src, bool inst); -void ModeLink_cleanup(ModeLink *self); - -bool ModeLink_initMode(ModeLink *self, const Mode *mode); -bool ModeLink_appendMode(ModeLink *self, const Mode *next); -bool ModeLink_appendStream(ModeLink *self, const ByteStream *next); - -void ModeLink_play(ModeLink *self); -ModeLink *ModeLink_unlinkSelf(ModeLink *self); -void ModeLink_linkAfter(ModeLink *self, ModeLink *link); -void ModeLink_linkBefore(ModeLink *self, ModeLink *link); - -Mode *ModeLink_instantiate(ModeLink *self); -void ModeLink_uninstantiate(ModeLink *self); -bool ModeLink_save(ModeLink *self); - -ByteStream *ModeLink_buffer(ModeLink *self); -Mode *ModeLink_mode(ModeLink *self); -ModeLink *ModeLink_next(ModeLink *self); -ModeLink *ModeLink_prev(ModeLink *self); - -// Modes module-level functions -bool Modes_init(void); -void Modes_cleanup(void); -bool Modes_load(void); -void Modes_play(void); - -bool Modes_serialize(ByteStream *modesBuffer); -bool Modes_unserialize(ByteStream *modesBuffer); -bool Modes_saveToBuffer(ByteStream *saveBuffer); -bool Modes_loadFromBuffer(ByteStream *saveBuffer); -bool Modes_saveHeader(void); -bool Modes_loadHeader(void); -bool Modes_saveStorage(void); -bool Modes_loadStorage(void); - -bool Modes_serializeSaveHeader(ByteStream *saveBuffer); -bool Modes_unserializeSaveHeader(ByteStream *saveHeader); - -bool Modes_setDefaults(void); -bool Modes_shiftCurMode(int32_t offset); -bool Modes_addMode(PatternID id, RGBColor c1, RGBColor c2, RGBColor c3, RGBColor c4, RGBColor c5, RGBColor c6, RGBColor c7, RGBColor c8); -bool Modes_addModeArgsSet(PatternID id, const PatternArgs *args, const Colorset *set); -bool Modes_addModeMode(const Mode *mode); -bool Modes_addSerializedMode(ByteStream *serializedMode); -bool Modes_addModeFromBuffer(ByteStream *serializedMode); -bool Modes_updateCurMode(const Mode *mode); -Mode *Modes_setCurMode(uint8_t index); -Mode *Modes_curMode(void); -Mode *Modes_nextMode(void); -Mode *Modes_previousMode(void); -Mode *Modes_nextModeSkipEmpty(void); - -uint8_t Modes_numModes(void); -uint8_t Modes_curModeIndex(void); -uint32_t Modes_lastSwitchTime(void); - -void Modes_deleteCurMode(void); -void Modes_clearModes(void); - -void Modes_setStartupMode(uint8_t index); -uint8_t Modes_startupMode(void); -Mode *Modes_switchToStartupMode(void); - -bool Modes_setFlag(uint8_t flag, bool enable, bool save); -bool Modes_getFlag(uint8_t flag); -void Modes_resetFlags(void); - -bool Modes_setOneClickMode(bool enable, bool save); -bool Modes_oneClickModeEnabled(void); -bool Modes_setLocked(bool locked, bool save); -bool Modes_locked(void); -bool Modes_setAdvancedMenus(bool active, bool save); -bool Modes_advancedMenusEnabled(void); -bool Modes_setKeychainMode(bool active, bool save); -bool Modes_keychainModeEnabled(void); +class Modes +{ + // private unimplemented constructor + Modes(); + +public: + // opting for static class here because there should only ever be one + // Modes control object and I don't like singletons + static bool init(); + static void cleanup(); + + // load modes so they are ready to play + static bool load(); + + // play the current mode + static void play(); + + // full save/load to/from buffer + static bool saveToBuffer(ByteStream &saveBuffer); + static bool loadFromBuffer(ByteStream &saveBuffer); + + // save/load the global settings to/from storage + static bool saveHeader(); + static bool loadHeader(); + + static bool saveHeaderAndMode(); + + // full save/load to/from storage + static bool saveStorage(); + static bool loadStorage(); + + // save load the savefile header from storage + static bool serializeSaveHeader(ByteStream &saveBuffer); + static bool unserializeSaveHeader(ByteStream &saveBuffer); + + // saves all modes to a buffer + static bool serialize(ByteStream &buffer); + // load all modes from a buffer, optionally specify number of leds + static bool unserialize(ByteStream &buffer); + + // set default settings (must save after) + static bool setDefaults(); + + // shift the current mode to a different position relative to current position + // negative values for up, positive values for down, 0 for no move + static bool shiftCurMode(int32_t offset = 1); + + // add a new mode in various different ways + static bool addMode(PatternID id, RGBColor c1, RGBColor c2 = RGB_OFF, + RGBColor c3 = RGB_OFF, RGBColor c4 = RGB_OFF, RGBColor c5 = RGB_OFF, + RGBColor c6 = RGB_OFF, RGBColor c7 = RGB_OFF, RGBColor c8 = RGB_OFF); + static bool addMode(PatternID id, const PatternArgs *args, const Colorset *set); + static bool addMode(const Mode *mode); + + // add a new mode by unserializing raw + static bool addSerializedMode(ByteStream &serializedMode); + // add a new mode by loading from a save buffer + static bool addModeFromBuffer(ByteStream &serializedMode); + + // update the current mode to match the given mode, optionally save + static bool updateCurMode(const Mode *mode); + + // set the current active mode by index + static Mode *setCurMode(uint8_t index); + + // get the current mode + static Mode *curMode(); + // iterate to next mode and return it + static Mode *nextMode(); + // iterate to previous mode and return it + static Mode *previousMode(); + + // iterate to the next mode but skip empty modes, will not skip mode 0 + // to prevent possibly skipping all modes and ending in an infinite loop + static Mode *nextModeSkipEmpty(); + + // the number of modes + static uint8_t numModes() { return m_numModes; } + static uint8_t curModeIndex() { return m_curMode; } + + // the last time the modes switched to a new mode + static uint32_t lastSwitchTime() { return m_lastSwitchTime; } + + // delete the current mode + // NOTE: THIS IS BROKEN ON THIS BRANCH + static void deleteCurMode(); + + // delete all modes in the list + static void clearModes(); + + // set the startup mode index (which mode will be displayed on startup) + static void setStartupMode(uint8_t index); + static uint8_t startupMode(); + static Mode *switchToStartupMode(); + + // set or get flags + static bool setFlag(uint8_t flag, bool enable, bool save = true); + static bool getFlag(uint8_t flag) { return ((m_globalFlags & flag) == flag); } + + // reset flags to factory default (must save after) + static void resetFlags() { m_globalFlags = 0; } + + // inline functions to toggle the various flags + static bool setOneClickMode(bool enable, bool save = true) { + return setFlag(MODES_FLAG_ONE_CLICK, enable, save); + } + static bool oneClickModeEnabled() { + return getFlag(MODES_FLAG_ONE_CLICK); + } + // toggle the locked state + static bool setLocked(bool locked, bool save = true) { + return setFlag(MODES_FLAG_LOCKED, locked, save); + } + static bool locked() { + return getFlag(MODES_FLAG_LOCKED); + } + // toggle advanced menus + static bool setAdvancedMenus(bool active, bool save = true) { + return setFlag(MODES_FLAG_ADV_MENUS, active, save); + } + static bool advancedMenusEnabled() { + return getFlag(MODES_FLAG_ADV_MENUS); + } + // toggle the keychain light mode + static bool setKeychainMode(bool active, bool save = true) { + return setFlag(MODES_FLAG_KEYCHAIN, active, save); + } + static bool keychainModeEnabled() { + return getFlag(MODES_FLAG_KEYCHAIN); + } #if MODES_TEST == 1 -void Modes_test(void); + static void test(); #endif #ifdef VORTEX_LIB -uint32_t Modes_maxModeSize(void); -uint32_t Modes_maxSaveSize(void); -uint8_t Modes_getGlobalFlags(void); + // get the maximum size a mode can occupy + static uint32_t maxModeSize(); + // get the maximum size a savefile can occupy + static uint32_t maxSaveSize(); + // global flags value + static uint8_t globalFlags(); #endif -// internal functions -ModeLink *Modes_getModeLink(uint32_t index); -Mode *Modes_initCurMode(bool force); -bool Modes_saveCurMode(void); +private: + // linked list of internal mode storage + class ModeLink { + friend class Modes; + public: + // construct a link and optionally instantiate the link + ModeLink(const Mode *src = nullptr); + ModeLink(const ByteStream &src); + + // init the link and append another link + bool init(const Mode *mode = nullptr); + bool init(const ByteStream &src); + + // instantiate/destroy the mode + Mode *instantiate(); + + // if the mode is instantiated and the instantiated version + // has changed at all then save will re-save it to the buffer + bool save(); + + // accessors + ByteStream &buffer() { return m_storedMode; } + + operator ByteStream &() { return m_storedMode; } + operator ByteStream() { return m_storedMode; } + private: + ByteStream m_storedMode; + }; + + // fetch a link from the chain by index + static ModeLink *getModeLink(uint32_t index); + + // initialize current mode from ByteStream, optionally force re-init which + // will destroy the current instantiated mode and re-load it from serial + static Mode *initCurMode(bool force = false); + static bool saveCurMode(); + + // whether modes have been loaded + static bool m_loaded; + + // the current mode we're on + static uint8_t m_curMode; + + // the number of modes loaded + static uint8_t m_numModes; + + // pointer to the current instantiated mode + static Mode *m_pCurMode; + + // the current instantiated mode and it's respective link + static ModeLink *m_pCurModeLink; + + // list of serialized version of bufers + static ModeLink m_storedModes[MAX_MODES]; + + // global flags for all modes + static uint8_t m_globalFlags; -// internal globals (accessed only through functions) -extern uint8_t Modes_globalFlags; + // the last switch time of the modes + static uint32_t m_lastSwitchTime; +}; #endif diff --git a/VortexEngine/src/Patterns/Multi/MultiLedPattern.h b/VortexEngine/src/Patterns/Multi/MultiLedPattern.h index b8a6904f71..9e105be4ff 100644 --- a/VortexEngine/src/Patterns/Multi/MultiLedPattern.h +++ b/VortexEngine/src/Patterns/Multi/MultiLedPattern.h @@ -3,14 +3,23 @@ #include "../Pattern.h" -typedef struct MultiLedPattern { - Pattern base; -} MultiLedPattern; +class MultiLedPattern : public Pattern +{ +protected: + // MultiLedPattern is an abstract class + MultiLedPattern(const PatternArgs &args); -extern const PatternVTable MultiLedPattern_vtable; +public: + virtual ~MultiLedPattern(); -void MultiLedPattern_init(MultiLedPattern *self, const PatternArgs *args); -void MultiLedPattern_bind(Pattern *self, LedPos pos); -void MultiLedPattern_initVirtual(Pattern *self); + // multi-led and single-led have different bind functions + virtual void bind(LedPos pos) override; + + // init the pattern to initial state + virtual void init() override; + +private: + +}; #endif diff --git a/VortexEngine/src/Patterns/Pattern.h b/VortexEngine/src/Patterns/Pattern.h index e7f6258f89..0a3454ce04 100644 --- a/VortexEngine/src/Patterns/Pattern.h +++ b/VortexEngine/src/Patterns/Pattern.h @@ -1,110 +1,146 @@ #ifndef PATTERN_H #define PATTERN_H -#include -#include -#include - #include "../Leds/LedTypes.h" #include "../Colors/Colorset.h" + #include "Patterns.h" #include "PatternArgs.h" #define MAX_PATTERN_ARGS 8 +// The heirarchy of pattern currently looks like this: /* - * Pattern + * pattern* * / \ - * SingleLedPattern MultiLedPattern - * / \ - * BasicPattern - * / \ - * SolidPattern BlendPattern + * single led pat* multi led pat* + * / \ / + * basic pat tracer pat hybrid pat* + * / / \ + * Advanced pattern Rabbit Pattern sequenced pattern + * + * * = abstract class that cannot be instantiated */ #define PATTERN_FLAGS_NONE 0 + +// the pattern is a multi-pattern #define PATTERN_FLAG_MULTI (1<<0) +// and arg_offset_t is the distance from the base of the object +// to the desired argument, in slim vortex we only need a single byte +// but in multi-led patterns they can be a bit further in #if VORTEX_SLIM == 1 typedef uint8_t arg_offset_t; #else typedef uint16_t arg_offset_t; #endif +// macro to register args of a pattern #ifdef VORTEX_LIB -#define REGISTER_ARG(self, arg) Pattern_registerArgName((Pattern *)(self), #arg, (arg_offset_t)((uintptr_t)&(arg) - (uintptr_t)(self))) +#define REGISTER_ARG(arg) \ + registerArg(#arg, (arg_offset_t)(((uintptr_t)&arg - (uintptr_t)this))); #else -#define REGISTER_ARG(self, arg) Pattern_registerArg((Pattern *)(self), (arg_offset_t)((uintptr_t)&(arg) - (uintptr_t)(self))) +#define REGISTER_ARG(arg) \ + registerArg((arg_offset_t)(((uintptr_t)&arg - (uintptr_t)this))); #endif -typedef struct ByteStream ByteStream; -typedef struct Pattern Pattern; - -typedef struct PatternVTable { - void (*destroy)(Pattern *self); - void (*play)(Pattern *self); - void (*init)(Pattern *self); - void (*bind)(Pattern *self, LedPos pos); - void (*onBlinkOn)(Pattern *self); - void (*onBlinkOff)(Pattern *self); - void (*beginGap)(Pattern *self); - void (*beginDash)(Pattern *self); -} PatternVTable; - -struct Pattern { - const PatternVTable *vtable; - PatternID patternID; - uint8_t patternFlags; - Colorset colorset; - LedPos ledPos; - uint8_t numArgs; - arg_offset_t argList[MAX_PATTERN_ARGS]; -#ifdef VORTEX_LIB - const char *argNameList[MAX_PATTERN_ARGS]; -#endif -}; +class ByteStream; + +class Pattern +{ + // PatternBuilder can access the Pattern internals + friend class PatternBuilder; + +protected: + // Pattern is an abstract class + Pattern(); + + Pattern(const PatternArgs &args); + +public: + virtual ~Pattern(); + + // bind a colorset and position to the pattern + virtual void bind(LedPos pos); -void Pattern_init(Pattern *self, const PatternArgs *args); -void Pattern_initBase(Pattern *self); -void Pattern_initVirtual(Pattern *self); -void Pattern_destroy(Pattern *self); -void Pattern_bind(Pattern *self, LedPos pos); -void Pattern_bindBase(Pattern *self, LedPos pos); -void Pattern_play(Pattern *self); + // init the pattern to initial state + virtual void init(); + + // pure virtual must override the play function + virtual void play() = 0; #ifdef VORTEX_LIB -void Pattern_skip(Pattern *self, uint32_t ticks); + // skip the pattern ahead some ticks + void skip(uint32_t ticks); #endif -bool Pattern_serialize(const Pattern *self, ByteStream *buffer); -bool Pattern_unserialize(Pattern *self, ByteStream *buffer); + // serialize and unserialize a pattern to a bytestream + bool serialize(ByteStream &buffer) const; + bool unserialize(ByteStream &buffer); + + // get or set a single arg + void setArg(uint8_t index, uint8_t value); + uint8_t getArg(uint8_t index) const; -void Pattern_setArg(Pattern *self, uint8_t index, uint8_t value); -uint8_t Pattern_getArg(const Pattern *self, uint8_t index); -uint8_t *Pattern_argRef(Pattern *self, uint8_t index); + uint8_t &argRef(uint8_t index); #ifdef VORTEX_LIB -const char *Pattern_getArgName(const Pattern *self, uint8_t index); + // get the name of an arg + const char *getArgName(uint8_t index) const { return index >= m_numArgs ? "" : m_argNameList[index]; } #endif -void Pattern_setArgs(Pattern *self, const PatternArgs *args); -void Pattern_getArgs(const Pattern *self, PatternArgs *args); -uint8_t Pattern_getNumArgs(const Pattern *self); -bool Pattern_equals(const Pattern *self, const Pattern *other); + // get or set the entire list of pattern args + void setArgs(const PatternArgs &args); + void getArgs(PatternArgs &args) const; + + // number of args the pattern has + uint8_t getNumArgs() const { return m_numArgs; } + + // comparison to other pattern + // NOTE: That may cause problems because the parameter is still a Pattern * + // which means comparison would need to cast the other upwards first + // NOTE2: Removing virtual because this probably shouldn't be overridden + bool equals(const Pattern *other); + + // change the colorset + const Colorset getColorset() const { return m_colorset; } + Colorset getColorset() { return m_colorset; } + void setColorset(const Colorset &set); + void clearColorset(); + + // change the led position + void setLedPos(LedPos pos) { m_ledPos = pos; } -Colorset Pattern_getColorset(const Pattern *self); -void Pattern_setColorset(Pattern *self, const Colorset *set); -void Pattern_clearColorset(Pattern *self); -void Pattern_setLedPos(Pattern *self, LedPos pos); -PatternID Pattern_getPatternID(const Pattern *self); -LedPos Pattern_getLedPos(const Pattern *self); -uint32_t Pattern_getFlags(const Pattern *self); -bool Pattern_hasFlags(const Pattern *self, uint32_t flags); + // get/set the ID of the pattern (set by mode builder) + PatternID getPatternID() const { return m_patternID; } + + // get a pointer to the colorset that is bound to the pattern + LedPos getLedPos() const { return m_ledPos; } + + // get the pattern flags + uint32_t getFlags() const { return m_patternFlags; } + bool hasFlags(uint32_t flags) const { return (m_patternFlags & flags) != 0; } + +protected: + // the ID of this pattern (set by pattern builder) + PatternID m_patternID; + // any flags the pattern has + uint8_t m_patternFlags; + // a copy of the colorset that this pattern is initialized with + Colorset m_colorset; + // the Led the pattern is running on + LedPos m_ledPos; + + uint8_t m_numArgs; + arg_offset_t m_argList[MAX_PATTERN_ARGS]; #ifdef VORTEX_LIB -void Pattern_registerArgName(Pattern *self, const char *name, arg_offset_t argOffset); + void registerArg(const char *name, arg_offset_t argOffset); + const char *m_argNameList[MAX_PATTERN_ARGS]; #else -void Pattern_registerArg(Pattern *self, arg_offset_t argOffset); + void registerArg(arg_offset_t argOffset); #endif +}; #endif diff --git a/VortexEngine/src/Patterns/PatternArgs.h b/VortexEngine/src/Patterns/PatternArgs.h index 3da1255eb6..67759ad682 100644 --- a/VortexEngine/src/Patterns/PatternArgs.h +++ b/VortexEngine/src/Patterns/PatternArgs.h @@ -2,14 +2,18 @@ #define PATTERN_ARGS_H #include -#include +// maximum number of args, this isn't really configurable so I'm +// not putting it in VortexConfig.h #define MAX_ARGS 8 -typedef struct ByteStream ByteStream; +class ByteStream; +// a bit map of arguments, this is used to indicate which arguments +// are present in a pattern when saved to storage typedef uint8_t ArgMap; +// some macros to access each arg in the argmap #define ARG_NONE 0 #define ARG(x) (1 << x) #define ARG1 (1 << 0) @@ -22,11 +26,57 @@ typedef uint8_t ArgMap; #define ARG8 (1 << 7) #define ARG_ALL 0xFF +// some helpers to set/clear/check #define ARGMAP_SET(map, arg) (map |= ARG(arg)) #define ARGMAP_CLEAR(map, arg) (map &= ~ARG(arg)) #define ARGMAP_ISSET(map, arg) ((map & ARG(arg)) != 0) -typedef struct PatternArgs_s { +// a structured method for passing arguments to patterns, there's definitely more dynamic +// ways to approach this but I'm aiming for simple and lightweight here +class PatternArgs +{ +public: + // constructor for each number of arguments, automatically fills the 'numArgs' field accordingly + PatternArgs(); + PatternArgs(uint8_t a1); + PatternArgs(uint8_t a1, uint8_t a2); + PatternArgs(uint8_t a1, uint8_t a2, uint8_t a3); + PatternArgs(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4); + PatternArgs(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5); + PatternArgs(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6); + PatternArgs(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7); + PatternArgs(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7, uint8_t a8); + + void init(); + void init(uint8_t a1); + void init(uint8_t a1, uint8_t a2); + void init(uint8_t a1, uint8_t a2, uint8_t a3); + void init(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4); + void init(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5); + void init(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6); + void init(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7); + void init(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7, uint8_t a8); + + void addArgs(uint8_t a1); + void addArgs(uint8_t a1, uint8_t a2); + void addArgs(uint8_t a1, uint8_t a2, uint8_t a3); + void addArgs(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4); + void addArgs(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5); + void addArgs(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6); + void addArgs(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7); + void addArgs(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7, uint8_t a8); + + bool operator!=(const PatternArgs &rhs); + bool operator==(const PatternArgs &rhs); + uint8_t operator[](int index) const; + + // serialize the pattern args with a specific mapping of which args to store + bool serialize(ByteStream &buffer, ArgMap argmap = ARG_ALL) const; + // unserialize the pattern args and return the argmap of which args were loaded + // NOTE: You should start with an instance of the default args before you unserialize + ArgMap unserialize(ByteStream &buffer); + + // public access to args union { uint8_t args[8]; struct { @@ -39,6 +89,7 @@ typedef struct PatternArgs_s { uint8_t arg7; uint8_t arg8; }; + // arg access for basic pattern struct { uint8_t onDuration; uint8_t offDuration; @@ -46,6 +97,7 @@ typedef struct PatternArgs_s { uint8_t dashDuration; uint8_t groupSize; } basic; + // arg access for blend struct { uint8_t onDuration; uint8_t offDuration; @@ -55,6 +107,7 @@ typedef struct PatternArgs_s { uint8_t blendSpeed; uint8_t numFlips; } blend; + // arg access for solid struct { uint8_t onDuration; uint8_t offDuration; @@ -64,33 +117,8 @@ typedef struct PatternArgs_s { uint8_t colorIndex; } solid; }; + // indicates how many of the above args are filled uint8_t numArgs; -} PatternArgs; - -void PatternArgs_init(PatternArgs *self); -void PatternArgs_init1(PatternArgs *self, uint8_t a1); -void PatternArgs_init2(PatternArgs *self, uint8_t a1, uint8_t a2); -void PatternArgs_init3(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3); -void PatternArgs_init4(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4); -void PatternArgs_init5(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5); -void PatternArgs_init6(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6); -void PatternArgs_init7(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7); -void PatternArgs_init8(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7, uint8_t a8); - -void PatternArgs_addArgs1(PatternArgs *self, uint8_t a1); -void PatternArgs_addArgs2(PatternArgs *self, uint8_t a1, uint8_t a2); -void PatternArgs_addArgs3(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3); -void PatternArgs_addArgs4(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4); -void PatternArgs_addArgs5(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5); -void PatternArgs_addArgs6(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6); -void PatternArgs_addArgs7(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7); -void PatternArgs_addArgs8(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7, uint8_t a8); - -bool PatternArgs_equals(const PatternArgs *self, const PatternArgs *rhs); -bool PatternArgs_notEquals(const PatternArgs *self, const PatternArgs *rhs); -uint8_t PatternArgs_get(const PatternArgs *self, int index); - -bool PatternArgs_serialize(const PatternArgs *self, ByteStream *buffer, ArgMap argmap); -ArgMap PatternArgs_unserialize(PatternArgs *self, ByteStream *buffer); +}; #endif diff --git a/VortexEngine/src/Patterns/PatternBuilder.h b/VortexEngine/src/Patterns/PatternBuilder.h index a49d729e51..7467f58888 100644 --- a/VortexEngine/src/Patterns/PatternBuilder.h +++ b/VortexEngine/src/Patterns/PatternBuilder.h @@ -1,21 +1,42 @@ #ifndef PATTERN_BUILDER_H #define PATTERN_BUILDER_H -#include -#include - #include "Patterns.h" -#include "PatternArgs.h" - -typedef struct ByteStream ByteStream; -typedef struct Pattern Pattern; - -Pattern *PatternBuilder_make(PatternID id, const PatternArgs *args); -Pattern *PatternBuilder_dupe(const Pattern *pat); -Pattern *PatternBuilder_makeSingle(PatternID id, const PatternArgs *args); -Pattern *PatternBuilder_makeMulti(PatternID id, const PatternArgs *args); -Pattern *PatternBuilder_unserialize(ByteStream *buffer); -PatternArgs PatternBuilder_getDefaultArgs(PatternID id); -uint8_t PatternBuilder_numDefaultArgs(PatternID id); + +class Pattern; +class PatternArgs; +class ByteStream; +class MultiLedPattern; +class SingleLedPattern; + +class PatternBuilder +{ +public: + // generic make any pattern + static Pattern *make(PatternID id, const PatternArgs *args = nullptr); + + // make a copy of an existing pattern + static Pattern *dupe(const Pattern *pat); + + // generate a single LED pattern (nullptr if patternid is not single LED) + static SingleLedPattern *makeSingle(PatternID id, const PatternArgs *args = nullptr); + + // generate a multi LED pattern (nullptr if patternid is not multi LED) + static MultiLedPattern *makeMulti(PatternID id, const PatternArgs *args = nullptr); + + // unserialize a buffer into a pattern + static Pattern *unserialize(ByteStream &buffer); + + // This is just the default arguments for any given pattern id + // it will *not* indicate the true amount of arguments a pattern has + static PatternArgs getDefaultArgs(PatternID id); + // this will give you actual amount of default args + static uint8_t numDefaultArgs(PatternID id); + +private: + // helper routines + static Pattern *makeInternal(PatternID id, const PatternArgs *args = nullptr); + static Pattern *generate(PatternID id, const PatternArgs *args = nullptr); +}; #endif diff --git a/VortexEngine/src/Patterns/Patterns.h b/VortexEngine/src/Patterns/Patterns.h index e690eb4e00..65f3b90bb7 100644 --- a/VortexEngine/src/Patterns/Patterns.h +++ b/VortexEngine/src/Patterns/Patterns.h @@ -2,14 +2,26 @@ #define PATTERNS_H #include -#include -typedef int8_t PatternID; +// List of patterns that can be built, both single and multi-led patterns are found in this list. +// Within both single and multi LED pattern lists there are 'core' patterns which are associated +// with a class, and there are 'shell' patterns which are simply wrapperns around another pattern +// with different parameters passed to the constructor. There is no way to know which patterns +// are 'core' patterns, except by looking at PatternBuilder::generate to see which classes exist +enum PatternID : int8_t +{ + // no pattern at all, use this sparingly and default to + // PATTERN_FIRST when possible + PATTERN_NONE = (PatternID)-1, -enum { - PATTERN_NONE = -1, + // first pattern of all PATTERN_FIRST = 0, + // first 'single led' pattern PATTERN_SINGLE_FIRST = PATTERN_FIRST, + // ===================================== + + // all 'single led' patterns below + PATTERN_STROBE = PATTERN_FIRST, PATTERN_HYPERSTROBE, PATTERN_PICOSTROBE, @@ -42,9 +54,20 @@ enum { PATTERN_COMPLEMENTARY_BLENDSTROBE, PATTERN_COMPLEMENTARY_BLENDSTROBEGAP, PATTERN_SOLID, + + // ADD NEW SINGLE LED PATTERNS HERE + // WARNING This will offset all multi-led patterns so sharing of patterns + // will be misaligned, for ex. IR and save files. + + // ===================================== + // Pattern Meta Constants: PATTERN_MULTI_FIRST, PATTERN_SINGLE_LAST = (PATTERN_MULTI_FIRST - 1), PATTERN_SINGLE_COUNT = (PATTERN_SINGLE_LAST - PATTERN_SINGLE_FIRST) + 1, + // ===================================== + + // all 'multi led' patterns below + PATTERN_HUE_SCROLL = PATTERN_MULTI_FIRST, PATTERN_THEATER_CHASE, PATTERN_CHASER, @@ -67,22 +90,44 @@ enum { PATTERN_SPLITSTROBIE, PATTERN_BACKSTROBE, PATTERN_VORTEX, - INTERNAL_PATTERNS_END, + + // ADD NEW MULTI LED PATTERNS HERE + + // ===================================== + INTERNAL_PATTERNS_END, // <<< DON'T USE OR TOUCH THIS ONE PATTERN_MULTI_LAST = (INTERNAL_PATTERNS_END - 1), PATTERN_MULTI_COUNT = (PATTERN_MULTI_LAST - PATTERN_MULTI_FIRST) + 1, PATTERN_LAST = PATTERN_MULTI_LAST, - PATTERN_COUNT = (PATTERN_LAST - PATTERN_FIRST) + 1, + PATTERN_COUNT = (PATTERN_LAST - PATTERN_FIRST) + 1, // total number of patterns }; -static inline bool isMultiLedPatternID(PatternID id) { +// some helper functions to improve readability +inline bool isMultiLedPatternID(PatternID id) { return id >= PATTERN_MULTI_FIRST && id <= PATTERN_MULTI_LAST; } -static inline bool isSingleLedPatternID(PatternID id) { +inline bool isSingleLedPatternID(PatternID id) { return id < PATTERN_MULTI_FIRST; } -static inline PatternID patternIDIncr(PatternID c) { return (PatternID)((uint16_t)c + 1); } -static inline PatternID patternIDAdd(PatternID c, int b) { return (PatternID)((uint16_t)c + (uint16_t)b); } -static inline PatternID patternIDSub(PatternID c, int b) { return (PatternID)((uint16_t)c - (uint16_t)b); } +// PatternID operators +inline PatternID &operator++(PatternID &c) +{ + c = PatternID(((uint16_t)c) + 1); + return c; +} +inline PatternID operator++(PatternID &c, int) +{ + PatternID temp = c; + ++c; + return temp; +} +inline PatternID operator+(PatternID &c, int b) +{ + return (PatternID)((uint16_t)c + b); +} +inline PatternID operator-(PatternID &c, int b) +{ + return (PatternID)((uint16_t)c - b); +} #endif diff --git a/VortexEngine/src/Patterns/Single/BasicPattern.h b/VortexEngine/src/Patterns/Single/BasicPattern.h index 88b0b4c6dd..39cc660bd4 100644 --- a/VortexEngine/src/Patterns/Single/BasicPattern.h +++ b/VortexEngine/src/Patterns/Single/BasicPattern.h @@ -4,38 +4,66 @@ #include "SingleLedPattern.h" #include "../../Time/Timer.h" -#define STATE_DISABLED 0 -#define STATE_BLINK_ON 1 -#define STATE_ON 2 -#define STATE_BLINK_OFF 3 -#define STATE_OFF 4 -#define STATE_BEGIN_GAP 5 -#define STATE_IN_GAP 6 -#define STATE_BEGIN_DASH 7 -#define STATE_IN_DASH 8 -#define STATE_BEGIN_GAP2 9 -#define STATE_IN_GAP2 10 - -typedef struct BasicPattern { - SingleLedPattern base; - uint8_t onDuration; - uint8_t offDuration; - uint8_t gapDuration; - uint8_t dashDuration; - uint8_t groupSize; - uint8_t groupCounter; - uint8_t state; - Timer blinkTimer; -} BasicPattern; - -extern const PatternVTable BasicPattern_vtable; - -void BasicPattern_init(BasicPattern *self, const PatternArgs *args); -void BasicPattern_play(Pattern *base); -void BasicPattern_initVirtual(Pattern *base); -void BasicPattern_onBlinkOn(Pattern *base); -void BasicPattern_onBlinkOff(Pattern *base); -void BasicPattern_beginGap(Pattern *base); -void BasicPattern_beginDash(Pattern *base); +class BasicPattern : public SingleLedPattern +{ +public: + BasicPattern(const PatternArgs &args); + virtual ~BasicPattern(); + + virtual void init() override; + virtual void play() override; + +protected: + // when blinking off + virtual void onBlinkOn(); + virtual void onBlinkOff(); + virtual void beginGap(); + virtual void beginDash(); + + // iterate to next state + void nextState(uint8_t timing); + + // the parameters of the pattern + uint8_t m_onDuration; + uint8_t m_offDuration; + uint8_t m_gapDuration; + uint8_t m_dashDuration; + uint8_t m_groupSize; + + uint8_t m_groupCounter; + + // the various different blinking states the pattern can be in + enum PatternState : uint8_t + { + // the led is disabled (there is no on or dash) + STATE_DISABLED, + + // the pattern is blinking on the next color in the set + STATE_BLINK_ON, + STATE_ON, + + // the pattern is blinking off + STATE_BLINK_OFF, + STATE_OFF, + + // the pattern is starting a gap after a colorset + STATE_BEGIN_GAP, + STATE_IN_GAP, + + // the pattern is beginning a dash after a colorset or gap + STATE_BEGIN_DASH, + STATE_IN_DASH, + + // the pattern is starting a gap after a dash + STATE_BEGIN_GAP2, + STATE_IN_GAP2, + }; + + // the state of the current pattern + PatternState m_state; + + // the blink timer used to measure blink timings + Timer m_blinkTimer; +}; #endif diff --git a/VortexEngine/src/Patterns/Single/BlendPattern.h b/VortexEngine/src/Patterns/Single/BlendPattern.h index 6407633880..bff3c1ff06 100644 --- a/VortexEngine/src/Patterns/Single/BlendPattern.h +++ b/VortexEngine/src/Patterns/Single/BlendPattern.h @@ -2,22 +2,33 @@ #define BLEND_PATTERN_H #include "BasicPattern.h" + #include "../../Time/Timings.h" -#include "../../Colors/ColorTypes.h" -typedef struct BlendPattern { - BasicPattern base; - uint8_t blendSpeed; - uint8_t numFlips; - RGBColor cur; - RGBColor next; - uint8_t flip; -} BlendPattern; +class BlendPattern : public BasicPattern +{ +public: + BlendPattern(const PatternArgs &args); + virtual ~BlendPattern(); + + virtual void init() override; + +protected: + // only override the onBlinkOn so we can control the color it blinks + virtual void onBlinkOn() override; + void interpolate(uint8_t ¤t, const uint8_t next); -extern const PatternVTable BlendPattern_vtable; + // offset of starting hue and number of flips + uint8_t m_blendSpeed; + uint8_t m_numFlips; -void BlendPattern_init(BlendPattern *self, const PatternArgs *args); -void BlendPattern_initVirtual(Pattern *base); -void BlendPattern_onBlinkOn(Pattern *base); + // current color and target blend color + RGBColor m_cur; + RGBColor m_next; + + // the current flip counter + uint8_t m_flip; +}; #endif + diff --git a/VortexEngine/src/Patterns/Single/SingleLedPattern.h b/VortexEngine/src/Patterns/Single/SingleLedPattern.h index a162175b1d..443045253c 100644 --- a/VortexEngine/src/Patterns/Single/SingleLedPattern.h +++ b/VortexEngine/src/Patterns/Single/SingleLedPattern.h @@ -3,10 +3,19 @@ #include "../Pattern.h" -typedef struct SingleLedPattern { - Pattern base; -} SingleLedPattern; +class SingleLedPattern : public Pattern +{ +protected: + // SingleLedPattern is an abstract class + SingleLedPattern(const PatternArgs &args); -void SingleLedPattern_init(SingleLedPattern *self, const PatternArgs *args); +public: + virtual ~SingleLedPattern(); + + virtual void init() override; + +private: + +}; #endif diff --git a/VortexEngine/src/Patterns/Single/SolidPattern.h b/VortexEngine/src/Patterns/Single/SolidPattern.h index 6fc94da6d1..ac50b71da9 100644 --- a/VortexEngine/src/Patterns/Single/SolidPattern.h +++ b/VortexEngine/src/Patterns/Single/SolidPattern.h @@ -2,16 +2,19 @@ #define SOLID_PATTERN_H #include "BasicPattern.h" + #include "../../Time/Timings.h" -typedef struct SolidPattern { - BasicPattern base; - uint8_t colIndex; -} SolidPattern; +class SolidPattern : public BasicPattern +{ +public: + SolidPattern(const PatternArgs &args); -extern const PatternVTable SolidPattern_vtable; +protected: + // callbacks for blinking on/off, can be overridden by derived classes + virtual void onBlinkOn() override; -void SolidPattern_init(SolidPattern *self, const PatternArgs *args); -void SolidPattern_onBlinkOn(Pattern *base); + uint8_t m_colIndex; +}; #endif diff --git a/VortexEngine/src/Random/Random.h b/VortexEngine/src/Random/Random.h index 11702de290..b413b7210f 100644 --- a/VortexEngine/src/Random/Random.h +++ b/VortexEngine/src/Random/Random.h @@ -1,17 +1,19 @@ -#ifndef RANDOM_H -#define RANDOM_H +#pragma once #include -#include -typedef struct Random_t { - uint32_t seed; -} Random; +class Random +{ +public: + Random(); + Random(uint32_t newseed); + ~Random(); -void Random_init(Random *self); -void Random_initWithSeed(Random *self, uint32_t newseed); -void Random_seed(Random *self, uint32_t newseed); -uint8_t Random_next8(Random *self, uint8_t minValue, uint8_t maxValue); -uint16_t Random_next16(Random *self, uint16_t minValue, uint16_t maxValue); + void seed(uint32_t newseed); -#endif + uint8_t next8(uint8_t minValue = 0, uint8_t maxValue = 0xFF); + uint16_t next16(uint16_t minValue = 0, uint16_t maxValue = 0xFFFF); + +private: + uint32_t m_seed; +}; diff --git a/VortexEngine/src/Serial/BitStream.h b/VortexEngine/src/Serial/BitStream.h index 8687b02220..87aba5d66e 100644 --- a/VortexEngine/src/Serial/BitStream.h +++ b/VortexEngine/src/Serial/BitStream.h @@ -2,38 +2,50 @@ #define BITSTREAM_H #include -#include -typedef struct BitStream +// A class to read/write a buffer of bits one bit at a time +class BitStream { - uint8_t *buf; - uint16_t buf_size; - uint16_t bit_pos; - bool buf_eof; - bool allocated; -} BitStream; - -void BitStream_init(BitStream *self); -void BitStream_initBuf(BitStream *self, uint8_t *buf, uint32_t size); -bool BitStream_initAlloc(BitStream *self, uint32_t size); -void BitStream_destroy(BitStream *self); - -void BitStream_reset(BitStream *self); -void BitStream_resetPos(BitStream *self); - -uint8_t BitStream_read1Bit(BitStream *self); -void BitStream_write1Bit(BitStream *self, bool bit); -uint8_t BitStream_readBits(BitStream *self, uint32_t numBits); -void BitStream_writeBits(BitStream *self, uint32_t numBits, uint32_t val); - -static inline bool BitStream_eof(const BitStream *self) { return self->buf_eof; } -static inline bool BitStream_allocated(const BitStream *self) { return self->allocated; } -static inline uint16_t BitStream_size(const BitStream *self) { return self->buf_size; } -static inline const uint8_t *BitStream_data(const BitStream *self) { return self->buf; } -static inline uint8_t BitStream_peekData(const BitStream *self, uint8_t pos) { return self->buf[pos]; } -static inline const uint32_t *BitStream_dwData(const BitStream *self) { return (const uint32_t *)self->buf; } -static inline uint16_t BitStream_dwordpos(const BitStream *self) { return self->bit_pos / 32; } -static inline uint16_t BitStream_bytepos(const BitStream *self) { return self->bit_pos / 8; } -static inline uint16_t BitStream_bitpos(const BitStream *self) { return self->bit_pos; } +public: + BitStream(); + BitStream(uint32_t size); + BitStream(uint8_t *buf, uint32_t size); + ~BitStream(); + + // init the stream with a buffer + bool init(uint8_t *buf, uint32_t size); + // init the stream and allocate a buffer + bool init(uint32_t size); + + // clear the target buffer to 0 and reset position + void reset(); + // reset the reader/writer position + void resetPos(); + + // read/write a single bit in LSB + uint8_t read1Bit(); + void write1Bit(bool bit); + // read/write multiple bits from left to right at LSB + uint8_t readBits(uint32_t numBits); + void writeBits(uint32_t numBits, uint32_t val); + + // metainfo about the bit stream + bool eof() const { return m_buf_eof; } + bool allocated() const { return m_allocated; } + uint16_t size() const { return m_buf_size; } + const uint8_t *data() const { return m_buf; } + uint8_t peekData(uint8_t pos) const { return m_buf[pos]; } + const uint32_t *dwData() const { return (uint32_t *)m_buf; } + uint16_t dwordpos() const { return m_bit_pos / 32; } + uint16_t bytepos() const { return m_bit_pos / 8; } + uint16_t bitpos() const { return m_bit_pos; } + +private: + uint8_t *m_buf; + uint16_t m_buf_size; + uint16_t m_bit_pos; + bool m_buf_eof; + bool m_allocated; +}; #endif diff --git a/VortexEngine/src/Serial/ByteStream.h b/VortexEngine/src/Serial/ByteStream.h index 99efa1c586..722aa81a08 100644 --- a/VortexEngine/src/Serial/ByteStream.h +++ b/VortexEngine/src/Serial/ByteStream.h @@ -2,101 +2,190 @@ #define BYTE_STREAM_H #include -#include -#include #include "../Log/Log.h" +// disable warning about using flexible array at end of structure +// otherwise visual studio dumps a million warnings about the buf[] +// flexible array member at the end of the inner data structure. +// Yes I know it's scary Visual Studio, please shut up now. #ifdef _MSC_VER #pragma warning(disable : 4200) #endif -struct RawBuffer -{ - uint32_t size; - uint32_t flags; - uint32_t crc32; - uint8_t buf[]; -}; +class FlashClass; -typedef struct RawBuffer RawBuffer; - -typedef struct ByteStream +class ByteStream { - RawBuffer *pData; - uint16_t position; - uint16_t capacity; -} ByteStream; - -struct FlashClass; - -// init/destroy -bool ByteStream_init(ByteStream *self, uint32_t size, const uint8_t *buf); -void ByteStream_destroy(ByteStream *self); -void ByteStream_copy(ByteStream *self, const ByteStream *other); -void ByteStream_assign(ByteStream *self, const ByteStream *other); - -void ByteStream_move(ByteStream *self, ByteStream *target); - -bool ByteStream_rawInit(ByteStream *self, const uint8_t *rawdata, uint32_t size); -bool ByteStream_init(ByteStream *self, uint32_t capacity, const uint8_t *buf); - -void ByteStream_clear(ByteStream *self); -bool ByteStream_shrink(ByteStream *self); -bool ByteStream_append(ByteStream *self, const ByteStream *other); -bool ByteStream_extend(ByteStream *self, uint32_t size); -void ByteStream_trim(ByteStream *self, uint32_t bytes); - -bool ByteStream_compress(ByteStream *self); -bool ByteStream_decompress(ByteStream *self); - -uint32_t ByteStream_recalcCRC(ByteStream *self, bool force); -void ByteStream_sanity(ByteStream *self); -bool ByteStream_checkCRC(const ByteStream *self); -bool ByteStream_isCRCDirty(const ByteStream *self); -void ByteStream_setCRCDirty(ByteStream *self); - -bool ByteStream_serialize8(ByteStream *self, uint8_t byte); -bool ByteStream_serialize16(ByteStream *self, uint16_t bytes); -bool ByteStream_serialize32(ByteStream *self, uint32_t bytes); - -void ByteStream_resetUnserializer(ByteStream *self); -void ByteStream_moveUnserializer(ByteStream *self, uint32_t idx); -bool ByteStream_unserializerAtEnd(const ByteStream *self); - -bool ByteStream_unserialize8(ByteStream *self, uint8_t *byte); -bool ByteStream_unserialize16(ByteStream *self, uint16_t *bytes); -bool ByteStream_unserialize32(ByteStream *self, uint32_t *bytes); - -bool ByteStream_consume8(ByteStream *self, uint8_t *byte); -bool ByteStream_consume16(ByteStream *self, uint16_t *bytes); -bool ByteStream_consume32(ByteStream *self, uint32_t *bytes); -bool ByteStream_consume(ByteStream *self, uint32_t size, void *bytes); - -uint8_t ByteStream_peek8(const ByteStream *self); -uint16_t ByteStream_peek16(const ByteStream *self); -uint32_t ByteStream_peek32(const ByteStream *self); - -uint8_t *ByteStream_get(ByteStream *self, uint32_t index); -uint8_t *ByteStream_getPtr(ByteStream *self); -const uint8_t *ByteStream_getConstPtr(const ByteStream *self); - -const uint8_t *ByteStream_data(const ByteStream *self); -void *ByteStream_rawData(const ByteStream *self); -uint16_t ByteStream_rawSize(const ByteStream *self); -uint32_t ByteStream_size(const ByteStream *self); -uint32_t ByteStream_capacity(const ByteStream *self); -bool ByteStream_is_compressed(const ByteStream *self); -uint32_t ByteStream_CRC(const ByteStream *self); - -uint8_t *ByteStream_frontUnserializer(const ByteStream *self); - -// -- private helpers (exposed for internal use) -- -uint8_t *ByteStream_frontSerializer(const ByteStream *self); - -// RawBuffer free functions -uint32_t RawBuffer_hash(const RawBuffer *self); -void RawBuffer_accumulate(RawBuffer *self, uint32_t val); -bool RawBuffer_verify(const RawBuffer *self); -void RawBuffer_recalcCRC(RawBuffer *self); + friend class Storage; + +public: + ByteStream(uint32_t size = 0, const uint8_t *buf = nullptr); + ~ByteStream(); + + // copy and assignment operators + ByteStream(const ByteStream &other); + void operator=(const ByteStream &other); + + // move operator since std::move not always available + void move(ByteStream *target); + + // used to initialize the full RawBuffer with a chunk of data, + // this includes the size, flags, crc, and data + bool rawInit(const uint8_t *rawdata, uint32_t size); + + // reset the buffer to a specific capcity + bool init(uint32_t capacity = 0, const uint8_t *buf = nullptr); + + // clear the buffer + void clear(); + + // shrink capacity down to size, to free unused space + bool shrink(); + + // append another buffer + bool append(const ByteStream &other); + + // extend the storage without changing the size of the data + bool extend(uint32_t size); + + // trim the storage and erase some bytes from the end + void trim(uint32_t bytes); + + // badly written in-place compression and decompression + // which just uses a table of values then compresses the + // data into indexes in the table packed into a buffer. + // Unfortunately sometimes compression does not yield a + // smaller buffer size, incases like this compress returns + // true but the data isn't compressed. Similarly, if that + // data is passed to decompress it will return true. + bool compress(); + bool decompress(); + + // re-calculate the built-in CRC on the bytestream if needed + // if the crc is up to date this will do nothing. Optionally + // force a re-calculation even if the dirty flag is missing. + // Will return the new CRC value. + uint32_t recalcCRC(bool force = false); + + // helper func to ensure the internal buffer is sane, use this + // after reading into the rawdata of the buffer + void sanity(); + // check the CRC without re-calculating, note, if the CRC is + // dirty then this will simply return false + bool checkCRC() const; + // check whether the data in the buffer has changed since the + // crc has been re-calculated, if it is dirty, call recalcCRC + bool isCRCDirty() const; + // mark the CRC as dirty (manually modified the buffer or something) + void setCRCDirty(); + + // serialize a byte into the buffer + bool serialize8(uint8_t byte); + bool serialize16(uint16_t bytes); + bool serialize32(uint32_t bytes); + + // reset the unserializer index + void resetUnserializer(); + // move the unserializer index manually + void moveUnserializer(uint32_t idx); + // check if the unserializer is at the end + bool unserializerAtEnd() const; + + // unserialize data out of the buffer + bool unserialize8(uint8_t *byte); + bool unserialize16(uint16_t *bytes); + bool unserialize32(uint32_t *bytes); + + // unserialize data and erase it from the buffer + bool consume8(uint8_t *byte = nullptr); + bool consume16(uint16_t *bytes = nullptr); + bool consume32(uint32_t *bytes = nullptr); + + // unserialize and consume a whole chunk + bool consume(uint32_t size, void *bytes = nullptr); + + uint8_t peek8() const; + uint16_t peek16() const; + uint32_t peek32() const; + + // overload [] for array access (no bounds check lol) + uint8_t &operator[](uint32_t index) { return m_pData->buf[index]; } + // overload (uint8_t *) for cast to buffer + operator uint8_t *() const { return m_pData ? m_pData->buf : nullptr; } + operator const uint8_t *() const { return m_pData ? m_pData->buf : nullptr; } + + // return the members + const uint8_t *data() const { return m_pData ? m_pData->buf : nullptr; } + void *rawData() const { return m_pData; } + uint16_t rawSize() const { return m_pData ? (uint16_t)m_pData->size + sizeof(RawBuffer) : 0; } + uint32_t size() const { return m_pData ? m_pData->size : 0; } + uint32_t capacity() const { return m_capacity; } + bool is_compressed() const; + uint32_t CRC() const { return m_pData ? m_pData->crc32 : 0; } + + uint8_t *frontUnserializer() const { return m_pData ? m_pData->buf + m_position : nullptr; } + +private: + // fetch pointers that walk the buffer to serialize/unserialize + //uint8_t *frontUnserializer() const { return m_pData ? m_pData->buf + m_position : nullptr; } + uint8_t *frontSerializer() const { return m_pData ? m_pData->buf + m_pData->size : nullptr; } + // check if the buffer is large enough for the amount + bool largeEnough(uint32_t amount) const; + // helper to get width of bits that makes up value + uint32_t getWidth(uint32_t value) const; + + // the structure of raw data that's written to storage + struct RawBuffer + { + RawBuffer() : size(0), flags(0), crc32(5381) {} + // hash the raw buffer into crc + uint32_t hash() const + { + uint32_t hash = 5381; + for (uint32_t i = 0; i < size; ++i) { + hash = ((hash << 5) + hash) + buf[i]; + } + return hash; + } + // accumulate the data into the hash + void accumulate(uint32_t val) + { + crc32 = ((crc32 << 5) + crc32) + val; + } + // veryify the crc + bool verify() const + { + // if the buffer is empty then 'verify' should just return true + if (!size) { + return true; + } + if (hash() != crc32) { + DEBUG_LOGF("CRC mismatch: %x should be %x", hash(), crc32); + return false; + } + return true; + } + // re-calculate the crc + void recalcCRC() + { + crc32 = hash(); + } + // the size of the buf of data + uint32_t size; + // any special flags about the data (compression etc) + uint32_t flags; + // the crc32 for the data + uint32_t crc32; + // the start of the buf of data + uint8_t buf[]; + }; + + // The raw buffer of data along with size and flags + RawBuffer *m_pData; + // the index in the raw buffer for unserialization + uint16_t m_position; + // the actual size of the buffer raw buffer + uint16_t m_capacity; +}; #endif diff --git a/VortexEngine/src/Serial/Serial.h b/VortexEngine/src/Serial/Serial.h index f0bfd9b6c9..0121b5bbb0 100644 --- a/VortexEngine/src/Serial/Serial.h +++ b/VortexEngine/src/Serial/Serial.h @@ -2,17 +2,40 @@ #define SERIAL_H #include -#include - -typedef struct ByteStream ByteStream; - -bool SerialComs_init(); -void SerialComs_cleanup(); -bool SerialComs_isConnected(); -bool SerialComs_checkSerial(); -void SerialComs_write(const char *msg, ...); -void SerialComs_writeStream(ByteStream *byteStream); -void SerialComs_read(ByteStream *byteStream); -bool SerialComs_dataReady(); + +class ByteStream; + +// Really wish I could name this Serial but arduino ruined that for me +class SerialComs +{ +public: + + // init serial + static bool init(); + static void cleanup(); + + // whether serial is initialized + static bool isConnected(); + + // check for any serial connection or messages + static bool checkSerial(); + + // write a message to serial + static void write(const char *msg, ...); + + // write raw data + static void write(ByteStream &byteStream); + + // read a message from serial + static void read(ByteStream &byteStream); + + // data in the socket ready to read + static bool dataReady(); + +private: + // whether serial communications are initialized + static bool m_serialConnected; + static uint32_t m_lastCheck; +}; #endif diff --git a/VortexEngine/src/Storage/Storage.h b/VortexEngine/src/Storage/Storage.h index eecf43cb4d..80da3edcb9 100644 --- a/VortexEngine/src/Storage/Storage.h +++ b/VortexEngine/src/Storage/Storage.h @@ -2,21 +2,46 @@ #define STORAGE_H #include -#include -typedef struct ByteStream ByteStream; +#ifdef VORTEX_LIB +#include +#endif + +class ByteStream; -bool Storage_init(); -void Storage_cleanup(); +class Storage +{ + Storage(); +public: -bool Storage_write(uint8_t slot, ByteStream *buffer); -bool Storage_read(uint8_t slot, ByteStream *buffer); + // init storage + static bool init(); + static void cleanup(); -uint32_t Storage_lastSaveSize(); + // store a serial buffer to storage + static bool write(uint8_t slot, ByteStream &buffer); + // read a serial buffer from storage + static bool read(uint8_t slot, ByteStream &buffer); + + // the last save size (use STORAGE_SIZE For total space) + static uint32_t lastSaveSize(); #ifdef VORTEX_LIB -void Storage_setStorageFilename(const char *name); -const char *Storage_getStorageFilename(); + static void setStorageFilename(const std::string &name) { m_storageFilename = name; } + static std::string getStorageFilename() { return m_storageFilename; } #endif +private: + // write out a single byte to the eeprom + static void eepromWriteByte(uint16_t index, uint8_t in); + static uint8_t eepromReadByte(uint16_t index); + +#ifdef VORTEX_LIB + static std::string m_storageFilename; +#endif + + // the size of the last save + static uint32_t m_lastSaveSize; +}; + #endif diff --git a/VortexEngine/src/Time/TimeControl.h b/VortexEngine/src/Time/TimeControl.h index 1741d29188..7c7a061984 100644 --- a/VortexEngine/src/Time/TimeControl.h +++ b/VortexEngine/src/Time/TimeControl.h @@ -2,66 +2,140 @@ #define TIME_CONTROL_H #include -#include + #include "../VortexConfig.h" #include "../Leds/LedTypes.h" #if VARIABLE_TICKRATE == 1 -#define MS_TO_TICKS(ms) Time_millisecondsToTicks(ms) -#define SEC_TO_TICKS(s) Time_secondsToTicks(s) +#define MS_TO_TICKS(ms) Time::millisecondsToTicks(ms) +#define SEC_TO_TICKS(s) Time::secondsToTicks(s) #else #define MS_TO_TICKS(ms) (uint32_t)(((uint32_t)(ms) * DEFAULT_TICKRATE) / 1000) #define SEC_TO_TICKS(s) (uint32_t)((uint32_t)(s) * DEFAULT_TICKRATE) #endif #ifdef VORTEX_LIB -#define SIMULATION_TICK Time_getSimulationTick() +#define SIMULATION_TICK getSimulationTick() #else #define SIMULATION_TICK 0 #endif -bool Time_init(void); -void Time_cleanup(void); -void Time_tickClock(void); -uint32_t Time_getCurtime(void); -uint32_t Time_getRealCurtime(void); -void Time_setTickrate(uint32_t tickrate); -uint32_t Time_getTickrate(void); +class Time +{ + // private unimplemented constructor + Time(); + +public: + // opting for static class here because there should only ever be one + // Settings control object and I don't like singletons + static bool init(); + static void cleanup(); + + // tick the clock forward to millis() + static void tickClock(); + + // get the current tick, offset by any active simulation (simulation only exists in vortexlib) + // Exposing this in the header seems to save on space a non negligible amount, it is used a lot + // and exposing in the header probably allows the compiler to optimize away repititive calls + static uint32_t getCurtime() { return m_curTick + SIMULATION_TICK; } + + // this ignore simulation time, it's used by timers to make simulations work + static uint32_t getRealCurtime(); + + // Set tickrate in Ticks Per Second (TPS) + // The valid range for this is 1 <= x <= 1000000 + // + // Setting a value of 0 will restore the default + // + // NOTE: Patterns will not change when this changes, you + // must re-create the pattern for the change to take + // effect. This is done by design to allow the test + // framework to control the speed of patterns. + // See PatternBuilder.cpp for more info. + static void setTickrate(uint32_t tickrate = 0); + + // The current tickrate + static uint32_t getTickrate(); + + // these apis are only present if the tickrate can change #if VARIABLE_TICKRATE == 1 -uint32_t Time_millisecondsToTicks(uint32_t ms); -uint32_t Time_secondsToTicks(uint32_t sec); + // convert milliseconds to a tickcount based on tickrate + static uint32_t millisecondsToTicks(uint32_t ms); + + // convert seconds to a tickcount based on tickrate + static uint32_t secondsToTicks(uint32_t sec) { return millisecondsToTicks(sec * 1000); } #endif -uint32_t Time_microseconds(void); -void Time_delayMicroseconds(uint32_t us); -void Time_delayMilliseconds(uint32_t ms); + + // Current microseconds since startup, only use this for things like measuring rapid data transfer timings. + // If you just need to perform regular time checks for a pattern or some logic then use getCurtime() and measure + // time in ticks, use the SEC_TO_TICKS() or MS_TO_TICKS() macros to convert timings to measures of ticks for + // purpose of comparing against getCurtime() + static uint32_t microseconds(); + + // delay for some number of microseconds or milliseconds, these are bad + static void delayMicroseconds(uint32_t us); + static void delayMilliseconds(uint32_t ms); #ifdef VORTEX_LIB -uint32_t Time_startSimulation(void); -uint32_t Time_tickSimulation(void); -bool Time_isSimulation(void); -uint32_t Time_getSimulationTick(void); -uint32_t Time_endSimulation(void); -void Time_setInstantTimestep(bool instant); -bool Time_isInstantStepping(void); + // Start a time simulation, while the simulation is active you can + // increment the 'current time' with tickSimulation() and all calls + // to Time::getCurtime will reflect the changes, then when you call + // endSimulation() the currentTime will be restored + static uint32_t startSimulation(); + + // Tick a time simulation forward, returning the next tick + static uint32_t tickSimulation(); + + // Whether running a time simulation + static bool isSimulation(); + + // get the current tick in the simulation + static uint32_t getSimulationTick(); + + // Finish a time simulation + static uint32_t endSimulation(); + + // Toggle instant step + static void setInstantTimestep(bool instant) { m_instantTimestep = instant; } + static bool isInstantStepping() { return m_instantTimestep; } #endif #if TIMER_TEST == 1 -void Time_test(void); + static void test(); +#endif + +private: +#ifdef VORTEX_EMBEDDED + static void initMCUTime(); #endif -extern uint32_t Time_curTick; #if VARIABLE_TICKRATE == 1 -extern uint32_t Time_tickrate; + // the number of ticks per second + static uint32_t m_tickrate; #endif + + // global tick counter + static uint32_t m_curTick; + #ifdef VORTEX_LIB -extern uint32_t Time_prevTime; -extern uint32_t Time_firstTime; -extern uint32_t Time_simulationTick; -extern bool Time_isSimulation_; -extern bool Time_instantTimestep; -#endif -#ifdef VORTEX_EMBEDDED -void Time_initMCUTime(void); + // the last frame timestamp + static uint32_t m_prevTime; + + // the first timestamp + static uint32_t m_firstTime; + + // the current simulation offset, simulations are + // used to fastforward patterns and colorsets by + // simulating tick changes and running pattern logic + // multiple times in a single frame + static uint32_t m_simulationTick; + + // whether the timer is running a simulation + static bool m_isSimulation; + + // whether to disable timestep and instantly tick + static bool m_instantTimestep; #endif +}; #endif diff --git a/VortexEngine/src/Time/Timer.h b/VortexEngine/src/Time/Timer.h index 63482286b1..bb69ab2530 100644 --- a/VortexEngine/src/Time/Timer.h +++ b/VortexEngine/src/Time/Timer.h @@ -2,51 +2,92 @@ #define TIMER_H #include -#include #include "../Leds/LedTypes.h" #define ALARM_NONE -1 +// an alarm id is just an int typedef int8_t AlarmID; #define TIMER_FLAGS_NONE 0 +// the number of alarms #define TIMER_1_ALARM 1 #define TIMER_2_ALARMS 2 #define TIMER_3_ALARMS 3 #define TIMER_4_ALARMS 4 +// bitmask for all alarms #define TIMER_ALARM_MASK ( 1 | 2 | 3 | 4 ) +// start the alarm #define TIMER_START (1 << 7) -typedef struct Timer_s { - uint32_t *alarms; - uint8_t numAlarms; - AlarmID curAlarm; - uint32_t startTime; -#ifdef VORTEX_LIB - uint32_t simStartTime; -#endif -} Timer; - -void Timer_init(Timer *self); -void Timer_cleanup(Timer *self); -void Timer_initFlags(Timer *self, uint8_t flags, uint8_t alarm1, uint8_t alarm2, uint8_t alarm3, uint8_t alarm4); -AlarmID Timer_addAlarm(Timer *self, uint32_t interval); -void Timer_restart(Timer *self, uint32_t offset); -void Timer_start(Timer *self, uint32_t offset); -void Timer_reset(Timer *self); -bool Timer_onStart(const Timer *self); -bool Timer_onEnd(const Timer *self); -AlarmID Timer_alarm(Timer *self); -uint32_t Timer_numAlarms(const Timer *self); -uint32_t Timer_curAlarm(const Timer *self); -uint32_t Timer_startTime(const Timer *self); +class Timer +{ +public: + Timer(); + ~Timer(); + + // init a timer with a number of alarms and optionally start it + void init(uint8_t flags = TIMER_FLAGS_NONE, uint8_t alarm1 = 0, + uint8_t alarm2 = 0, uint8_t alarm3 = 0, uint8_t alarm4 = 0); + + // add alarms to the timer and returns it's ID + // Alarm IDs start at 0 and count upward + AlarmID addAlarm(uint32_t interval); + + // restart the timer entirely + void restart(uint32_t offset = 0); + + // start the timer but don't change current alarm, this shifts + // the timer startTime but does not reset it's alarm state + void start(uint32_t offset = 0); + + // delete all alarms from the timer and reset + void reset(); + + // returns true when the current timer is starting, used for synchronization + bool onStart() const; + // returns true when the current timer is ending, used for synchronization + bool onEnd() const; + + // Will return the alarmID of the alarm that triggered + // If no alarm hits it will return ALARM_NONE. + // Outputs an integer indicating the number of times the alarm has triggered + // since the start time or the last detection of that alarm ID + AlarmID alarm(); + + // the number of alarms + uint32_t numAlarms() const { return m_numAlarms; } + uint32_t curAlarm() const { return m_curAlarm; } + + // the start time of the timer + uint32_t startTime() const { return m_startTime; } #if TIMER_TEST == 1 -void Timer_test(void); + static void test(); #endif +private: + // helpers to set/get start time + uint32_t getStartTime() const; + void setStartTime(uint32_t); + + // the list of alarms and number of alarms + uint32_t *m_alarms; + uint8_t m_numAlarms; + // the current alarm being checked + AlarmID m_curAlarm; + // start time in microseconds + uint32_t m_startTime; + + // only need time simulations for test framework pattern strip generation +#ifdef VORTEX_LIB + // the simulation start time + uint32_t m_simStartTime; #endif +}; + +#endif \ No newline at end of file diff --git a/VortexEngine/src/VortexEngine.h b/VortexEngine/src/VortexEngine.h index d36b0f3b95..ae1b054840 100644 --- a/VortexEngine/src/VortexEngine.h +++ b/VortexEngine/src/VortexEngine.h @@ -2,37 +2,77 @@ #define VORTEX_ENGINE_H #include -#include #include "VortexConfig.h" -typedef struct ByteStream ByteStream; -typedef struct Mode Mode; +class ByteStream; +class Mode; -bool VortexEngine_init(void); -void VortexEngine_cleanup(void); -void VortexEngine_tick(void); -void VortexEngine_runMainLogic(void); -bool VortexEngine_serializeVersion(ByteStream *stream); -bool VortexEngine_checkVersion(uint8_t major, uint8_t minor); -Mode *VortexEngine_curMode(void); +class VortexEngine +{ + // private unimplemented constructor + VortexEngine(); + +public: + // setup and initialization function + static bool init(); + // cleanup + static void cleanup(); + + // tick function for each loop + static void tick(); + + // run the main logic of the engine + static void runMainLogic(); + + // serialize the version number to a byte stream + static bool serializeVersion(ByteStream &stream); + static bool checkVersion(uint8_t major, uint8_t minor); + + // get the current mode + static Mode *curMode(); #ifdef VORTEX_LIB -uint32_t VortexEngine_totalStorageSpace(void); -uint32_t VortexEngine_savefileSize(void); -bool VortexEngine_isSleeping(void); + // the total available storage space + static uint32_t totalStorageSpace(); + // the size of the savefile + static uint32_t savefileSize(); + // whether sleeping or not + static bool isSleeping() { return m_sleeping; } #endif -void VortexEngine_enterSleep(bool save); -void VortexEngine_wakeup(bool reset); -void VortexEngine_toggleForceSleep(bool enabled); -void VortexEngine_setAutoCycle(bool enabled); -void VortexEngine_compressionTest(void); -void VortexEngine_serializationTest(void); -void VortexEngine_timerTest(void); + // enter/leave sleep mode + static void enterSleep(bool save = true); + static void wakeup(bool reset = true); + + // toggle force sleep from working + static void toggleForceSleep(bool enabled) { m_forceSleepEnabled = enabled; } + // toggle auto cycle enabled + static void setAutoCycle(bool enabled) { m_autoCycle = enabled; } #ifdef VORTEX_EMBEDDED -void VortexEngine_clearOutputPins(void); -void VortexEngine_enableMOSFET(bool enabled); + // clear output pins + static void clearOutputPins(); + // enable/disable the mosfet + static void enableMOSFET(bool enabled); +#endif + +private: + // bool in vortexlib to simulate sleeping + static volatile bool m_sleeping; + static bool m_forceSleepEnabled; + + // whether auto cycle modes is turned on + static bool m_autoCycle; + +#if COMPRESSION_TEST == 1 + static void compressionTest(); +#endif +#if SERIALIZATION_TEST == 1 + static void serializationTest(); +#endif +#if TIMER_TEST == 1 + static void timerTest(); #endif +}; #endif diff --git a/VortexEngine/src/Wireless/IRReceiver.h b/VortexEngine/src/Wireless/IRReceiver.h index 2c3ded705e..14dff9069f 100644 --- a/VortexEngine/src/Wireless/IRReceiver.h +++ b/VortexEngine/src/Wireless/IRReceiver.h @@ -2,7 +2,6 @@ #define IR_RECEIVER_H #include -#include #include "../Serial/BitStream.h" @@ -10,43 +9,73 @@ #if IR_ENABLE_RECEIVER == 1 -typedef struct ByteStream ByteStream; -typedef struct Mode Mode; +class ByteStream; +class Mode; -typedef enum { - IR_WAITING_HEADER_MARK, - IR_WAITING_HEADER_SPACE, - IR_READING_DATA_MARK, - IR_READING_DATA_SPACE -} IRRecvState; +class IRReceiver +{ + IRReceiver(); -extern BitStream IRReceiver_irData; -extern IRRecvState IRReceiver_recvState; -extern uint32_t IRReceiver_prevTime; -extern uint8_t IRReceiver_pinState; -extern uint32_t IRReceiver_previousBytes; +public: + // init and cleanup the receiver + static bool init(); + static void cleanup(); -bool IRReceiver_init(); -void IRReceiver_cleanup(); + // check whether a full IR message is ready to read + static bool dataReady(); + // whether actively receiving + static bool isReceiving(); + // the percent of data received + static uint8_t percentReceived(); + static uint16_t bytesReceived() { return m_irData.bytepos(); } -bool IRReceiver_dataReady(); -bool IRReceiver_isReceiving(); -uint8_t IRReceiver_percentReceived(); + // receive the IR message into a target mode + static bool receiveMode(Mode *pMode); -static inline uint16_t IRReceiver_bytesReceived() { - return BitStream_bytepos(&IRReceiver_irData); -} + // turn the receiver on/off + static bool beginReceiving(); + static bool endReceiving(); + // checks if there is new data since the last reset or check + static bool onNewData(); + // reset IR receiver buffer + static void resetIRState(); -bool IRReceiver_receiveMode(Mode *pMode); +private: -bool IRReceiver_beginReceiving(); -bool IRReceiver_endReceiving(); -bool IRReceiver_onNewData(); -void IRReceiver_resetIRState(); + // reading functions + // PCI handler for when IR receiver pin changes states + static bool read(ByteStream &data); + static void recvPCIHandler(); + static void handleIRTiming(uint32_t diff); -bool IRReceiver_read(ByteStream *data); -void IRReceiver_recvPCIHandler(); -void IRReceiver_handleIRTiming(uint32_t diff); + // =================== + // private data: + + // BitStream object that IR data is fed to bit by bit + static BitStream m_irData; + + // Receive state used for state machine in PCIhandler + enum RecvState : uint8_t + { + WAITING_HEADER_MARK, + WAITING_HEADER_SPACE, + READING_DATA_MARK, + READING_DATA_SPACE + }; + + // state information used by the PCIHandler + static RecvState m_recvState; + // used to track pin changes + static uint32_t m_prevTime; + static uint8_t m_pinState; + + // used to compare if received data has changed since last checking + static uint32_t m_previousBytes; + +#ifdef VORTEX_LIB + friend class Vortex; +#endif +}; #endif diff --git a/VortexEngine/src/Wireless/IRSender.h b/VortexEngine/src/Wireless/IRSender.h index ea6c5eee42..d8e9df30eb 100644 --- a/VortexEngine/src/Wireless/IRSender.h +++ b/VortexEngine/src/Wireless/IRSender.h @@ -1,9 +1,6 @@ #ifndef IR_SENDER_H #define IR_SENDER_H -#include -#include - #include "../Serial/ByteStream.h" #include "../Serial/BitStream.h" @@ -11,43 +8,58 @@ #if IR_ENABLE_SENDER == 1 -typedef struct Mode Mode; +class Mode; + +class IRSender +{ + IRSender(); + +public: + static bool init(); + static void cleanup(); -typedef struct IRSenderCallbacks_s { - void (*infraredWrite)(bool mark, uint32_t amount); -} IRSenderCallbacks; + // initialize the IR sender with a mode to send + static bool loadMode(const Mode *targetMode); + static bool send(); -extern IRSenderCallbacks *g_irSenderCallbacks; + static bool isSending() { return m_isSending; } -extern ByteStream IRSender_serialBuf; -extern BitStream IRSender_bitStream; -extern bool IRSender_isSending; -extern uint32_t IRSender_lastSendTime; -extern uint32_t IRSender_size; -extern uint8_t IRSender_numBlocks; -extern uint8_t IRSender_remainder; -extern uint32_t IRSender_blockSize; -extern uint32_t IRSender_writeCounter; + static uint32_t percentDone() { return (uint32_t)(((float)m_writeCounter / (float)m_size) * 100.0); } -bool IRSender_init(); -void IRSender_cleanup(); +private: + // sender functions + static void beginSend(); + // send a full 8 bits in a tight loop + static void sendByte(uint8_t data); + // send a mark/space by turning PWM on/off + static void sendMark(uint16_t time); + static void sendSpace(uint16_t time); + // Pulse-Width Modulator (IR Transmitter) + static void initPWM(); + // turn the IR transmitter on/off in realtime + static void startPWM(); + static void stopPWM(); -bool IRSender_loadMode(const Mode *targetMode); -bool IRSender_send(); + // the serial buffer for the data + static ByteStream m_serialBuf; + // a bit walker for the serial data + static BitStream m_bitStream; + static bool m_isSending; + static uint32_t m_lastSendTime; -static inline bool IRSender_isSending_get() { return IRSender_isSending; } + // some runtime meta info + static uint32_t m_size; + // the number of blocks that will be sent + static uint8_t m_numBlocks; + // the amount in the final block + static uint8_t m_remainder; -static inline uint32_t IRSender_percentDone() { - return (uint32_t)(((float)IRSender_writeCounter / (float)IRSender_size) * 100.0); -} + // configuration options for the sender + static uint32_t m_blockSize; -void IRSender_beginSend(); -void IRSender_sendByte(uint8_t data); -void IRSender_sendMark(uint16_t time); -void IRSender_sendSpace(uint16_t time); -void IRSender_initPWM(); -void IRSender_startPWM(); -void IRSender_stopPWM(); + // write total + static uint32_t m_writeCounter; +}; #endif diff --git a/VortexEngine/src/Wireless/VLReceiver.h b/VortexEngine/src/Wireless/VLReceiver.h index ace7b89df1..96482cea1f 100644 --- a/VortexEngine/src/Wireless/VLReceiver.h +++ b/VortexEngine/src/Wireless/VLReceiver.h @@ -2,7 +2,6 @@ #define VL_RECEIVER_H #include -#include #include "../Serial/BitStream.h" @@ -10,56 +9,96 @@ #if VL_ENABLE_RECEIVER == 1 -typedef struct ByteStream ByteStream; -typedef struct Mode Mode; - -typedef enum { - VL_WAITING_HEADER_MARK, - VL_WAITING_HEADER_SPACE, - VL_READING_BAUD_MARK, - VL_READING_BAUD_SPACE, - VL_READING_DATA_MARK, - VL_READING_DATA_SPACE, - VL_READING_DATA_PARITY_MARK, - VL_READING_DATA_PARITY_SPACE -} VLRecvState; - -extern BitStream VLReceiver_vlData; -extern VLRecvState VLReceiver_recvState; -extern uint32_t VLReceiver_prevTime; -extern uint8_t VLReceiver_pinState; -extern uint16_t VLReceiver_previousBytes; -extern uint16_t VLReceiver_vlMarkThreshold; -extern uint16_t VLReceiver_vlSpaceThreshold; -extern uint8_t VLReceiver_counter; -extern uint8_t VLReceiver_parityBit; -extern bool VLReceiver_legacy; - -bool VLReceiver_init(); -void VLReceiver_cleanup(); - -bool VLReceiver_dataReady(); -bool VLReceiver_isReceiving(); -uint8_t VLReceiver_percentReceived(); - -static inline uint16_t VLReceiver_bytesReceived() { - return BitStream_bytepos(&VLReceiver_vlData); -} - -bool VLReceiver_receiveMode(Mode *pMode); - -bool VLReceiver_beginReceiving(); -bool VLReceiver_endReceiving(); -bool VLReceiver_onNewData(); -void VLReceiver_resetVLState(); - -void VLReceiver_setLegacyReceiver(bool legacy); - -void VLReceiver_recvPCIHandler(); - -bool VLReceiver_read(ByteStream *data); -void VLReceiver_handleVLTiming(uint16_t diff); -void VLReceiver_handleVLTimingLegacy(uint16_t diff); +class ByteStream; +class Mode; + +class VLReceiver +{ + VLReceiver(); + +public: + // init and cleanup the receiver + static bool init(); + static void cleanup(); + + // check whether a full VL message is ready to read + static bool dataReady(); + // whether actively receiving + static bool isReceiving(); + // the percent of data received + static uint8_t percentReceived(); + static uint16_t bytesReceived() { return m_vlData.bytepos(); } + + // receive the VL message into a target mode + static bool receiveMode(Mode *pMode); + + // turn the receiver on/off + static bool beginReceiving(); + static bool endReceiving(); + // checks if there is new data since the last reset or check + static bool onNewData(); + // reset VL receiver buffer + static void resetVLState(); + + // turn on/off the legacy receiver + static void setLegacyReceiver(bool legacy) { m_legacy = legacy; } + + // The handler called when the VL receiver flips between on/off + // this has to be public because it's called from a global ISR + // but technically it's an internal function for VLReceiver + static void recvPCIHandler(); +private: + // reading functions + // PCI handler for when VL receiver pin changes states + static bool read(ByteStream &data); + static void handleVLTiming(uint16_t diff); + static void handleVLTimingLegacy(uint16_t diff); + + // =================== + // private data: + + // BitStream object that VL data is fed to bit by bit + static BitStream m_vlData; + + // Receive state used for state machine in PCIhandler + enum RecvState : uint8_t + { + WAITING_HEADER_MARK, + WAITING_HEADER_SPACE, + READING_BAUD_MARK, + READING_BAUD_SPACE, + READING_DATA_MARK, + READING_DATA_SPACE, + READING_DATA_PARITY_MARK, + READING_DATA_PARITY_SPACE + }; + + // state information used by the PCIHandler + static RecvState m_recvState; + // used to track pin changes + static uint32_t m_prevTime; + static uint8_t m_pinState; + + // used to compare if received data has changed since last checking + static uint16_t m_previousBytes; + + // the determined time based on sync + static uint16_t m_vlMarkThreshold; + // the determined time based on sync + static uint16_t m_vlSpaceThreshold; + + // count of the sync bits (similar length starter bits) + static uint8_t m_counter; + + static uint8_t m_parityBit; + + // legacy mode + static bool m_legacy; + +#ifdef VORTEX_LIB + friend class Vortex; +#endif +}; #endif diff --git a/VortexEngine/src/Wireless/VLSender.h b/VortexEngine/src/Wireless/VLSender.h index 3ad2880583..a368201d99 100644 --- a/VortexEngine/src/Wireless/VLSender.h +++ b/VortexEngine/src/Wireless/VLSender.h @@ -1,9 +1,6 @@ #ifndef VL_SENDER_H #define VL_SENDER_H -#include -#include - #include "../Serial/ByteStream.h" #include "../Serial/BitStream.h" @@ -11,31 +8,41 @@ #if VL_ENABLE_SENDER == 1 -typedef struct Mode Mode; - -typedef struct VLSenderCallbacks_s { - void (*infraredWrite)(bool mark, uint32_t amount); -} VLSenderCallbacks; - -extern VLSenderCallbacks *g_vlSenderCallbacks; - -extern ByteStream VLSender_serialBuf; -extern BitStream VLSender_bitStream; -extern uint8_t VLSender_size; -extern uint8_t VLSender_parity; - -bool VLSender_init(); -void VLSender_cleanup(); - -void VLSender_send(const Mode *targetMode); -void VLSender_sendLegacy(const Mode *targetMode); - -bool VLSender_loadMode(const Mode *targetMode); -void VLSender_sendByte(uint8_t data); -void VLSender_sendByteLegacy(uint8_t data); -void VLSender_sendMarkSpace(uint16_t markTime, uint16_t spaceTime); -void VLSender_startPWM(); -void VLSender_stopPWM(); +class Mode; + +class VLSender +{ + VLSender(); + +public: + static bool init(); + static void cleanup(); + + // send a mode + static void send(const Mode *targetMode); + static void sendLegacy(const Mode *targetMode); + +private: + static bool loadMode(const Mode *targetMode); + // send a full 8 bits in a tight loop + static void sendByte(uint8_t data); + // send full 8 bits legacy protocol + static void sendByteLegacy(uint8_t data); + // send a mark/space by turning PWM on/off + static void sendMarkSpace(uint16_t markTime, uint16_t spaceTime); + // turn the VL transmitter on/off in realtime + static void startPWM(); + static void stopPWM(); + + // the serial buffer for the data + static ByteStream m_serialBuf; + // a bit walker for the serial data + static BitStream m_bitStream; + + // some runtime meta info + static uint8_t m_size; + static uint8_t m_parity; +}; #endif From 1f2bf1acd019c2e4b2a877befbede7fb38914ca8 Mon Sep 17 00:00:00 2001 From: Unreal-Dan Date: Fri, 10 Jul 2026 01:26:46 -0700 Subject: [PATCH 06/10] bypass lib and cli --- .github/workflows/duo_build.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/duo_build.yml b/.github/workflows/duo_build.yml index f7f5bdbf75..f62e507f7e 100644 --- a/.github/workflows/duo_build.yml +++ b/.github/workflows/duo_build.yml @@ -60,17 +60,17 @@ jobs: run: sudo apt-get install valgrind g++ make --fix-missing - name: Build desktop library + CLI run: | - export VORTEX_VERSION_MAJOR=${{ needs.setup.outputs.vortex_version_major }} - export VORTEX_VERSION_MINOR=${{ needs.setup.outputs.vortex_version_minor }} - export VORTEX_BUILD_NUMBER=${{ needs.setup.outputs.vortex_build_number }} - export VORTEX_VERSION_NUMBER=${{ needs.setup.outputs.vortex_version_number }} - make lib + #export VORTEX_VERSION_MAJOR=${{ needs.setup.outputs.vortex_version_major }} + #export VORTEX_VERSION_MINOR=${{ needs.setup.outputs.vortex_version_minor }} + #export VORTEX_BUILD_NUMBER=${{ needs.setup.outputs.vortex_build_number }} + #export VORTEX_VERSION_NUMBER=${{ needs.setup.outputs.vortex_version_number }} + #make lib working-directory: VortexEngine - name: Set execute permissions for test script - run: chmod +x ./runtests.sh + run: #chmod +x ./runtests.sh working-directory: VortexEngine/tests - name: Run general tests - run: ./runtests.sh --general + run: #./runtests.sh --general working-directory: VortexEngine/tests embedded: From 2e88d1d43c69e83f7c17111dd219ddaa702778c8 Mon Sep 17 00:00:00 2001 From: Unreal-Dan Date: Fri, 10 Jul 2026 01:30:28 -0700 Subject: [PATCH 07/10] small test fix --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0a1f85ffb9..7d10cd3d51 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ ifeq ($(OS),Windows_NT) # Windows DEVICE_DIR="C:/Program Files (x86)/Atmel/Studio/7.0/Packs/atmel/ATtiny_DFP/1.10.348/gcc/dev/attiny3217" INCLUDE_DIR="C:/Program Files (x86)/Atmel/Studio/7.0/Packs/atmel/ATtiny_DFP/1.10.348/include/" else # linux - BINDIR=~/atmel_setup/avr8-gnu-toolchain-linux_x86_64/bin/ + BINDIR=~/atmel_setup/avr8-gnu-toolchain-linux_x86_64/bin DEVICE_DIR=~/atmel_setup/gcc/dev/attiny3217 INCLUDE_DIR=~/atmel_setup/include/ endif From 62dfadbb26c18f6a486b41227c83f33c3f92a71f Mon Sep 17 00:00:00 2001 From: Unreal-Dan Date: Fri, 18 Sep 2026 13:33:38 -0700 Subject: [PATCH 08/10] fixed led stuck on green issue --- VortexEngine/appmain.c | 2 +- VortexEngine/src/Buttons/Button.c | 6 +- VortexEngine/src/Buttons/Buttons.c | 12 +- VortexEngine/src/Colors/ColorTypes.c | 3 +- VortexEngine/src/Colors/Colorset.c | 6 +- VortexEngine/src/Leds/LedStash.c | 2 +- VortexEngine/src/Leds/Leds.c | 6 +- VortexEngine/src/Log/Log.c | 4 +- VortexEngine/src/Memory/Memory.c | 3 +- VortexEngine/src/Menus/Menu.c | 14 +- VortexEngine/src/Menus/MenuList/ColorSelect.c | 17 +- .../src/Menus/MenuList/EditorConnection.c | 17 +- .../src/Menus/MenuList/FactoryReset.c | 15 +- .../src/Menus/MenuList/GlobalBrightness.c | 15 +- VortexEngine/src/Menus/MenuList/ModeSharing.c | 17 +- .../src/Menus/MenuList/PatternSelect.c | 16 +- VortexEngine/src/Menus/MenuList/Randomizer.c | 20 +- VortexEngine/src/Menus/Menus.c | 29 +- VortexEngine/src/Modes/DefaultModes.c | 3 +- VortexEngine/src/Modes/Mode.c | 20 +- VortexEngine/src/Modes/Modes.c | 563 ++++++------------ VortexEngine/src/Modes/Modes.h | 35 +- .../src/Patterns/Multi/BackStrobePattern.c | 2 +- .../src/Patterns/Multi/BouncePattern.c | 2 +- .../src/Patterns/Multi/CompoundPattern.c | 2 +- .../src/Patterns/Multi/CrossDopsPattern.c | 2 +- .../src/Patterns/Multi/DoubleStrobePattern.c | 2 +- .../src/Patterns/Multi/DripMorphPattern.c | 2 +- VortexEngine/src/Patterns/Multi/DripPattern.c | 2 +- VortexEngine/src/Patterns/Multi/FillPattern.c | 2 +- .../src/Patterns/Multi/HueShiftPattern.c | 2 +- .../src/Patterns/Multi/LighthousePattern.c | 2 +- .../src/Patterns/Multi/MeteorPattern.c | 2 +- .../src/Patterns/Multi/MultiLedPattern.c | 3 +- .../src/Patterns/Multi/PulsishPattern.c | 2 +- .../Patterns/Multi/Sequencer/ChaserPattern.c | 1 + .../src/Patterns/Multi/Sequencer/Sequence.c | 1 + .../Multi/Sequencer/SequencedPattern.c | 2 +- .../src/Patterns/Multi/SnowballPattern.c | 2 +- .../src/Patterns/Multi/SparkleTracePattern.c | 2 +- .../src/Patterns/Multi/TheaterChasePattern.c | 2 +- .../src/Patterns/Multi/VortexPattern.c | 2 +- .../src/Patterns/Multi/VortexWipePattern.c | 2 +- VortexEngine/src/Patterns/Multi/WarpPattern.c | 2 +- .../src/Patterns/Multi/WarpWormPattern.c | 2 +- .../src/Patterns/Multi/ZigzagPattern.c | 2 +- VortexEngine/src/Patterns/Pattern.c | 9 +- VortexEngine/src/Patterns/PatternArgs.c | 3 +- VortexEngine/src/Patterns/PatternBuilder.c | 72 ++- .../src/Patterns/Single/BasicPattern.c | 5 +- .../src/Patterns/Single/BlendPattern.c | 5 +- .../src/Patterns/Single/SingleLedPattern.c | 2 +- .../src/Patterns/Single/SolidPattern.c | 2 +- VortexEngine/src/Random/Random.c | 2 +- VortexEngine/src/Serial/BitStream.c | 63 +- VortexEngine/src/Serial/ByteStream.c | 122 ++-- VortexEngine/src/Serial/Compression.c | 4 +- VortexEngine/src/Serial/Serial.c | 7 +- VortexEngine/src/Storage/Storage.c | 14 +- VortexEngine/src/Time/TimeControl.c | 6 +- VortexEngine/src/Time/Timer.c | 21 +- VortexEngine/src/VortexEngine.c | 22 +- VortexEngine/src/Wireless/IRReceiver.c | 8 +- VortexEngine/src/Wireless/IRSender.c | 6 +- VortexEngine/src/Wireless/VLReceiver.c | 18 +- VortexEngine/src/Wireless/VLSender.c | 11 +- 66 files changed, 461 insertions(+), 813 deletions(-) diff --git a/VortexEngine/appmain.c b/VortexEngine/appmain.c index 6fe4dfe7fb..1063ff10c4 100644 --- a/VortexEngine/appmain.c +++ b/VortexEngine/appmain.c @@ -1,5 +1,5 @@ -#include "src/VortexEngine.h" +#include "src/c_types.h" int main(void) { VortexEngine_init(); diff --git a/VortexEngine/src/Buttons/Button.c b/VortexEngine/src/Buttons/Button.c index 66c32e88b5..afe31aee7b 100644 --- a/VortexEngine/src/Buttons/Button.c +++ b/VortexEngine/src/Buttons/Button.c @@ -1,15 +1,11 @@ -#include "Button.h" -#include "../Time/TimeControl.h" -#include "../Time/Timings.h" -#include "../Log/Log.h" +#include "../c_types.h" #ifdef VORTEX_LIB #include "VortexLib.h" #endif #ifdef VORTEX_EMBEDDED -#include "../VortexEngine.h" #include #include diff --git a/VortexEngine/src/Buttons/Buttons.c b/VortexEngine/src/Buttons/Buttons.c index 9326c85c37..f380fc891f 100644 --- a/VortexEngine/src/Buttons/Buttons.c +++ b/VortexEngine/src/Buttons/Buttons.c @@ -1,22 +1,17 @@ -#include "Buttons.h" -#include "../Memory/Memory.h" +#include "../c_types.h" #ifdef VORTEX_LIB #include "VortexLib.h" #else -#include "../Time/TimeControl.h" -#include "../Time/Timings.h" #endif Button *g_pButton = NULL; +static Button s_button; bool Buttons_init(uint8_t pin) { - g_pButton = (Button *)vmalloc(sizeof(Button)); - if (!g_pButton) { - return false; - } + g_pButton = &s_button; Button_init(g_pButton); return Button_initPin(g_pButton, pin); } @@ -25,7 +20,6 @@ void Buttons_cleanup(void) { if (g_pButton) { Button_cleanup(g_pButton); - vfree(g_pButton); g_pButton = NULL; } } diff --git a/VortexEngine/src/Colors/ColorTypes.c b/VortexEngine/src/Colors/ColorTypes.c index e22d80745d..622a33d10b 100644 --- a/VortexEngine/src/Colors/ColorTypes.c +++ b/VortexEngine/src/Colors/ColorTypes.c @@ -1,7 +1,6 @@ -#include "ColorTypes.h" -#include "../Serial/ByteStream.h" +#include "../c_types.h" hsv_to_rgb_algorithm g_hsv_rgb_alg = HSV_TO_RGB_GENERIC; void HSVColor_init(HSVColor *self) { diff --git a/VortexEngine/src/Colors/Colorset.c b/VortexEngine/src/Colors/Colorset.c index abb9528086..e3f5050fee 100644 --- a/VortexEngine/src/Colors/Colorset.c +++ b/VortexEngine/src/Colors/Colorset.c @@ -1,10 +1,6 @@ -#include "Colorset.h" -#include "../Serial/ByteStream.h" -#include "../Random/Random.h" -#include "../Memory/Memory.h" -#include "../Log/Log.h" #include +#include "../c_types.h" #define INDEX_NONE UINT8_MAX void Colorset_init(Colorset *self) diff --git a/VortexEngine/src/Leds/LedStash.c b/VortexEngine/src/Leds/LedStash.c index 46f6e0da22..606da6b058 100644 --- a/VortexEngine/src/Leds/LedStash.c +++ b/VortexEngine/src/Leds/LedStash.c @@ -1,5 +1,5 @@ -#include "LedStash.h" +#include "../c_types.h" void LedStash_init(LedStash *self) { (void)self; diff --git a/VortexEngine/src/Leds/Leds.c b/VortexEngine/src/Leds/Leds.c index 20a697d896..503e15e713 100644 --- a/VortexEngine/src/Leds/Leds.c +++ b/VortexEngine/src/Leds/Leds.c @@ -1,13 +1,9 @@ #include -#include "LedStash.h" -#include "Leds.h" -#include "../Time/TimeControl.h" -#include "../Modes/Modes.h" -#include "../VortexConfig.h" +#include "../c_types.h" #ifdef VORTEX_LIB #include "../../VortexLib/VortexLib.h" #endif diff --git a/VortexEngine/src/Log/Log.c b/VortexEngine/src/Log/Log.c index defabb61be..21902cc21f 100644 --- a/VortexEngine/src/Log/Log.c +++ b/VortexEngine/src/Log/Log.c @@ -1,12 +1,10 @@ -#include "Log.h" -#include "../Time/TimeControl.h" -#include "../Serial/Serial.h" #include #include #include +#include "../c_types.h" #ifdef VORTEX_LIB #include "VortexLib.h" #endif diff --git a/VortexEngine/src/Memory/Memory.c b/VortexEngine/src/Memory/Memory.c index d27ebba9fb..4d3cc60c05 100644 --- a/VortexEngine/src/Memory/Memory.c +++ b/VortexEngine/src/Memory/Memory.c @@ -1,10 +1,9 @@ -#include "Memory.h" #include #include -#include "../Log/Log.h" +#include "../c_types.h" #if DEBUG_ALLOCATIONS == 1 static uint32_t cur_mem_usage = 0; diff --git a/VortexEngine/src/Menus/Menu.c b/VortexEngine/src/Menus/Menu.c index 3ef226b52b..18a585c0eb 100644 --- a/VortexEngine/src/Menus/Menu.c +++ b/VortexEngine/src/Menus/Menu.c @@ -1,14 +1,6 @@ -#include "Menu.h" -#include "../Time/TimeControl.h" -#include "../Time/Timings.h" -#include "../Buttons/Button.h" -#include "../Menus/Menus.h" -#include "../Modes/Modes.h" -#include "../Modes/Mode.h" -#include "../Leds/Leds.h" -#include "../Log/Log.h" +#include "../c_types.h" void Menu_construct(Menu *self, RGBColor col, bool advanced) { self->vtable = &g_menuVTable; @@ -23,7 +15,7 @@ void Menu_construct(Menu *self, RGBColor col, bool advanced) void Menu_destroy(Menu *self) { - (void)self; + Mode_cleanup(&self->previewMode); } bool Menu_init(Menu *self) @@ -36,8 +28,10 @@ bool Menu_init(Menu *self) Mode tmpMode; Mode_initFromID(&tmpMode, PATTERN_STROBE, NULL); if (!Modes_addModeMode(&tmpMode)) { + Mode_cleanup(&tmpMode); return false; } + Mode_cleanup(&tmpMode); } if (!Modes_curMode()) { return false; diff --git a/VortexEngine/src/Menus/MenuList/ColorSelect.c b/VortexEngine/src/Menus/MenuList/ColorSelect.c index 7770d42bff..235c005ad5 100644 --- a/VortexEngine/src/Menus/MenuList/ColorSelect.c +++ b/VortexEngine/src/Menus/MenuList/ColorSelect.c @@ -1,21 +1,8 @@ -#include "ColorSelect.h" #include -#include "../../VortexEngine.h" -#include "../../Time/TimeControl.h" -#include "../../Patterns/PatternBuilder.h" -#include "../../Patterns/Pattern.h" -#include "../../Colors/Colorset.h" -#include "../../Buttons/Button.h" -#include "../../Random/Random.h" -#include "../../Time/Timings.h" -#include "../../Menus/Menus.h" -#include "../../Modes/Modes.h" -#include "../../Modes/Mode.h" -#include "../../Leds/Leds.h" -#include "../../Log/Log.h" +#include "../../c_types.h" #define COL32(v) ((RGBColor){ ((v) >> 16) & 0xFF, ((v) >> 8) & 0xFF, (v) & 0xFF }) static const uint8_t s_sats[4] = { SAT_OPTION_4, SAT_OPTION_3, SAT_OPTION_2, SAT_OPTION_1 }; @@ -29,7 +16,7 @@ extern void VortexEngine_toggleForceSleep(bool enabled); Menu *ColorSelectMenu_Create(RGBColor col, bool advanced) { - ColorSelectMenu *self = (ColorSelectMenu *)malloc(sizeof(ColorSelectMenu)); + ColorSelectMenu *self = &g_menuWorkspace.colorSelect; if (!self) return NULL; Menu_construct(&self->base, col, advanced); self->base.vtable = &g_colorSelectMenuVTable; diff --git a/VortexEngine/src/Menus/MenuList/EditorConnection.c b/VortexEngine/src/Menus/MenuList/EditorConnection.c index f286217fd7..dbd634d245 100644 --- a/VortexEngine/src/Menus/MenuList/EditorConnection.c +++ b/VortexEngine/src/Menus/MenuList/EditorConnection.c @@ -1,20 +1,9 @@ -#include "EditorConnection.h" - -#include "../../Patterns/PatternArgs.h" -#include "../../Serial/ByteStream.h" -#include "../../Serial/Serial.h" -#include "../../Storage/Storage.h" -#include "../../Wireless/VLSender.h" -#include "../../Time/TimeControl.h" -#include "../../Colors/Colorset.h" -#include "../../Modes/Modes.h" -#include "../../Modes/Mode.h" -#include "../../Leds/Leds.h" -#include "../../Log/Log.h" + #include #include +#include "../../c_types.h" #define COL32(v) ((RGBColor){ ((v) >> 16) & 0xFF, ((v) >> 8) & 0xFF, (v) & 0xFF }) static void clearDemo(EditorConnectionMenu *self); @@ -51,7 +40,7 @@ static const EditorConnectionCommandState s_commands[] = { Menu *EditorConnectionMenu_Create(RGBColor col, bool advanced) { - EditorConnectionMenu *self = (EditorConnectionMenu *)malloc(sizeof(EditorConnectionMenu)); + EditorConnectionMenu *self = &g_menuWorkspace.editorConnection; if (!self) return NULL; Menu_construct(&self->base, col, advanced); self->base.vtable = &g_editorConnectionMenuVTable; diff --git a/VortexEngine/src/Menus/MenuList/FactoryReset.c b/VortexEngine/src/Menus/MenuList/FactoryReset.c index ad7ec418a9..143a0ae2c5 100644 --- a/VortexEngine/src/Menus/MenuList/FactoryReset.c +++ b/VortexEngine/src/Menus/MenuList/FactoryReset.c @@ -1,19 +1,8 @@ -#include "FactoryReset.h" #include -#include "../../VortexEngine.h" -#include "../../Modes/DefaultModes.h" -#include "../../Time/TimeControl.h" -#include "../../Patterns/Pattern.h" -#include "../../Buttons/Button.h" -#include "../../Time/Timings.h" -#include "../../Modes/Modes.h" -#include "../../Modes/Mode.h" -#include "../../Leds/Leds.h" -#include "../../Log/Log.h" -#include "../../VortexConfig.h" +#include "../../c_types.h" #define COL32(v) ((RGBColor){ ((v) >> 16) & 0xFF, ((v) >> 8) & 0xFF, (v) & 0xFF }) static void showReset(FactoryResetMenu *self); @@ -22,7 +11,7 @@ extern void VortexEngine_setAutoCycle(bool enabled); Menu *FactoryResetMenu_Create(RGBColor col, bool advanced) { - FactoryResetMenu *self = (FactoryResetMenu *)malloc(sizeof(FactoryResetMenu)); + FactoryResetMenu *self = &g_menuWorkspace.factoryReset; if (!self) return NULL; Menu_construct(&self->base, col, advanced); self->base.vtable = &g_factoryResetMenuVTable; diff --git a/VortexEngine/src/Menus/MenuList/GlobalBrightness.c b/VortexEngine/src/Menus/MenuList/GlobalBrightness.c index 919b08b665..0f7674f340 100644 --- a/VortexEngine/src/Menus/MenuList/GlobalBrightness.c +++ b/VortexEngine/src/Menus/MenuList/GlobalBrightness.c @@ -1,19 +1,8 @@ -#include "GlobalBrightness.h" #include -#include "../../VortexEngine.h" -#include "../../Patterns/PatternArgs.h" -#include "../../Patterns/Pattern.h" -#include "../../Time/TimeControl.h" -#include "../../Colors/Colorset.h" -#include "../../Buttons/Button.h" -#include "../../Time/Timings.h" -#include "../../Menus/Menus.h" -#include "../../Modes/Modes.h" -#include "../../Leds/Leds.h" -#include "../../Log/Log.h" +#include "../../c_types.h" #define COL32(v) ((RGBColor){ ((v) >> 16) & 0xFF, ((v) >> 8) & 0xFF, (v) & 0xFF }) static const uint8_t s_brightnessOptions[4] = { @@ -31,7 +20,7 @@ extern void VortexEngine_enterSleep(bool save); Menu *GlobalBrightnessMenu_Create(RGBColor col, bool advanced) { - GlobalBrightnessMenu *self = (GlobalBrightnessMenu *)malloc(sizeof(GlobalBrightnessMenu)); + GlobalBrightnessMenu *self = &g_menuWorkspace.globalBrightness; if (!self) return NULL; Menu_construct(&self->base, col, advanced); self->base.vtable = &g_globalBrightnessMenuVTable; diff --git a/VortexEngine/src/Menus/MenuList/ModeSharing.c b/VortexEngine/src/Menus/MenuList/ModeSharing.c index df48bf8d1e..5a04250756 100644 --- a/VortexEngine/src/Menus/MenuList/ModeSharing.c +++ b/VortexEngine/src/Menus/MenuList/ModeSharing.c @@ -1,20 +1,7 @@ -#include "ModeSharing.h" #include -#include "../../VortexEngine.h" -#include "../../Serial/ByteStream.h" -#include "../../Serial/Serial.h" -#include "../../Time/TimeControl.h" -#include "../../Time/Timings.h" -#include "../../Wireless/VLReceiver.h" -#include "../../Wireless/VLSender.h" -#include "../../Patterns/Pattern.h" -#include "../../Buttons/Button.h" -#include "../../Modes/Modes.h" -#include "../../Modes/Mode.h" -#include "../../Leds/Leds.h" -#include "../../Log/Log.h" +#include "../../c_types.h" #define COL32(v) ((RGBColor){ ((v) >> 16) & 0xFF, ((v) >> 8) & 0xFF, (v) & 0xFF }) static void receiveMode(ModeSharingMenu *self); @@ -24,7 +11,7 @@ extern void VortexEngine_toggleForceSleep(bool enabled); Menu *ModeSharingMenu_Create(RGBColor col, bool advanced) { - ModeSharingMenu *self = (ModeSharingMenu *)malloc(sizeof(ModeSharingMenu)); + ModeSharingMenu *self = &g_menuWorkspace.modeSharing; if (!self) return NULL; Menu_construct(&self->base, col, advanced); self->base.vtable = &g_modeSharingMenuVTable; diff --git a/VortexEngine/src/Menus/MenuList/PatternSelect.c b/VortexEngine/src/Menus/MenuList/PatternSelect.c index 78da3e671f..e11ee67381 100644 --- a/VortexEngine/src/Menus/MenuList/PatternSelect.c +++ b/VortexEngine/src/Menus/MenuList/PatternSelect.c @@ -1,24 +1,12 @@ -#include "PatternSelect.h" #include -#include "../../Patterns/PatternBuilder.h" -#include "../../Patterns/PatternArgs.h" -#include "../../Patterns/Pattern.h" -#include "../../Serial/ByteStream.h" -#include "../../Time/TimeControl.h" -#include "../../Buttons/Button.h" -#include "../../Random/Random.h" -#include "../../Time/Timings.h" -#include "../../Modes/Modes.h" -#include "../../Menus/Menus.h" -#include "../../Leds/Leds.h" -#include "../../Log/Log.h" +#include "../../c_types.h" #define COL32(v) ((RGBColor){ ((v) >> 16) & 0xFF, ((v) >> 8) & 0xFF, (v) & 0xFF }) Menu *PatternSelectMenu_Create(RGBColor col, bool advanced) { - PatternSelectMenu *self = (PatternSelectMenu *)malloc(sizeof(PatternSelectMenu)); + PatternSelectMenu *self = &g_menuWorkspace.patternSelect; if (!self) return NULL; Menu_construct(&self->base, col, advanced); self->base.vtable = &g_patternSelectMenuVTable; diff --git a/VortexEngine/src/Menus/MenuList/Randomizer.c b/VortexEngine/src/Menus/MenuList/Randomizer.c index e6672f2e6e..c4b91b5723 100644 --- a/VortexEngine/src/Menus/MenuList/Randomizer.c +++ b/VortexEngine/src/Menus/MenuList/Randomizer.c @@ -1,22 +1,12 @@ -#include "Randomizer.h" #include -#include "../../Memory/Memory.h" -#include "../../Patterns/PatternBuilder.h" -#include "../../Patterns/Pattern.h" -#include "../../Colors/Colorset.h" -#include "../../Random/Random.h" -#include "../../Time/Timings.h" -#include "../../Buttons/Button.h" -#include "../../Menus/Menus.h" -#include "../../Modes/Modes.h" -#include "../../Modes/Mode.h" -#include "../../Leds/Leds.h" -#include "../../Log/Log.h" +#include "../../c_types.h" #define COL32(v) ((RGBColor){ ((v) >> 16) & 0xFF, ((v) >> 8) & 0xFF, (v) & 0xFF }) +static uint8_t s_randLedStore[64]; + static bool reRoll(RandomizerMenu *self); static void showRandomizationSelect(RandomizerMenu *self); #if VORTEX_SLIM == 0 @@ -34,7 +24,7 @@ static void crushPattern(Random *ctx, PatternArgs *outArgs); Menu *RandomizerMenu_Create(RGBColor col, bool advanced) { - RandomizerMenu *self = (RandomizerMenu *)malloc(sizeof(RandomizerMenu)); + RandomizerMenu *self = &g_menuWorkspace.randomizer; if (!self) return NULL; Menu_construct(&self->base, col, advanced); self->base.vtable = &g_randomizerMenuVTable; @@ -73,7 +63,7 @@ bool RandomizerMenu_init(Menu *self) #endif for (LedPos l = LED_FIRST; l < LED_COUNT; ++l) { ByteStream ledData; - ByteStream_init(&ledData, 0, NULL); + ByteStream_initStatic(&ledData, s_randLedStore, sizeof(s_randLedStore)); Pattern *pat = Mode_getPattern(cur, l); if (pat) { if (!Pattern_serialize(pat, &ledData)) { diff --git a/VortexEngine/src/Menus/Menus.c b/VortexEngine/src/Menus/Menus.c index 2cdb68f374..7650aea668 100644 --- a/VortexEngine/src/Menus/Menus.c +++ b/VortexEngine/src/Menus/Menus.c @@ -1,23 +1,11 @@ -#include "Menus.h" - -#include "Menu.h" -#include "MenuList/GlobalBrightness.h" -#include "MenuList/EditorConnection.h" -#include "MenuList/FactoryReset.h" -#include "MenuList/ModeSharing.h" -#include "MenuList/ColorSelect.h" -#include "MenuList/PatternSelect.h" -#include "MenuList/Randomizer.h" - -#include "../Time/TimeControl.h" -#include "../Time/Timings.h" -#include "../Buttons/Button.h" -#include "../Modes/Modes.h" -#include "../Leds/Leds.h" -#include "../Log/Log.h" + + +#include "../c_types.h" #define COL32(v) ((RGBColor){ ((v) >> 16) & 0xFF, ((v) >> 8) & 0xFF, (v) & 0xFF }) +MenuWorkspace g_menuWorkspace; + // Menu state static enum { MENU_STATE_NOT_OPEN = 0, @@ -190,6 +178,10 @@ bool Menus_openMenu(uint32_t index, bool advanced) return false; } m_selection = index; + if (m_pCurMenu) { + m_pCurMenu->vtable->destroy(m_pCurMenu); + m_pCurMenu = NULL; + } Menu *newMenu = menuList[m_selection].initMenu(menuList[m_selection].color, advanced); if (!newMenu) { return false; @@ -199,9 +191,6 @@ bool Menus_openMenu(uint32_t index, bool advanced) newMenu->vtable->destroy(newMenu); return false; } - if (m_pCurMenu) { - m_pCurMenu->vtable->destroy(m_pCurMenu); - } m_pCurMenu = newMenu; Leds_clearAll(); m_menuState = MENU_STATE_IN_MENU; diff --git a/VortexEngine/src/Modes/DefaultModes.c b/VortexEngine/src/Modes/DefaultModes.c index 54d8082aaf..3949f294f7 100644 --- a/VortexEngine/src/Modes/DefaultModes.c +++ b/VortexEngine/src/Modes/DefaultModes.c @@ -1,7 +1,6 @@ -#include "DefaultModes.h" -#include "../Colors/ColorTypes.h" +#include "../c_types.h" const uint32_t rgbCols[] = { RGB_RED, RGB_GREEN, RGB_BLUE, }; const uint32_t pat2TipCols = 0x97709F; const uint32_t pat2TopCols = 0x4D00B2; diff --git a/VortexEngine/src/Modes/Mode.c b/VortexEngine/src/Modes/Mode.c index 1ff6f09787..a454d674c4 100644 --- a/VortexEngine/src/Modes/Mode.c +++ b/VortexEngine/src/Modes/Mode.c @@ -1,16 +1,6 @@ -#include "Mode.h" - -#include "../Patterns/Patterns.h" -#include "../Patterns/PatternArgs.h" -#include "../Patterns/Pattern.h" -#include "../Patterns/PatternBuilder.h" -#include "../Colors/Colorset.h" -#include "../Serial/ByteStream.h" -#include "../Leds/Leds.h" -#include "../Memory/Memory.h" -#include "../Log/Log.h" -#include "../VortexEngine.h" + +#include "../c_types.h" void Mode_initFromEntry(Mode *self, const struct DefaultModeEntry *entry) { Mode_init(self); @@ -438,7 +428,7 @@ bool Mode_setPattern(Mode *self, PatternID pat, LedPos pos, const PatternArgs *a case LED_MULTI: if (self->m_multiPat) { Pattern_destroy(self->m_multiPat); - vfree(self->m_multiPat); + PatternBuilder_release(self->m_multiPat); self->m_multiPat = NULL; } if (isMultiLedPatternID(pat)) { @@ -476,7 +466,7 @@ bool Mode_setPattern(Mode *self, PatternID pat, LedPos pos, const PatternArgs *a } if (self->m_singlePats[pos]) { Pattern_destroy(self->m_singlePats[pos]); - vfree(self->m_singlePats[pos]); + PatternBuilder_release(self->m_singlePats[pos]); } self->m_singlePats[pos] = PatternBuilder_makeSingle(pat, args); if (self->m_singlePats[pos]) { @@ -505,7 +495,7 @@ void Mode_copyPatternFrom(Mode *self, const Mode *other, LedPos to, LedPos from) } if (self->m_singlePats[to]) { Pattern_destroy(self->m_singlePats[to]); - vfree(self->m_singlePats[to]); + PatternBuilder_release(self->m_singlePats[to]); } self->m_singlePats[to] = PatternBuilder_dupe(other->m_singlePats[from]); } diff --git a/VortexEngine/src/Modes/Modes.c b/VortexEngine/src/Modes/Modes.c index f213d106dd..bff7dec150 100644 --- a/VortexEngine/src/Modes/Modes.c +++ b/VortexEngine/src/Modes/Modes.c @@ -1,30 +1,58 @@ -#include "Modes.h" -#include "DefaultModes.h" -#include "Mode.h" - -#include "../Patterns/Patterns.h" -#include "../Patterns/PatternArgs.h" -#include "../Patterns/Pattern.h" -#include "../Patterns/PatternBuilder.h" -#include "../Colors/Colorset.h" -#include "../Serial/ByteStream.h" -#include "../Time/TimeControl.h" -#include "../Storage/Storage.h" -#include "../Buttons/Buttons.h" -#include "../Buttons/Button.h" -#include "../Time/Timings.h" -#include "../Leds/Leds.h" -#include "../Log/Log.h" -#include "../Memory/Memory.h" + +#include "../c_types.h" // file-scope globals (replaces static class members) static bool s_loaded = false; static uint8_t s_curMode = 0; static uint8_t s_numModes = 0; -static ModeLink *s_pCurModeLink = NULL; -static ModeLink *s_storedModes = NULL; uint8_t Modes_globalFlags = 0; static uint32_t s_lastSwitchTime = 0; +static Mode s_liveMode; +static bool s_liveValid = false; +static uint8_t s_modeStoreData[MAX_MODE_SIZE]; +static ByteStream s_modeBuf; +static uint8_t s_headerStoreData[MAX_MODE_SIZE]; +static ByteStream s_headerBuf; + +static void Modes_resetModeBuf(void) +{ + ByteStream_initStatic(&s_modeBuf, s_modeStoreData, sizeof(s_modeStoreData)); +} + +static void Modes_resetHeaderBuf(void) +{ + ByteStream_initStatic(&s_headerBuf, s_headerStoreData, sizeof(s_headerStoreData)); +} + +static void Modes_unloadLive(void) +{ + if (s_liveValid) { + Mode_cleanup(&s_liveMode); + s_liveValid = false; + } +} + +static bool Modes_loadLive(void) +{ + if (s_liveValid) { + return true; + } + if (!s_numModes) { + return false; + } + Modes_resetModeBuf(); + if (!Storage_read(s_curMode + 1, &s_modeBuf)) { + DEBUG_LOG("Failed to read current mode from storage"); + return false; + } + Mode_init(&s_liveMode); + if (!Mode_loadFromBuffer(&s_liveMode, &s_modeBuf)) { + Mode_cleanup(&s_liveMode); + return false; + } + s_liveValid = true; + return true; +} bool Modes_init(void) { @@ -33,12 +61,10 @@ bool Modes_init(void) Modes_test(); return true; #endif - ByteStream headerBuffer; - ByteStream_init(&headerBuffer, 0, NULL); - if (!Storage_read(0, &headerBuffer) || !Modes_unserializeSaveHeader(&headerBuffer)) { + Modes_resetHeaderBuf(); + if (!Storage_read(0, &s_headerBuf) || !Modes_unserializeSaveHeader(&s_headerBuf)) { Modes_globalFlags |= MODES_FLAG_NEW_FIRMWARE; } - ByteStream_destroy(&headerBuffer); s_loaded = false; #ifdef VORTEX_LIB Modes_globalFlags |= MODES_FLAG_ADV_MENUS; @@ -48,7 +74,7 @@ bool Modes_init(void) void Modes_cleanup(void) { - Modes_clearModes(); + Modes_unloadLive(); } bool Modes_load(void) @@ -74,7 +100,7 @@ void Modes_play(void) Leds_clearAll(); return; } - if (!s_pCurModeLink && !Modes_initCurMode(false)) { + if (!s_liveValid && !Modes_loadLive()) { DEBUG_LOG("Error failed to load any modes!"); return; } @@ -86,7 +112,7 @@ void Modes_play(void) } Modes_nextMode(); } - ModeLink_play(s_pCurModeLink); + Mode_play(&s_liveMode); } bool Modes_saveToBuffer(ByteStream *modesBuffer) @@ -114,35 +140,27 @@ bool Modes_loadFromBuffer(ByteStream *modesBuffer) bool Modes_saveHeader(void) { - ByteStream headerBuffer; - ByteStream_init(&headerBuffer, MAX_MODE_SIZE, NULL); - if (!Modes_serializeSaveHeader(&headerBuffer)) { - ByteStream_destroy(&headerBuffer); + Modes_resetHeaderBuf(); + if (!Modes_serializeSaveHeader(&s_headerBuf)) { return false; } - if (!Storage_write(0, &headerBuffer)) { - ByteStream_destroy(&headerBuffer); + if (!Storage_write(0, &s_headerBuf)) { return false; } - ByteStream_destroy(&headerBuffer); return true; } bool Modes_loadHeader(void) { - ByteStream headerBuffer; - ByteStream_init(&headerBuffer, 0, NULL); - if (!Storage_read(0, &headerBuffer) || !ByteStream_size(&headerBuffer)) { + Modes_resetHeaderBuf(); + if (!Storage_read(0, &s_headerBuf) || !ByteStream_size(&s_headerBuf)) { DEBUG_LOG("Empty buffer read from storage"); - ByteStream_destroy(&headerBuffer); return false; } Modes_clearModes(); - if (!Modes_unserializeSaveHeader(&headerBuffer)) { - ByteStream_destroy(&headerBuffer); + if (!Modes_unserializeSaveHeader(&s_headerBuf)) { return false; } - ByteStream_destroy(&headerBuffer); return true; } @@ -151,77 +169,36 @@ bool Modes_saveStorage(void) DEBUG_LOG("Saving modes..."); Modes_saveHeader(); Modes_saveCurMode(); - if (s_pCurModeLink) { - ModeLink_uninstantiate(s_pCurModeLink); - } - uint8_t i = 0; - ModeLink *ptr = s_storedModes; - while (ptr && i < MAX_MODES) { - ByteStream modeBuffer; - ByteStream_init(&modeBuffer, MAX_MODE_SIZE, NULL); - Mode *mode = ModeLink_instantiate(ptr); - if (!mode) { - ERROR_OUT_OF_MEMORY(); - ByteStream_destroy(&modeBuffer); - return false; - } - if (!Mode_serialize(mode, &modeBuffer, 0)) { - ByteStream_destroy(&modeBuffer); - return false; - } - ModeLink_uninstantiate(ptr); - ptr = ModeLink_next(ptr); - if (!Storage_write(++i, &modeBuffer)) { - ByteStream_destroy(&modeBuffer); - return false; - } - ByteStream_destroy(&modeBuffer); - } - if (s_pCurModeLink && !ModeLink_instantiate(s_pCurModeLink)) { - return false; - } DEBUG_LOGF("Serialized num modes: %u", s_numModes); return true; } bool Modes_loadStorage(void) { - ByteStream headerBuffer; - ByteStream_init(&headerBuffer, 0, NULL); - if (!Storage_read(0, &headerBuffer) || !ByteStream_size(&headerBuffer)) { + Modes_resetHeaderBuf(); + if (!Storage_read(0, &s_headerBuf) || !ByteStream_size(&s_headerBuf)) { DEBUG_LOG("Empty buffer read from storage"); - ByteStream_destroy(&headerBuffer); return false; } Modes_clearModes(); - if (!Modes_unserializeSaveHeader(&headerBuffer)) { - ByteStream_destroy(&headerBuffer); + if (!Modes_unserializeSaveHeader(&s_headerBuf)) { return false; } uint8_t numModes = 0; - if (!ByteStream_unserialize8(&headerBuffer, &numModes)) { - ByteStream_destroy(&headerBuffer); + if (!ByteStream_unserialize8(&s_headerBuf, &numModes)) { return false; } if (!numModes) { DEBUG_LOG("Did not find any modes"); - ByteStream_destroy(&headerBuffer); return false; } - for (uint8_t i = 0; i < numModes; ++i) { - ByteStream modeBuffer; - ByteStream_init(&modeBuffer, MAX_MODE_SIZE, NULL); - if (!Storage_read(i + 1, &modeBuffer) || !Modes_addSerializedMode(&modeBuffer)) { - ByteStream_destroy(&modeBuffer); - ByteStream_destroy(&headerBuffer); - return false; - } - ByteStream_destroy(&modeBuffer); + if (numModes > MAX_MODES) { + numModes = MAX_MODES; } + s_numModes = numModes; if (Modes_oneClickModeEnabled()) { Modes_switchToStartupMode(); } - ByteStream_destroy(&headerBuffer); return true; } @@ -286,25 +263,22 @@ bool Modes_serialize(ByteStream *modesBuffer) if (!ByteStream_serialize8(modesBuffer, s_numModes)) { return false; } - Modes_saveCurMode(); - if (s_pCurModeLink) { - ModeLink_uninstantiate(s_pCurModeLink); - } - ModeLink *ptr = s_storedModes; - while (ptr) { - Mode *mode = ModeLink_instantiate(ptr); - if (!mode) { - ERROR_OUT_OF_MEMORY(); + for (uint8_t i = 0; i < s_numModes; ++i) { + Mode tmpMode; + Modes_resetModeBuf(); + if (!Storage_read(i + 1, &s_modeBuf)) { return false; } - if (!Mode_serialize(mode, modesBuffer, 0)) { + Mode_init(&tmpMode); + if (!Mode_loadFromBuffer(&tmpMode, &s_modeBuf)) { + Mode_cleanup(&tmpMode); + return false; + } + bool result = Mode_serialize(&tmpMode, modesBuffer, 0); + Mode_cleanup(&tmpMode); + if (!result) { return false; } - ModeLink_uninstantiate(ptr); - ptr = ModeLink_next(ptr); - } - if (s_pCurModeLink && !ModeLink_instantiate(s_pCurModeLink)) { - return false; } DEBUG_LOGF("Serialized num modes: %u", s_numModes); return true; @@ -339,7 +313,9 @@ bool Modes_setDefaults(void) for (uint8_t i = 0; i < MAX_MODES; ++i) { Mode defMode; Mode_initFromEntry(&defMode, &defaultModes[i]); - if (!Modes_addModeMode(&defMode)) { + bool result = Modes_addModeMode(&defMode); + Mode_cleanup(&defMode); + if (!result) { ERROR_LOGF("Failed to add default mode %u", i); return false; } @@ -357,10 +333,13 @@ bool Modes_addSerializedMode(ByteStream *serializedMode) Mode tmpMode; Mode_init(&tmpMode); if (!Mode_unserialize(&tmpMode, serializedMode)) { + Mode_cleanup(&tmpMode); return false; } Mode_initMode(&tmpMode); - return Modes_addModeMode(&tmpMode); + bool result = Modes_addModeMode(&tmpMode); + Mode_cleanup(&tmpMode); + return result; } bool Modes_addModeFromBuffer(ByteStream *serializedMode) @@ -370,18 +349,15 @@ bool Modes_addModeFromBuffer(ByteStream *serializedMode) return false; } #endif - if (!s_storedModes) { - s_storedModes = (ModeLink *)vmalloc(sizeof(ModeLink)); - if (!s_storedModes) { - ERROR_OUT_OF_MEMORY(); - return false; - } - ModeLink_initFromStream(s_storedModes, serializedMode, false); - } else { - if (!ModeLink_appendStream(s_storedModes, serializedMode)) { - ERROR_OUT_OF_MEMORY(); - return false; - } + if (!ByteStream_size(serializedMode)) { + return false; + } + if (ByteStream_rawSize(serializedMode) > MAX_MODE_SIZE) { + ERROR_LOG("Mode too big for storage space"); + return false; + } + if (!Storage_write(s_numModes + 1, serializedMode)) { + return false; } s_numModes++; return true; @@ -390,29 +366,29 @@ bool Modes_addModeFromBuffer(ByteStream *serializedMode) bool Modes_shiftCurMode(int32_t offset) { uint32_t newPos = (uint32_t)((int32_t)s_curMode + offset); - if (newPos >= s_numModes) { - return false; - } - if (newPos == s_curMode) { - return true; - } - ModeLink *target = Modes_getModeLink(newPos); - if (!target) { + if (!s_numModes || offset == 0 || newPos >= s_numModes) { return false; } - if (!s_curMode && offset > 0) { - s_storedModes = ModeLink_next(s_storedModes); - } - ModeLink_unlinkSelf(s_pCurModeLink); - s_curMode = (uint8_t)newPos; - if (offset < 0) { - ModeLink_linkBefore(target, s_pCurModeLink); - if (!s_curMode) { - s_storedModes = s_pCurModeLink; + Modes_unloadLive(); + int8_t step = (offset > 0) ? 1 : -1; + uint8_t pos = s_curMode; + while (pos != (uint8_t)newPos) { + uint8_t other = (uint8_t)((int8_t)pos + step); + if (!Storage_read(pos + 1, &s_modeBuf)) { + return false; } - } else { - ModeLink_linkAfter(target, s_pCurModeLink); + if (!Storage_read(other + 1, &s_headerBuf)) { + return false; + } + if (!Storage_write(pos + 1, &s_headerBuf)) { + return false; + } + if (!Storage_write(other + 1, &s_modeBuf)) { + return false; + } + pos = other; } + s_curMode = (uint8_t)newPos; return true; } @@ -437,28 +413,31 @@ bool Modes_addModeArgsSet(PatternID id, const PatternArgs *args, const Colorset Mode tmpMode; Mode_initFromIDArgsSetPtr(&tmpMode, id, args, set); Mode_initMode(&tmpMode); - return Modes_addModeMode(&tmpMode); + bool result = Modes_addModeMode(&tmpMode); + Mode_cleanup(&tmpMode); + return result; } bool Modes_addModeMode(const Mode *mode) { + if (!mode) { + return false; + } #if MAX_MODES != 0 if (s_numModes >= MAX_MODES) { return false; } #endif - if (!s_storedModes) { - s_storedModes = (ModeLink *)vmalloc(sizeof(ModeLink)); - if (!s_storedModes) { - ERROR_OUT_OF_MEMORY(); - return false; - } - ModeLink_init(s_storedModes, mode, false); - } else { - if (!ModeLink_appendMode(s_storedModes, mode)) { - ERROR_OUT_OF_MEMORY(); - return false; - } + Modes_resetModeBuf(); + if (!Mode_saveToBuffer(mode, &s_modeBuf, 0)) { + return false; + } + if (ByteStream_rawSize(&s_modeBuf) > MAX_MODE_SIZE) { + ERROR_LOG("Mode too big for storage space"); + return false; + } + if (!Storage_write(s_numModes + 1, &s_modeBuf)) { + return false; } s_numModes++; return true; @@ -477,7 +456,8 @@ bool Modes_updateCurMode(const Mode *mode) if (!Modes_saveCurMode()) { return false; } - return Modes_initCurMode(false) != NULL; + Modes_unloadLive(); + return Modes_curMode() != NULL; } Mode *Modes_setCurMode(uint8_t index) @@ -486,23 +466,16 @@ Mode *Modes_setCurMode(uint8_t index) return NULL; } Leds_clearAll(); - if (s_pCurModeLink) { - ModeLink_uninstantiate(s_pCurModeLink); - } + Modes_unloadLive(); int8_t newModeIdx = index % s_numModes; - ModeLink *newCurLink = Modes_getModeLink((uint32_t)newModeIdx); - if (!newCurLink) { - return NULL; - } - Mode *newCur = ModeLink_instantiate(newCurLink); + s_curMode = (uint8_t)newModeIdx; + s_lastSwitchTime = Time_getCurtime(); + Modes_setStartupMode((uint8_t)newModeIdx); + Mode *newCur = Modes_curMode(); if (!newCur) { ERROR_OUT_OF_MEMORY(); return NULL; } - s_curMode = (uint8_t)newModeIdx; - s_pCurModeLink = newCurLink; - s_lastSwitchTime = Time_getCurtime(); - Modes_setStartupMode((uint8_t)newModeIdx); DEBUG_LOGF("Switch to Mode: %u / %u (pattern id: %u)", s_curMode, s_numModes - 1, Pattern_getPatternID(Mode_getPatternConst(newCur, LED_ANY))); return newCur; @@ -513,13 +486,13 @@ Mode *Modes_curMode(void) if (!s_numModes) { return NULL; } - if (!s_pCurModeLink) { - if (!Modes_initCurMode(false)) { + if (!s_liveValid) { + if (!Modes_loadLive()) { ERROR_LOG("Failed to initialize current mode"); return NULL; } } - return ModeLink_instantiate(s_pCurModeLink); + return &s_liveMode; } Mode *Modes_nextMode(void) @@ -553,34 +526,36 @@ Mode *Modes_nextModeSkipEmpty(void) void Modes_deleteCurMode(void) { - if (!s_numModes || !s_pCurModeLink) { + if (!s_numModes || s_curMode >= s_numModes) { return; } - ModeLink *newCur = ModeLink_unlinkSelf(s_pCurModeLink); - ModeLink_cleanup(s_pCurModeLink); - vfree(s_pCurModeLink); - s_pCurModeLink = newCur; - if (s_curMode) { - s_curMode--; - } else { - s_storedModes = s_pCurModeLink; + Modes_unloadLive(); + for (uint8_t i = s_curMode; (uint8_t)(i + 1) < s_numModes; ++i) { + Modes_resetModeBuf(); + if (!Storage_read(i + 2, &s_modeBuf)) { + return; + } + if (!Storage_write(i + 1, &s_modeBuf)) { + return; + } } s_numModes--; if (!s_numModes) { - s_storedModes = NULL; + s_curMode = 0; + Leds_clearAll(); + return; + } + if (s_curMode >= s_numModes) { + s_curMode = s_numModes - 1; } + Modes_loadLive(); } void Modes_clearModes(void) { - if (!s_numModes || !s_storedModes) { - return; - } - ModeLink_cleanup(s_storedModes); - vfree(s_storedModes); - s_pCurModeLink = NULL; - s_storedModes = NULL; + Modes_unloadLive(); s_numModes = 0; + s_curMode = 0; Leds_clearAll(); } @@ -611,10 +586,8 @@ bool Modes_setFlag(uint8_t flag, bool enable, bool save) if (!save) { return true; } - ByteStream headerBuffer; - ByteStream_init(&headerBuffer, 0, NULL); - if (!Storage_read(0, &headerBuffer) || !ByteStream_size(&headerBuffer)) { - ByteStream_destroy(&headerBuffer); + Modes_resetHeaderBuf(); + if (!Storage_read(0, &s_headerBuf) || !ByteStream_size(&s_headerBuf)) { return Modes_saveHeader(); } typedef struct { @@ -624,12 +597,10 @@ bool Modes_setFlag(uint8_t flag, bool enable, bool save) uint8_t brightness; uint8_t numModes; } SaveHeader; - SaveHeader *pHeader = (SaveHeader *)ByteStream_data(&headerBuffer); + SaveHeader *pHeader = (SaveHeader *)ByteStream_data(&s_headerBuf); pHeader->globalFlags = Modes_globalFlags; - ByteStream_setCRCDirty(&headerBuffer); - bool result = Storage_write(0, &headerBuffer); - ByteStream_destroy(&headerBuffer); - return result; + ByteStream_setCRCDirty(&s_headerBuf); + return Storage_write(0, &s_headerBuf); } bool Modes_getFlag(uint8_t flag) @@ -762,220 +733,32 @@ uint8_t Modes_getGlobalFlags(void) } #endif -ModeLink *Modes_getModeLink(uint32_t index) -{ - if (index >= s_numModes) { - return NULL; - } - ModeLink *ptr = s_storedModes; - while (index > 0 && ptr) { - ptr = ModeLink_next(ptr); - index--; - } - return ptr; -} - Mode *Modes_initCurMode(bool force) { - if (!s_numModes) { - return NULL; - } - if (s_pCurModeLink) { - ModeLink_uninstantiate(s_pCurModeLink); - } - s_pCurModeLink = Modes_getModeLink(s_curMode); - if (!s_pCurModeLink) { - return NULL; - } - if (force) { - ModeLink_uninstantiate(s_pCurModeLink); - } - return ModeLink_instantiate(s_pCurModeLink); + (void)force; + Modes_unloadLive(); + return Modes_curMode(); } bool Modes_saveCurMode(void) { - if (!s_pCurModeLink) { - return false; - } - return ModeLink_save(s_pCurModeLink); -} - -// ModeLink implementation -void ModeLink_init(ModeLink *self, const Mode *src, bool inst) -{ - self->m_pInstantiatedMode = NULL; - ByteStream_init(&self->m_storedMode, 0, NULL); - self->m_next = NULL; - self->m_prev = NULL; - if (src) { - ModeLink_initMode(self, src); - } - if (src && inst) { - ModeLink_instantiate(self); - } -} - -void ModeLink_initFromStream(ModeLink *self, const ByteStream *src, bool inst) -{ - self->m_pInstantiatedMode = NULL; - ByteStream_copy(&self->m_storedMode, src); - self->m_next = NULL; - self->m_prev = NULL; - if (ByteStream_size(&self->m_storedMode) && inst) { - ModeLink_instantiate(self); - } -} - -void ModeLink_cleanup(ModeLink *self) -{ - if (self->m_next) { - ModeLink_cleanup(self->m_next); - vfree(self->m_next); - } - if (self->m_pInstantiatedMode) { - Mode_cleanup(self->m_pInstantiatedMode); - vfree(self->m_pInstantiatedMode); - } - ByteStream_destroy(&self->m_storedMode); -} - -bool ModeLink_initMode(ModeLink *self, const Mode *mode) -{ - if (!mode) { - return false; - } - ByteStream_clear(&self->m_storedMode); - return Mode_saveToBuffer(mode, &self->m_storedMode, 0); -} - -bool ModeLink_appendMode(ModeLink *self, const Mode *next) -{ - if (!next) { - return false; - } - if (self->m_next) { - return ModeLink_appendMode(self->m_next, next); + if (!s_liveValid) { + return true; } - self->m_next = (ModeLink *)vmalloc(sizeof(ModeLink)); - if (!self->m_next) { - ERROR_OUT_OF_MEMORY(); + Modes_resetModeBuf(); + if (!Mode_saveToBuffer(&s_liveMode, &s_modeBuf, 0)) { return false; } - ModeLink_init(self->m_next, next, false); - self->m_next->m_prev = self; - return true; -} - -bool ModeLink_appendStream(ModeLink *self, const ByteStream *next) -{ - if (!ByteStream_size(next)) { + if (ByteStream_rawSize(&s_modeBuf) > MAX_MODE_SIZE) { + ERROR_LOG("Mode too big for storage space"); return false; } - if (self->m_next) { - return ModeLink_appendStream(self->m_next, next); - } - self->m_next = (ModeLink *)vmalloc(sizeof(ModeLink)); - if (!self->m_next) { - ERROR_OUT_OF_MEMORY(); + if (!Storage_write(s_curMode + 1, &s_modeBuf)) { return false; } - ModeLink_initFromStream(self->m_next, next, false); - self->m_next->m_prev = self; return true; } -void ModeLink_play(ModeLink *self) -{ - if (!self->m_pInstantiatedMode) { - return; - } - Mode_play(self->m_pInstantiatedMode); -} - -ModeLink *ModeLink_unlinkSelf(ModeLink *self) -{ - if (self->m_prev) { - self->m_prev->m_next = self->m_next; - } - if (self->m_next) { - self->m_next->m_prev = self->m_prev; - } - ModeLink *newLink = self->m_prev ? self->m_prev : self->m_next; - self->m_prev = NULL; - self->m_next = NULL; - return newLink; -} - -void ModeLink_linkAfter(ModeLink *self, ModeLink *link) -{ - if (!link) { - return; - } - if (self->m_next) { - self->m_next->m_prev = link; - link->m_next = self->m_next; - } - self->m_next = link; - link->m_prev = self; -} - -void ModeLink_linkBefore(ModeLink *self, ModeLink *link) -{ - if (!link) { - return; - } - if (self->m_prev) { - self->m_prev->m_next = link; - link->m_prev = self->m_prev; - } - self->m_prev = link; - link->m_next = self; -} - -Mode *ModeLink_instantiate(ModeLink *self) -{ - if (self->m_pInstantiatedMode) { - return self->m_pInstantiatedMode; - } - Mode *newMode = (Mode *)vmalloc(sizeof(Mode)); - if (!newMode) { - ERROR_OUT_OF_MEMORY(); - return NULL; - } - Mode_init(newMode); - ByteStream_resetUnserializer(&self->m_storedMode); - if (!Mode_loadFromBuffer(newMode, &self->m_storedMode)) { - vfree(newMode); - return NULL; - } - self->m_pInstantiatedMode = newMode; - return self->m_pInstantiatedMode; -} - -void ModeLink_uninstantiate(ModeLink *self) -{ - if (self->m_pInstantiatedMode) { - Mode_cleanup(self->m_pInstantiatedMode); - vfree(self->m_pInstantiatedMode); - self->m_pInstantiatedMode = NULL; - } -} - -bool ModeLink_save(ModeLink *self) -{ - if (!self->m_pInstantiatedMode) { - return false; - } - ByteStream_clear(&self->m_storedMode); - return Mode_saveToBuffer(self->m_pInstantiatedMode, &self->m_storedMode, 0); -} - -ByteStream *ModeLink_buffer(ModeLink *self) { return &self->m_storedMode; } -Mode *ModeLink_mode(ModeLink *self) { return self->m_pInstantiatedMode; } -ModeLink *ModeLink_next(ModeLink *self) { return self->m_next; } -ModeLink *ModeLink_prev(ModeLink *self) { return self->m_prev; } - #if MODES_TEST == 1 #include #include diff --git a/VortexEngine/src/Modes/Modes.h b/VortexEngine/src/Modes/Modes.h index 6c7e4d384c..4e415a49b5 100644 --- a/VortexEngine/src/Modes/Modes.h +++ b/VortexEngine/src/Modes/Modes.h @@ -64,8 +64,6 @@ class Modes static bool saveHeader(); static bool loadHeader(); - static bool saveHeaderAndMode(); - // full save/load to/from storage static bool saveStorage(); static bool loadStorage(); @@ -123,7 +121,6 @@ class Modes static uint32_t lastSwitchTime() { return m_lastSwitchTime; } // delete the current mode - // NOTE: THIS IS BROKEN ON THIS BRANCH static void deleteCurMode(); // delete all modes in the list @@ -186,18 +183,30 @@ class Modes private: // linked list of internal mode storage class ModeLink { - friend class Modes; public: // construct a link and optionally instantiate the link - ModeLink(const Mode *src = nullptr); - ModeLink(const ByteStream &src); + ModeLink(const Mode *src = nullptr, bool inst = false); + ModeLink(const ByteStream &src, bool inst = false); + ~ModeLink(); // init the link and append another link bool init(const Mode *mode = nullptr); - bool init(const ByteStream &src); + bool append(const Mode *next); + bool append(const ByteStream &next); + + // play the instantiated mode inside + void play(); + + // unlink self from the chain, returns link that takes position + ModeLink *unlinkSelf(); + + // link in a node before or after self + void linkAfter(ModeLink *link); + void linkBefore(ModeLink *link); // instantiate/destroy the mode Mode *instantiate(); + void uninstantiate(); // if the mode is instantiated and the instantiated version // has changed at all then save will re-save it to the buffer @@ -205,11 +214,18 @@ class Modes // accessors ByteStream &buffer() { return m_storedMode; } + Mode *mode() { return m_pInstantiatedMode; } + ModeLink *next() { return m_next; } + ModeLink *prev() { return m_prev; } operator ByteStream &() { return m_storedMode; } operator ByteStream() { return m_storedMode; } + operator Mode *() { return m_pInstantiatedMode; } private: + Mode *m_pInstantiatedMode; ByteStream m_storedMode; + ModeLink *m_next; + ModeLink *m_prev; }; // fetch a link from the chain by index @@ -229,14 +245,11 @@ class Modes // the number of modes loaded static uint8_t m_numModes; - // pointer to the current instantiated mode - static Mode *m_pCurMode; - // the current instantiated mode and it's respective link static ModeLink *m_pCurModeLink; // list of serialized version of bufers - static ModeLink m_storedModes[MAX_MODES]; + static ModeLink *m_storedModes; // global flags for all modes static uint8_t m_globalFlags; diff --git a/VortexEngine/src/Patterns/Multi/BackStrobePattern.c b/VortexEngine/src/Patterns/Multi/BackStrobePattern.c index f7319fd191..9c204e99a7 100644 --- a/VortexEngine/src/Patterns/Multi/BackStrobePattern.c +++ b/VortexEngine/src/Patterns/Multi/BackStrobePattern.c @@ -1,5 +1,5 @@ -#include "BackStrobePattern.h" +#include "../../c_types.h" static const PatternVTable BackStrobePattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; void BackStrobePattern_init(BackStrobePattern *self, const PatternArgs *args) { diff --git a/VortexEngine/src/Patterns/Multi/BouncePattern.c b/VortexEngine/src/Patterns/Multi/BouncePattern.c index d9deeb3e06..64bb9c3ca9 100644 --- a/VortexEngine/src/Patterns/Multi/BouncePattern.c +++ b/VortexEngine/src/Patterns/Multi/BouncePattern.c @@ -1,5 +1,5 @@ -#include "BouncePattern.h" +#include "../../c_types.h" static const PatternVTable BouncePattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; void BouncePattern_init(BouncePattern *self, const PatternArgs *args) { diff --git a/VortexEngine/src/Patterns/Multi/CompoundPattern.c b/VortexEngine/src/Patterns/Multi/CompoundPattern.c index f67daa9349..37aa09e644 100644 --- a/VortexEngine/src/Patterns/Multi/CompoundPattern.c +++ b/VortexEngine/src/Patterns/Multi/CompoundPattern.c @@ -1,5 +1,5 @@ -#include "CompoundPattern.h" +#include "../../c_types.h" static const PatternVTable CompoundPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; void CompoundPattern_init(CompoundPattern *self, const PatternArgs *args) { diff --git a/VortexEngine/src/Patterns/Multi/CrossDopsPattern.c b/VortexEngine/src/Patterns/Multi/CrossDopsPattern.c index 9d8a46476c..b959adf211 100644 --- a/VortexEngine/src/Patterns/Multi/CrossDopsPattern.c +++ b/VortexEngine/src/Patterns/Multi/CrossDopsPattern.c @@ -1,5 +1,5 @@ -#include "CrossDopsPattern.h" +#include "../../c_types.h" static const PatternVTable CrossDopsPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; void CrossDopsPattern_init(CrossDopsPattern *self, const PatternArgs *args) { diff --git a/VortexEngine/src/Patterns/Multi/DoubleStrobePattern.c b/VortexEngine/src/Patterns/Multi/DoubleStrobePattern.c index f1d0029d2d..f2351c71bb 100644 --- a/VortexEngine/src/Patterns/Multi/DoubleStrobePattern.c +++ b/VortexEngine/src/Patterns/Multi/DoubleStrobePattern.c @@ -1,5 +1,5 @@ -#include "DoubleStrobePattern.h" +#include "../../c_types.h" static const PatternVTable DoubleStrobePattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; void DoubleStrobePattern_init(DoubleStrobePattern *self, const PatternArgs *args) { diff --git a/VortexEngine/src/Patterns/Multi/DripMorphPattern.c b/VortexEngine/src/Patterns/Multi/DripMorphPattern.c index 63da135386..b239d85550 100644 --- a/VortexEngine/src/Patterns/Multi/DripMorphPattern.c +++ b/VortexEngine/src/Patterns/Multi/DripMorphPattern.c @@ -1,5 +1,5 @@ -#include "DripMorphPattern.h" +#include "../../c_types.h" static const PatternVTable DripMorphPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; void DripMorphPattern_init(DripMorphPattern *self, const PatternArgs *args) { diff --git a/VortexEngine/src/Patterns/Multi/DripPattern.c b/VortexEngine/src/Patterns/Multi/DripPattern.c index 811c44331a..10f883f971 100644 --- a/VortexEngine/src/Patterns/Multi/DripPattern.c +++ b/VortexEngine/src/Patterns/Multi/DripPattern.c @@ -1,5 +1,5 @@ -#include "DripPattern.h" +#include "../../c_types.h" static const PatternVTable DripPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; void DripPattern_init(DripPattern *self, const PatternArgs *args) { diff --git a/VortexEngine/src/Patterns/Multi/FillPattern.c b/VortexEngine/src/Patterns/Multi/FillPattern.c index 74a4f9bc43..ebad12e6b5 100644 --- a/VortexEngine/src/Patterns/Multi/FillPattern.c +++ b/VortexEngine/src/Patterns/Multi/FillPattern.c @@ -1,5 +1,5 @@ -#include "FillPattern.h" +#include "../../c_types.h" static const PatternVTable FillPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; void FillPattern_init(FillPattern *self, const PatternArgs *args) { diff --git a/VortexEngine/src/Patterns/Multi/HueShiftPattern.c b/VortexEngine/src/Patterns/Multi/HueShiftPattern.c index 02f9aabc1a..363abec04c 100644 --- a/VortexEngine/src/Patterns/Multi/HueShiftPattern.c +++ b/VortexEngine/src/Patterns/Multi/HueShiftPattern.c @@ -1,5 +1,5 @@ -#include "HueShiftPattern.h" +#include "../../c_types.h" static const PatternVTable HueShiftPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; void HueShiftPattern_init(HueShiftPattern *self, const PatternArgs *args) { diff --git a/VortexEngine/src/Patterns/Multi/LighthousePattern.c b/VortexEngine/src/Patterns/Multi/LighthousePattern.c index 9d2a9cb00f..811990705f 100644 --- a/VortexEngine/src/Patterns/Multi/LighthousePattern.c +++ b/VortexEngine/src/Patterns/Multi/LighthousePattern.c @@ -1,5 +1,5 @@ -#include "LighthousePattern.h" +#include "../../c_types.h" static const PatternVTable LighthousePattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; void LighthousePattern_init(LighthousePattern *self, const PatternArgs *args) { diff --git a/VortexEngine/src/Patterns/Multi/MeteorPattern.c b/VortexEngine/src/Patterns/Multi/MeteorPattern.c index 348993ba62..e6c449b344 100644 --- a/VortexEngine/src/Patterns/Multi/MeteorPattern.c +++ b/VortexEngine/src/Patterns/Multi/MeteorPattern.c @@ -1,5 +1,5 @@ -#include "MeteorPattern.h" +#include "../../c_types.h" static const PatternVTable MeteorPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; void MeteorPattern_init(MeteorPattern *self, const PatternArgs *args) { diff --git a/VortexEngine/src/Patterns/Multi/MultiLedPattern.c b/VortexEngine/src/Patterns/Multi/MultiLedPattern.c index bdb0c5c4ec..21a2037ac5 100644 --- a/VortexEngine/src/Patterns/Multi/MultiLedPattern.c +++ b/VortexEngine/src/Patterns/Multi/MultiLedPattern.c @@ -1,7 +1,6 @@ -#include "MultiLedPattern.h" -#include "../Pattern.h" +#include "../../c_types.h" const PatternVTable MultiLedPattern_vtable = { NULL, NULL, diff --git a/VortexEngine/src/Patterns/Multi/PulsishPattern.c b/VortexEngine/src/Patterns/Multi/PulsishPattern.c index c10a34b7e7..79a18aa7e4 100644 --- a/VortexEngine/src/Patterns/Multi/PulsishPattern.c +++ b/VortexEngine/src/Patterns/Multi/PulsishPattern.c @@ -1,5 +1,5 @@ -#include "PulsishPattern.h" +#include "../../c_types.h" static const PatternVTable PulsishPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; void PulsishPattern_init(PulsishPattern *self, const PatternArgs *args) { diff --git a/VortexEngine/src/Patterns/Multi/Sequencer/ChaserPattern.c b/VortexEngine/src/Patterns/Multi/Sequencer/ChaserPattern.c index 6bd764e789..d64eeb596c 100644 --- a/VortexEngine/src/Patterns/Multi/Sequencer/ChaserPattern.c +++ b/VortexEngine/src/Patterns/Multi/Sequencer/ChaserPattern.c @@ -1,5 +1,6 @@ #include "../../Patterns/Multi/Sequencer/ChaserPattern.h" +#include "../../../c_types.h" static const PatternVTable ChaserPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; void ChaserPattern_init(ChaserPattern *self, const PatternArgs *args) { diff --git a/VortexEngine/src/Patterns/Multi/Sequencer/Sequence.c b/VortexEngine/src/Patterns/Multi/Sequencer/Sequence.c index 4f66444f93..81257dc12b 100644 --- a/VortexEngine/src/Patterns/Multi/Sequencer/Sequence.c +++ b/VortexEngine/src/Patterns/Multi/Sequencer/Sequence.c @@ -2,6 +2,7 @@ #include +#include "../../../c_types.h" #if VORTEX_SLIM == 1 void PatternMap_init(PatternMap *self) { diff --git a/VortexEngine/src/Patterns/Multi/Sequencer/SequencedPattern.c b/VortexEngine/src/Patterns/Multi/Sequencer/SequencedPattern.c index 768fe3cacc..071e02a634 100644 --- a/VortexEngine/src/Patterns/Multi/Sequencer/SequencedPattern.c +++ b/VortexEngine/src/Patterns/Multi/Sequencer/SequencedPattern.c @@ -1,5 +1,5 @@ -#include "SequencedPattern.h" +#include "../../../c_types.h" static const PatternVTable SequencedPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; void SequencedPattern_init(SequencedPattern *self, const PatternArgs *args) { diff --git a/VortexEngine/src/Patterns/Multi/SnowballPattern.c b/VortexEngine/src/Patterns/Multi/SnowballPattern.c index 3505fb2f91..ef5edd3b86 100644 --- a/VortexEngine/src/Patterns/Multi/SnowballPattern.c +++ b/VortexEngine/src/Patterns/Multi/SnowballPattern.c @@ -1,5 +1,5 @@ -#include "SnowballPattern.h" +#include "../../c_types.h" static const PatternVTable SnowballPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; void SnowballPattern_init(SnowballPattern *self, const PatternArgs *args) { diff --git a/VortexEngine/src/Patterns/Multi/SparkleTracePattern.c b/VortexEngine/src/Patterns/Multi/SparkleTracePattern.c index 294b9a32ba..1f5675c0b4 100644 --- a/VortexEngine/src/Patterns/Multi/SparkleTracePattern.c +++ b/VortexEngine/src/Patterns/Multi/SparkleTracePattern.c @@ -1,5 +1,5 @@ -#include "SparkleTracePattern.h" +#include "../../c_types.h" static const PatternVTable SparkleTracePattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; void SparkleTracePattern_init(SparkleTracePattern *self, const PatternArgs *args) { diff --git a/VortexEngine/src/Patterns/Multi/TheaterChasePattern.c b/VortexEngine/src/Patterns/Multi/TheaterChasePattern.c index c9433b4b44..9d9d915780 100644 --- a/VortexEngine/src/Patterns/Multi/TheaterChasePattern.c +++ b/VortexEngine/src/Patterns/Multi/TheaterChasePattern.c @@ -1,5 +1,5 @@ -#include "TheaterChasePattern.h" +#include "../../c_types.h" static const PatternVTable TheaterChasePattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; void TheaterChasePattern_init(TheaterChasePattern *self, const PatternArgs *args) { diff --git a/VortexEngine/src/Patterns/Multi/VortexPattern.c b/VortexEngine/src/Patterns/Multi/VortexPattern.c index 7bcd78e7e5..0966eea024 100644 --- a/VortexEngine/src/Patterns/Multi/VortexPattern.c +++ b/VortexEngine/src/Patterns/Multi/VortexPattern.c @@ -1,5 +1,5 @@ -#include "VortexPattern.h" +#include "../../c_types.h" static const PatternVTable VortexPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; void VortexPattern_init(VortexPattern *self, const PatternArgs *args) { diff --git a/VortexEngine/src/Patterns/Multi/VortexWipePattern.c b/VortexEngine/src/Patterns/Multi/VortexWipePattern.c index 11ca260802..09a6094fa6 100644 --- a/VortexEngine/src/Patterns/Multi/VortexWipePattern.c +++ b/VortexEngine/src/Patterns/Multi/VortexWipePattern.c @@ -1,5 +1,5 @@ -#include "VortexWipePattern.h" +#include "../../c_types.h" static const PatternVTable VortexWipePattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; void VortexWipePattern_init(VortexWipePattern *self, const PatternArgs *args) { diff --git a/VortexEngine/src/Patterns/Multi/WarpPattern.c b/VortexEngine/src/Patterns/Multi/WarpPattern.c index 02ed134f9e..27671e10e7 100644 --- a/VortexEngine/src/Patterns/Multi/WarpPattern.c +++ b/VortexEngine/src/Patterns/Multi/WarpPattern.c @@ -1,5 +1,5 @@ -#include "WarpPattern.h" +#include "../../c_types.h" static const PatternVTable WarpPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; void WarpPattern_init(WarpPattern *self, const PatternArgs *args) { diff --git a/VortexEngine/src/Patterns/Multi/WarpWormPattern.c b/VortexEngine/src/Patterns/Multi/WarpWormPattern.c index bf948658a8..dade05cf57 100644 --- a/VortexEngine/src/Patterns/Multi/WarpWormPattern.c +++ b/VortexEngine/src/Patterns/Multi/WarpWormPattern.c @@ -1,5 +1,5 @@ -#include "WarpWormPattern.h" +#include "../../c_types.h" static const PatternVTable WarpWormPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; void WarpWormPattern_init(WarpWormPattern *self, const PatternArgs *args) { diff --git a/VortexEngine/src/Patterns/Multi/ZigzagPattern.c b/VortexEngine/src/Patterns/Multi/ZigzagPattern.c index 0ab9fe5bd2..9854978a84 100644 --- a/VortexEngine/src/Patterns/Multi/ZigzagPattern.c +++ b/VortexEngine/src/Patterns/Multi/ZigzagPattern.c @@ -1,5 +1,5 @@ -#include "ZigzagPattern.h" +#include "../../c_types.h" static const PatternVTable ZigzagPattern_vtable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; void ZigzagPattern_init(ZigzagPattern *self, const PatternArgs *args) { diff --git a/VortexEngine/src/Patterns/Pattern.c b/VortexEngine/src/Patterns/Pattern.c index 05c7682b85..7d18105076 100644 --- a/VortexEngine/src/Patterns/Pattern.c +++ b/VortexEngine/src/Patterns/Pattern.c @@ -1,13 +1,6 @@ -#include "Pattern.h" -#include "../Patterns/PatternBuilder.h" -#include "../Serial/ByteStream.h" -#include "../Time/TimeControl.h" -#include "../Colors/Colorset.h" -#include "../Memory/Memory.h" -#include "../Log/Log.h" -#include "../VortexConfig.h" +#include "../c_types.h" void Pattern_init(Pattern *self, const PatternArgs *args) { (void)args; diff --git a/VortexEngine/src/Patterns/PatternArgs.c b/VortexEngine/src/Patterns/PatternArgs.c index 6988bbade1..191829c9ec 100644 --- a/VortexEngine/src/Patterns/PatternArgs.c +++ b/VortexEngine/src/Patterns/PatternArgs.c @@ -1,7 +1,6 @@ -#include "PatternArgs.h" -#include "../Serial/ByteStream.h" +#include "../c_types.h" void PatternArgs_init(PatternArgs *self) { self->arg1 = 0; self->arg2 = 0; self->arg3 = 0; self->arg4 = 0; self->arg5 = 0; self->arg6 = 0; self->arg7 = 0; self->arg8 = 0; diff --git a/VortexEngine/src/Patterns/PatternBuilder.c b/VortexEngine/src/Patterns/PatternBuilder.c index 5fb6c881e6..5559c58726 100644 --- a/VortexEngine/src/Patterns/PatternBuilder.c +++ b/VortexEngine/src/Patterns/PatternBuilder.c @@ -1,48 +1,44 @@ -#include "PatternBuilder.h" -#include "../Serial/ByteStream.h" -#include "../Time/TimeControl.h" -#include "../Time/Timings.h" -#include "../Log/Log.h" -#include "../Memory/Memory.h" -#include "Pattern.h" -#include "Single/SingleLedPattern.h" -#include "Single/BasicPattern.h" -#include "Single/BlendPattern.h" -#include "Single/SolidPattern.h" +#include "../c_types.h" #if VORTEX_SLIM == 0 -#include "Multi/Sequencer/SequencedPattern.h" -#include "Multi/Sequencer/ChaserPattern.h" -#include "Multi/Sequencer/Sequence.h" -#include "Multi/TheaterChasePattern.h" -#include "Multi/HueShiftPattern.h" -#include "Multi/ZigzagPattern.h" -#include "Multi/DripPattern.h" -#include "Multi/DripMorphPattern.h" -#include "Multi/CrossDopsPattern.h" -#include "Multi/DoubleStrobePattern.h" -#include "Multi/MeteorPattern.h" -#include "Multi/SparkleTracePattern.h" -#include "Multi/VortexWipePattern.h" -#include "Multi/WarpPattern.h" -#include "Multi/WarpWormPattern.h" -#include "Multi/FillPattern.h" -#include "Multi/SnowballPattern.h" -#include "Multi/LighthousePattern.h" -#include "Multi/PulsishPattern.h" -#include "Multi/BouncePattern.h" -#include "Multi/BackStrobePattern.h" -#include "Multi/VortexPattern.h" #endif // Forward declarations static Pattern *PatternBuilder_generate(PatternID id, const PatternArgs *args); static Pattern *PatternBuilder_makeInternal(PatternID id, const PatternArgs *args); +static PatternUnion s_patternPool[MAX_PATTERN_POOL]; +static uint8_t s_patternPoolInUse[MAX_PATTERN_POOL]; + +Pattern *PatternBuilder_alloc(void) +{ + for (uint8_t i = 0; i < MAX_PATTERN_POOL; ++i) { + if (!s_patternPoolInUse[i]) { + s_patternPoolInUse[i] = 1; + return &s_patternPool[i].base; + } + } + ERROR_OUT_OF_MEMORY(); + return NULL; +} + +void PatternBuilder_release(Pattern *pat) +{ + if (!pat) { + return; + } + for (uint8_t i = 0; i < MAX_PATTERN_POOL; ++i) { + if (&s_patternPool[i].base == pat) { + s_patternPoolInUse[i] = 0; + return; + } + } +} + Pattern *PatternBuilder_make(PatternID id, const PatternArgs *args) { #if VORTEX_SLIM == 0 @@ -106,7 +102,7 @@ Pattern *PatternBuilder_unserialize(ByteStream *buffer) } if (!Pattern_unserialize(pat, buffer)) { Pattern_destroy(pat); - vfree(pat); + PatternBuilder_release(pat); return NULL; } return pat; @@ -202,7 +198,7 @@ uint8_t PatternBuilder_numDefaultArgs(PatternID id) } uint8_t numArgs = Pattern_getNumArgs(pat); Pattern_destroy(pat); - vfree(pat); + PatternBuilder_release(pat); return numArgs; } @@ -238,7 +234,7 @@ static Pattern *PatternBuilder_generate(PatternID id, const PatternArgs *userArg case PATTERN_TRACER: case PATTERN_RIBBON: case PATTERN_MINIRIBBON: { - BasicPattern *bp = (BasicPattern *)vmalloc(sizeof(BasicPattern)); + BasicPattern *bp = (BasicPattern *)PatternBuilder_alloc(); if (!bp) return NULL; BasicPattern_init(bp, args); return &bp->base.base; @@ -249,13 +245,13 @@ static Pattern *PatternBuilder_generate(PatternID id, const PatternArgs *userArg case PATTERN_COMPLEMENTARY_BLEND: case PATTERN_COMPLEMENTARY_BLENDSTROBE: case PATTERN_COMPLEMENTARY_BLENDSTROBEGAP: { - BlendPattern *bp = (BlendPattern *)vmalloc(sizeof(BlendPattern)); + BlendPattern *bp = (BlendPattern *)PatternBuilder_alloc(); if (!bp) return NULL; BlendPattern_init(bp, args); return &bp->base.base.base; } case PATTERN_SOLID: { - SolidPattern *sp = (SolidPattern *)vmalloc(sizeof(SolidPattern)); + SolidPattern *sp = (SolidPattern *)PatternBuilder_alloc(); if (!sp) return NULL; SolidPattern_init(sp, args); return &sp->base.base.base; diff --git a/VortexEngine/src/Patterns/Single/BasicPattern.c b/VortexEngine/src/Patterns/Single/BasicPattern.c index 21276606b1..fecbbe273c 100644 --- a/VortexEngine/src/Patterns/Single/BasicPattern.c +++ b/VortexEngine/src/Patterns/Single/BasicPattern.c @@ -1,9 +1,6 @@ -#include "BasicPattern.h" -#include "../../Time/TimeControl.h" -#include "../../Colors/Colorset.h" -#include "../../Leds/Leds.h" +#include "../../c_types.h" const PatternVTable BasicPattern_vtable = { NULL, BasicPattern_play, diff --git a/VortexEngine/src/Patterns/Single/BlendPattern.c b/VortexEngine/src/Patterns/Single/BlendPattern.c index 17aa7a6c2d..8596fcf19b 100644 --- a/VortexEngine/src/Patterns/Single/BlendPattern.c +++ b/VortexEngine/src/Patterns/Single/BlendPattern.c @@ -1,9 +1,6 @@ -#include "BlendPattern.h" -#include "../../Time/TimeControl.h" -#include "../../Colors/Colorset.h" -#include "../../Leds/Leds.h" +#include "../../c_types.h" const PatternVTable BlendPattern_vtable = { NULL, BasicPattern_play, diff --git a/VortexEngine/src/Patterns/Single/SingleLedPattern.c b/VortexEngine/src/Patterns/Single/SingleLedPattern.c index c0e35b1e84..e05d8f3b64 100644 --- a/VortexEngine/src/Patterns/Single/SingleLedPattern.c +++ b/VortexEngine/src/Patterns/Single/SingleLedPattern.c @@ -1,5 +1,5 @@ -#include "SingleLedPattern.h" +#include "../../c_types.h" void SingleLedPattern_init(SingleLedPattern *self, const PatternArgs *args) { Pattern_init(&self->base, args); diff --git a/VortexEngine/src/Patterns/Single/SolidPattern.c b/VortexEngine/src/Patterns/Single/SolidPattern.c index 5603bb6bd1..e2d3ce3d65 100644 --- a/VortexEngine/src/Patterns/Single/SolidPattern.c +++ b/VortexEngine/src/Patterns/Single/SolidPattern.c @@ -1,5 +1,5 @@ -#include "SolidPattern.h" +#include "../../c_types.h" const PatternVTable SolidPattern_vtable = { NULL, BasicPattern_play, diff --git a/VortexEngine/src/Random/Random.c b/VortexEngine/src/Random/Random.c index bb0fab9d38..0e86601886 100644 --- a/VortexEngine/src/Random/Random.c +++ b/VortexEngine/src/Random/Random.c @@ -1,5 +1,5 @@ -#include "Random.h" +#include "../c_types.h" void Random_init(Random *self) { self->seed = 0; } diff --git a/VortexEngine/src/Serial/BitStream.c b/VortexEngine/src/Serial/BitStream.c index d8a9c4dedf..6b7efe3855 100644 --- a/VortexEngine/src/Serial/BitStream.c +++ b/VortexEngine/src/Serial/BitStream.c @@ -1,10 +1,12 @@ -#include "BitStream.h" -#include "../Memory/Memory.h" -#include "../Log/Log.h" #include +#include "../c_types.h" + +#define BITSTREAM_STATIC_BYTES VL_RECV_BUF_SIZE +static uint32_t s_bitStreamStatic[(BITSTREAM_STATIC_BYTES + 3) / sizeof(uint32_t)]; + void BitStream_init(BitStream *self) { self->buf = NULL; @@ -24,25 +26,18 @@ void BitStream_initBuf(BitStream *self, uint8_t *buf, uint32_t size) bool BitStream_initAlloc(BitStream *self, uint32_t size) { - if (self->buf) { - vfree(self->buf); - } - self->buf = (uint8_t *)vcalloc(1, size); - if (!self->buf) { + if (size > sizeof(s_bitStreamStatic)) { ERROR_OUT_OF_MEMORY(); return false; } - self->buf_size = (uint16_t)size; - self->allocated = true; - BitStream_resetPos(self); + BitStream_initBuf(self, (uint8_t *)s_bitStreamStatic, size); + memset(self->buf, 0, size); return true; } void BitStream_destroy(BitStream *self) { - if (self->allocated) { - vfree(self->buf); - } + (void)self; } void BitStream_reset(BitStream *self) @@ -117,3 +112,43 @@ void BitStream_writeBits(BitStream *self, uint32_t numBits, uint32_t val) BitStream_write1Bit(self, (val >> ((numBits - 1) - i)) & 1); } } + +bool BitStream_eof(const BitStream *self) +{ + return self->buf_eof; +} + +bool BitStream_allocated(const BitStream *self) +{ + return self->allocated; +} + +uint16_t BitStream_size(const BitStream *self) +{ + return self->buf_size; +} + +const uint8_t *BitStream_data(const BitStream *self) +{ + return self->buf; +} + +uint8_t BitStream_peekData(const BitStream *self, uint8_t pos) +{ + return self->buf[pos]; +} + +uint16_t BitStream_dwordpos(const BitStream *self) +{ + return (uint16_t)(self->bit_pos / 32); +} + +uint16_t BitStream_bytepos(const BitStream *self) +{ + return (uint16_t)(self->bit_pos / 8); +} + +uint16_t BitStream_bitpos(const BitStream *self) +{ + return self->bit_pos; +} diff --git a/VortexEngine/src/Serial/ByteStream.c b/VortexEngine/src/Serial/ByteStream.c index 96d348f142..6fb2139be0 100644 --- a/VortexEngine/src/Serial/ByteStream.c +++ b/VortexEngine/src/Serial/ByteStream.c @@ -1,16 +1,21 @@ -#include "ByteStream.h" -#include "../Serial/BitStream.h" -#include "../Memory/Memory.h" -#include "../Log/Log.h" #include -#include "Compression.h" +#include "../c_types.h" #define BUFFER_FLAG_COMRPESSED (1<<0) #define BUFFER_FLAG_DIRTY (1<<1) +#define BYTESTREAM_SCRATCH_BYTES 96 +static uint32_t s_scratch[BYTESTREAM_SCRATCH_BYTES / sizeof(uint32_t)]; +static bool s_scratchBusy = false; + +static bool ByteStream_usesScratch(const ByteStream *self) +{ + return self->pData == (const RawBuffer *)s_scratch; +} + // --- RawBuffer free functions --- uint32_t RawBuffer_hash(const RawBuffer *self) @@ -53,9 +58,6 @@ void ByteStream_destroy(ByteStream *self) void ByteStream_copy(ByteStream *self, const ByteStream *other) { - self->pData = NULL; - self->position = 0; - self->capacity = 0; ByteStream_init(self, other->capacity, ByteStream_data(other)); if (self->pData && other->pData) { self->pData->flags = other->pData->flags; @@ -79,6 +81,7 @@ void ByteStream_move(ByteStream *self, ByteStream *target) if (!target) { return; } + bool wasScratch = ByteStream_usesScratch(self); ByteStream_clear(target); target->pData = self->pData; target->position = self->position; @@ -86,6 +89,36 @@ void ByteStream_move(ByteStream *self, ByteStream *target) self->pData = NULL; self->position = 0; self->capacity = 0; + if (wasScratch) { + s_scratchBusy = true; + } +} + +void ByteStream_resetData(ByteStream *self) +{ + if (!self->pData) { + return; + } + self->pData->size = 0; + self->pData->flags = 0; + self->pData->crc32 = 0; + self->position = 0; +} + +void ByteStream_initStatic(ByteStream *self, uint8_t *storage, uint32_t storageSize) +{ + if (ByteStream_usesScratch(self)) { + s_scratchBusy = false; + } + if (!storage || storageSize <= sizeof(RawBuffer)) { + self->pData = NULL; + self->capacity = 0; + self->position = 0; + return; + } + self->pData = (RawBuffer *)storage; + self->capacity = (uint16_t)((storageSize - sizeof(RawBuffer)) & ~3u); + ByteStream_resetData(self); } bool ByteStream_rawInit(ByteStream *self, const uint8_t *rawdata, uint32_t size) @@ -94,12 +127,13 @@ bool ByteStream_rawInit(ByteStream *self, const uint8_t *rawdata, uint32_t size) DEBUG_LOGF("Cannot rawInit: %p %u", (const void *)rawdata, (unsigned)size); return false; } - self->capacity = (uint16_t)((size + 4) - (size % 4)); - self->pData = (RawBuffer *)vcalloc(1, (size_t)self->capacity + sizeof(RawBuffer)); - if (!self->pData) { - self->capacity = 0; - ERROR_OUT_OF_MEMORY(); - return false; + uint16_t capacity = (uint16_t)((size + 4) - (size % 4)); + if (ByteStream_capacity(self) < capacity) { + if (!ByteStream_init(self, capacity, NULL)) { + return false; + } + } else { + ByteStream_resetData(self); } memcpy(self->pData, rawdata, size); ByteStream_sanity(self); @@ -109,20 +143,25 @@ bool ByteStream_rawInit(ByteStream *self, const uint8_t *rawdata, uint32_t size) bool ByteStream_init(ByteStream *self, uint32_t size, const uint8_t *buf) { ByteStream_clear(self); + uint16_t capacity; if (size) { - self->capacity = (uint16_t)((size + 4) - (size % 4)); - self->pData = (RawBuffer *)vcalloc(1, (size_t)self->capacity + sizeof(RawBuffer)); - if (!self->pData) { - self->capacity = 0; - ERROR_OUT_OF_MEMORY(); - return false; - } - self->pData->size = 0; - self->pData->flags = 0; - self->pData->crc32 = 0; - memset(self->pData->buf, 0, self->capacity); + capacity = (uint16_t)((size + 4) - (size % 4)); + } else { + capacity = (uint16_t)((sizeof(s_scratch) - sizeof(RawBuffer)) & ~3u); + } + if (((uint32_t)capacity + sizeof(RawBuffer) > sizeof(s_scratch)) || s_scratchBusy) { + ERROR_OUT_OF_MEMORY(); + self->capacity = 0; + return false; } - if (buf && self->pData) { + s_scratchBusy = true; + self->capacity = capacity; + self->pData = (RawBuffer *)s_scratch; + self->pData->size = 0; + self->pData->flags = 0; + self->pData->crc32 = 0; + memset(self->pData->buf, 0, capacity); + if (buf) { memcpy(self->pData->buf, buf, size); self->pData->size = size; RawBuffer_recalcCRC(self->pData); @@ -132,10 +171,10 @@ bool ByteStream_init(ByteStream *self, uint32_t size, const uint8_t *buf) void ByteStream_clear(ByteStream *self) { - if (self->pData) { - vfree(self->pData); - self->pData = NULL; + if (ByteStream_usesScratch(self)) { + s_scratchBusy = false; } + self->pData = NULL; self->capacity = 0; self->position = 0; } @@ -145,16 +184,7 @@ bool ByteStream_shrink(ByteStream *self) if (!self->pData) { return false; } - if (self->pData->size == self->capacity) { - return true; - } self->capacity = (uint16_t)self->pData->size; - RawBuffer *temp = (RawBuffer *)vrealloc(self->pData, (size_t)self->pData->size + sizeof(RawBuffer)); - if (!temp) { - ERROR_OUT_OF_MEMORY(); - return false; - } - self->pData = temp; return true; } @@ -196,20 +226,8 @@ bool ByteStream_extend(ByteStream *self, uint32_t size) if (!self->capacity) { return ByteStream_init(self, size, NULL); } - uint32_t buffer_size = (uint32_t)self->capacity + size + 3; - buffer_size -= buffer_size % 4; - uint32_t new_size = buffer_size + sizeof(RawBuffer); - RawBuffer *temp = (RawBuffer *)vrealloc(self->pData, new_size); - if (!temp) { - ERROR_OUT_OF_MEMORY(); - return false; - } - uint8_t *new_mem = ((uint8_t *)temp) + sizeof(RawBuffer) + self->capacity; - size_t new_mem_size = buffer_size - self->capacity; - memset(new_mem, 0, new_mem_size); - self->pData = temp; - self->capacity = (uint16_t)buffer_size; - return true; + ERROR_OUT_OF_MEMORY(); + return false; } bool ByteStream_compress(ByteStream *self) diff --git a/VortexEngine/src/Serial/Compression.c b/VortexEngine/src/Serial/Compression.c index 529c45d1a2..ad1ab3b989 100644 --- a/VortexEngine/src/Serial/Compression.c +++ b/VortexEngine/src/Serial/Compression.c @@ -1,9 +1,7 @@ -#include "Compression.h" +#include "../c_types.h" // This is a heavily stripped down version of lz4 -#include "../Memory/Memory.h" -#include "../VortexConfig.h" #include #include diff --git a/VortexEngine/src/Serial/Serial.c b/VortexEngine/src/Serial/Serial.c index 3dff9d68d9..0dee2bf35e 100644 --- a/VortexEngine/src/Serial/Serial.c +++ b/VortexEngine/src/Serial/Serial.c @@ -1,12 +1,7 @@ -#include "Serial.h" -#include "../Serial/ByteStream.h" -#include "../Time/TimeControl.h" -#include "../Time/Timings.h" -#include "../Log/Log.h" -#include "../VortexEngine.h" +#include "../c_types.h" #ifdef VORTEX_LIB #include "VortexLib.h" #include diff --git a/VortexEngine/src/Storage/Storage.c b/VortexEngine/src/Storage/Storage.c index 75d7203b39..7f52ae398e 100644 --- a/VortexEngine/src/Storage/Storage.c +++ b/VortexEngine/src/Storage/Storage.c @@ -1,13 +1,9 @@ -#include "Storage.h" #include #include -#include "../VortexConfig.h" -#include "../Memory/Memory.h" -#include "../Serial/ByteStream.h" -#include "../Log/Log.h" +#include "../c_types.h" #ifdef VORTEX_EMBEDDED #include #include @@ -161,8 +157,12 @@ bool Storage_read(uint8_t slot, ByteStream *buffer) if (size > STORAGE_SIZE || size < sizeof(RawBuffer) + 4 || slot >= NUM_MODE_SLOTS) { return false; } - if (!ByteStream_init(buffer, size, NULL)) { - return false; + if (ByteStream_capacity(buffer) < size) { + if (!ByteStream_init(buffer, size, NULL)) { + return false; + } + } else { + ByteStream_resetData(buffer); } #ifdef VORTEX_EMBEDDED uint8_t *buf = (uint8_t *)ByteStream_rawData(buffer); diff --git a/VortexEngine/src/Time/TimeControl.c b/VortexEngine/src/Time/TimeControl.c index 6c80b42d5a..abc8e5c00b 100644 --- a/VortexEngine/src/Time/TimeControl.c +++ b/VortexEngine/src/Time/TimeControl.c @@ -1,14 +1,10 @@ -#include "TimeControl.h" #include -#include "../Memory/Memory.h" -#include "../Log/Log.h" -#include "Timings.h" -#include "../Leds/Leds.h" +#include "../c_types.h" #ifdef VORTEX_EMBEDDED #include #include diff --git a/VortexEngine/src/Time/Timer.c b/VortexEngine/src/Time/Timer.c index e2390aeada..8f6b2105a4 100644 --- a/VortexEngine/src/Time/Timer.c +++ b/VortexEngine/src/Time/Timer.c @@ -1,10 +1,7 @@ -#include "Timer.h" -#include "TimeControl.h" -#include "../Memory/Memory.h" -#include "../Log/Log.h" +#include "../c_types.h" static uint32_t Timer_getStartTime(const Timer *self) { #ifdef VORTEX_LIB @@ -28,7 +25,6 @@ static void Timer_setStartTime(Timer *self, uint32_t tick) void Timer_init(Timer *self) { - self->alarms = NULL; self->numAlarms = 0; self->curAlarm = 0; self->startTime = 0; @@ -39,10 +35,7 @@ void Timer_init(Timer *self) void Timer_cleanup(Timer *self) { - if (self->alarms) { - vfree(self->alarms); - self->alarms = NULL; - } + (void)self; } void Timer_initFlags(Timer *self, uint8_t flags, uint8_t alarm1, uint8_t alarm2, uint8_t alarm3, uint8_t alarm4) @@ -57,12 +50,10 @@ void Timer_initFlags(Timer *self, uint8_t flags, uint8_t alarm1, uint8_t alarm2, AlarmID Timer_addAlarm(Timer *self, uint32_t interval) { - void *temp = vrealloc(self->alarms, sizeof(uint32_t) * (self->numAlarms + 1)); - if (!temp) { + if (self->numAlarms >= TIMER_4_ALARMS) { ERROR_OUT_OF_MEMORY(); return -1; } - self->alarms = (uint32_t *)temp; self->alarms[self->numAlarms] = interval; return (AlarmID)(self->numAlarms++); } @@ -83,8 +74,6 @@ void Timer_start(Timer *self, uint32_t offset) void Timer_reset(Timer *self) { - vfree(self->alarms); - self->alarms = NULL; self->numAlarms = 0; self->curAlarm = 0; self->startTime = 0; @@ -103,7 +92,7 @@ bool Timer_onStart(const Timer *self) bool Timer_onEnd(const Timer *self) { - if (!self->alarms || !self->numAlarms) { + if (!self->numAlarms) { return false; } uint32_t alarmTime = self->alarms[self->curAlarm]; @@ -121,7 +110,7 @@ bool Timer_onEnd(const Timer *self) AlarmID Timer_alarm(Timer *self) { - if (!self->numAlarms || !self->alarms || self->curAlarm == ALARM_NONE) { + if (!self->numAlarms || self->curAlarm == ALARM_NONE) { return ALARM_NONE; } uint32_t now = Time_getCurtime(); diff --git a/VortexEngine/src/VortexEngine.c b/VortexEngine/src/VortexEngine.c index cab704080e..a57e9d8e70 100644 --- a/VortexEngine/src/VortexEngine.c +++ b/VortexEngine/src/VortexEngine.c @@ -1,21 +1,6 @@ -#include "VortexEngine.h" - -#include "Wireless/IRReceiver.h" -#include "Wireless/IRSender.h" -#include "Wireless/VLReceiver.h" -#include "Wireless/VLSender.h" -#include "Wireless/IRConfig.h" -#include "Wireless/VLConfig.h" -#include "Storage/Storage.h" -#include "Buttons/Buttons.h" -#include "Time/TimeControl.h" -#include "Time/Timings.h" -#include "Serial/Serial.h" -#include "Modes/Modes.h" -#include "Menus/Menus.h" -#include "Modes/Mode.h" -#include "Leds/Leds.h" + +#include "c_types.h" #ifdef VORTEX_EMBEDDED #include #include @@ -417,9 +402,6 @@ void VortexEngine_enableMOSFET(bool enabled) #if COMPRESSION_TEST == 1 #include #include -#include "Colors/Colorset.h" -#include "Memory/Memory.h" -#include "Random/Random.h" void VortexEngine_compressionTest(void) { Random rand; diff --git a/VortexEngine/src/Wireless/IRReceiver.c b/VortexEngine/src/Wireless/IRReceiver.c index 7b799b9435..d1b82bb89b 100644 --- a/VortexEngine/src/Wireless/IRReceiver.c +++ b/VortexEngine/src/Wireless/IRReceiver.c @@ -1,13 +1,7 @@ -#include "IRReceiver.h" -#include "IRConfig.h" +#include "../c_types.h" #if IR_ENABLE_RECEIVER == 1 -#include "../Serial/ByteStream.h" -#include "../Serial/BitStream.h" -#include "../Time/TimeControl.h" -#include "../Modes/Mode.h" -#include "../Log/Log.h" BitStream IRReceiver_irData; IRRecvState IRReceiver_recvState = IR_WAITING_HEADER_MARK; diff --git a/VortexEngine/src/Wireless/IRSender.c b/VortexEngine/src/Wireless/IRSender.c index 3fa754776c..ea001693b6 100644 --- a/VortexEngine/src/Wireless/IRSender.c +++ b/VortexEngine/src/Wireless/IRSender.c @@ -1,11 +1,7 @@ -#include "IRSender.h" -#include "IRConfig.h" +#include "../c_types.h" #if IR_ENABLE_SENDER == 1 -#include "../Time/TimeControl.h" -#include "../Modes/Mode.h" -#include "../Log/Log.h" #ifdef VORTEX_LIB #include "VortexLib.h" diff --git a/VortexEngine/src/Wireless/VLReceiver.c b/VortexEngine/src/Wireless/VLReceiver.c index 9332393cac..2e857c54f8 100644 --- a/VortexEngine/src/Wireless/VLReceiver.c +++ b/VortexEngine/src/Wireless/VLReceiver.c @@ -1,14 +1,7 @@ -#include "VLReceiver.h" -#include "IRConfig.h" +#include "../c_types.h" #if VL_ENABLE_RECEIVER == 1 -#include "../Serial/ByteStream.h" -#include "../Serial/BitStream.h" -#include "../Time/TimeControl.h" -#include "../Modes/Mode.h" -#include "../Leds/Leds.h" -#include "../Log/Log.h" #ifdef VORTEX_EMBEDDED #include @@ -86,7 +79,9 @@ uint8_t VLReceiver_percentReceived() bool VLReceiver_receiveMode(Mode *pMode) { - ByteStream buf; + static uint8_t s_vlModeStore[96]; + static ByteStream buf; + ByteStream_initStatic(&buf, s_vlModeStore, sizeof(s_vlModeStore)); if (!VLReceiver_read(&buf)) { DEBUG_LOG("No data available to read, or error reading"); return false; @@ -132,6 +127,11 @@ bool VLReceiver_onNewData() return true; } +uint16_t VLReceiver_bytesReceived() +{ + return BitStream_bytepos(&VLReceiver_vlData); +} + bool VLReceiver_read(ByteStream *data) { if (!BitStream_bytepos(&VLReceiver_vlData) || BitStream_bytepos(&VLReceiver_vlData) > VL_MAX_DATA_TRANSFER) { diff --git a/VortexEngine/src/Wireless/VLSender.c b/VortexEngine/src/Wireless/VLSender.c index 21ce06e4d9..fc28300664 100644 --- a/VortexEngine/src/Wireless/VLSender.c +++ b/VortexEngine/src/Wireless/VLSender.c @@ -1,12 +1,7 @@ -#include "VLSender.h" -#include "IRConfig.h" +#include "../c_types.h" #if VL_ENABLE_SENDER == 1 -#include "../Time/TimeControl.h" -#include "../Modes/Mode.h" -#include "../Leds/Leds.h" -#include "../Log/Log.h" #ifdef VORTEX_LIB #include "VortexLib.h" @@ -19,6 +14,8 @@ BitStream VLSender_bitStream; uint8_t VLSender_size = 0; uint8_t VLSender_parity = 0; +static uint8_t s_vlSenderStore[88]; + bool VLSender_init() { return true; @@ -30,7 +27,7 @@ void VLSender_cleanup() bool VLSender_loadMode(const Mode *targetMode) { - ByteStream_clear(&VLSender_serialBuf); + ByteStream_initStatic(&VLSender_serialBuf, s_vlSenderStore, sizeof(s_vlSenderStore)); if (!Mode_saveToBuffer(targetMode, &VLSender_serialBuf, 2)) { DEBUG_LOG("Failed to save mode to buffer"); return false; From 9e15098a0598f6e9cf0ed9e6b028aef9a8616aad Mon Sep 17 00:00:00 2001 From: Unreal-Dan Date: Fri, 18 Sep 2026 13:34:15 -0700 Subject: [PATCH 09/10] missing c types file committed --- VortexEngine/src/c_types.h | 2059 ++++++++++++++++++++++++++++++++++++ 1 file changed, 2059 insertions(+) create mode 100644 VortexEngine/src/c_types.h diff --git a/VortexEngine/src/c_types.h b/VortexEngine/src/c_types.h new file mode 100644 index 0000000000..45d2008abe --- /dev/null +++ b/VortexEngine/src/c_types.h @@ -0,0 +1,2059 @@ +#ifndef C_TYPES_H +#define C_TYPES_H + +#include +#include +#include +#include +#include +#include +#include +#include "VortexConfig.h" + +// ============================================================================= +// Forward declarations +// ============================================================================= +typedef struct ByteStream ByteStream; +typedef struct BitStream BitStream; +typedef struct Colorset Colorset; +typedef struct Mode Mode; +typedef struct Pattern Pattern; +typedef struct PatternArgs PatternArgs; +typedef struct Menu Menu; +typedef struct Random_t Random_t; +typedef struct Random_t Random; +typedef struct LedStash LedStash; + +// ============================================================================= +// LedTypes.h +// ============================================================================= +typedef uint8_t LedPos; +typedef uint8_t Pair; +typedef uint64_t LedMap; + +enum { + LED_FIRST = 0, + LED_0 = LED_FIRST, + LED_1, + LED_COUNT, + LED_LAST = (LED_COUNT - 1), + LED_ALL = LED_COUNT, + LED_MULTI = (LED_COUNT + 1), + LED_ALL_SINGLE = (LED_COUNT + 2), + LED_ANY = (LED_COUNT + 3), +}; + +#define LED_TIP LED_0 +#define LED_TOP LED_1 + +enum { + PAIR_FIRST = 0, + PAIR_0 = PAIR_FIRST, + PAIR_COUNT, + PAIR_LAST = (PAIR_COUNT - 1), +}; + +#define LED_2 LED_0 +#define LED_3 LED_1 +#define LED_4 LED_0 +#define LED_5 LED_1 +#define LED_6 LED_0 +#define LED_7 LED_1 +#define LED_8 LED_0 +#define LED_9 LED_1 + +#define PAIR_1 PAIR_0 +#define PAIR_2 PAIR_0 +#define PAIR_3 PAIR_0 +#define PAIR_4 PAIR_0 + +#define isEven(pos) ((pos % 2) == 0) +#define isOdd(pos) ((pos % 2) != 0) + +#define pairEven(pair) ((LedPos)((uint32_t)(pair) * 2)) +#define pairOdd(pair) ((LedPos)(((uint32_t)(pair) * 2) + 1)) +#define ledToPair(pos) ((Pair)((uint32_t)(pos) / 2)) + +#define MAP_LED(led) ((LedMap)((uint64_t)1 << (led))) +#define MAP_PAIR_EVEN(pair) MAP_LED(pairEven(pair)) +#define MAP_PAIR_ODD(pair) MAP_LED(pairOdd(pair)) +#define MAP_PAIR(pair) (MAP_PAIR_EVEN(pair) | MAP_PAIR_ODD(pair)) +#define MAP_IS_ONE_LED(map) ((map) && !((map) & ((map)-1))) +#define MAP_LED_ALL ((2 << (LED_COUNT - 1)) - 1) +#define MAP_LED_NONE 0 +#define MAP_INVERSE(map) ((~(map)) & MAP_LED_ALL) +#define MAP_PAIR_EVENS (((1 << LED_COUNT) - 1) & 0x55555555) +#define MAP_PAIR_ODDS (((1 << LED_COUNT) - 1) & 0xAAAAAAAA) +#define MAP_PAIR_ODD_EVENS (MAP_PAIR_EVEN(PAIR_0) | MAP_PAIR_EVEN(PAIR_2) | MAP_PAIR_EVEN(PAIR_4)) +#define MAP_PAIR_ODD_ODDS (MAP_PAIR_ODD(PAIR_0) | MAP_PAIR_ODD(PAIR_2) | MAP_PAIR_ODD(PAIR_4)) +#define MAP_PAIR_EVEN_EVENS (MAP_PAIR_EVEN(PAIR_3) | MAP_PAIR_EVEN(PAIR_1)) +#define MAP_PAIR_EVEN_ODDS (MAP_PAIR_ODD(PAIR_3) | MAP_PAIR_ODD(PAIR_1)) + +#define MAP_FOREACH_LED(map) for (LedPos pos = ledmapGetFirstLed(map); pos != LED_COUNT; pos = ledmapGetNextLed(map, pos)) + +static inline LedPos ledmapGetFirstLed(LedMap map) +{ + if (map == MAP_LED(LED_MULTI)) { + return LED_MULTI; + } + LedPos pos = LED_FIRST; + while (map && pos < LED_COUNT) { + if (map & 1) { + return pos; + } + map >>= 1; + pos = (LedPos)(pos + 1); + } + return LED_COUNT; +} + +static inline LedPos ledmapGetNextLed(LedMap map, LedPos pos) +{ + pos = (LedPos)(pos + 1); + map >>= pos; + while (map && pos < LED_COUNT) { + if (map & 1) { + return pos; + } + map >>= 1; + pos = (LedPos)(pos + 1); + } + return LED_COUNT; +} + +static inline bool ledmapCheckLed(LedMap map, LedPos pos) +{ + return ((map & ((LedMap)1 << pos)) != 0); +} + +static inline bool ledmapCheckPair(LedMap map, Pair pair) +{ + return ledmapCheckLed(map, pairEven(pair)) && ledmapCheckLed(map, pairOdd(pair)); +} + +static inline void ledmapSetLed(LedMap *map, LedPos pos) +{ + if (pos < LED_COUNT) { + *map |= ((LedMap)1 << pos); + } +} + +static inline void ledmapSetPair(LedMap *map, Pair pair) +{ + ledmapSetLed(map, pairEven(pair)); + ledmapSetLed(map, pairOdd(pair)); +} + +// ============================================================================= +// ColorTypes.h + ColorConstants.h +// ============================================================================= + +typedef enum hsv_to_rgb_algorithm hsv_to_rgb_algorithm; +enum hsv_to_rgb_algorithm { + HSV_TO_RGB_GENERIC, + HSV_TO_RGB_RAW, + HSV_TO_RGB_RAINBOW +}; + +extern enum hsv_to_rgb_algorithm g_hsv_rgb_alg; + +typedef struct { + uint8_t red; + uint8_t green; + uint8_t blue; +} RGBColor; + +typedef struct { + uint8_t hue; + uint8_t sat; + uint8_t val; +} HSVColor; + +void HSVColor_init(HSVColor *self); +void HSVColor_initHSV(HSVColor *self, uint8_t h, uint8_t s, uint8_t v); +void HSVColor_initFromU32(HSVColor *self, uint32_t dwVal); +void HSVColor_assignU32(HSVColor *self, const uint32_t *rhs); +void HSVColor_assignRGB(HSVColor *self, const RGBColor *rhs); +void HSVColor_initFromRGB(HSVColor *self, const RGBColor *rhs); +void HSVColor_assignHSV(HSVColor *self, const HSVColor *rhs); +void HSVColor_clear(HSVColor *self); +bool HSVColor_empty(const HSVColor *self); +uint32_t HSVColor_raw(const HSVColor *self); +bool HSVColor_equals(const HSVColor *self, const HSVColor *other); +void HSVColor_copy(HSVColor *self, const HSVColor *other); + +void RGBColor_init(RGBColor *self); +void RGBColor_initRGB(RGBColor *self, uint8_t r, uint8_t g, uint8_t b); +void RGBColor_initFromU32(RGBColor *self, uint32_t dwVal); +void RGBColor_assignU32(RGBColor *self, const uint32_t *rhs); +void RGBColor_assignHSV(RGBColor *self, const HSVColor *rhs); +void RGBColor_assignRGB(RGBColor *self, const RGBColor *rhs); +void RGBColor_clear(RGBColor *self); +bool RGBColor_empty(const RGBColor *self); +uint32_t RGBColor_raw(const RGBColor *self); +bool RGBColor_equals(const RGBColor *self, const RGBColor *other); +void RGBColor_copy(RGBColor *self, const RGBColor *other); +void RGBColor_adjustBrightness(RGBColor *self, uint8_t fadeBy); +bool RGBColor_serialize(const RGBColor *self, ByteStream *buffer); +bool RGBColor_unserialize(RGBColor *self, ByteStream *buffer); +RGBColor RGBColor_adjustBrightnessCopy(const RGBColor *self, uint8_t fadeBy); + +RGBColor hsv_to_rgb_rainbow(const HSVColor *rhs); +RGBColor hsv_to_rgb_raw_C(const HSVColor *rhs); +RGBColor hsv_to_rgb_generic(const HSVColor *rhs); +HSVColor rgb_to_hsv_approx(const RGBColor *rhs); +HSVColor rgb_to_hsv_generic(const RGBColor *rhs); + +#define HSV_HUE_RED 0 +#define HSV_HUE_ORANGE 32 +#define HSV_HUE_YELLOW 64 +#define HSV_HUE_GREEN 96 +#define HSV_HUE_AQUA 128 +#define HSV_HUE_BLUE 160 +#define HSV_HUE_PURPLE 192 +#define HSV_HUE_PINK 224 + +#define HSV_BIT ((uint32_t)1 << 31) +#ifndef HSV +#define HSV(h, s, v) (HSV_BIT | ((uint32_t)(h) << 16) | ((uint32_t)(s) << 8) | (uint32_t)(v)) +#endif + +#define HSV_WHITE HSV_BIT | (uint32_t)0x00006E +#define HSV_BLUE HSV_BIT | (uint32_t)0xA0FF6E +#define HSV_YELLOW HSV_BIT | (uint32_t)0x3CFF6E +#define HSV_RED HSV_BIT | (uint32_t)0x00FF6E +#define HSV_GREEN HSV_BIT | (uint32_t)0x55FF6E +#define HSV_CYAN HSV_BIT | (uint32_t)0x78FF6E +#define HSV_PURPLE HSV_BIT | (uint32_t)0xD4FF6E +#define HSV_ORANGE HSV_BIT | (uint32_t)0x14FF6E +#define HSV_OFF HSV_BIT | (uint32_t)0x000000 + +#define RGB_WHITE (uint32_t)0xFFFFFF +#define RGB_BLUE (uint32_t)0x0000FF +#define RGB_YELLOW (uint32_t)0xFFFF00 +#define RGB_RED (uint32_t)0xFF0000 +#define RGB_GREEN (uint32_t)0x00FF00 +#define RGB_CYAN (uint32_t)0x00FFFF +#define RGB_PURPLE (uint32_t)0x9933FF +#define RGB_ORANGE (uint32_t)0xFF8300 +#define RGB_PINK (uint32_t)0xFF0099 +#define RGB_MAGENTA (uint32_t)0xFF00FF +#define RGB_OFF (uint32_t)0x000000 + +#define RGB_WHITE0 (uint32_t)0x101010 +#define RGB_WHITE1 (uint32_t)0x1C1C1C +#define RGB_WHITE2 (uint32_t)0x383838 +#define RGB_WHITE3 (uint32_t)0x545454 +#define RGB_WHITE4 (uint32_t)0x707070 +#define RGB_WHITE5 (uint32_t)0x8C8C8C +#define RGB_WHITE6 (uint32_t)0xA8A8A8 +#define RGB_WHITE7 (uint32_t)0xC4C4C4 +#define RGB_WHITE8 (uint32_t)0xE0E0E0 +#define RGB_WHITE9 (uint32_t)0xFCFCFC + +#define RGB_BLUE0 (uint32_t)0x000010 +#define RGB_BLUE1 (uint32_t)0x00001C +#define RGB_BLUE2 (uint32_t)0x000038 +#define RGB_BLUE3 (uint32_t)0x000054 +#define RGB_BLUE4 (uint32_t)0x000070 +#define RGB_BLUE5 (uint32_t)0x00008C +#define RGB_BLUE6 (uint32_t)0x0000A8 +#define RGB_BLUE7 (uint32_t)0x0000C4 +#define RGB_BLUE8 (uint32_t)0x0000E0 +#define RGB_BLUE9 (uint32_t)0x0000FC + +#define RGB_YELLOW0 (uint32_t)0x101000 +#define RGB_YELLOW1 (uint32_t)0x1C1C00 +#define RGB_YELLOW2 (uint32_t)0x383800 +#define RGB_YELLOW3 (uint32_t)0x545400 +#define RGB_YELLOW4 (uint32_t)0x707000 +#define RGB_YELLOW5 (uint32_t)0x8C8C00 +#define RGB_YELLOW6 (uint32_t)0xA8A800 +#define RGB_YELLOW7 (uint32_t)0xC4C400 +#define RGB_YELLOW8 (uint32_t)0xE0E000 +#define RGB_YELLOW9 (uint32_t)0xFCFC00 + +#define RGB_RED0 (uint32_t)0x100000 +#define RGB_RED1 (uint32_t)0x1C0000 +#define RGB_RED2 (uint32_t)0x380000 +#define RGB_RED3 (uint32_t)0x540000 +#define RGB_RED4 (uint32_t)0x700000 +#define RGB_RED5 (uint32_t)0x8C0000 +#define RGB_RED6 (uint32_t)0xA80000 +#define RGB_RED7 (uint32_t)0xC40000 +#define RGB_RED8 (uint32_t)0xE00000 +#define RGB_RED9 (uint32_t)0xFC0000 + +#define RGB_GREEN0 (uint32_t)0x001000 +#define RGB_GREEN1 (uint32_t)0x001C00 +#define RGB_GREEN2 (uint32_t)0x003800 +#define RGB_GREEN3 (uint32_t)0x005400 +#define RGB_GREEN4 (uint32_t)0x007000 +#define RGB_GREEN5 (uint32_t)0x008C00 +#define RGB_GREEN6 (uint32_t)0x00A800 +#define RGB_GREEN7 (uint32_t)0x00C400 +#define RGB_GREEN8 (uint32_t)0x00E000 +#define RGB_GREEN9 (uint32_t)0x00FC00 + +#define RGB_CYAN0 (uint32_t)0x001010 +#define RGB_CYAN1 (uint32_t)0x001C1C +#define RGB_CYAN2 (uint32_t)0x003838 +#define RGB_CYAN3 (uint32_t)0x005454 +#define RGB_CYAN4 (uint32_t)0x007070 +#define RGB_CYAN5 (uint32_t)0x008C8C +#define RGB_CYAN6 (uint32_t)0x00A8A8 +#define RGB_CYAN7 (uint32_t)0x00C4C4 +#define RGB_CYAN8 (uint32_t)0x00E0E0 +#define RGB_CYAN9 (uint32_t)0x00FCFC + +#define RGB_MAGENTA0 (uint32_t)0x100010 +#define RGB_MAGENTA1 (uint32_t)0x1C001C +#define RGB_MAGENTA2 (uint32_t)0x380038 +#define RGB_MAGENTA3 (uint32_t)0x540054 +#define RGB_MAGENTA4 (uint32_t)0x700070 +#define RGB_MAGENTA5 (uint32_t)0x8C008C +#define RGB_MAGENTA6 (uint32_t)0xA800A8 +#define RGB_MAGENTA7 (uint32_t)0xC400C4 +#define RGB_MAGENTA8 (uint32_t)0xE000E0 +#define RGB_MAGENTA9 (uint32_t)0xFC00FC + +#define RGB_ORANGE0 (uint32_t)0x100800 +#define RGB_ORANGE1 (uint32_t)0x1C0E00 +#define RGB_ORANGE2 (uint32_t)0x381C00 +#define RGB_ORANGE3 (uint32_t)0x542B00 +#define RGB_ORANGE4 (uint32_t)0x703900 +#define RGB_ORANGE5 (uint32_t)0x8C4800 +#define RGB_ORANGE6 (uint32_t)0xA85600 +#define RGB_ORANGE7 (uint32_t)0xC46500 +#define RGB_ORANGE8 (uint32_t)0xE07300 +#define RGB_ORANGE9 (uint32_t)0xFC8200 + +// ============================================================================= +// Colorset.h +// ============================================================================= + +typedef enum ValueStyle ValueStyle; +enum ValueStyle { + VAL_STYLE_RANDOM = 0, + VAL_STYLE_LOW_FIRST_COLOR, + VAL_STYLE_HIGH_FIRST_COLOR, + VAL_STYLE_ALTERNATING, + VAL_STYLE_ASCENDING, + VAL_STYLE_DESCENDING, + VAL_STYLE_CONSTANT, + VAL_STYLE_COUNT +}; + +typedef enum ColorMode ColorMode; +enum ColorMode { + COLOR_MODE_THEORY, + COLOR_MODE_MONOCHROMATIC, + COLOR_MODE_EVENLY_SPACED +}; + +typedef enum ColorMode2 ColorMode2; +enum ColorMode2 { + COLOR_MODE2_DOUBLE_SPLIT_COMPLIMENTARY, + COLOR_MODE2_TETRADIC +}; + +struct Colorset { + RGBColor palette[MAX_COLOR_SLOTS]; + uint8_t curIndex; + uint8_t numColors; +}; + +void Colorset_init(Colorset *self); +void Colorset_initColors(Colorset *self, RGBColor c1, RGBColor c2, RGBColor c3, RGBColor c4, RGBColor c5, RGBColor c6, RGBColor c7, RGBColor c8); +void Colorset_initFromU32s(Colorset *self, uint8_t numCols, const uint32_t *cols); +void Colorset_destroy(Colorset *self); +void Colorset_copy(Colorset *self, const Colorset *other); +void Colorset_assign(Colorset *self, const Colorset *other); +void Colorset_clear(Colorset *self); +bool Colorset_equals(const Colorset *self, const Colorset *other); +bool Colorset_equalsPtr(const Colorset *self, const Colorset *set); +bool Colorset_addColor(Colorset *self, RGBColor col); +bool Colorset_addColorHSV(Colorset *self, uint8_t hue, uint8_t sat, uint8_t val); +void Colorset_addColorWithValueStyle(Colorset *self, Random *ctx, uint8_t hue, uint8_t sat, ValueStyle valStyle, uint8_t numColors, uint8_t colorPos); +void Colorset_removeColor(Colorset *self, uint8_t index); +void Colorset_randomize(Colorset *self, Random_t *ctx, uint8_t numColors); +void Colorset_randomizeColors(Colorset *self, Random *ctx, uint8_t numColors, ColorMode mode); +void Colorset_randomizeColors2(Colorset *self, Random *ctx, ColorMode2 mode); +void Colorset_adjustBrightness(Colorset *self, uint8_t fadeby); +RGBColor Colorset_get(const Colorset *self, uint8_t index); +void Colorset_set(Colorset *self, uint8_t index, RGBColor col); +void Colorset_skip(Colorset *self, int32_t amount); +RGBColor Colorset_cur(Colorset *self); +void Colorset_setCurIndex(Colorset *self, uint8_t index); +void Colorset_resetIndex(Colorset *self); +uint8_t Colorset_curIndex(const Colorset *self); +RGBColor Colorset_getPrev(Colorset *self); +RGBColor Colorset_getNext(Colorset *self); +RGBColor Colorset_peek(const Colorset *self, int32_t offset); +uint8_t Colorset_numColors(const Colorset *self); +bool Colorset_onStart(const Colorset *self); +bool Colorset_onEnd(const Colorset *self); +bool Colorset_serialize(const Colorset *self, ByteStream *buffer); +bool Colorset_unserialize(Colorset *self, ByteStream *buffer); + +// ============================================================================= +// Random.h +// ============================================================================= + +typedef struct Random_t Random; + +struct Random_t { + uint32_t seed; +}; + +void Random_init(Random_t *self); +void Random_initWithSeed(Random_t *self, uint32_t seed); +void Random_seed(Random_t *self, uint32_t newseed); +uint8_t Random_next8(Random_t *self, uint8_t min, uint8_t max); +uint16_t Random_next16(Random_t *self, uint16_t min, uint16_t max); + +// ============================================================================= +// Patterns.h +// ============================================================================= + +typedef int8_t PatternID; + +enum { + PATTERN_NONE = (PatternID)-1, + PATTERN_FIRST = 0, + PATTERN_SINGLE_FIRST = PATTERN_FIRST, + PATTERN_STROBE = PATTERN_FIRST, + PATTERN_HYPERSTROBE, + PATTERN_PICOSTROBE, + PATTERN_STROBIE, + PATTERN_DOPS, + PATTERN_ULTRADOPS, + PATTERN_STROBEGAP, + PATTERN_HYPERGAP, + PATTERN_PICOGAP, + PATTERN_STROBIEGAP, + PATTERN_DOPSGAP, + PATTERN_ULTRAGAP, + PATTERN_BLINKIE, + PATTERN_GHOSTCRUSH, + PATTERN_DOUBLEDOPS, + PATTERN_CHOPPER, + PATTERN_DASHGAP, + PATTERN_DASHDOPS, + PATTERN_DASHCRUSH, + PATTERN_ULTRADASH, + PATTERN_GAPCYCLE, + PATTERN_DASHCYCLE, + PATTERN_TRACER, + PATTERN_RIBBON, + PATTERN_MINIRIBBON, + PATTERN_BLEND, + PATTERN_BLENDSTROBE, + PATTERN_BLENDSTROBEGAP, + PATTERN_COMPLEMENTARY_BLEND, + PATTERN_COMPLEMENTARY_BLENDSTROBE, + PATTERN_COMPLEMENTARY_BLENDSTROBEGAP, + PATTERN_SOLID, + PATTERN_MULTI_FIRST, + PATTERN_SINGLE_LAST = (PATTERN_MULTI_FIRST - 1), + PATTERN_SINGLE_COUNT = (PATTERN_SINGLE_LAST - PATTERN_SINGLE_FIRST) + 1, + PATTERN_HUE_SCROLL = PATTERN_MULTI_FIRST, + PATTERN_THEATER_CHASE, + PATTERN_CHASER, + PATTERN_ZIGZAG, + PATTERN_ZIPFADE, + PATTERN_DRIP, + PATTERN_DRIPMORPH, + PATTERN_CROSSDOPS, + PATTERN_DOUBLESTROBE, + PATTERN_METEOR, + PATTERN_SPARKLETRACE, + PATTERN_VORTEXWIPE, + PATTERN_WARP, + PATTERN_WARPWORM, + PATTERN_SNOWBALL, + PATTERN_LIGHTHOUSE, + PATTERN_PULSISH, + PATTERN_FILL, + PATTERN_BOUNCE, + PATTERN_SPLITSTROBIE, + PATTERN_BACKSTROBE, + PATTERN_VORTEX, + INTERNAL_PATTERNS_END, + PATTERN_MULTI_LAST = (INTERNAL_PATTERNS_END - 1), + PATTERN_MULTI_COUNT = (PATTERN_MULTI_LAST - PATTERN_MULTI_FIRST) + 1, + PATTERN_LAST = PATTERN_MULTI_LAST, + PATTERN_COUNT = (PATTERN_LAST - PATTERN_FIRST) + 1, +}; + +static inline bool isMultiLedPatternID(PatternID id) +{ + return id >= PATTERN_MULTI_FIRST && id <= PATTERN_MULTI_LAST; +} + +static inline bool isSingleLedPatternID(PatternID id) +{ + return id < PATTERN_MULTI_FIRST; +} + +// ============================================================================= +// PatternArgs.h +// ============================================================================= + +#define MAX_ARGS 8 + +typedef uint8_t ArgMap; + +#define ARG_NONE 0 +#define ARG(x) (1 << (x)) +#define ARG1 (1 << 0) +#define ARG2 (1 << 1) +#define ARG3 (1 << 2) +#define ARG4 (1 << 3) +#define ARG5 (1 << 4) +#define ARG6 (1 << 5) +#define ARG7 (1 << 6) +#define ARG8 (1 << 7) +#define ARG_ALL 0xFF + +#define ARGMAP_SET(map, arg) ((map) |= ARG(arg)) +#define ARGMAP_CLEAR(map, arg) ((map) &= ~ARG(arg)) +#define ARGMAP_ISSET(map, arg) (((map) & ARG(arg)) != 0) + +struct PatternArgs { + union { + uint8_t args[8]; + struct { + uint8_t arg1; + uint8_t arg2; + uint8_t arg3; + uint8_t arg4; + uint8_t arg5; + uint8_t arg6; + uint8_t arg7; + uint8_t arg8; + }; + struct { + uint8_t onDuration; + uint8_t offDuration; + uint8_t gapDuration; + uint8_t dashDuration; + uint8_t groupSize; + } basic; + struct { + uint8_t onDuration; + uint8_t offDuration; + uint8_t gapDuration; + uint8_t dashDuration; + uint8_t groupSize; + uint8_t blendSpeed; + uint8_t numFlips; + } blend; + struct { + uint8_t onDuration; + uint8_t offDuration; + uint8_t gapDuration; + uint8_t dashDuration; + uint8_t groupSize; + uint8_t colorIndex; + } solid; + }; + uint8_t numArgs; +}; + +void PatternArgs_init(PatternArgs *self); +void PatternArgs_init1(PatternArgs *self, uint8_t a1); +void PatternArgs_init2(PatternArgs *self, uint8_t a1, uint8_t a2); +void PatternArgs_init3(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3); +void PatternArgs_init4(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4); +void PatternArgs_init5(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5); +void PatternArgs_init6(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6); +void PatternArgs_init7(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7); +void PatternArgs_init8(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7, uint8_t a8); +void PatternArgs_addArgs1(PatternArgs *self, uint8_t a1); +void PatternArgs_addArgs2(PatternArgs *self, uint8_t a1, uint8_t a2); +void PatternArgs_addArgs3(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3); +void PatternArgs_addArgs4(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4); +void PatternArgs_addArgs5(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5); +void PatternArgs_addArgs6(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6); +void PatternArgs_addArgs7(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7); +void PatternArgs_addArgs8(PatternArgs *self, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7, uint8_t a8); +bool PatternArgs_equals(const PatternArgs *self, const PatternArgs *rhs); +bool PatternArgs_serialize(const PatternArgs *self, ByteStream *buffer, uint8_t argmap); +uint8_t PatternArgs_unserialize(PatternArgs *self, ByteStream *buffer); + +// ============================================================================= +// Pattern.h +// ============================================================================= + +#define MAX_PATTERN_ARGS 8 +#define PATTERN_FLAGS_NONE 0 +#define PATTERN_FLAG_MULTI (1<<0) + +#if VORTEX_SLIM == 1 +typedef uint8_t arg_offset_t; +#else +typedef uint16_t arg_offset_t; +#endif + +typedef struct PatternVTable PatternVTable; +struct PatternVTable { + void (*destroy)(Pattern *self); + void (*play)(Pattern *self); + void (*init)(Pattern *self); + void (*bind)(Pattern *self, LedPos pos); + void (*onBlinkOn)(Pattern *self); + void (*onBlinkOff)(Pattern *self); + void (*beginGap)(Pattern *self); + void (*beginDash)(Pattern *self); +}; + +struct Pattern { + const PatternVTable *vtable; + PatternID patternID; + uint8_t patternFlags; + Colorset colorset; + LedPos ledPos; + uint8_t numArgs; + arg_offset_t argList[MAX_PATTERN_ARGS]; +}; + +void Pattern_init(Pattern *self, const PatternArgs *args); +void Pattern_play(Pattern *self); +void Pattern_bind(Pattern *self, LedPos pos); +void Pattern_bindBase(Pattern *self, LedPos pos); +void Pattern_initBase(Pattern *self); +void Pattern_initVirtual(Pattern *self); +void Pattern_destroy(Pattern *self); +PatternID Pattern_getPatternID(const Pattern *self); +Colorset Pattern_getColorset(const Pattern *self); +void Pattern_setColorset(Pattern *self, const Colorset *set); +void Pattern_clearColorset(Pattern *self); +uint8_t Pattern_getNumArgs(const Pattern *self); +void Pattern_setArg(Pattern *self, uint8_t index, uint8_t value); +uint8_t Pattern_getArg(const Pattern *self, uint8_t index); +void Pattern_setArgs(Pattern *self, const PatternArgs *args); +void Pattern_getArgs(const Pattern *self, PatternArgs *args); +LedPos Pattern_getLedPos(const Pattern *self); +uint32_t Pattern_getFlags(const Pattern *self); +bool Pattern_hasFlags(const Pattern *self, uint32_t flags); +void Pattern_setLedPos(Pattern *self, LedPos pos); +bool Pattern_serialize(const Pattern *self, ByteStream *buffer); +bool Pattern_unserialize(Pattern *self, ByteStream *buffer); +void Pattern_registerArg(Pattern *self, arg_offset_t offset); +bool Pattern_equals(const Pattern *self, const Pattern *other); +#ifdef VORTEX_LIB +void Pattern_skip(Pattern *self, uint32_t ticks); +#endif + +#define REGISTER_ARG(self, arg) Pattern_registerArg((Pattern *)(self), (arg_offset_t)((uintptr_t)&(arg) - (uintptr_t)(self))) + +// ============================================================================= +// SingleLedPattern.h +// ============================================================================= +typedef struct { + Pattern base; +} SingleLedPattern; + +void SingleLedPattern_init(SingleLedPattern *self, const PatternArgs *args); + +// ============================================================================= +// BasicPattern.h +// ============================================================================= + +// ============================================================================= +// Timer.h - defined here because needed by BasicPattern and BlinkStepPattern +// ============================================================================= +typedef int8_t AlarmID; + +#define ALARM_NONE -1 +#define TIMER_FLAGS_NONE 0 +#define TIMER_1_ALARM 1 +#define TIMER_2_ALARMS 2 +#define TIMER_3_ALARMS 3 +#define TIMER_4_ALARMS 4 +#define TIMER_ALARM_MASK ( 1 | 2 | 3 | 4 ) +#define TIMER_START (1 << 7) + +typedef struct Timer { + uint32_t alarms[TIMER_4_ALARMS]; + uint8_t numAlarms; + AlarmID curAlarm; + uint32_t startTime; +} Timer; + +void Timer_init(Timer *self); +void Timer_initFlags(Timer *self, uint8_t flags, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4); +AlarmID Timer_addAlarm(Timer *self, uint32_t interval); +void Timer_start(Timer *self, uint32_t offset); +void Timer_restart(Timer *self, uint32_t offset); +void Timer_reset(Timer *self); +AlarmID Timer_alarm(Timer *self); +bool Timer_onStart(const Timer *self); +bool Timer_onEnd(const Timer *self); +uint32_t Timer_numAlarms(const Timer *self); +uint32_t Timer_curAlarm(const Timer *self); +uint32_t Timer_startTime(const Timer *self); + +typedef uint8_t PatternState; +enum { + STATE_DISABLED, + STATE_BLINK_ON, + STATE_ON, + STATE_BLINK_OFF, + STATE_OFF, + STATE_BEGIN_GAP, + STATE_IN_GAP, + STATE_BEGIN_DASH, + STATE_IN_DASH, + STATE_BEGIN_GAP2, + STATE_IN_GAP2, +}; + +typedef struct { + SingleLedPattern base; + uint8_t onDuration; + uint8_t offDuration; + uint8_t gapDuration; + uint8_t dashDuration; + uint8_t groupSize; + uint8_t groupCounter; + uint8_t state; + Timer blinkTimer; +} BasicPattern; + +void BasicPattern_init(BasicPattern *self, const PatternArgs *args); +void BasicPattern_initVirtual(Pattern *self); +void BasicPattern_play(Pattern *self); +void BasicPattern_onBlinkOn(Pattern *self); +void BasicPattern_onBlinkOff(Pattern *self); +void BasicPattern_beginGap(Pattern *self); +void BasicPattern_beginDash(Pattern *self); +void BasicPattern_nextState(BasicPattern *self, uint8_t timing); + +// ============================================================================= +// SolidPattern.h +// ============================================================================= +typedef struct { + BasicPattern base; + uint8_t colIndex; +} SolidPattern; + +void SolidPattern_init(SolidPattern *self, const PatternArgs *args); +void SolidPattern_onBlinkOn(Pattern *self); + +// ============================================================================= +// BlendPattern.h +// ============================================================================= +typedef struct { + BasicPattern base; + uint8_t blendSpeed; + uint8_t numFlips; + RGBColor cur; + RGBColor next; + uint8_t flip; +} BlendPattern; + +void BlendPattern_init(BlendPattern *self, const PatternArgs *args); +void BlendPattern_initVirtual(Pattern *self); +void BlendPattern_onBlinkOn(Pattern *self); + +// ============================================================================= +// MultiLedPattern.h +// ============================================================================= +typedef struct { + Pattern base; +} MultiLedPattern; + +void MultiLedPattern_init(MultiLedPattern *self, const PatternArgs *args); +void MultiLedPattern_bind(MultiLedPattern *self, LedPos pos); +void MultiLedPattern_initVirtual(MultiLedPattern *self); + +// ============================================================================= +// CompoundPattern.h +// ============================================================================= +typedef struct { + MultiLedPattern base; + SingleLedPattern *ledPatterns[LED_COUNT]; +} CompoundPattern; + +void CompoundPattern_init(CompoundPattern *self, const PatternArgs *args); +void CompoundPattern_play(CompoundPattern *self); +void CompoundPattern_initVirtual(CompoundPattern *self); +void CompoundPattern_setPatternAtID(CompoundPattern *self, LedPos pos, PatternID id, const PatternArgs *args, const Colorset *set); +void CompoundPattern_setPatternAt(CompoundPattern *self, LedPos pos, SingleLedPattern *pat, const Colorset *set); +void CompoundPattern_clearPatterns(CompoundPattern *self); +void CompoundPattern_setEvensOdds(CompoundPattern *self, PatternID tipPat, PatternID topPat, const PatternArgs *tipArgs, const PatternArgs *topArgs); + +// ============================================================================= +// BlinkStepPattern.h +// ============================================================================= +typedef struct { + MultiLedPattern base; + uint8_t blinkOnDuration; + uint8_t blinkOffDuration; + uint8_t stepDuration; + Timer blinkTimer; + Timer stepTimer; +} BlinkStepPattern; + +void BlinkStepPattern_init(BlinkStepPattern *self, const PatternArgs *args); +void BlinkStepPattern_play(BlinkStepPattern *self); +void BlinkStepPattern_initVirtual(BlinkStepPattern *self); +void BlinkStepPattern_blinkOn(BlinkStepPattern *self); +void BlinkStepPattern_blinkOff(BlinkStepPattern *self); +void BlinkStepPattern_prestep(BlinkStepPattern *self); +void BlinkStepPattern_poststep(BlinkStepPattern *self); + +// ============================================================================= +// Sequence.h +// ============================================================================= +typedef struct { + PatternID m_patternMap[LED_COUNT]; +} PatternMap; + +void PatternMap_init(PatternMap *self); +void PatternMap_setPatternAt(PatternMap *self, PatternID pattern, LedMap positions); +PatternID PatternMap_getAt(const PatternMap *self, LedPos index); +bool PatternMap_serialize(const PatternMap *self, ByteStream *buffer); +bool PatternMap_unserialize(PatternMap *self, ByteStream *buffer); + +typedef struct { + Colorset colorsetMap[LED_COUNT]; +} ColorsetMap; + +void ColorsetMap_init(ColorsetMap *self); +void ColorsetMap_setColorsetAt(ColorsetMap *self, const Colorset *colorset, LedMap positions); +const Colorset *ColorsetMap_getAt(const ColorsetMap *self, LedPos index); +bool ColorsetMap_serialize(const ColorsetMap *self, ByteStream *buffer); +bool ColorsetMap_unserialize(ColorsetMap *self, ByteStream *buffer); + +typedef struct { + uint16_t duration; + PatternMap patternMap; + ColorsetMap colorsetMap; +} SequenceStep; + +void SequenceStep_init(SequenceStep *self); +void SequenceStep_initFull(SequenceStep *self, uint16_t duration, const PatternMap *patternMap, const ColorsetMap *colorsetMap); +bool SequenceStep_serialize(const SequenceStep *self, ByteStream *buffer); +bool SequenceStep_unserialize(SequenceStep *self, ByteStream *buffer); + +typedef struct { + SequenceStep *sequenceSteps; + uint8_t numSteps; +} Sequence; + +void Sequence_init(Sequence *self); +void Sequence_destroy(Sequence *self); +void Sequence_copy(Sequence *self, const Sequence *other); +void Sequence_initSteps(Sequence *self, uint8_t numSteps); +uint8_t Sequence_addStep(Sequence *self, const SequenceStep *step); +void Sequence_clear(Sequence *self); +bool Sequence_serialize(const Sequence *self, ByteStream *buffer); +bool Sequence_unserialize(Sequence *self, ByteStream *buffer); +uint8_t Sequence_numSteps(const Sequence *self); +const SequenceStep *Sequence_getStep(const Sequence *self, uint8_t index); + +// ============================================================================= +// SequencedPattern.h +// ============================================================================= +typedef struct { + CompoundPattern base; + Sequence sequence; + uint8_t curSequence; + Timer timer; +} SequencedPattern; + +void SequencedPattern_init(SequencedPattern *self, const PatternArgs *args); +void SequencedPattern_initSeq(SequencedPattern *self, const PatternArgs *args, const Sequence *seq); +void SequencedPattern_play(SequencedPattern *self); +void SequencedPattern_initVirtual(SequencedPattern *self); +void SequencedPattern_bindSequence(SequencedPattern *self, const Sequence *seq); + +// ============================================================================= +// ChaserPattern.h (VORTEX_SLIM == 0 only) +// ============================================================================= +typedef SequencedPattern ChaserPattern; +void ChaserPattern_init(ChaserPattern *self, const PatternArgs *args); + +// Other multi-led pattern types (all VORTEX_SLIM == 0) +typedef struct { + BlinkStepPattern base; +} TheaterChasePattern; +void TheaterChasePattern_init(TheaterChasePattern *self, const PatternArgs *args); +typedef BlinkStepPattern HueShiftPattern; +void HueShiftPattern_init(HueShiftPattern *self, const PatternArgs *args); +typedef BlinkStepPattern ZigzagPattern; +void ZigzagPattern_init(ZigzagPattern *self, const PatternArgs *args); +typedef BlinkStepPattern DripPattern; +void DripPattern_init(DripPattern *self, const PatternArgs *args); +typedef BlinkStepPattern DripMorphPattern; +void DripMorphPattern_init(DripMorphPattern *self, const PatternArgs *args); +typedef CompoundPattern CrossDopsPattern; +void CrossDopsPattern_init(CrossDopsPattern *self, const PatternArgs *args); +typedef BlinkStepPattern DoubleStrobePattern; +void DoubleStrobePattern_init(DoubleStrobePattern *self, const PatternArgs *args); +typedef BlinkStepPattern MeteorPattern; +void MeteorPattern_init(MeteorPattern *self, const PatternArgs *args); +typedef CompoundPattern SparkleTracePattern; +void SparkleTracePattern_init(SparkleTracePattern *self, const PatternArgs *args); +typedef BlinkStepPattern VortexWipePattern; +void VortexWipePattern_init(VortexWipePattern *self, const PatternArgs *args); +typedef BlinkStepPattern WarpPattern; +void WarpPattern_init(WarpPattern *self, const PatternArgs *args); +typedef CompoundPattern WarpWormPattern; +void WarpWormPattern_init(WarpWormPattern *self, const PatternArgs *args); +typedef BlinkStepPattern SnowballPattern; +void SnowballPattern_init(SnowballPattern *self, const PatternArgs *args); +typedef CompoundPattern LighthousePattern; +void LighthousePattern_init(LighthousePattern *self, const PatternArgs *args); +typedef BlinkStepPattern PulsishPattern; +void PulsishPattern_init(PulsishPattern *self, const PatternArgs *args); +typedef BlinkStepPattern FillPattern; +void FillPattern_init(FillPattern *self, const PatternArgs *args); +typedef CompoundPattern BouncePattern; +void BouncePattern_init(BouncePattern *self, const PatternArgs *args); +typedef BlinkStepPattern BackStrobePattern; +void BackStrobePattern_init(BackStrobePattern *self, const PatternArgs *args); +typedef BlinkStepPattern VortexPattern; +void VortexPattern_init(VortexPattern *self, const PatternArgs *args); + +// ============================================================================= +// ByteStream.h +// ============================================================================= +typedef struct { + uint32_t size; + uint32_t flags; + uint32_t crc32; + uint8_t buf[]; +} RawBuffer; + +uint32_t RawBuffer_hash(const RawBuffer *self); +void RawBuffer_accumulate(RawBuffer *self, uint32_t val); +bool RawBuffer_verify(const RawBuffer *self); +void RawBuffer_recalcCRC(RawBuffer *self); + +struct ByteStream { + RawBuffer *pData; + uint16_t position; + uint16_t capacity; +}; + +bool ByteStream_init(ByteStream *self, uint32_t capacity, const uint8_t *buf); +void ByteStream_initStatic(ByteStream *self, uint8_t *storage, uint32_t storageSize); +void ByteStream_resetData(ByteStream *self); +void ByteStream_destroy(ByteStream *self); +void ByteStream_clear(ByteStream *self); +bool ByteStream_shrink(ByteStream *self); +bool ByteStream_append(ByteStream *self, const ByteStream *other); +bool ByteStream_extend(ByteStream *self, uint32_t size); +bool ByteStream_serialize8(ByteStream *self, uint8_t byte); +bool ByteStream_serialize16(ByteStream *self, uint16_t bytes); +bool ByteStream_serialize32(ByteStream *self, uint32_t bytes); +bool ByteStream_unserialize8(ByteStream *self, uint8_t *byte); +bool ByteStream_unserialize16(ByteStream *self, uint16_t *bytes); +bool ByteStream_unserialize32(ByteStream *self, uint32_t *bytes); +void ByteStream_resetUnserializer(ByteStream *self); +void ByteStream_moveUnserializer(ByteStream *self, uint32_t idx); +bool ByteStream_unserializerAtEnd(const ByteStream *self); +void ByteStream_sanity(ByteStream *self); +uint32_t ByteStream_size(const ByteStream *self); +uint32_t ByteStream_capacity(const ByteStream *self); +const uint8_t *ByteStream_data(const ByteStream *self); +void *ByteStream_rawData(const ByteStream *self); +uint16_t ByteStream_rawSize(const ByteStream *self); +bool ByteStream_compress(ByteStream *self); +bool ByteStream_decompress(ByteStream *self); +uint32_t ByteStream_recalcCRC(ByteStream *self, bool force); +bool ByteStream_checkCRC(const ByteStream *self); +bool ByteStream_isCRCDirty(const ByteStream *self); +void ByteStream_setCRCDirty(ByteStream *self); +bool ByteStream_is_compressed(const ByteStream *self); +uint32_t ByteStream_CRC(const ByteStream *self); +void ByteStream_trim(ByteStream *self, uint32_t bytes); +bool ByteStream_rawInit(ByteStream *self, const uint8_t *rawdata, uint32_t size); +void ByteStream_move(ByteStream *self, ByteStream *target); +uint8_t ByteStream_peek8(const ByteStream *self); +uint16_t ByteStream_peek16(const ByteStream *self); +uint32_t ByteStream_peek32(const ByteStream *self); +bool ByteStream_consume8(ByteStream *self, uint8_t *byte); +bool ByteStream_consume16(ByteStream *self, uint16_t *bytes); +bool ByteStream_consume32(ByteStream *self, uint32_t *bytes); +bool ByteStream_consume(ByteStream *self, uint32_t size, void *bytes); +uint8_t *ByteStream_frontUnserializer(const ByteStream *self); +uint8_t *ByteStream_frontSerializer(const ByteStream *self); +void ByteStream_copy(ByteStream *self, const ByteStream *other); + +// ============================================================================= +// BitStream.h +// ============================================================================= +struct BitStream { + uint8_t *buf; + uint16_t buf_size; + uint16_t bit_pos; + bool buf_eof; + bool allocated; +}; + +void BitStream_init(BitStream *self); +void BitStream_initBuf(BitStream *self, uint8_t *buf, uint32_t size); +bool BitStream_initAlloc(BitStream *self, uint32_t size); +void BitStream_destroy(BitStream *self); +void BitStream_reset(BitStream *self); +void BitStream_resetPos(BitStream *self); +uint8_t BitStream_read1Bit(BitStream *self); +void BitStream_write1Bit(BitStream *self, bool bit); +uint8_t BitStream_readBits(BitStream *self, uint32_t numBits); +void BitStream_writeBits(BitStream *self, uint32_t numBits, uint32_t val); +bool BitStream_eof(const BitStream *self); +bool BitStream_allocated(const BitStream *self); +uint16_t BitStream_size(const BitStream *self); +const uint8_t *BitStream_data(const BitStream *self); +uint8_t BitStream_peekData(const BitStream *self, uint8_t pos); +uint16_t BitStream_dwordpos(const BitStream *self); +uint16_t BitStream_bytepos(const BitStream *self); +uint16_t BitStream_bitpos(const BitStream *self); + +// ============================================================================= +// Compression.h +// ============================================================================= +#if VORTEX_SLIM == 1 +#define compress_size(srcSize) 0 +#define compress_buffer(src, dst, srcSize, dstCapacity) +#define decompress_buffer(src, dst, srcSize, dstCapacity) +#else +#define compress_size(srcSize) LZ4_compressBound(srcSize) +#define compress_buffer(src, dst, srcSize, dstCapacity) LZ4_compress_default((const char *)(src), (char *)(dst), (srcSize), (dstCapacity)) +#define decompress_buffer(src, dst, srcSize, dstCapacity) LZ4_decompress_safe((const char *)(src), (char *)(dst), (srcSize), (dstCapacity)) +#define LZ4_MEMORY_USAGE_MIN 10 +#define LZ4_MEMORY_USAGE_DEFAULT 10 +#define LZ4_MEMORY_USAGE_MAX 20 +#ifndef LZ4_MEMORY_USAGE +#define LZ4_MEMORY_USAGE LZ4_MEMORY_USAGE_DEFAULT +#endif +#define LZ4_MAX_INPUT_SIZE 0x7E000000 +#define LZ4_COMPRESSBOUND(isize) ((unsigned)(isize) > (unsigned)LZ4_MAX_INPUT_SIZE ? 0 : (isize) + ((isize)/255) + 16) +#define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2) +#define LZ4_HASHTABLESIZE (1 << LZ4_MEMORY_USAGE) +#define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) +#define LZ4_STREAMSIZE ((1UL << LZ4_MEMORY_USAGE) + 32) +#define LZ4_STREAMSIZE_VOIDP (LZ4_STREAMSIZE / sizeof(void*)) +#define LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize) (65536 + 14 + (maxBlockSize)) +#define LZ4_DECOMPRESS_INPLACE_MARGIN(compressedSize) (((compressedSize) >> 8) + 32) +#define LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize) ((decompressedSize) + LZ4_DECOMPRESS_INPLACE_MARGIN(decompressedSize)) +#define LZ4_DISTANCE_MAX 65535 +#define LZ4_COMPRESS_INPLACE_MARGIN (LZ4_DISTANCE_MAX + 32) +#define LZ4_COMPRESS_INPLACE_BUFFER_SIZE(maxCompressedSize) ((maxCompressedSize) + LZ4_COMPRESS_INPLACE_MARGIN) +typedef int8_t LZ4_i8; +typedef uint8_t LZ4_byte; +typedef uint16_t LZ4_u16; +typedef uint32_t LZ4_u32; +typedef struct LZ4_stream_t_internal LZ4_stream_t_internal; +struct LZ4_stream_t_internal { + LZ4_u32 hashTable[LZ4_HASH_SIZE_U32]; + LZ4_u32 currentOffset; + LZ4_u32 tableType; + const LZ4_byte *dictionary; + const LZ4_stream_t_internal *dictCtx; + LZ4_u32 dictSize; +}; +typedef struct { + const LZ4_byte *externalDict; + size_t extDictSize; + const LZ4_byte *prefixEnd; + size_t prefixSize; +} LZ4_streamDecode_t_internal; +typedef union LZ4_stream_u LZ4_stream_t; +union LZ4_stream_u { + void *table[LZ4_STREAMSIZE_VOIDP]; + LZ4_stream_t_internal internal_donotuse; +}; +typedef union LZ4_streamDecode_u LZ4_streamDecode_t; +union LZ4_streamDecode_u { + unsigned long long table[LZ4_STREAMDECODESIZE_U64]; + LZ4_streamDecode_t_internal internal_donotuse; +}; +#define LZ4_STREAMDECODESIZE_U64 (4 + ((sizeof(void*)==16) ? 2 : 0)) +#define LZ4_STREAMDECODESIZE (LZ4_STREAMDECODESIZE_U64 * sizeof(unsigned long long)) +int LZ4_compress_default(const char *src, char *dst, int srcSize, int dstCapacity); +int LZ4_decompress_safe(const char *src, char *dst, int compressedSize, int dstCapacity); +int LZ4_compressBound(int inputSize); +int LZ4_compress_fast(const char *src, char *dst, int srcSize, int dstCapacity, int acceleration); +int LZ4_compress_fast_extState(void *state, const char *src, char *dst, int srcSize, int dstCapacity, int acceleration); +LZ4_stream_t *LZ4_initStream(void *buffer, size_t size); +#endif + +// ============================================================================= +// Button.h +// ============================================================================= +typedef struct { + uint32_t pressTime; + uint32_t releaseTime; + uint32_t holdDuration; + uint32_t releaseDuration; + uint8_t consecutivePresses; + uint8_t releaseCount; + bool buttonState; + bool newPress; + bool newRelease; + bool isPressed; + bool shortClick; + bool longClick; +} Button; + +void Button_init(Button *self); +bool Button_check(Button *self); +void Button_update(Button *self); +bool Button_onPress(const Button *self); +bool Button_onRelease(const Button *self); +bool Button_isPressed(const Button *self); +bool Button_onShortClick(const Button *self); +bool Button_onLongClick(const Button *self); +bool Button_onConsecutivePresses(Button *self, uint8_t num); +uint32_t Button_holdDuration(const Button *self); +uint32_t Button_releaseDuration(const Button *self); +uint32_t Button_pressTime(const Button *self); +uint32_t Button_releaseTime(const Button *self); +uint8_t Button_consecutivePresses(const Button *self); +uint8_t Button_releaseCount(const Button *self); +void Button_enableWake(Button *self); + +extern Button *g_pButton; + +// ============================================================================= +// Buttons.h +// ============================================================================= +#define NUM_BUTTONS 1 + +bool Buttons_init(uint8_t pin); +void Buttons_cleanup(void); +void Buttons_update(void); + +// ============================================================================= +// Leds.h +// ============================================================================= +bool Leds_init(void); +void Leds_cleanup(void); +void Leds_setIndex(LedPos target, RGBColor col); +void Leds_setRange(LedPos first, LedPos last, RGBColor col); +void Leds_setAll(RGBColor col); +void Leds_clearIndex(LedPos target); +void Leds_clearRange(LedPos first, LedPos last); +void Leds_clearAll(void); +void Leds_setPair(Pair pair, RGBColor col); +void Leds_setPairs(Pair first, Pair last, RGBColor col); +void Leds_clearPair(Pair pair); +void Leds_clearPairs(Pair first, Pair last); +void Leds_setRangeEvens(Pair first, Pair last, RGBColor col); +void Leds_setAllEvens(RGBColor col); +void Leds_setRangeOdds(Pair first, Pair last, RGBColor col); +void Leds_setAllOdds(RGBColor col); +void Leds_clearRangeEvens(Pair first, Pair last); +void Leds_clearAllEvens(void); +void Leds_clearRangeOdds(Pair first, Pair last); +void Leds_clearAllOdds(void); +void Leds_setMap(LedMap map, RGBColor col); +void Leds_clearMap(LedMap map); +void Leds_stashAll(LedStash *stash); +void Leds_restoreAll(const LedStash *stash); +void Leds_adjustBrightnessIndex(LedPos target, uint8_t fadeBy); +void Leds_adjustBrightnessRange(LedPos first, LedPos last, uint8_t fadeBy); +void Leds_adjustBrightnessAll(uint8_t fadeBy); +void Leds_blinkIndexOffset(LedPos target, uint32_t time, uint16_t offMs, uint16_t onMs, RGBColor col); +void Leds_blinkRangeOffset(LedPos first, LedPos last, uint32_t time, uint16_t offMs, uint16_t onMs, RGBColor col); +void Leds_blinkIndex(LedPos target, uint16_t offMs, uint16_t onMs, RGBColor col); +void Leds_blinkRange(LedPos first, LedPos last, uint16_t offMs, uint16_t onMs, RGBColor col); +void Leds_blinkMap(LedMap targets, uint16_t offMs, uint16_t onMs, RGBColor col); +void Leds_blinkAll(uint16_t offMs, uint16_t onMs, RGBColor col); +void Leds_blinkPair(Pair pair, uint16_t offMs, uint16_t onMs, RGBColor col); +void Leds_blinkPairs(Pair first, Pair last, uint16_t offMs, uint16_t onMs, RGBColor col); +void Leds_breatheIndex(LedPos target, uint8_t hue, uint32_t variance, uint32_t magnitude, uint8_t sat, uint8_t val); +void Leds_breatheRange(LedPos first, LedPos last, uint8_t hue, uint32_t variance, uint32_t magnitude, uint8_t sat, uint8_t val); +void Leds_breatheIndexSat(LedPos target, uint8_t hue, uint32_t variance, uint32_t magnitude, uint8_t sat, uint8_t val); +void Leds_breatheIndexVal(LedPos target, uint8_t hue, uint32_t variance, uint32_t magnitude, uint8_t sat, uint8_t val); +void Leds_holdAll(RGBColor col); +RGBColor Leds_getLed(LedPos pos); +uint8_t Leds_getBrightness(void); +void Leds_setBrightness(uint8_t brightness); +void Leds_update(void); + +extern uint8_t Leds_brightness; +extern RGBColor Leds_ledColors[LED_COUNT]; + +// ============================================================================= +// LedStash.h +// ============================================================================= +struct LedStash { + RGBColor ledColorsStash[LED_COUNT]; +}; + +void LedStash_init(LedStash *self); +void LedStash_setIndex(LedStash *self, LedPos pos, RGBColor col); +void LedStash_clear(LedStash *self); + +// ============================================================================= +// TimeControl.h +// ============================================================================= +#if VARIABLE_TICKRATE == 1 +#define MS_TO_TICKS(ms) Time_millisecondsToTicks(ms) +#define SEC_TO_TICKS(s) Time_secondsToTicks(s) +#else +#define MS_TO_TICKS(ms) (uint32_t)(((uint32_t)(ms) * DEFAULT_TICKRATE) / 1000) +#define SEC_TO_TICKS(s) (uint32_t)((uint32_t)(s) * DEFAULT_TICKRATE) +#endif + +#ifdef VORTEX_LIB +#define SIMULATION_TICK getSimulationTick() +#else +#define SIMULATION_TICK 0 +#endif + +bool Time_init(void); +void Time_cleanup(void); +void Time_tickClock(void); +uint32_t Time_getCurtime(void); +uint32_t Time_getRealCurtime(void); +void Time_setTickrate(uint32_t tickrate); +uint32_t Time_getTickrate(void); +#if VARIABLE_TICKRATE == 1 +uint32_t Time_millisecondsToTicks(uint32_t ms); +uint32_t Time_secondsToTicks(uint32_t sec); +#endif +uint32_t Time_microseconds(void); +void Time_delayMicroseconds(uint32_t us); +void Time_delayMilliseconds(uint32_t ms); +#ifdef VORTEX_LIB +uint32_t Time_startSimulation(void); +uint32_t Time_tickSimulation(void); +bool Time_isSimulation(void); +uint32_t Time_getSimulationTick(void); +uint32_t Time_endSimulation(void); +void Time_setInstantTimestep(bool instant); +bool Time_isInstantStepping(void); +#endif + +// ============================================================================= +// Timings.h +// ============================================================================= +#define MENU_TRIGGER_THRESHOLD_TICKS MS_TO_TICKS(MENU_TRIGGER_TIME) +#define SHORT_CLICK_THRESHOLD_TICKS MS_TO_TICKS(CLICK_THRESHOLD) +#define CONSECUTIVE_WINDOW_TICKS MS_TO_TICKS(CONSECUTIVE_WINDOW) +#define AUTO_RANDOM_DELAY_TICKS MS_TO_TICKS(AUTO_RANDOM_DELAY) +#define SLEEP_ENTER_THRESHOLD_TICKS MS_TO_TICKS(SLEEP_TRIGGER_TIME) +#define SLEEP_WINDOW_THRESHOLD_TICKS MS_TO_TICKS(SLEEP_WINDOW_TIME) +#define FORCE_SLEEP_THRESHOLD_TICKS MS_TO_TICKS(FORCE_SLEEP_TIME) +#define ONE_CLICK_THRESHOLD_TICKS MS_TO_TICKS(ONE_CLICK_MODE_TRHESHOLD) +#define DELETE_THRESHOLD_TICKS MS_TO_TICKS(COL_DELETE_THRESHOLD) +#define DELETE_CYCLE_TICKS MS_TO_TICKS(COL_DELETE_CYCLE) +#define FACTORY_RESET_THRESHOLD_TICKS MS_TO_TICKS(RESET_HOLD_TIME) +#define MAX_SERIAL_CHECK_INTERVAL MS_TO_TICKS(SERIAL_CHECK_TIME) +#define MAX_TIMEOUT_DURATION MS_TO_TICKS(IR_RECEIVER_TIMEOUT_DURATION) +#define MAX_WAIT_DURATION MS_TO_TICKS(IR_SENDER_WAIT_DURATION) +#define ADV_MENU_DURATION_TICKS MS_TO_TICKS(ADVANCED_MENU_ENTER_DURATION) +#ifdef UNLOCK_WAKE_WINDOW +#define UNLOCK_WAKE_WINDOW_TICKS MS_TO_TICKS(UNLOCK_WAKE_WINDOW) +#endif + +#define STROBE_ON_DURATION 6 +#define STROBE_OFF_DURATION 6 +#define HYPERSTROBE_ON_DURATION 16 +#define HYPERSTROBE_OFF_DURATION 20 +#define PICOSTROBE_ON_DURATION 6 +#define PICOSTROBE_OFF_DURATION 40 +#define DOPS_ON_DURATION 1 +#define DOPS_OFF_DURATION 10 +#define ULTRADOPS_ON_DURATION 1 +#define ULTRADOPS_OFF_DURATION 3 +#define STROBIE_ON_DURATION 2 +#define STROBIE_OFF_DURATION 28 +#define SIGNAL_ON_DURATION 10 +#define SIGNAL_OFF_DURATION 296 +#define BLEND_ON_DURATION 2 +#define BLEND_OFF_DURATION 13 +#define RIBBON_DURATION 6 +#define BULB_SELECT_ON_MS 150 +#define BULB_SELECT_OFF_MS 50 +#define EXIT_MENU_ON_MS 300 +#define EXIT_MENU_OFF_MS 150 + +// ============================================================================= +// Mode.h +// ============================================================================= +typedef uint8_t ModeFlags; + +#define MODE_FLAG_NONE 0 +#define MODE_FLAG_MULTI_LED (1 << 0) +#define MODE_FLAG_SINGLE_LED (1 << 1) +#define MODE_FLAG_ALL_SAME_SINGLE (1 << 2) +#define MODE_FLAG_SPARSE_SINGLES (1 << 3) + +struct Mode { +#if VORTEX_SLIM == 0 + Pattern *m_multiPat; +#endif +#if FIXED_LED_COUNT == 0 + uint8_t m_numLeds; + Pattern **m_singlePats; +#else + Pattern *m_singlePats[LED_COUNT]; +#endif +}; + +void Mode_init(Mode *self); +void Mode_initFromID(Mode *self, PatternID id, const Colorset *set); +void Mode_initMode(Mode *self); +void Mode_initModeArgs(Mode *self, PatternID id, const PatternArgs *args, const Colorset *set); +void Mode_initFull(Mode *self, PatternID id, const Colorset *set); +Colorset Mode_getColorsetMut(Mode *self, LedPos pos); +void Mode_destroy(Mode *self); +void Mode_assign(Mode *self, const Mode *other); +void Mode_copy(Mode *self, const Mode *other); +bool Mode_equals(const Mode *self, const Mode *other); +void Mode_initVirtual(Mode *self); +void Mode_play(Mode *self); +bool Mode_saveToBuffer(const Mode *self, ByteStream *saveBuffer, uint8_t numLeds); +bool Mode_loadFromBuffer(Mode *self, ByteStream *saveBuffer); +bool Mode_serialize(const Mode *self, ByteStream *buffer, uint8_t numLeds); +bool Mode_unserialize(Mode *self, ByteStream *buffer); +bool Mode_setPattern(Mode *self, PatternID pat, LedPos pos, const PatternArgs *args, const Colorset *set); +bool Mode_setPatternMap(Mode *self, LedMap pos, PatternID pat, const PatternArgs *args, const Colorset *set); +void Mode_copyPatternFrom(Mode *self, const Mode *other, LedPos to, LedPos from); +void Mode_swapPatterns(Mode *self, LedPos a, LedPos b); +bool Mode_setColorset(Mode *self, const Colorset *set, LedPos pos); +bool Mode_setColorsetMap(Mode *self, LedMap map, const Colorset *set); +void Mode_clearPattern(Mode *self, LedPos pos); +void Mode_clearPatternMap(Mode *self, LedMap map); +void Mode_clearColorset(Mode *self, LedPos pos); +void Mode_clearColorsetMap(Mode *self, LedMap map); +void Mode_setArg(Mode *self, uint8_t index, uint8_t value, LedMap map); +uint8_t Mode_getArg(Mode *self, uint8_t index, LedPos pos); +ModeFlags Mode_getFlags(const Mode *self); +bool Mode_hasMultiLed(const Mode *self); +bool Mode_hasSingleLed(const Mode *self); +bool Mode_hasSameSingleLed(const Mode *self); +bool Mode_hasSparseSingleLed(const Mode *self); +bool Mode_isEmpty(const Mode *self); +LedMap Mode_getSingleLedMap(const Mode *self); +bool Mode_isMultiLed(const Mode *self); +uint8_t Mode_getLedCount(const Mode *self); +Pattern *Mode_getPattern(Mode *self, LedPos pos); +const Pattern *Mode_getPatternConst(const Mode *self, LedPos pos); +Colorset Mode_getColorset(const Mode *self, LedPos pos); +PatternID Mode_getPatternID(const Mode *self, LedPos pos); +#if FIXED_LED_COUNT == 0 +bool Mode_setLedCount(Mode *self, uint8_t numLeds); +#endif + +// ============================================================================= +// Modes.h +// ============================================================================= +#define MODES_FLAG_LOCKED (1 << 0) +#define MODES_FLAG_ONE_CLICK (1 << 1) +#define MODES_FLAG_ADV_MENUS (1 << 2) +#define MODES_FLAG_KEYCHAIN (1 << 3) +#define MODES_FLAG_NEW_FIRMWARE 0xF0 + +bool Modes_init(void); +void Modes_cleanup(void); +bool Modes_load(void); +void Modes_play(void); +bool Modes_saveToBuffer(ByteStream *saveBuffer); +bool Modes_loadFromBuffer(ByteStream *saveBuffer); +bool Modes_saveHeader(void); +bool Modes_loadHeader(void); +bool Modes_saveHeaderAndMode(void); +bool Modes_saveStorage(void); +bool Modes_loadStorage(void); +bool Modes_serializeSaveHeader(ByteStream *saveBuffer); +bool Modes_unserializeSaveHeader(ByteStream *saveBuffer); +bool Modes_serialize(ByteStream *buffer); +bool Modes_unserialize(ByteStream *buffer); +bool Modes_setDefaults(void); +bool Modes_shiftCurMode(int32_t offset); +bool Modes_addMode(PatternID id, RGBColor c1, RGBColor c2, RGBColor c3, RGBColor c4, RGBColor c5, RGBColor c6, RGBColor c7, RGBColor c8); +bool Modes_addModeArgs(PatternID id, const PatternArgs *args, const Colorset *set); +bool Modes_addModeMode(const Mode *mode); +bool Modes_addSerializedMode(ByteStream *serializedMode); +bool Modes_addModeFromBuffer(ByteStream *serializedMode); +bool Modes_updateCurMode(const Mode *mode); +Mode *Modes_setCurMode(uint8_t index); +Mode *Modes_curMode(void); +Mode *Modes_nextMode(void); +Mode *Modes_previousMode(void); +Mode *Modes_nextModeSkipEmpty(void); +uint8_t Modes_numModes(void); +uint8_t Modes_curModeIndex(void); +uint32_t Modes_lastSwitchTime(void); +void Modes_deleteCurMode(void); +void Modes_clearModes(void); +void Modes_setStartupMode(uint8_t index); +uint8_t Modes_startupMode(void); +Mode *Modes_switchToStartupMode(void); +bool Modes_setFlag(uint8_t flag, bool enable, bool save); +bool Modes_getFlag(uint8_t flag); +void Modes_resetFlags(void); +bool Modes_setOneClickMode(bool enable, bool save); +bool Modes_oneClickModeEnabled(void); +bool Modes_setLocked(bool locked, bool save); +bool Modes_locked(void); +bool Modes_setAdvancedMenus(bool active, bool save); +bool Modes_advancedMenusEnabled(void); +bool Modes_setKeychainMode(bool active, bool save); +bool Modes_keychainModeEnabled(void); + +// ============================================================================= +// DefaultModes.h +// ============================================================================= +struct DefaultLedEntry { + PatternID patternID; + uint8_t numColors; + const uint32_t *cols; +}; + +struct DefaultModeEntry { + struct DefaultLedEntry leds[LED_COUNT]; +}; + +extern const struct DefaultModeEntry defaultModes[MAX_MODES]; + +// ============================================================================= +// Serial.h +// ============================================================================= +bool SerialComs_init(void); +void SerialComs_cleanup(void); +bool SerialComs_isConnected(void); +bool SerialComs_checkSerial(void); +void SerialComs_write(const char *msg, ...); +void SerialComs_writeStream(ByteStream *byteStream); +void SerialComs_read(ByteStream *byteStream); +bool SerialComs_dataReady(void); + +// ============================================================================= +// Storage.h +// ============================================================================= +bool Storage_init(void); +void Storage_cleanup(void); +bool Storage_write(uint8_t slot, ByteStream *buffer); +bool Storage_read(uint8_t slot, ByteStream *buffer); +uint32_t Storage_lastSaveSize(void); +#ifdef VORTEX_LIB +void Storage_setStorageFilename(const char *name); +const char *Storage_getStorageFilename(void); +#endif + +// ============================================================================= +// Wireless headers +// ============================================================================= + +// IRSender +bool IRSender_init(void); +void IRSender_cleanup(void); +bool IRSender_loadMode(const Mode *targetMode); +bool IRSender_send(void); +bool IRSender_isSending(void); + +// IRReceiver +bool IRReceiver_init(void); +void IRReceiver_cleanup(void); +bool IRReceiver_dataReady(void); +bool IRReceiver_isReceiving(void); +uint8_t IRReceiver_percentReceived(void); +bool IRReceiver_receiveMode(Mode *pMode); +bool IRReceiver_beginReceiving(void); +bool IRReceiver_endReceiving(void); +bool IRReceiver_onNewData(void); +void IRReceiver_resetIRState(void); + +// VLSender +bool VLSender_init(void); +void VLSender_cleanup(void); +void VLSender_send(const Mode *targetMode); +void VLSender_sendLegacy(const Mode *targetMode); +void VLSender_sendMarkSpace(uint16_t markTime, uint16_t spaceTime); +void VLSender_sendByte(uint8_t data); +void VLSender_sendByteLegacy(uint8_t data); +void VLSender_startPWM(void); +void VLSender_stopPWM(void); + +// VLReceiver +bool VLReceiver_init(void); +void VLReceiver_cleanup(void); +bool VLReceiver_dataReady(void); +bool VLReceiver_isReceiving(void); +uint8_t VLReceiver_percentReceived(void); +bool VLReceiver_receiveMode(Mode *pMode); +bool VLReceiver_beginReceiving(void); +bool VLReceiver_endReceiving(void); +bool VLReceiver_onNewData(void); +void VLReceiver_resetVLState(void); +void VLReceiver_setLegacyReceiver(bool legacy); +void VLReceiver_recvPCIHandler(void); +bool VLReceiver_read(ByteStream *data); +void VLReceiver_handleVLTiming(uint16_t diff); +void VLReceiver_handleVLTimingLegacy(uint16_t diff); + +// ============================================================================= +// VortexEngine.h +// ============================================================================= +bool VortexEngine_init(void); +void VortexEngine_cleanup(void); +void VortexEngine_tick(void); +void VortexEngine_runMainLogic(void); +bool VortexEngine_serializeVersion(ByteStream *stream); +bool VortexEngine_checkVersion(uint8_t major, uint8_t minor); +Mode *VortexEngine_curMode(void); +void VortexEngine_enterSleep(bool save); +void VortexEngine_wakeup(bool reset); +void VortexEngine_toggleForceSleep(bool enabled); +void VortexEngine_setAutoCycle(bool enabled); + +// ============================================================================= +// PatternBuilder.h +// ============================================================================= +#define MAX_PATTERN_POOL 6 + +typedef union { + Pattern base; + SingleLedPattern single; + BasicPattern basic; + BlendPattern blend; + SolidPattern solid; +} PatternUnion; + +Pattern *PatternBuilder_make(PatternID id, const PatternArgs *args); +Pattern *PatternBuilder_dupe(const Pattern *pat); +Pattern *PatternBuilder_makeSingle(PatternID id, const PatternArgs *args); +Pattern *PatternBuilder_unserialize(ByteStream *buffer); +PatternArgs PatternBuilder_getDefaultArgs(PatternID id); +uint8_t PatternBuilder_numDefaultArgs(PatternID id); +Pattern *PatternBuilder_alloc(void); +void PatternBuilder_release(Pattern *pat); + +// ============================================================================= +// Log.h +// ============================================================================= +#if LOGGING_LEVEL > 0 +#define INFO_LOG(msg) InfoMsg(msg) +#define INFO_LOGF(msg, ...) InfoMsg(msg, __VA_ARGS__) +void InfoMsg(const char *msg, ...); +#else +#define INFO_LOG(msg) +#define INFO_LOGF(msg, ...) +#endif +#if LOGGING_LEVEL > 1 +#define ERROR_LOG(msg) ErrorMsg(__FUNCTION__, msg) +#define ERROR_LOGF(msg, ...) ErrorMsg(__FUNCTION__, msg, __VA_ARGS__) +void ErrorMsg(const char *func, const char *msg, ...); +#else +#define ERROR_LOG(msg) +#define ERROR_LOGF(msg, ...) +#endif +#if LOGGING_LEVEL > 2 +#define DEBUG_LOG(msg) DebugMsg(__FILE__, __FUNCTION__, __LINE__, msg) +#define DEBUG_LOGF(msg, ...) DebugMsg(__FILE__, __FUNCTION__, __LINE__, msg, __VA_ARGS__) +void DebugMsg(const char *file, const char *func, int line, const char *msg, ...); +#else +#define DEBUG_LOG(msg) +#define DEBUG_LOGF(msg, ...) +#endif +#define ERROR_OUT_OF_MEMORY() ERROR_LOG("Out of memory") + +// ============================================================================= +// Memory.h +// ============================================================================= +#if DEBUG_ALLOCATIONS == 1 +#define vmalloc(size) _vmalloc(size) +#define vcalloc(size, amount) _vcalloc(size, amount) +#define vrealloc(ptr, size) _vrealloc(ptr, size) +#define vfree(ptr) _vfree(ptr) +void *_vmalloc(uint32_t size); +void *_vcalloc(uint32_t size, uint32_t amount); +void *_vrealloc(void *ptr, uint32_t size); +void _vfree(void *ptr); +uint32_t cur_memory_usage(void); +uint32_t cur_memory_usage_background(void); +uint32_t cur_memory_usage_total(void); +#else +#define vmalloc(size) malloc(size) +#define vcalloc(size, amount) calloc(size, amount) +#define vrealloc(ptr, size) realloc(ptr, size) +#define vfree(ptr) free(ptr) +#endif + +// ============================================================================= +// Menu.h +// ============================================================================= + +typedef uint8_t MenuAction; +enum { + MENU_QUIT, + MENU_CONTINUE, + MENU_SKIP +}; + +typedef struct MenuVTable MenuVTable; +struct MenuVTable { + void (*destroy)(Menu *self); + bool (*init)(Menu *self); + MenuAction (*run)(Menu *self); + void (*onLedSelected)(Menu *self); + void (*onShortClick)(Menu *self); + void (*onLongClick)(Menu *self); + void (*leaveMenu)(Menu *self, bool doSave); +}; + +struct Menu { + const MenuVTable *vtable; + Mode previewMode; + RGBColor menuColor; + LedMap targetLeds; + uint8_t curSelection; + bool ledSelected; + bool advanced; + bool shouldClose; +}; + +void Menu_construct(Menu *self, RGBColor col, bool advanced); +void Menu_destroy(Menu *self); +bool Menu_init(Menu *self); +MenuAction Menu_run(Menu *self); +void Menu_onLedSelected(Menu *self); +void Menu_onShortClick(Menu *self); +void Menu_onLongClick(Menu *self); +void Menu_leaveMenu(Menu *self, bool doSave); +void Menu_showBulbSelection(Menu *self); +void Menu_showExit(Menu *self); +void Menu_nextBulbSelection(Menu *self); +void Menu_bypassLedSelection(Menu *self, LedMap map); + +// ============================================================================= +// Menus.h +// ============================================================================= + +enum MenuEntryID { + MENU_NONE = -1, + MENU_FIRST = 0, + MENU_RANDOMIZER = MENU_FIRST, + MENU_MODE_SHARING, +#if ENABLE_EDITOR_CONNECTION == 1 + MENU_EDITOR_CONNECTION, +#endif + MENU_COLOR_SELECT, + MENU_PATTERN_SELECT, + MENU_GLOBAL_BRIGHTNESS, + MENU_FACTORY_RESET, + MENU_COUNT +}; + +bool Menus_init(void); +void Menus_cleanup(void); +bool Menus_run(void); +bool Menus_openMenuSelection(void); +bool Menus_openMenu(uint32_t index, bool advanced); +void Menus_showSelection(RGBColor colval); +bool Menus_checkOpen(void); +bool Menus_checkInMenu(void); +Menu *Menus_curMenu(void); +enum MenuEntryID Menus_curMenuID(void); + +// ============================================================================= +// Menu List type forward declarations and constructors +// ============================================================================= + +typedef struct { + Menu base; + uint8_t state; + HSVColor newColor; + Colorset colorset; + uint8_t targetSlot; + uint8_t targetHue1; +} ColorSelectMenu; + +typedef struct { + Menu base; + LedPos srcLed; + bool started; +} PatternSelectMenu; + +typedef struct { + Menu base; +#if FIXED_LED_COUNT == 0 + Random_t singlesRandCtx[]; +#else + Random_t singlesRandCtx[LED_COUNT]; +#endif +#if VORTEX_SLIM == 0 + Random_t multiRandCtx; +#endif + uint32_t lastRandomization; + uint8_t flags; + uint8_t displayHue; + bool needToSelect; + bool autoCycle; +} RandomizerMenu; + +typedef struct { + Menu base; + uint8_t brightnessOptions[4]; + uint8_t keychainModeState; + uint32_t lastStateChange; + uint8_t colorIndex; +} GlobalBrightnessMenu; + +typedef struct { + Menu base; +} FactoryResetMenu; + +typedef struct { + Menu base; + ByteStream receiveBuffer; + uint8_t state; + bool allowReset; + uint8_t previousModeIndex; + uint8_t numModesToReceive; + uint8_t rv; +} EditorConnectionMenu; + +typedef struct { + Menu base; + uint8_t sharingMode; + uint32_t timeOutStartTime; + uint32_t lastPercentChange; + uint8_t lastPercent; +} ModeSharingMenu; + +Menu *ColorSelectMenu_Create(RGBColor col, bool advanced); +Menu *PatternSelectMenu_Create(RGBColor col, bool advanced); +Menu *RandomizerMenu_Create(RGBColor col, bool advanced); +Menu *GlobalBrightnessMenu_Create(RGBColor col, bool advanced); +Menu *FactoryResetMenu_Create(RGBColor col, bool advanced); +Menu *EditorConnectionMenu_Create(RGBColor col, bool advanced); +Menu *ModeSharingMenu_Create(RGBColor col, bool advanced); + +typedef union { + ColorSelectMenu colorSelect; + PatternSelectMenu patternSelect; + RandomizerMenu randomizer; + GlobalBrightnessMenu globalBrightness; + FactoryResetMenu factoryReset; + EditorConnectionMenu editorConnection; + ModeSharingMenu modeSharing; +} MenuWorkspace; + +extern MenuWorkspace g_menuWorkspace; + +extern const MenuVTable g_menuVTable; + +// ============================================================================= +// IRConfig.h and VLConfig.h (used by some .c files) +// ============================================================================= +#define VL_ENABLE_SENDER 1 +#define VL_ENABLE_RECEIVER 1 +#define VL_DEFAULT_BLOCK_SIZE 256 +#define VL_DEFAULT_BLOCK_SPACING MS_TO_TICKS(5) +#define VL_MAX_DWORDS_TRANSFER 128 +#define VL_MAX_DATA_TRANSFER (VL_MAX_DWORDS_TRANSFER * sizeof(uint32_t)) +#define VL_RECV_BUF_SIZE VL_MAX_DATA_TRANSFER +#define VL_THRESHOLD 0.5 +#define VL_THRES_UP (1 + VL_THRESHOLD) +#define VL_THRES_DOWN (1 - VL_THRESHOLD) +#define VL_TIMING (uint16_t)(2230) +#define VL_HEADER_MARK (uint16_t)(VL_TIMING * 16) +#define VL_HEADER_SPACE (uint16_t)(VL_TIMING * 8) +#define VL_HEADER_MARK_MIN ((uint16_t)(VL_HEADER_MARK * VL_THRES_DOWN)) +#define VL_HEADER_SPACE_MIN ((uint16_t)(VL_HEADER_SPACE * VL_THRES_DOWN)) +#define VL_HEADER_MARK_MAX ((uint16_t)(VL_HEADER_MARK * VL_THRES_UP)) +#define VL_HEADER_SPACE_MAX ((uint16_t)(VL_HEADER_SPACE * VL_THRES_UP)) +#define VL_TIMING_BIT_ONE (uint16_t)(VL_TIMING * 3) +#define VL_TIMING_BIT_ZERO (uint16_t)(VL_TIMING) +#define VL_TIMING_BIT(bit) ((bit) ? VL_TIMING_BIT_ONE : VL_TIMING_BIT_ZERO) +#define VL_TIMING_LEGACY (uint16_t)(3230) +#define VL_HEADER_MARK_LEGACY (uint16_t)(VL_TIMING_LEGACY * 16) +#define VL_HEADER_SPACE_LEGACY (uint16_t)(VL_TIMING_LEGACY * 8) +#define VL_HEADER_MARK_MIN_LEGACY ((uint16_t)(VL_HEADER_MARK_LEGACY * VL_THRES_DOWN)) +#define VL_HEADER_SPACE_MIN_LEGACY ((uint16_t)(VL_HEADER_SPACE_LEGACY * VL_THRES_DOWN)) +#define VL_HEADER_MARK_MAX_LEGACY ((uint16_t)(VL_HEADER_MARK_LEGACY * VL_THRES_UP)) +#define VL_HEADER_SPACE_MAX_LEGACY ((uint16_t)(VL_HEADER_SPACE_LEGACY * VL_THRES_UP)) +#define VL_TIMING_BIT_ONE_LEGACY (uint16_t)(VL_TIMING_LEGACY * 3) +#define VL_TIMING_BIT_ZERO_LEGACY (uint16_t)(VL_TIMING_LEGACY) +#define VL_TIMING_BIT_LEGACY(bit) ((bit) ? VL_TIMING_BIT_ONE_LEGACY : VL_TIMING_BIT_ZERO_LEGACY) +#define VL_SEND_PWM_PIN 0 +#define VL_RECEIVER_PIN 0 + +#define IR_ENABLE_SENDER 0 +#define IR_ENABLE_RECEIVER 0 +#define IR_DEFAULT_BLOCK_SIZE 32 +#define IR_DEFAULT_BLOCK_SPACING MS_TO_TICKS(300) +#define IR_MAX_DWORDS_TRANSFER 1024 +#define IR_MAX_DATA_TRANSFER (IR_MAX_DWORDS_TRANSFER * sizeof(uint32_t)) +#define IR_RECV_BUF_SIZE IR_MAX_DATA_TRANSFER +#define IR_THRESHOLD 0.25 +#define IR_THRES_UP (1 + IR_THRESHOLD) +#define IR_THRES_DOWN (1 - IR_THRESHOLD) +#define IR_TIMING (uint32_t)562 +#define IR_TIMING_MIN ((uint32_t)(IR_TIMING * IR_THRES_DOWN)) +#define IR_HEADER_MARK (uint32_t)(IR_TIMING * 16) +#define IR_HEADER_SPACE (uint32_t)(IR_TIMING * 8) +#define IR_HEADER_MARK_MIN ((uint32_t)(IR_HEADER_MARK * IR_THRES_DOWN)) +#define IR_HEADER_SPACE_MIN ((uint32_t)(IR_HEADER_SPACE * IR_THRES_DOWN)) +#define IR_HEADER_MARK_MAX ((uint32_t)(IR_HEADER_MARK * IR_THRES_UP)) +#define IR_HEADER_SPACE_MAX ((uint32_t)(IR_HEADER_SPACE * IR_THRES_UP)) +#define IR_DIVIDER_SPACE IR_HEADER_MARK +#define IR_DIVIDER_SPACE_MIN IR_HEADER_MARK_MIN +#define IR_DIVIDER_SPACE_MAX IR_HEADER_MARK_MAX +#define IR_SEND_PWM_PIN 0 +#define IR_RECEIVER_PIN 2 + +// ============================================================================= +// IR and VL Receiver state enums (used by the wireless .c files) +// ============================================================================= + +typedef uint8_t VLRecvState; +enum { + VL_WAITING_HEADER_MARK, + VL_WAITING_HEADER_SPACE, + VL_READING_BAUD_MARK, + VL_READING_BAUD_SPACE, + VL_READING_DATA_MARK, + VL_READING_DATA_SPACE, + VL_READING_DATA_PARITY_MARK, + VL_READING_DATA_PARITY_SPACE +}; + +typedef uint8_t IRRecvState; +enum { + IR_WAITING_HEADER_MARK, + IR_WAITING_HEADER_SPACE, + IR_READING_DATA_MARK, + IR_READING_DATA_SPACE +}; + +extern BitStream VLReceiver_vlData; +extern VLRecvState VLReceiver_recvState; +extern uint32_t VLReceiver_prevTime; +extern uint8_t VLReceiver_pinState; +extern uint16_t VLReceiver_previousBytes; +extern uint16_t VLReceiver_vlMarkThreshold; +extern uint16_t VLReceiver_vlSpaceThreshold; +extern uint8_t VLReceiver_counter; +extern uint8_t VLReceiver_parityBit; +extern bool VLReceiver_legacy; + +uint16_t VLReceiver_bytesReceived(void); + +extern BitStream IRReceiver_irData; +extern IRRecvState IRReceiver_recvState; +extern uint32_t IRReceiver_prevTime; +extern uint8_t IRReceiver_pinState; +extern uint32_t IRReceiver_previousBytes; + +uint16_t IRReceiver_bytesReceived(void); + +// ============================================================================= +// ModeLink (internal linked list type for Modes) +// ============================================================================= +typedef struct ModeLink ModeLink; +struct ModeLink { + Mode *m_pInstantiatedMode; + ByteStream m_storedMode; + ModeLink *m_next; + ModeLink *m_prev; +}; + +ModeLink *ModeLink_next(ModeLink *self); +ModeLink *ModeLink_prev(ModeLink *self); +void ModeLink_init(ModeLink *self, const Mode *src, bool inst); +void ModeLink_initFromStream(ModeLink *self, const ByteStream *src, bool inst); +void ModeLink_cleanup(ModeLink *self); +bool ModeLink_initMode(ModeLink *self, const Mode *mode); +bool ModeLink_appendMode(ModeLink *self, const Mode *next); +bool ModeLink_appendStream(ModeLink *self, const ByteStream *next); +void ModeLink_play(ModeLink *self); +ModeLink *ModeLink_unlinkSelf(ModeLink *self); +void ModeLink_linkAfter(ModeLink *self, ModeLink *link); +void ModeLink_linkBefore(ModeLink *self, ModeLink *link); +Mode *ModeLink_instantiate(ModeLink *self); +void ModeLink_uninstantiate(ModeLink *self); +bool ModeLink_save(ModeLink *self); +ByteStream *ModeLink_buffer(ModeLink *self); +Mode *ModeLink_mode(ModeLink *self); + +// ============================================================================= +// EditorConnection types +// ============================================================================= +typedef uint8_t ReturnCode; +enum { + RV_FAIL = 0, + RV_OK, + RV_WAIT, +}; + +typedef uint8_t EditorConnectionState; +enum { + STATE_DISCONNECTED, + STATE_GREETING, + STATE_IDLE, + STATE_PULL_MODES, + STATE_PULL_MODES_SEND, + STATE_PULL_MODES_DONE, + STATE_PUSH_MODES, + STATE_PUSH_MODES_RECEIVE, + STATE_PUSH_MODES_DONE, + STATE_DEMO_MODE, + STATE_DEMO_MODE_RECEIVE, + STATE_DEMO_MODE_DONE, + STATE_CLEAR_DEMO, + STATE_TRANSMIT_MODE_VL, + STATE_TRANSMIT_MODE_VL_DONE, + STATE_PULL_EACH_MODE, + STATE_PULL_EACH_MODE_COUNT, + STATE_PULL_EACH_MODE_SEND, + STATE_PULL_EACH_MODE_WAIT, + STATE_PULL_EACH_MODE_DONE, + STATE_PUSH_EACH_MODE, + STATE_PUSH_EACH_MODE_COUNT, + STATE_PUSH_EACH_MODE_RECEIVE, + STATE_PUSH_EACH_MODE_WAIT, + STATE_PUSH_EACH_MODE_DONE, + STATE_SET_GLOBAL_BRIGHTNESS, + STATE_SET_GLOBAL_BRIGHTNESS_RECEIVE, + STATE_GET_GLOBAL_BRIGHTNESS, +}; + +// ============================================================================= +// ModeShare types +// ============================================================================= +typedef uint8_t ModeShareState; +enum { + SHARE_SEND_RECEIVE, + SHARE_SEND_RECEIVE_LEGACY, + SHARE_EXIT, +}; + +// ============================================================================= +// ColorSelect types +// ============================================================================= +typedef uint8_t ColorSelectState; +enum { + COLOR_SELECT_STATE_INIT, + COLOR_SELECT_STATE_PICK_SLOT, + COLOR_SELECT_STATE_PICK_HUE1, + COLOR_SELECT_STATE_PICK_HUE2, + COLOR_SELECT_STATE_PICK_SAT, + COLOR_SELECT_STATE_PICK_VAL, +}; + +// ============================================================================= +// Randomizer types +// ============================================================================= +typedef uint8_t RandomizeFlags; +enum { + RANDOMIZE_NONE = 0, + RANDOMIZE_COLORSET = (1 << 0), + RANDOMIZE_PATTERN = (1 << 1), + RANDOMIZE_BOTH = (RANDOMIZE_COLORSET | RANDOMIZE_PATTERN), +}; + +// ============================================================================= +// GlobalBrightness types +// ============================================================================= +typedef uint8_t KeychainModeState; +enum { + KEYCHAIN_MODE_STATE_OFF = 0, + KEYCHAIN_MODE_STATE_SOLID, + KEYCHAIN_MODE_STATE_DOPS, + KEYCHAIN_MODE_STATE_SIGNAL, + KEYCHAIN_MODE_STATE_COUNT +}; + +// ============================================================================= +// Additional Button functions +// ============================================================================= +bool Button_initPin(Button *self, uint8_t pin); +void Button_cleanup(Button *self); + +// ============================================================================= +// Additional Colorset functions +// ============================================================================= +bool Colorset_initPaletteInternal(Colorset *self, uint8_t numColors); + +// ============================================================================= +// Additional RGBColor functions used in .c files +// ============================================================================= +void RGBColor_initFromHSV(RGBColor *self, const HSVColor *hsv); + +// ============================================================================= +// Menu VTable externs (defined in individual .c files) +// ============================================================================= +extern const MenuVTable g_colorSelectMenuVTable; +extern const MenuVTable g_patternSelectMenuVTable; +extern const MenuVTable g_randomizerMenuVTable; +extern const MenuVTable g_globalBrightnessMenuVTable; +extern const MenuVTable g_factoryResetMenuVTable; +extern const MenuVTable g_editorConnectionMenuVTable; +extern const MenuVTable g_modeSharingMenuVTable; + +// ============================================================================= +// EditorConnection command state +// ============================================================================= +typedef struct { + const char *cmd; + EditorConnectionState cmdState; +} EditorConnectionCommandState; + +// defined as static in EditorConnection.c + +// ============================================================================= +// Additional Mode functions needed by .c files +// ============================================================================= +void Mode_initFromIDArgsSetPtr(Mode *self, PatternID id, const PatternArgs *args, const Colorset *set); +void Mode_initFromEntry(Mode *self, const struct DefaultModeEntry *entry); +void Mode_cleanup(Mode *self); +bool Mode_saveToBuffer(const Mode *self, ByteStream *saveBuffer, uint8_t numLeds); +bool Mode_loadFromBuffer(Mode *self, ByteStream *saveBuffer); +bool Mode_setPatternID(Mode *self, PatternID id); +bool Mode_setPatternMap(Mode *self, LedMap map, PatternID id, const PatternArgs *args, const Colorset *set); +bool Modes_addModeArgsSet(PatternID id, const PatternArgs *args, const Colorset *set); +ModeLink *Modes_getModeLink(uint32_t index); +Mode *Modes_initCurMode(bool force); +bool Modes_saveCurMode(void); + +// ============================================================================= +// Modes globals +// ============================================================================= +extern uint8_t Modes_globalFlags; + +// ============================================================================= +// Additional Colorset functions +// ============================================================================= +void Colorset_initPalette(Colorset *self, RGBColor c1, RGBColor c2, RGBColor c3, RGBColor c4, RGBColor c5, RGBColor c6, RGBColor c7, RGBColor c8); + +// ============================================================================= +// Additional VortexEngine functions (embedded-specific) +// ============================================================================= +#ifdef VORTEX_EMBEDDED +void VortexEngine_clearOutputPins(void); +void VortexEngine_enableMOSFET(bool enabled); +#endif + +#ifdef VORTEX_LIB +bool VortexEngine_isSleeping(void); +#endif + +// ============================================================================= +// VLSender types (used by VLSender.c) +// ============================================================================= +typedef struct VLSenderCallbacks VLSenderCallbacks; + +// ============================================================================= +// Helpful color macro used in some .c files +// ============================================================================= +#ifndef RGB_COLOR +#define RGB_COLOR(v) ((RGBColor){ ((v) >> 16) & 0xFF, ((v) >> 8) & 0xFF, (v) & 0xFF }) +#endif +#ifndef COL32 +#define COL32(v) ((RGBColor){ ((v) >> 16) & 0xFF, ((v) >> 8) & 0xFF, (v) & 0xFF }) +#endif + +#endif // C_TYPES_H From 51c919d4aeb86701fd1af948cdc9d558d2170265 Mon Sep 17 00:00:00 2001 From: Unreal-Dan Date: Sun, 27 Sep 2026 13:07:03 -0700 Subject: [PATCH 10/10] saving for now --- VortexEngine/src/Menus/Menu.cpp | 160 +++++ .../src/Menus/MenuList/ColorSelect.cpp | 274 ++++++++ .../src/Menus/MenuList/EditorConnection.cpp | 602 ++++++++++++++++++ .../src/Menus/MenuList/FactoryReset.cpp | 129 ++++ .../src/Menus/MenuList/GlobalBrightness.cpp | 212 ++++++ .../src/Menus/MenuList/ModeSharing.cpp | 184 ++++++ .../src/Menus/MenuList/PatternSelect.cpp | 80 +++ .../src/Menus/MenuList/Randomizer.cpp | 390 ++++++++++++ VortexEngine/src/Menus/Menus.cpp | 296 +++++++++ VortexEngine/tests/runtests.sh | 0 10 files changed, 2327 insertions(+) create mode 100644 VortexEngine/src/Menus/Menu.cpp create mode 100644 VortexEngine/src/Menus/MenuList/ColorSelect.cpp create mode 100644 VortexEngine/src/Menus/MenuList/EditorConnection.cpp create mode 100644 VortexEngine/src/Menus/MenuList/FactoryReset.cpp create mode 100644 VortexEngine/src/Menus/MenuList/GlobalBrightness.cpp create mode 100644 VortexEngine/src/Menus/MenuList/ModeSharing.cpp create mode 100644 VortexEngine/src/Menus/MenuList/PatternSelect.cpp create mode 100644 VortexEngine/src/Menus/MenuList/Randomizer.cpp create mode 100644 VortexEngine/src/Menus/Menus.cpp mode change 100644 => 100755 VortexEngine/tests/runtests.sh diff --git a/VortexEngine/src/Menus/Menu.cpp b/VortexEngine/src/Menus/Menu.cpp new file mode 100644 index 0000000000..97c904b763 --- /dev/null +++ b/VortexEngine/src/Menus/Menu.cpp @@ -0,0 +1,160 @@ +#include "Menu.h" + +#include "../Time/TimeControl.h" +#include "../Time/Timings.h" +#include "../Buttons/Button.h" +#include "../Menus/Menus.h" +#include "../Modes/Modes.h" +#include "../Modes/Mode.h" +#include "../Leds/Leds.h" +#include "../Log/Log.h" + +Menu::Menu(const RGBColor &col, bool advanced) : + m_previewMode(), + m_menuColor(col), + m_targetLeds(MAP_LED_ALL), + m_curSelection(0), + m_ledSelected(false), + m_advanced(advanced), + m_shouldClose(false) +{ +} + +Menu::~Menu() +{ +} + +bool Menu::init() +{ + // menu is initialized before being run + if (!Modes::curMode()) { + // if you enter a menu and there's no modes, it will add an empty one + if (Modes::numModes() > 0) { + // some kind of serious error + return false; + } + if (!Modes::addMode(PATTERN_STROBE, RGBColor(RGB_OFF))) { + // some kind of serious error + return false; + } + if (!Modes::curMode()) { + // serious error again + return false; + } + } + // copy the current mode into the demo mode and initialize it + m_previewMode = *Modes::curMode(); + m_previewMode.init(); + // just in case + m_shouldClose = false; + return true; +} + +Menu::MenuAction Menu::run() +{ + // check whether the close flag has been set + if (m_shouldClose) { + // reset flag and close + m_shouldClose = false; + return MENU_QUIT; + } + + // if the led has already been selected continue as normal + // and allow derived menu logic to perform work + if (m_ledSelected) { + return MENU_CONTINUE; + } + + // using manual onShortClick and onLongClick check here because + // there is no guarantee the child class will call the parent + // class's onShortClick and onLongClick functions so + + // every time the button is clicked, change the target led + if (g_pButton->onShortClick()) { + nextBulbSelection(); + } + // on a long press of the button, lock in the target led + if (g_pButton->onLongClick()) { + m_ledSelected = true; + // call led selected callback + onLedSelected(); + // log about the selection + DEBUG_LOGF("Led Selected: 0x%x (%s)", m_targetLeds, + (m_targetLeds == MAP_LED(LED_MULTI)) ? "multi" + : (m_targetLeds == MAP_LED_ALL) ? "all" + : "some singles"); + } + + // render the bulb selection + showBulbSelection(); + + // return MENU_SKIP to indicate to derived menu classes that + // they should skip their logic and just return so that the + // bulb selection can be rendered + return MENU_SKIP; +} + +void Menu::showBulbSelection() +{ + Leds::clearAll(); + Leds::blinkMap(m_targetLeds, BULB_SELECT_OFF_MS, BULB_SELECT_ON_MS, RGB_MAGENTA1); + // blink when selecting + Menus::showSelection(RGB_MAGENTA1); +} + +void Menu::showExit() +{ + if (g_pButton->isPressed() && g_pButton->holdDuration() > SHORT_CLICK_THRESHOLD_TICKS) { + Leds::setIndex(LED_1, RGB_RED); + } else { + Leds::clearIndex(LED_1); + Leds::blinkIndex(LED_0, EXIT_MENU_OFF_MS, EXIT_MENU_ON_MS, RGB_WHITE0); + Leds::blinkIndex(LED_1, EXIT_MENU_OFF_MS, EXIT_MENU_ON_MS, RGB_RED0); + } +} + +void Menu::nextBulbSelection() +{ + // The target led can be 0 through LED_COUNT to represent any led or all leds + // modulo by LED_COUNT + 1 to include LED_COUNT (all) as a target + switch (m_targetLeds) { + case MAP_LED_ALL: + m_targetLeds = MAP_LED(LED_FIRST); + break; + case MAP_LED(LED_LAST): + m_targetLeds = MAP_LED_ALL; + break; + default: // LED_FIRST through LED_LAST + // iterate as normal + m_targetLeds = MAP_LED(((ledmapGetFirstLed(m_targetLeds) + 1) % (LED_COUNT + 1))); + break; + } +} + +void Menu::bypassLedSelection(LedMap map) +{ + m_ledSelected = true; + m_targetLeds = map; + onLedSelected(); +} + +void Menu::onLedSelected() +{ +} + +void Menu::onShortClick() +{ +} + +void Menu::onLongClick() +{ + leaveMenu(false); +} + +void Menu::leaveMenu(bool doSave) +{ + m_shouldClose = true; + if (doSave) { + Modes::saveStorage(); + } +} diff --git a/VortexEngine/src/Menus/MenuList/ColorSelect.cpp b/VortexEngine/src/Menus/MenuList/ColorSelect.cpp new file mode 100644 index 0000000000..3e56e6d603 --- /dev/null +++ b/VortexEngine/src/Menus/MenuList/ColorSelect.cpp @@ -0,0 +1,274 @@ +#include "ColorSelect.h" + +#include "../../VortexEngine.h" + +#include "../../Time/TimeControl.h" +#include "../../Patterns/PatternBuilder.h" +#include "../../Patterns/Pattern.h" +#include "../../Colors/Colorset.h" +#include "../../Buttons/Button.h" +#include "../../Random/Random.h" +#include "../../Time/Timings.h" +#include "../../Menus/Menus.h" +#include "../../Modes/Modes.h" +#include "../../Modes/Mode.h" +#include "../../Leds/Leds.h" +#include "../../Log/Log.h" + +ColorSelect::ColorSelect(const RGBColor &col, bool advanced) : + Menu(col, advanced), + m_state(STATE_PICK_SLOT), + m_newColor(), + m_colorset(), + m_targetSlot(0), + m_targetHue1(0) +{ + // NOTE! Specifically using hsv_to_rgb_rainbow instead of generic because + // it will generate nicer looking colors and a nicer rainbow to select + // from than a regular hsv conversion will. This is because the rainbow + // function uses a different algorithm to generate the colors that results + // in a smaller color space with less bright colors. The tradeoff is you lose + // the bright colors but the rainbow looks a lot better + g_hsv_rgb_alg = HSV_TO_RGB_RAINBOW; +} + +ColorSelect::~ColorSelect() +{ + // revert the hsv to rgb algorithm to normal + g_hsv_rgb_alg = HSV_TO_RGB_GENERIC; + // make sure force sleep is re-enabled when we leave + VortexEngine::toggleForceSleep(true); +} + +bool ColorSelect::init() +{ + if (!Menu::init()) { + return false; + } + Mode *cur = Modes::curMode(); + if (cur->isEmpty()) { + // cannot work with an empty mode + return false; + } +#if VORTEX_SLIM == 0 + if (cur->isMultiLed()) { + bypassLedSelection(MAP_LED(LED_MULTI)); + } +#endif + m_state = STATE_INIT; + DEBUG_LOG("Entered color select"); + return true; +} + +Menu::MenuAction ColorSelect::run() +{ + MenuAction result = Menu::run(); + if (result != MENU_CONTINUE) { + return result; + } + + // all states start with a blank slate + Leds::clearAll(); + switch (m_state) { + case STATE_INIT: + // reset all the selection members + m_newColor = HSVColor(0, 255, 255); + m_targetHue1 = 0; + m_targetSlot = 0; + // move on to picking slot + m_state = STATE_PICK_SLOT; + break; + case STATE_PICK_SLOT: + showSlotSelection(); + break; + case STATE_PICK_HUE1: + case STATE_PICK_HUE2: + case STATE_PICK_SAT: + case STATE_PICK_VAL: + showSelection(m_state); + break; + } + // show selections + Menus::showSelection(); + return MENU_CONTINUE; +} + +// callback after the user selects the target led +void ColorSelect::onLedSelected() +{ + Mode *cur = Modes::curMode(); + // grab the colorset from our selected target led + if (m_targetLeds == MAP_LED_ALL) { + m_colorset = cur->getColorset(); + } else { + m_colorset = cur->getColorset(ledmapGetFirstLed(m_targetLeds)); + } +} + +void ColorSelect::onShortClick() +{ + // increment selection + m_curSelection++; + if (m_state == STATE_PICK_SLOT) { + m_curSelection %= (m_colorset.numColors() + 1 + (m_colorset.numColors() < MAX_COLOR_SLOTS)); + } else { + m_curSelection %= 5; + } +} + +void ColorSelect::onLongClick() +{ + // if we're on 'exit' and we're on any menu past the slot selection + if (m_curSelection == 4 && m_state > STATE_PICK_SLOT) { + // move back to the previous selection + m_state = (ColorSelectState)(m_state - 1); + // if we're back to the slot selection then set our position back to the + // slot we selected, otherwise it's more visually appealing to just return + // to the first selection on the previous menu + m_curSelection = (m_state == STATE_PICK_SLOT) ? m_targetSlot : 0; + return; + } + // reuse these variables lots + uint8_t numColors = m_colorset.numColors(); + uint32_t holdDur = g_pButton->holdDuration(); + switch (m_state) { + case STATE_INIT: + // nothing + return; + case STATE_PICK_SLOT: + // if the exit is selected then save and exit, this depends on the number + // of colors in the colorset. If the colorset is full then the exit is + // just the number of colors (8) but if it's not full then the exit is the + // number of colors + 1. Example: with 4 cols, cols are on 0, 1, 2, 3, + // add-color is 4, and exit is 5 + if (m_curSelection == numColors + (numColors < MAX_COLOR_SLOTS)) { + Mode *cur = Modes::curMode(); + cur->setColorsetMap(m_targetLeds, m_colorset); + cur->init(); + leaveMenu(true); + return; + } + // handle if user releases during the delete option + if (m_curSelection < numColors && + holdDur >= DELETE_THRESHOLD_TICKS && + (holdDur % (DELETE_CYCLE_TICKS * 2)) > (DELETE_CYCLE_TICKS)) { + // delete current slot + m_colorset.removeColor(m_curSelection); + if (m_curSelection > numColors) { + m_curSelection--; + } + return; + } + // otherwise store the target slot continue onto the hue selection + m_targetSlot = m_curSelection; + break; + case STATE_PICK_HUE1: + m_targetHue1 = m_curSelection; + m_newColor.hue = m_targetHue1 * (255 / 4); + break; + case STATE_PICK_HUE2: + m_newColor.hue = (m_targetHue1 * (255 / 4)) + m_curSelection * (255 / 16); + break; + case STATE_PICK_SAT: + m_newColor.sat = sats[m_curSelection]; + break; + case STATE_PICK_VAL: + // no m_targetVal because you can't go back after this + m_newColor.val = vals[m_curSelection]; + // specifically using hsv to rgb rainbow to generate the color + m_colorset.set(m_targetSlot, m_newColor); + m_curSelection = m_targetSlot; + m_state = STATE_PICK_SLOT; + return; + } + m_state = (ColorSelectState)(m_state + 1); + m_curSelection = 0; +} + +void ColorSelect::showSlotSelection() +{ + uint8_t exitIndex = m_colorset.numColors(); + uint32_t holdDur = g_pButton->holdDuration(); + bool withinNumColors = m_curSelection < exitIndex; + bool holdDurationCheck = g_pButton->isPressed() && holdDur >= DELETE_THRESHOLD_TICKS; + bool holdDurationModCheck = (holdDur % (DELETE_CYCLE_TICKS * 2)) > DELETE_CYCLE_TICKS; + const RGBColor &col = m_colorset[m_curSelection]; + if (withinNumColors && holdDurationCheck && holdDurationModCheck) { + // breath red for delete slot + Leds::blinkIndex(LED_0, 50, 100, col); + Leds::breatheIndex(LED_1, 0, holdDur); + } else if (withinNumColors) { + if (col.empty()) { + Leds::setIndex(LED_0, RGB_WHITE0); + } + Leds::blinkAll(150, 650, col); + } else if (exitIndex < MAX_COLOR_SLOTS) { + if (m_curSelection == exitIndex) { + // blink both leds and blink faster to indicate 'add' new color + Leds::blinkAll(100, 150, RGB_WHITE2); + } + exitIndex++; + } + if (m_curSelection == exitIndex) { + // display the full set + showFullSet(50, 100); + // set LED_1 to green to indicate save and exit + Leds::setIndex(LED_1, RGB_GREEN2); + // if not on exitIndex or add new color set LED_1 based on button state + } else if (m_curSelection != m_colorset.numColors() && !holdDurationCheck) { + Leds::setIndex(LED_1, g_pButton->isPressed() ? RGB_OFF : RGB_WHITE2); + } +} + +void ColorSelect::showSelection(ColorSelectState mode) +{ + if (m_curSelection >= 4) { + showExit(); + return; + } + uint32_t now = Time::getCurtime(); + uint8_t hue = m_newColor.hue; + uint8_t sat = m_newColor.sat; + uint8_t val = 255; + switch (mode) { + default: + return; + case STATE_PICK_HUE1: + hue = m_curSelection * (255 / 4); + Leds::breatheIndex(LED_0, hue, (now / 2), 22, 255, 180); + Leds::breatheIndex(LED_1, hue, (now / 2) + 125, 22, 255, 180); + // force sat at hue level1 + sat = 255; + // NOTE: return here + return; + case STATE_PICK_HUE2: + hue = m_targetHue1 * (255 / 4) + (m_curSelection * (255 / 16)); + Leds::setIndex(LED_1, RGB_WHITE0); + // force sat at hue level2 + sat = 255; + break; + case STATE_PICK_SAT: + sat = sats[m_curSelection]; + Leds::breatheIndexSat(LED_1, hue, (now / 3), 100, 150, 150); + break; + case STATE_PICK_VAL: + val = vals[m_curSelection]; + Leds::breatheIndexVal(LED_1, hue, (now / 3), 100, sat, 150); + break; + } + Leds::setMap(MAP_PAIR_EVENS, HSVColor(hue, sat, val)); +} + +void ColorSelect::showFullSet(uint8_t offMs, uint8_t onMs) +{ + uint8_t numCols = m_colorset.numColors(); + uint8_t offOnMs = MS_TO_TICKS(offMs + onMs); + if (!numCols || !offOnMs) { + return; + } + uint32_t now = Time::getCurtime(); + if ((now % offOnMs) < MS_TO_TICKS(onMs)) { + Leds::setAll(m_colorset.get((now / offOnMs) % numCols)); + } + Leds::setIndex(LED_1, RGB_GREEN0); +} diff --git a/VortexEngine/src/Menus/MenuList/EditorConnection.cpp b/VortexEngine/src/Menus/MenuList/EditorConnection.cpp new file mode 100644 index 0000000000..c1fc1d5927 --- /dev/null +++ b/VortexEngine/src/Menus/MenuList/EditorConnection.cpp @@ -0,0 +1,602 @@ +#include "EditorConnection.h" + +#include "../../Patterns/PatternArgs.h" +#include "../../Serial/ByteStream.h" +#include "../../Serial/Serial.h" +#include "../../Storage/Storage.h" +#include "../../Wireless/VLSender.h" +#include "../../Time/TimeControl.h" +#include "../../Colors/Colorset.h" +#include "../../Modes/Modes.h" +#include "../../Modes/Mode.h" +#include "../../Leds/Leds.h" +#include "../../Log/Log.h" + +#include + +EditorConnection::EditorConnection(const RGBColor &col, bool advanced) : + Menu(col, advanced), + m_state(STATE_DISCONNECTED), + m_allowReset(true), + m_previousModeIndex(0), + m_numModesToReceive(0), + m_rv(RV_OK) +{ +} + +EditorConnection::~EditorConnection() +{ + clearDemo(); +} + +bool EditorConnection::init() +{ + if (!Menu::init()) { + return false; + } + // skip led selection + m_ledSelected = true; + clearDemo(); + DEBUG_LOG("Entering Editor Connection"); + return true; +} + +Menu::MenuAction EditorConnection::run() +{ + MenuAction result = Menu::run(); + if (result != MENU_CONTINUE) { + return result; + } + // TODO: auto leave the editor menu when unplugged + // show the editor + showEditor(); + // receive any data from serial into the receive buffer + receiveData(); + // handle the current state + handleState(); + return MENU_CONTINUE; +} + +void EditorConnection::onShortClick() +{ + // if the device has received any commands do not reset! + if (!m_allowReset) { + return; + } + // reset, this won't actually disconnect the com port + m_state = STATE_DISCONNECTED; + // clear the demo + clearDemo(); + // sent a reset, do not allow another + m_allowReset = false; +} + +void EditorConnection::onLongClick() +{ + leaveMenu(true); +} + +void EditorConnection::leaveMenu(bool doSave) +{ + SerialComs::write(EDITOR_VERB_GOODBYE); + Menu::leaveMenu(true); +} + +void EditorConnection::clearDemo() +{ + Colorset set(RGB_WHITE0); + PatternArgs args(1, 0, 0); + m_previewMode.setPattern(PATTERN_STROBE, LED_ALL, &args, &set); + m_previewMode.init(); +} + +void EditorConnection::handleErrors() +{ + if (m_rv == RV_FAIL) { + // handle failure from before, reset rv + m_rv = RV_OK; + // clear the buffer I guess + m_receiveBuffer.clear(); + } + // TODO: Custom error codes? +} + +const EditorConnection::CommandState EditorConnection::commands[] = { + { EDITOR_VERB_PULL_MODES, STATE_PULL_MODES }, + { EDITOR_VERB_PUSH_MODES, STATE_PUSH_MODES }, + { EDITOR_VERB_DEMO_MODE, STATE_DEMO_MODE }, + { EDITOR_VERB_CLEAR_DEMO, STATE_CLEAR_DEMO }, + { EDITOR_VERB_PULL_EACH_MODE, STATE_PULL_EACH_MODE }, + { EDITOR_VERB_PUSH_EACH_MODE, STATE_PUSH_EACH_MODE }, + { EDITOR_VERB_TRANSMIT_VL, STATE_TRANSMIT_MODE_VL }, + { EDITOR_VERB_SET_GLOBAL_BRIGHTNESS, STATE_SET_GLOBAL_BRIGHTNESS }, + { EDITOR_VERB_GET_GLOBAL_BRIGHTNESS, STATE_GET_GLOBAL_BRIGHTNESS }, +}; +#define NUM_COMMANDS (sizeof(commands) / sizeof(commands[0])) + +void EditorConnection::handleCommand() +{ + if (m_receiveBuffer.size() < 1) { + return; + } + for (uint32_t i = 0; i < NUM_COMMANDS; ++i) { + // commands are just one letter, don't consume it yet so we can compare + // each cmd in the list, this probably should be a lookup table + char receiveCmd = m_receiveBuffer.data()[0]; + // if the command matches + if (receiveCmd != commands[i].cmd[0]) { + continue; + } + // then consume the data from the receive buffer and update the state + m_receiveBuffer.consume8(); + m_state = commands[i].cmdState; + // we have now received at least one command, do not allow resetting + m_allowReset = false; + // don't need to check any more commands + break; + } +} + +void EditorConnection::handleState() +{ + // operate on the state of the editor connection + switch (m_state) { + // ------------------------------- + // Disconnected + case STATE_DISCONNECTED: + default: + // not connected yet so check for connections + if (!SerialComs::isConnected()) { + if (!SerialComs::checkSerial()) { + // no connection found just continue waiting + break; + } + } + // a connection was found, say hello + m_state = STATE_GREETING; + break; + + // ------------------------------- + // Send Greeting + case STATE_GREETING: + // send the hello greeting with our version number and build time + SerialComs::write(EDITOR_VERB_GREETING); + m_state = STATE_IDLE; + break; + + // ------------------------------- + // Chillin + case STATE_IDLE: + // handle any errors that may have occurred last run + handleErrors(); + // parse the receive buffer for any commands from the editor + handleCommand(); + // watch for disconnects + if (!SerialComs::isConnected()) { + Leds::holdAll(RGB_RED); + leaveMenu(true); + } + break; + + // ------------------------------- + // Send Modes to PC + case STATE_PULL_MODES: + // editor requested pull modes, send the modes + sendModes(); + m_state = STATE_PULL_MODES_SEND; + break; + case STATE_PULL_MODES_SEND: + // recive the send modes ack from the editor + if (receiveMessage(EDITOR_VERB_PULL_MODES_DONE) == RV_WAIT) { + // just wait + break; + } + m_state = STATE_PULL_MODES_DONE; + break; + case STATE_PULL_MODES_DONE: + // send our acknowledgement that the modes were sent + SerialComs::write(EDITOR_VERB_PULL_MODES_ACK); + // go idle + m_state = STATE_IDLE; + break; + + // ------------------------------- + // Receive Modes from PC + case STATE_PUSH_MODES: + // now say we are ready + SerialComs::write(EDITOR_VERB_READY); + // move to receiving + m_state = STATE_PUSH_MODES_RECEIVE; + break; + case STATE_PUSH_MODES_RECEIVE: + // receive the modes into the receive buffer + if (receiveModes() == RV_WAIT) { + // just keep waiting + break; + } + // success modes were received send the done + m_state = STATE_PUSH_MODES_DONE; + break; + case STATE_PUSH_MODES_DONE: + SerialComs::write(EDITOR_VERB_PUSH_MODES_ACK); + m_state = STATE_IDLE; + break; + + // ------------------------------- + // Demo Mode from PC + case STATE_DEMO_MODE: + // now say we are ready + SerialComs::write(EDITOR_VERB_READY); + // move to receiving + m_state = STATE_DEMO_MODE_RECEIVE; + break; + case STATE_DEMO_MODE_RECEIVE: + // receive the modes into the receive buffer + if (receiveDemoMode() == RV_WAIT) { + // just wait + break; + } + // success modes were received send the done + m_state = STATE_DEMO_MODE_DONE; + break; + case STATE_DEMO_MODE_DONE: + // say we are done + SerialComs::write(EDITOR_VERB_DEMO_MODE_ACK); + m_state = STATE_IDLE; + break; + + // ------------------------------- + // Reset Demo to Nothing + case STATE_CLEAR_DEMO: + clearDemo(); + SerialComs::write(EDITOR_VERB_CLEAR_DEMO_ACK); + m_state = STATE_IDLE; + break; + + // ------------------------------- + // Send Mode to Duo + case STATE_TRANSMIT_MODE_VL: +#if VL_ENABLE_SENDER == 1 + // immediately load the mode and send it now + VLSender::send(&m_previewMode); +#endif + m_state = STATE_TRANSMIT_MODE_VL_DONE; + break; + case STATE_TRANSMIT_MODE_VL_DONE: + // done transmitting + SerialComs::write(EDITOR_VERB_TRANSMIT_VL_ACK); + m_state = STATE_IDLE; + break; + + // ------------------------------- + // Send Modes to PC Safer + case STATE_PULL_EACH_MODE: + // editor requested pull modes, send the modes + sendModeCount(); + m_state = STATE_PULL_EACH_MODE_COUNT; + break; + case STATE_PULL_EACH_MODE_COUNT: + if (receiveMessage(EDITOR_VERB_PULL_EACH_MODE_ACK) == RV_WAIT) { + // just wait + break; + } + if (Modes::numModes() == 0) { + m_state = STATE_PULL_EACH_MODE_DONE; + } else { + m_previousModeIndex = Modes::curModeIndex(); + m_state = STATE_PULL_EACH_MODE_SEND; + } + break; + case STATE_PULL_EACH_MODE_SEND: + // send the current mode + sendCurMode(); + // wait for the ack + m_state = STATE_PULL_EACH_MODE_WAIT; + break; + case STATE_PULL_EACH_MODE_WAIT: + // recive the ack from the editor to send next mode + if (receiveMessage(EDITOR_VERB_PULL_EACH_MODE_ACK) == RV_WAIT) { + // just wait + break; + } + // if there is still more modes + if (Modes::curModeIndex() < (Modes::numModes() - 1)) { + // then iterate to the next mode and send + Modes::nextMode(); + m_state = STATE_PULL_EACH_MODE_SEND; + } else { + // otherwise done sending modes + m_state = STATE_PULL_EACH_MODE_DONE; + } + break; + case STATE_PULL_EACH_MODE_DONE: + // send our acknowledgement that the modes were sent + SerialComs::write(EDITOR_VERB_PULL_EACH_MODE_DONE); + // switch back to the previous mode + Modes::setCurMode(m_previousModeIndex); + // go idle + m_state = STATE_IDLE; + break; + + // ------------------------------- + // Receive Modes from PC Safer + case STATE_PUSH_EACH_MODE: + // editor requested to push modes, find out how many + // ack the command and wait for the amount of modes + SerialComs::write(EDITOR_VERB_PUSH_EACH_MODE_ACK); + m_state = STATE_PUSH_EACH_MODE_COUNT; + break; + case STATE_PUSH_EACH_MODE_COUNT: + if (receiveModeCount() == RV_WAIT) { + // just wait + break; + } + // clear modes and start receiving + Modes::clearModes(); + // write out an ack + SerialComs::write(EDITOR_VERB_PUSH_EACH_MODE_ACK); + // ready to receive a mode + m_state = STATE_PUSH_EACH_MODE_RECEIVE; + break; + case STATE_PUSH_EACH_MODE_RECEIVE: + // receive the modes into the receive buffer + if (receiveMode() == RV_WAIT) { + // just wait + break; + } + SerialComs::write(EDITOR_VERB_PUSH_EACH_MODE_ACK); + if (m_numModesToReceive > 0) { + m_numModesToReceive--; + } + if (!m_numModesToReceive) { + // success modes were received send the done + m_state = STATE_PUSH_EACH_MODE_DONE; + } + break; + case STATE_PUSH_EACH_MODE_DONE: + // save the new modes + Modes::saveStorage(); + // did originally receive/send a DONE message here but it wasn't working + // on lightshow.lol so just skip to IDLE + m_state = STATE_IDLE; + break; + + // ------------------------------- + // Set Global Brightness + case STATE_SET_GLOBAL_BRIGHTNESS: + SerialComs::write(EDITOR_VERB_READY); + m_state = STATE_SET_GLOBAL_BRIGHTNESS_RECEIVE; + break; + case STATE_SET_GLOBAL_BRIGHTNESS_RECEIVE: + // set the brightness of the device + if (receiveBrightness() == RV_WAIT) { + // just keep waiting + break; + } + m_state = STATE_IDLE; + break; + + // ------------------------------- + // Get Global Brightness + case STATE_GET_GLOBAL_BRIGHTNESS: + sendBrightness(); + m_state = STATE_IDLE; + break; + } +} + +void EditorConnection::showEditor() +{ + switch (m_state) { + case STATE_DISCONNECTED: + Leds::clearAll(); + Leds::blinkAll(250, 150, RGB_WHITE0); + break; + case STATE_IDLE: + m_previewMode.play(); + break; + default: + // do nothing! + // Note if you clear the leds while selecting color + // it may make the color selection choppy + break; + } +} + +void EditorConnection::receiveData() +{ + // read more data into the receive buffer + SerialComs::read(m_receiveBuffer); +} + +void EditorConnection::sendModes() +{ + ByteStream modesBuffer; + Modes::saveToBuffer(modesBuffer); + SerialComs::write(modesBuffer); +} + +void EditorConnection::sendModeCount() +{ + ByteStream buffer; + buffer.serialize8(Modes::numModes()); + SerialComs::write(buffer); +} + +void EditorConnection::sendCurMode() +{ + ByteStream modeBuffer; + Mode *cur = Modes::curMode(); + if (!cur) { + // ?? + return; + } + if (!cur->saveToBuffer(modeBuffer)) { + // ?? + return; + } + SerialComs::write(modeBuffer); +} + +void EditorConnection::sendCurModeVL() +{ +#if VL_ENABLE_SENDER == 1 + m_state = STATE_TRANSMIT_MODE_VL; +#endif +} + +ReturnCode EditorConnection::sendBrightness() +{ + ByteStream brightnessBuf; + if (!brightnessBuf.serialize8(Leds::getBrightness())) { + return RV_FAIL; + } + SerialComs::write(brightnessBuf); + return RV_OK; +} + +ReturnCode EditorConnection::receiveBuffer(ByteStream &buffer) +{ + // need at least the buffer size first + uint32_t size = 0; + if (m_receiveBuffer.size() < sizeof(size)) { + // wait, not enough data available yet + return RV_WAIT; + } + // grab the size out of the start + m_receiveBuffer.resetUnserializer(); + size = m_receiveBuffer.peek32(); + if (m_receiveBuffer.size() < (size + sizeof(size))) { + // don't unserialize yet, not ready + return RV_WAIT; + } + // okay unserialize now, first unserialize the size + if (!m_receiveBuffer.consume32(&size)) { + // this shouldn't fail! + return RV_FAIL; + } + // create a new ByteStream that will hold the full buffer of data + if (!buffer.init(size)) { + return RV_FAIL; + } + // then copy everything from the receive buffer into the rawdata + // which is going to overwrite the crc/size/flags of the ByteStream + if (!m_receiveBuffer.consume(size, buffer.rawData())) { + return RV_FAIL; + } + buffer.sanity(); + if (!buffer.checkCRC()) { + buffer.clear(); + // return true otherwise we'll get locked in this state forever + return RV_FAIL; + } + return RV_OK; +} + +ReturnCode EditorConnection::receiveModes() +{ + // create a new ByteStream that will hold the full buffer of data + ByteStream buf; + m_rv = receiveBuffer(buf); + if (m_rv != RV_OK) { + return m_rv; + } + if (!Modes::loadFromBuffer(buf) || !Modes::saveStorage()) { + return RV_FAIL; + } + return RV_OK; +} + +ReturnCode EditorConnection::receiveModeCount() +{ + ByteStream buf; + m_rv = receiveBuffer(buf); + if (m_rv != RV_OK) { + return m_rv; + } + // unserialize the mode count + if (!buf.consume8(&m_numModesToReceive)) { + return RV_FAIL; + } + if (m_numModesToReceive > MAX_MODES) { + return RV_FAIL; + } + // good mode count + return RV_OK; +} + +ReturnCode EditorConnection::receiveMode() +{ + ByteStream buf; + m_rv = receiveBuffer(buf); + if (m_rv != RV_OK) { + return m_rv; + } + // unserialize the mode into the demo mode + if (!Modes::addModeFromBuffer(buf)) { + return RV_FAIL; + } + return RV_OK; +} + +ReturnCode EditorConnection::receiveDemoMode() +{ + // create a new ByteStream that will hold the full buffer of data + ByteStream buf; + m_rv = receiveBuffer(buf); + if (m_rv != RV_OK) { + return m_rv; + } + // unserialize the mode into the demo mode + if (!m_previewMode.loadFromBuffer(buf)) { + // failure + return RV_FAIL; + } + return RV_OK; +} + +ReturnCode EditorConnection::receiveMessage(const char *message) +{ + size_t len = strlen(message); + uint8_t byte = 0; + // wait for the editor to ack the idle + if (m_receiveBuffer.size() < len) { + return RV_WAIT; + } + if (memcmp(m_receiveBuffer.data(), message, len) != 0) { + return RV_FAIL; + } + for (size_t i = 0; i < len; ++i) { + if (!m_receiveBuffer.consume8(&byte)) { + return RV_FAIL; + } + } + // we have now received at least one command, do not allow resetting + m_allowReset = false; + return RV_OK; +} + +ReturnCode EditorConnection::receiveBrightness() +{ + // create a new ByteStream that will hold the full buffer of data + ByteStream buf; + m_rv = receiveBuffer(buf); + if (m_rv != RV_OK) { + // RV_WAIT or RV_FAIL + return m_rv; + } + if (!buf.size()) { + // failure + return RV_FAIL; + } + uint8_t brightness = 255; + if (!buf.consume8(&brightness) || brightness == 0) { + // they should never send 0 brightness + return RV_FAIL; + } + if (brightness > 0) { + Leds::setBrightness(brightness); + Modes::saveHeader(); + } + return RV_OK; +} diff --git a/VortexEngine/src/Menus/MenuList/FactoryReset.cpp b/VortexEngine/src/Menus/MenuList/FactoryReset.cpp new file mode 100644 index 0000000000..5489144ed0 --- /dev/null +++ b/VortexEngine/src/Menus/MenuList/FactoryReset.cpp @@ -0,0 +1,129 @@ +#include "FactoryReset.h" + +#include "../../VortexEngine.h" +#include "../../Modes/DefaultModes.h" +#include "../../Time/TimeControl.h" +#include "../../Patterns/Pattern.h" +#include "../../Buttons/Button.h" +#include "../../Time/Timings.h" +#include "../../Modes/Modes.h" +#include "../../Modes/Mode.h" +#include "../../Leds/Leds.h" +#include "../../Log/Log.h" + +#include "../../VortexConfig.h" + +FactoryReset::FactoryReset(const RGBColor &col, bool advanced) : + Menu(col, advanced) +{ +} + +FactoryReset::~FactoryReset() +{ +} + +bool FactoryReset::init() +{ + if (!Menu::init()) { + return false; + } + // bypass led selection for fac reset if a multi was set on + // the current slot because it doesn't make sense to pick + Mode *cur = Modes::curMode(); + if (!cur) { + return false; + } + if (cur->isMultiLed()) { + m_ledSelected = true; + m_targetLeds = MAP_LED(LED_MULTI); + } + if (!m_advanced) { + // skip led selection + m_ledSelected = true; + } + // Start on exit by default + m_curSelection = false; + DEBUG_LOG("Entered factory reset"); + return true; +} + +Menu::MenuAction FactoryReset::run() +{ + MenuAction result = Menu::run(); + if (result != MENU_CONTINUE) { + return result; + } + showReset(); + return MENU_CONTINUE; +} + +void FactoryReset::onShortClick() +{ + m_curSelection = (uint8_t)!m_curSelection; +} + +void FactoryReset::onLongClick() +{ + if (m_curSelection == 0) { + // if the selection isn't actually on factory reset then just leave + leaveMenu(); + return; + } + // if the button hasn't been held long enough just return + if (g_pButton->holdDuration() <= (FACTORY_RESET_THRESHOLD_TICKS + MS_TO_TICKS(10))) { + return; + } + // the button was held down long enough so actually perform the factory reset + // restore defaults and then leave menu and save + if (m_advanced) { + // reset the target mode slot on the target led + const DefaultModeEntry &defMode = defaultModes[Modes::curModeIndex()]; + Mode *cur = Modes::curMode(); + MAP_FOREACH_LED(m_targetLeds) { + const DefaultLedEntry &led = defMode.leds[pos]; + Colorset set(led.numColors, led.cols); + cur->setPattern(led.patternID, pos, nullptr, &set); + } + cur->init(); + } else { + Leds::setBrightness(DEFAULT_BRIGHTNESS); + VortexEngine::setAutoCycle(false); + Modes::setDefaults(); + Modes::setCurMode(0); + Modes::resetFlags(); + } + leaveMenu(true); +} + +void FactoryReset::showReset() +{ + if (m_curSelection == 0) { + Leds::clearAll(); + Leds::blinkAll(350, 350, RGB_WHITE0); + return; + } + bool isPressed = g_pButton->isPressed(); + if (!isPressed) { + Leds::clearAll(); + Leds::blinkAll(50, 50, RGB_RED4); + return; + } + // don't start the fill until the button has been held for a bit + uint32_t holdDur = g_pButton->holdDuration(); + if (holdDur < MS_TO_TICKS(100)) { + return; + } + uint16_t progress = ((holdDur * 100) / FACTORY_RESET_THRESHOLD_TICKS); + DEBUG_LOGF("progress: %d", progress); + if (progress >= 100) { + Leds::setAll(RGB_WHITE); + return; + } + uint8_t offMs = 100; + uint8_t onMs = (progress > 60) ? 30 : 100; + uint8_t sat = (uint8_t)((progress * 5) >> 1); // Using bit shift for division by 2 + Leds::clearAll(); + Leds::blinkIndex(LED_0, offMs, onMs, HSVColor(0, 255 - sat, 180)); + Leds::blinkIndex(LED_1, offMs, onMs, RGB_WHITE0); +} + diff --git a/VortexEngine/src/Menus/MenuList/GlobalBrightness.cpp b/VortexEngine/src/Menus/MenuList/GlobalBrightness.cpp new file mode 100644 index 0000000000..96b186498e --- /dev/null +++ b/VortexEngine/src/Menus/MenuList/GlobalBrightness.cpp @@ -0,0 +1,212 @@ +#include "GlobalBrightness.h" + +#include "../../VortexEngine.h" + +#include "../../Patterns/PatternArgs.h" +#include "../../Patterns/Pattern.h" +#include "../../Time/TimeControl.h" +#include "../../Colors/Colorset.h" +#include "../../Buttons/Button.h" +#include "../../Time/Timings.h" +#include "../../Menus/Menus.h" +#include "../../Modes/Modes.h" +#include "../../Time/Timer.h" +#include "../../Leds/Leds.h" +#include "../../Log/Log.h" + +// allow the number of brightness options to be adjusted dynamically +#define NUM_BRIGHTNESS_OPTIONS (sizeof(m_brightnessOptions) / sizeof(m_brightnessOptions[0])) + +GlobalBrightness::GlobalBrightness(const RGBColor &col, bool advanced) : + Menu(col, advanced), + m_keychain_modeState(KEYCHAIN_MODE_STATE_OFF), + m_lastStateChange(0), + m_colorIndex(0) +{ +} + +GlobalBrightness::~GlobalBrightness() +{ + // ensure force sleep is turned back on when we leave + VortexEngine::toggleForceSleep(true); +} + +bool GlobalBrightness::init() +{ + if (!Menu::init()) { + return false; + } + // bypass led selection + m_ledSelected = true; + // would be nice if there was a more elegant way to do this + for (uint8_t i = 0; i < NUM_BRIGHTNESS_OPTIONS; ++i) { + if (m_brightnessOptions[i] == Leds::getBrightness()) { + // make sure the default selection matches cur value + m_curSelection = i; + } + } + if (m_advanced) { + // turn off force sleep while running keychain mode + VortexEngine::toggleForceSleep(false); + // enable keychain mode so if the device sleeps it will wake into keychain mode + Modes::setKeychainMode(true); + // start in the off position, this call is necessary to update the + // lastStateChange time to the current time of init + setKeychainModeState(KEYCHAIN_MODE_STATE_SOLID); + // reset the color index to ensure it starts on first color + m_colorIndex = 0; + } + DEBUG_LOG("Entered global brightness"); + return true; +} + +Menu::MenuAction GlobalBrightness::run() +{ + MenuAction result = Menu::run(); + if (result != MENU_CONTINUE) { + return result; + } + if (m_advanced) { + return runKeychainMode(); + } + // show the current brightness + showBrightnessSelection(); + // show selections + Menus::showSelection(); + // continue + return MENU_CONTINUE; +} + +void GlobalBrightness::onShortClick() +{ + if (m_advanced) { + return; + } + // include one extra option for the exit slot + m_curSelection = (m_curSelection + 1) % (NUM_BRIGHTNESS_OPTIONS + 1); +} + +void GlobalBrightness::onLongClick() +{ + if (m_advanced) { + return; + } + if (m_curSelection >= NUM_BRIGHTNESS_OPTIONS) { + // no save exit + leaveMenu(); + return; + } + // set the global brightness + Leds::setBrightness(m_brightnessOptions[m_curSelection]); + // done here, save settings with new brightness + leaveMenu(true); +} + +void GlobalBrightness::showBrightnessSelection() +{ + if (m_curSelection >= NUM_BRIGHTNESS_OPTIONS) { + showExit(); + return; + } + Leds::setAll(HSVColor(38, 255, m_brightnessOptions[m_curSelection])); +} + +// ==================== KEYCHAIN_MODE STUFF ==================== + +// The general timer for keychain mode to remain active +#define KEYCHAIN_MODE_TIMER_MS 2100 +#define KEYCHAIN_MODE_TIMER_TICKS MS_TO_TICKS(KEYCHAIN_MODE_TIMER_MS) +// the number of clicks needed to exit keychain mode +#define KEYCHAIN_MODE_EXIT_CLICKS 8 +// This is the duration of idle time on blank before it will auto-sleep +// give a solid 300 seconds (5 minutes) before it goes to sleep +#define KEYCHAIN_MODE_SLEEP_TICKS SEC_TO_TICKS(300) + +// bonus simulate keychain_mode in this menu +Menu::MenuAction GlobalBrightness::runKeychainMode() +{ + // check for exit + if (g_pButton->onConsecutivePresses(KEYCHAIN_MODE_EXIT_CLICKS)) { + // turn off keychain mode now + Modes::setKeychainMode(false); + return MENU_QUIT; + } + uint32_t now = Time::getCurtime(); + // whether the button was pressed before the timer expired + // when the button is first pressed after the window has expired switch off + if (g_pButton->onPress() && m_keychain_modeState != KEYCHAIN_MODE_STATE_OFF && now > (m_lastStateChange + KEYCHAIN_MODE_TIMER_TICKS)) { + setKeychainModeState(KEYCHAIN_MODE_STATE_OFF); + return MENU_CONTINUE; + } + // when the button is released, but only if they pressed it within this state + if (g_pButton->onRelease() && g_pButton->pressTime() > m_lastStateChange) { + // advance the state or turn off based on press time + setKeychainModeState((keychain_mode_state)(m_keychain_modeState + 1)); + return MENU_CONTINUE; + } + // as long as the button is held down the leds clear + if (g_pButton->isPressed()) { + Leds::clearAll(); + return MENU_CONTINUE; + } + // check for sleep after some seconds + if (m_keychain_modeState == KEYCHAIN_MODE_STATE_OFF && now > (m_lastStateChange + KEYCHAIN_MODE_SLEEP_TICKS)) { + // Optional: indicate when the keychain is turning off (useful mainly for debugging) + //Leds::holdAll(RGB_PURPLE); + VortexEngine::enterSleep(false); + return MENU_QUIT; + } + // render the signal mode in a different way + if (m_keychain_modeState == KEYCHAIN_MODE_STATE_SIGNAL) { + // use a custom blink because the SIGNAL_OFF duration is too large for uint8 params + Leds::clearAll(); + Leds::blinkIndexOffset(LED_ALL, + Time::getCurtime() - m_lastStateChange, + SIGNAL_OFF_DURATION, + SIGNAL_ON_DURATION, + m_previewMode.getColorset().get(m_colorIndex)); + } else { + // play the keychain_mode mode + m_previewMode.play(); + } + return MENU_CONTINUE; +} + +void GlobalBrightness::setKeychainModeState(keychain_mode_state newState) +{ + // update the keychain_mode state + m_keychain_modeState = (keychain_mode_state)(newState % KEYCHAIN_MODE_STATE_COUNT); + // record the time of this statechange + m_lastStateChange = Time::getCurtime(); + // this could + uint8_t numCols = Modes::curMode()->getColorset().numColors(); + // the args that will define the timing of the blink on the solid pattern + PatternArgs args; + switch (m_keychain_modeState) { + case KEYCHAIN_MODE_STATE_OFF: + default: + // iterate to next color and wrap to 0, don't use modulus because numCols could be 0 + m_colorIndex++; + if (m_colorIndex >= numCols) { + m_colorIndex = 0; + } + break; + case KEYCHAIN_MODE_STATE_SOLID: + args.init(200); + break; + case KEYCHAIN_MODE_STATE_DOPS: + args.init(1, 10); + break; + case KEYCHAIN_MODE_STATE_SIGNAL: + // unfortunately the signal blink timing is too large to fit into the uint8 params + // so we just use a custom blink to render that one, but use the previewMode for + // the other two states + break; + } + // update the mode and ensure current colorset is always used, use PATTERN_SOLID + // because that will never iterate to the next color and allows us to force + // the color index via argument 6 + args.arg6 = m_colorIndex; + m_previewMode.setPattern(PATTERN_SOLID, LED_ALL, &args); + m_previewMode.init(); +} diff --git a/VortexEngine/src/Menus/MenuList/ModeSharing.cpp b/VortexEngine/src/Menus/MenuList/ModeSharing.cpp new file mode 100644 index 0000000000..dfee67f5e5 --- /dev/null +++ b/VortexEngine/src/Menus/MenuList/ModeSharing.cpp @@ -0,0 +1,184 @@ +#include "ModeSharing.h" + +#include "../../VortexEngine.h" +#include "../../Serial/ByteStream.h" +#include "../../Serial/Serial.h" +#include "../../Time/TimeControl.h" +#include "../../Time/Timings.h" +#include "../../Wireless/VLReceiver.h" +#include "../../Wireless/VLSender.h" +#include "../../Patterns/Pattern.h" +#include "../../Buttons/Button.h" +#include "../../Modes/Modes.h" +#include "../../Modes/Mode.h" +#include "../../Leds/Leds.h" +#include "../../Log/Log.h" + +ModeSharing::ModeSharing(const RGBColor &col, bool advanced) : + Menu(col, advanced), + m_sharingMode(ModeShareState::SHARE_SEND_RECEIVE), + m_timeOutStartTime(0), + m_lastPercentChange(0), + m_lastPercent(0) + +{ +} + +ModeSharing::~ModeSharing() +{ + VLReceiver::endReceiving(); + VortexEngine::toggleForceSleep(true); +} + +bool ModeSharing::init() +{ + if (!Menu::init()) { + return false; + } + // skip led selection + if (!m_advanced) { + // skip led selection + m_ledSelected = true; + } + // start on receive because it's the more responsive of the two + // the odds of opening receive and then accidentally receiving + // a mode that is being broadcast nearby is completely unlikely + VLReceiver::beginReceiving(); + // turn off force sleep while using modesharing + VortexEngine::toggleForceSleep(false); + DEBUG_LOG("Entering Mode Sharing"); + return true; +} + +Menu::MenuAction ModeSharing::run() +{ + MenuAction result = Menu::run(); + if (result != MENU_CONTINUE) { + return result; + } + if (m_sharingMode == ModeShareState::SHARE_EXIT) { + showExit(); + return MENU_CONTINUE; + } + // if the button is held for at least a long click, then continuously send + if (g_pButton->isPressed() && g_pButton->holdDuration() >= CLICK_THRESHOLD) { + // exit is handled above so the only two options are normal or legacy + if (m_sharingMode == ModeShareState::SHARE_SEND_RECEIVE) { + // send normal + VLSender::send(&m_previewMode); + } else { + // send legacy + VLSender::sendLegacy(&m_previewMode); + } + } + // render the 'receive mode' lights whether legacy or not + showReceiveMode(); + receiveMode(); + return MENU_CONTINUE; +} + +void ModeSharing::onLedSelected() +{ + // if we selected leds that implies advanced mode + if (m_targetLeds == MAP_LED(LED_1)) { + // if we selected the top led then simply swap the two patterns + // so that the top led is sent first -- if the receiver is receiving + // into one slot then it will only use the first pattern to do so + m_previewMode.swapPatterns(LED_0, LED_1); + } +} + +// handlers for clicks +void ModeSharing::onShortClick() +{ + if (g_pButton->holdDuration() >= CLICK_THRESHOLD) { + return; + } + switch (m_sharingMode) { + case ModeShareState::SHARE_SEND_RECEIVE: + // stop receiving, send the mode, go back to receiving + VLReceiver::setLegacyReceiver(true); + m_sharingMode = ModeShareState::SHARE_SEND_RECEIVE_LEGACY; + break; + case ModeShareState::SHARE_SEND_RECEIVE_LEGACY: + VLReceiver::setLegacyReceiver(false); + m_sharingMode = ModeShareState::SHARE_EXIT; + break; + case ModeShareState::SHARE_EXIT: + m_sharingMode = ModeShareState::SHARE_SEND_RECEIVE; + break; + default: + break; + } + Leds::clearAll(); +} + +void ModeSharing::onLongClick() +{ + if (m_sharingMode == ModeShareState::SHARE_EXIT) { + leaveMenu(); + } +} + +void ModeSharing::receiveMode() +{ + uint32_t now = Time::getCurtime(); + // if reveiving new data set our last data time + if (VLReceiver::onNewData()) { + m_timeOutStartTime = now; + // if our last data was more than time out duration reset the recveiver + } else if (m_timeOutStartTime > 0 && (m_timeOutStartTime + MAX_TIMEOUT_DURATION) < now) { + VLReceiver::resetVLState(); + m_timeOutStartTime = 0; + return; + } + // check if the VLReceiver has a full packet available + if (!VLReceiver::dataReady()) { + uint8_t percent = VLReceiver::percentReceived(); + if (percent != m_lastPercent) { + m_lastPercent = percent; + m_lastPercentChange = now; + } + // nothing available yet + return; + } + DEBUG_LOG("Mode ready to receive! Receiving..."); + // receive the VL mode into the current mode + if (!VLReceiver::receiveMode(&m_previewMode)) { + ERROR_LOG("Failed to receive mode"); + return; + } + DEBUG_LOGF("Success receiving mode: %u", m_previewMode.getPatternID()); + if (m_advanced && m_targetLeds != MAP_LED_ALL) { + LedPos target = ledmapGetFirstLed(m_targetLeds); + LedPos other = LED_1; + // if the user picked the top led to copy into then swap the patterns + // in the incoming mode so the 0th pattern is on the top led + if (target == LED_1) { + other = LED_0; + m_previewMode.swapPatterns(LED_0, LED_1); + } + m_previewMode.copyPatternFrom(Modes::curMode(), other, other); + } + Modes::updateCurMode(&m_previewMode); + // leave menu and save settings, even if the mode was the same whatever + leaveMenu(true); +} + +void ModeSharing::showReceiveMode() +{ + // if the receiver is actively receiving right now then + if (VLReceiver::isReceiving()) { + uint32_t diff = (Time::getCurtime() - m_lastPercentChange); + // this generates the red flash when the receiver hasn't received something for + // some amount of time, 100 is just arbitray idk if it could be a better value + if (diff > 100) { + Leds::setIndex(LED_0, RGB_RED3); + } else { + Leds::setIndex(LED_0, RGBColor(0, VLReceiver::percentReceived(), 0)); + } + Leds::clearIndex(LED_1); + } else { + Leds::setAll((m_sharingMode == ModeShareState::SHARE_SEND_RECEIVE) ? 0x000F05 : RGB_WHITE0); + } +} diff --git a/VortexEngine/src/Menus/MenuList/PatternSelect.cpp b/VortexEngine/src/Menus/MenuList/PatternSelect.cpp new file mode 100644 index 0000000000..227a69f835 --- /dev/null +++ b/VortexEngine/src/Menus/MenuList/PatternSelect.cpp @@ -0,0 +1,80 @@ +#include "PatternSelect.h" + +#include "../../Patterns/PatternBuilder.h" +#include "../../Patterns/PatternArgs.h" +#include "../../Patterns/Pattern.h" +#include "../../Serial/ByteStream.h" +#include "../../Time/TimeControl.h" +#include "../../Buttons/Button.h" +#include "../../Random/Random.h" +#include "../../Time/Timings.h" +#include "../../Modes/Modes.h" +#include "../../Menus/Menus.h" +#include "../../Leds/Leds.h" +#include "../../Log/Log.h" + +PatternSelect::PatternSelect(const RGBColor &col, bool advanced) : + Menu(col, advanced), + m_srcLed(LED_FIRST), + m_started(false) +{ +} + +PatternSelect::~PatternSelect() +{ +} + +bool PatternSelect::init() +{ + if (!Menu::init()) { + return false; + } + DEBUG_LOG("Entered pattern select"); + return true; +} + +Menu::MenuAction PatternSelect::run() +{ + MenuAction result = Menu::run(); + if (result != MENU_CONTINUE) { + return result; + } + // run the current mode + m_previewMode.play(); + // show dimmer selections in advanced mode + Menus::showSelection(RGB_WHITE5); + return MENU_CONTINUE; +} + +void PatternSelect::onLedSelected() +{ + m_srcLed = ledmapGetFirstLed(m_targetLeds); +} + +void PatternSelect::onShortClick() +{ + PatternID newID = (PatternID)(m_previewMode.getPatternID(m_srcLed) + 1); + if (newID > PATTERN_SINGLE_LAST) { + newID = PATTERN_SINGLE_FIRST; + Leds::holdAll(RGB_WHITE); + } + if (!m_started) { + m_started = true; + newID = PATTERN_FIRST; + } + // set the new pattern id + if (isMultiLedPatternID(newID)) { + m_previewMode.setPattern(newID); + } else { + m_previewMode.setPatternMap(m_targetLeds, newID); + } + m_previewMode.init(); + DEBUG_LOGF("Iterated to pattern id %d", newID); +} + +void PatternSelect::onLongClick() +{ + // store the mode as current mode + Modes::updateCurMode(&m_previewMode); + leaveMenu(true); +} diff --git a/VortexEngine/src/Menus/MenuList/Randomizer.cpp b/VortexEngine/src/Menus/MenuList/Randomizer.cpp new file mode 100644 index 0000000000..ffa65bd7db --- /dev/null +++ b/VortexEngine/src/Menus/MenuList/Randomizer.cpp @@ -0,0 +1,390 @@ +#include "Randomizer.h" + +#include "../../Memory/Memory.h" + +#include "../../Patterns/PatternBuilder.h" +#include "../../Patterns/Pattern.h" +#include "../../Colors/Colorset.h" +#include "../../Random/Random.h" +#include "../../Time/Timings.h" +#include "../../Buttons/Button.h" +#include "../../Menus/Menus.h" +#include "../../Modes/Modes.h" +#include "../../Modes/Mode.h" +#include "../../Leds/Leds.h" +#include "../../Log/Log.h" + +Randomizer::Randomizer(const RGBColor &col, bool advanced) : + Menu(col, advanced), + m_lastRandomization(0), + m_flags(advanced ? RANDOMIZE_COLORSET : RANDOMIZE_BOTH), + m_displayHue(0), + m_needToSelect(advanced), + m_autoCycle(false) +{ +} + +Randomizer::~Randomizer() +{ +} + +bool Randomizer::init() +{ + if (!Menu::init()) { + return false; + } + // grab the multi ld pattern colorset crc if it's present + Mode *cur = Modes::curMode(); +#if VORTEX_SLIM == 0 + if (cur->hasMultiLed()) { + ByteStream ledData; + Pattern *pat = cur->getPattern(LED_MULTI); + if (pat) { + if (!pat->serialize(ledData)) { + return false; + } + } + m_multiRandCtx.seed(ledData.recalcCRC()); + } +#endif + // initialize the randomseed of each led with the + // CRC of the colorset on the respective LED + for (LedPos l = LED_FIRST; l < LED_COUNT; ++l) { + ByteStream ledData; + Pattern *pat = cur->getPattern(l); + if (pat) { + if (!pat->serialize(ledData)) { + return false; + } + } + m_singlesRandCtx[l].seed(ledData.recalcCRC()); + } + DEBUG_LOG("Entered randomizer"); + return true; +} + +Menu::MenuAction Randomizer::run() +{ + // run the base menu logic + MenuAction result = Menu::run(); + if (result != MENU_CONTINUE) { + return result; + } + // if the ranomization flags haven't been set yet just show a selection + if (m_needToSelect) { + // display the randomization selection menu + showRandomizationSelect(); + return MENU_CONTINUE; + } +#if VORTEX_SLIM == 0 + // if they are trying to randomize a multi-led pattern just convert + // the pattern to all singles with the same colorset upon entry + if (m_previewMode.isMultiLed() && m_targetLeds != MAP_LED(LED_MULTI)) { + // convert the pattern to a single led pattern, this will map + // all multi led patterns to single led patterns using modulo + // so no matter which multi-led pattern they have selected it + // will convert to a single led pattern of some kind + PatternID newID = (PatternID)((m_previewMode.getPatternID() - PATTERN_MULTI_FIRST) % PATTERN_SINGLE_COUNT); + // solid sucks + if (newID == PATTERN_SOLID) ++newID; + m_previewMode.setPattern(newID); + m_previewMode.init(); + } +#endif + uint32_t now = Time::getCurtime(); + if (m_autoCycle && (m_lastRandomization + AUTO_RANDOM_DELAY_TICKS < now)) { + m_lastRandomization = now; + reRoll(); + } + // display the randomized mode + m_previewMode.play(); + // show the selection + Menus::showSelection(); + // return true to continue staying in randomizer menu + return MENU_CONTINUE; +} + +void Randomizer::onShortClick() +{ + if (m_needToSelect) { + if (m_flags == RANDOMIZE_BOTH) { + m_flags = RANDOMIZE_COLORSET; + } else { + m_flags = (RandomizeFlags)(m_flags + 1); + } + return; + } + // if the user fast-clicks 3 times then toggle automode + if (m_autoCycle || g_pButton->onConsecutivePresses(AUTO_CYCLE_RANDOMIZER_CLICKS)) { + // toggle the auto cycle flag + m_autoCycle = !m_autoCycle; + // display a quick flash of either green or red to indicate whether auto mode is on or not + Leds::holdAll(m_autoCycle ? RGB_GREEN : RGB_RED); + return; + } + // shortClick re-roll the randomization + reRoll(); +} + +void Randomizer::onLongClick() +{ + // if done the randomization selection part + if (m_needToSelect) { + // we are complete the randomization selection stage + m_needToSelect = false; + return; + } + // update the current mode with the new randomized mode + Modes::updateCurMode(&m_previewMode); + // then done here, save if the mode was different + leaveMenu(true); +} + +bool Randomizer::reRoll() +{ +#if VORTEX_SLIM == 0 + if (m_targetLeds == MAP_LED(LED_MULTI)) { + if (!reRollMulti()) { + return false; + } + } else if (!reRollSingles()) { + return false; + } +#else + if (!reRollSingles()) { + return false; + } +#endif + // initialize the mode with the new pattern and colorset + m_previewMode.init(); + return true; +} + +void Randomizer::showRandomizationSelect() +{ + // show iterating rainbow if they are randomizing color, otherwise 0 sat if they + // are only randomizing the pattern + Leds::setAll(HSVColor(m_displayHue++, (m_flags & RANDOMIZE_COLORSET) * 255, 84)); + if (m_flags & RANDOMIZE_PATTERN) { + // this is blinking the light to off so the params are switched but still effectively correct + Leds::blinkAll(DOPS_ON_DURATION, DOPS_OFF_DURATION); + } + // indicate on the 2nd led whether the button is pressed + Leds::setIndex(LED_1, g_pButton->isPressed() ? RGB_OFF : RGB_WHITE1); + // render the click selection blink + Menus::showSelection(); +} + +#if VORTEX_SLIM == 0 +bool Randomizer::reRollMulti() +{ + if (m_flags & RANDOMIZE_PATTERN) { + // TODO: Roll custom multi pattern? + //if (m_advanced) { + // if (!rollCustomPattern(ctx, &m_previewMode, pos)) { + // ERROR_LOG("Failed to roll custom pattern"); + // return false; + // } + //} + if (!m_previewMode.setPattern(rollMultiLedPatternID(m_multiRandCtx), LED_MULTI)) { + ERROR_LOG("Failed to select pattern"); + return false; + } + } + if (m_flags & RANDOMIZE_COLORSET) { + if (!m_previewMode.setColorset(rollColorset(m_multiRandCtx), LED_MULTI)) { + ERROR_LOG("Failed to roll new colorset"); + return false; + } + } + return true; +} + +PatternID Randomizer::rollMultiLedPatternID(Random &ctx) +{ + return (PatternID)ctx.next8(PATTERN_MULTI_FIRST, PATTERN_MULTI_LAST); +} +#endif + +bool Randomizer::reRollSingles() +{ + // re-roll each led position with it's respective random context + MAP_FOREACH_LED(m_targetLeds) { + Random &ctx = m_singlesRandCtx[pos]; + if (m_flags & RANDOMIZE_PATTERN) { + // in advanced mode, when not randomizing the multi position, use a + // special function to randomize totally custom led pattern timings + if (m_advanced) { + if (!rollCustomPattern(ctx, &m_previewMode, pos)) { + ERROR_LOG("Failed to roll custom pattern"); + return false; + } + } else { + if (!m_previewMode.setPattern(rollSingleLedPatternID(ctx), pos)) { + ERROR_LOG("Failed to select pattern"); + return false; + } + } + } + if (m_flags & RANDOMIZE_COLORSET) { + if (!m_previewMode.setColorset(rollColorset(ctx), pos)) { + ERROR_LOG("Failed to roll new colorset"); + return false; + } + } + } + return true; +} + +PatternID Randomizer::rollSingleLedPatternID(Random &ctx) +{ + PatternID newPat; + // the random range begin/end + do { + // continuously re-randomize the pattern so we don't get undesirable patterns + newPat = (PatternID)ctx.next8(PATTERN_SINGLE_FIRST, PATTERN_SINGLE_LAST); + } while (newPat == PATTERN_SOLID || newPat == PATTERN_RIBBON || newPat == PATTERN_MINIRIBBON); + return newPat; +} + +Colorset Randomizer::rollColorset(Random &ctx) +{ + Colorset randomSet; + // pick a random type of randomizer to use then use + // the randomizer to generate a random colorset + uint8_t randType = ctx.next8(0, 8); + switch (randType) { + default: + case 0: + randomSet.randomize(ctx); + break; + case 1: + randomSet.randomizeColors(ctx, 0, Colorset::ColorMode::THEORY); + break; + case 2: + randomSet.randomizeColors(ctx, 0, Colorset::ColorMode::MONOCHROMATIC); + break; + case 3: + randomSet.randomizeColors2(ctx, Colorset::ColorMode2::DOUBLE_SPLIT_COMPLIMENTARY); + break; + case 4: + randomSet.randomizeColors2(ctx, Colorset::ColorMode2::TETRADIC); + break; + case 5: + randomSet.randomize(ctx, 1); + break; + case 6: + randomSet.randomizeColors(ctx, 0, Colorset::ColorMode::EVENLY_SPACED); + break; + case 7: + randomSet.randomizeColors(ctx, 2, Colorset::ColorMode::EVENLY_SPACED); + break; + case 8: + randomSet.randomizeColors(ctx, 3, Colorset::ColorMode::EVENLY_SPACED); + break; + } + + // take the randomized colorset and repeate colors to make an additional pattern + if (randomSet.numColors() <= 4 && ctx.next8(0, 1) != 0) { + // using uint8_t here actually costs more bytes than int16_t + int16_t startingNumColors = randomSet.numColors(); + for (int16_t i = 0; i < startingNumColors - 1; ++i) { + // add a duplicate of each color in the colorset to in reverse + randomSet.addColor(randomSet.get(startingNumColors - (i + 2))); + } + } + return randomSet; +} + +bool Randomizer::rollCustomPattern(Random &ctx, Mode *pMode, LedPos pos) +{ + PatternArgs args; + // pick a random type of randomizer to use then use + // the randomizer to generate a random pattern + uint8_t patternType = ctx.next8(0, 3); + switch (patternType) { + default: + case 0: + traditionalPattern(ctx, args); + break; + case 1: + gapPattern(ctx, args); + break; + case 2: + dashPattern(ctx, args); + break; + case 3: + crushPattern(ctx, args); + break; + } + PatternID newPat = PATTERN_STROBE; + // 1/3 chance to roll a blend pattern instead which will animate between + // colors instead of blinking each color in the set + if (!ctx.next8(0, 3)) { + newPat = PATTERN_BLEND; + // set the blend speed + args.arg6 = ctx.next8(1, 10); + // 1/2 chance to make the blend a flipping blend of some kind + if (!ctx.next8(0, 2)) { + // this is the number of blinks to a complementary color + args.arg7 = ctx.next8(0, 3); + } + // up to arg7 is filled now + args.numArgs = 7; + } + return pMode->setPattern(newPat, pos, &args); +} + +void Randomizer::traditionalPattern(Random &ctx, PatternArgs &outArgs) +{ + // call next8 explicitly in this order because the order they + // are called is undefined when called as parameters to another function. + // ex: f(a,b,c) may call in the order a,b,c or c,b,a depending on compiler. + // So different compilers may produce different results, + // but like this it is explicit + uint8_t off = ctx.next8(8, 60); // off duration 0 -> 60 + uint8_t on = ctx.next8(1, 20); // on duration 1 -> 20 + outArgs.init(on, off); +} + +void Randomizer::gapPattern(Random &ctx, PatternArgs &outArgs) +{ + // call next8 explicitly in this order because the order they + // are called is undefined when called as parameters to another function. + // ex: f(a,b,c) may call in the order a,b,c or c,b,a depending on compiler. + // So different compilers may produce different results, + // but like this it is explicit + uint8_t gap = ctx.next8(40, 100); // gap duration 40 -> 100 + uint8_t off = ctx.next8(0, 6); // off duration 0 -> 6 + uint8_t on = ctx.next8(1, 10); // on duration 1 -> 10 + outArgs.init(on, off, gap); +} + +void Randomizer::dashPattern(Random &ctx, PatternArgs &outArgs) +{ + // call next8 explicitly in this order because the order they + // are called is undefined when called as parameters to another function. + // ex: f(a,b,c) may call in the order a,b,c or c,b,a depending on compiler. + // So different compilers may produce different results, + // but like this it is explicit + uint8_t dash = ctx.next8(20, 30); // dash duration 20 -> 30 + uint8_t gap = ctx.next8(20, 30); // need gap 20 -> 30 + uint8_t off = ctx.next8(0, 10); // off duration 0 -> 10 + uint8_t on = ctx.next8(1, 10); // on duration 1 -> 10 + outArgs.init(on, off, gap, dash); +} + +void Randomizer::crushPattern(Random &ctx, PatternArgs &outArgs) +{ + // call next8 explicitly in this order because the order they + // are called is undefined when called as parameters to another function. + // ex: f(a,b,c) may call in the order a,b,c or c,b,a depending on compiler. + // So different compilers may produce different results, + // but like this it is explicit + uint8_t group = ctx.next8(0, 8); // groupsize 0 to 8 + uint8_t dash = 0; // dash 0 + uint8_t gap = ctx.next8(20, 40); // need gap 20 -> 40 + uint8_t off = ctx.next8(0, 10); // off duration 0 -> 5 + uint8_t on = ctx.next8(1, 10); // on duration 1 -> 10 + outArgs.init(on, off, gap, dash, group); +} diff --git a/VortexEngine/src/Menus/Menus.cpp b/VortexEngine/src/Menus/Menus.cpp new file mode 100644 index 0000000000..abeaffa074 --- /dev/null +++ b/VortexEngine/src/Menus/Menus.cpp @@ -0,0 +1,296 @@ +#include "Menus.h" + +// menus +#include "MenuList/GlobalBrightness.h" +#include "MenuList/EditorConnection.h" +#include "MenuList/FactoryReset.h" +#include "MenuList/ModeSharing.h" +#include "MenuList/ColorSelect.h" +#include "MenuList/PatternSelect.h" +#include "MenuList/Randomizer.h" + +#include "../Time/TimeControl.h" +#include "../Time/Timings.h" +#include "../VortexEngine.h" +#include "../Buttons/Button.h" +#include "../Serial/Serial.h" +#include "../Modes/Modes.h" +#include "../Leds/Leds.h" +#include "../Log/Log.h" + +// static members +Menus::MenuState Menus::m_menuState = MENU_STATE_NOT_OPEN; +uint8_t Menus::m_selection = 0; +uint32_t Menus::m_openTime = 0; +Menu *Menus::m_pCurMenu = nullptr; + +// typedef for the menu initialization function +typedef Menu *(*initMenuFn_t)(const RGBColor &col, bool advanced); + +// entries for the ring menu +struct MenuEntry { +#if LOGGING_LEVEL > 2 + // only store the name if the logging is enabled + const char *menuName; +#endif + initMenuFn_t initMenu; + RGBColor color; +}; + +// a template to initialize ringmenu functions +template +Menu *initMenu(const RGBColor &col, bool advanced) { return new T(col, advanced); } + +// a simple macro to simplify the entries in the menu list +#if LOGGING_LEVEL > 2 +// if the logging is enabled then we need to store the name of the menu +#define ENTRY(classname, color) { #classname, initMenu, color } +#else +#define ENTRY(classname, color) { initMenu, color } +#endif + +// The list of menus that are registered with colors to show in ring menu +const MenuEntry menuList[] = { + // ========================= + // Default menu setup: + ENTRY(Randomizer, RGB_MENU_RANDOMIZER), + ENTRY(ModeSharing, RGB_MENU_MODE_SHARING), +#if ENABLE_EDITOR_CONNECTION == 1 + ENTRY(EditorConnection, RGB_MENU_EDITOR_CONNECTION), +#endif + ENTRY(ColorSelect, RGB_MENU_COLOR_SELECT), + ENTRY(PatternSelect, RGB_MENU_PATTERN_SELECT), + ENTRY(GlobalBrightness, RGB_MENU_BRIGHTNESS_SELECT), + ENTRY(FactoryReset, RGB_MENU_FACTORY_RESET), +}; + +// the number of menus in the above array +#define NUM_MENUS (sizeof(menuList) / sizeof(menuList[0])) + +bool Menus::init() +{ + m_menuState = MENU_STATE_NOT_OPEN; + // sub-menus are initialized right before they run + return true; +} + +void Menus::cleanup() +{ + if (m_pCurMenu) { + delete m_pCurMenu; + m_pCurMenu = nullptr; + } + m_menuState = MENU_STATE_NOT_OPEN; + m_selection = 0; + m_openTime = 0; +} + +bool Menus::run() +{ + // if there are no menus, then we don't need to run + switch (m_menuState) { + case MENU_STATE_NOT_OPEN: + default: + // nothing to run + return false; + case MENU_STATE_MENU_SELECTION: + return runMenuSelection(); + case MENU_STATE_IN_MENU: + return runCurMenu(); + } +} + +bool Menus::runMenuSelection() +{ + if (g_pButton->onShortClick()) { + // otherwise increment selection and wrap around at num menus + m_selection = (m_selection + 1) % NUM_MENUS; +#if ENABLE_EDITOR_CONNECTION == 1 + // Hide the editor connection menu because it opens automatically + // TODO: Create a better way to hide this menu color, this menu + // will automatically open when the device is plugged in + if (m_selection == MENU_EDITOR_CONNECTION) { + m_selection++; + } +#endif + DEBUG_LOGF("Cyling to ring menu %u", m_selection); + // reset the open time so that it starts again + m_openTime = Time::getCurtime(); + // clear the leds + Leds::clearAll(); + return true; + } + // clear the leds so it always fills instead of replacing + Leds::clearAll(); + // timings for blink later + uint8_t offtime = 200; + uint8_t ontime = 200; + // whether advanced menus are enabled + bool advMenus = Modes::advancedMenusEnabled(); + // if the button was long pressed then select this menu, but we + // need to check the presstime to ensure we don't catch the initial + // release after opening the ringmenu + if (g_pButton->pressTime() >= m_openTime) { + // whether to open advanced menus or not + bool openAdv = (g_pButton->holdDuration() > ADV_MENU_DURATION_TICKS) && advMenus; + if (g_pButton->onLongClick()) { + // ringmenu is open so select the menu + DEBUG_LOGF("Selected ringmenu %s", menuList[m_selection].menuName); + // open the menu we have selected + if (!openMenu(m_selection, openAdv)) { + DEBUG_LOGF("Failed to initialize %s menu", menuList[m_selection].menuName); + return false; + } + // display the newly opened menu + return true; + } + // if holding down to select the menu option + if (g_pButton->isPressed() && openAdv) { + // make it strobe aw yiss + offtime = HYPERSTROBE_OFF_DURATION; + ontime = HYPERSTROBE_ON_DURATION; + } + } + // blink every even/odd of every pair + for (Pair p = PAIR_FIRST; p < PAIR_COUNT; ++p) { + if (pairEven(p) < LED_COUNT) { + Leds::blinkIndex(pairEven(p), offtime, ontime, menuList[m_selection].color); + } + if (pairOdd(p) < LED_COUNT) { + Leds::setIndex(pairOdd(p), menuList[m_selection].color); + Leds::blinkIndex(pairOdd(p), offtime, ontime, RGB_OFF); + } + } + // check if the advanced menus have been enabled + if (g_pButton->onConsecutivePresses(ADVANCED_MENU_CLICKS)) { + // toggle the advanced menu + Modes::setAdvancedMenus(!advMenus); + // display a pink or red depending on whether the menu was enabled + for (uint16_t i = 0; i < 2; ++i) { + Leds::holdAll(advMenus ? RGB_RED : RGB_PINK); + } + } + // show when the user selects a menu option + showSelection(RGBColor( + menuList[m_selection].color.red << 3, + menuList[m_selection].color.green << 3, + menuList[m_selection].color.blue << 3)); + // continue in the menu + return true; +} + +bool Menus::runCurMenu() +{ + // if the menu run handler returns false that signals the + // menu was closed by the user leaving the menu + switch (m_pCurMenu->run()) { + case Menu::MENU_QUIT: + // close the current menu when run returns false + closeCurMenu(); + // return false to let the modes play + return false; + case Menu::MENU_CONTINUE: + // if Menu continue run the click handlers for the menu + if (g_pButton->onShortClick()) { + m_pCurMenu->onShortClick(); + } + if (g_pButton->onLongClick()) { + m_pCurMenu->onLongClick(); + } + break; + case Menu::MENU_SKIP: + // dont run click handlers in this case + // so the core menu class can handle them + break; + } + // the opened menu and don't play modes + return true; +} + +// open the menu selection ring +bool Menus::openMenuSelection() +{ + if (m_menuState != MENU_STATE_NOT_OPEN) { + return false; + } + // save the time of when we open the menu so we can fill based on curtime from then + m_openTime = Time::getCurtime(); + // open the menu + m_menuState = MENU_STATE_MENU_SELECTION; + // clear the leds + Leds::clearAll(); + return true; +} + +bool Menus::openMenu(uint32_t index, bool advanced) +{ + if (index >= NUM_MENUS) { + return false; + } + m_selection = index; + Menu *newMenu = menuList[m_selection].initMenu(menuList[m_selection].color, advanced); + if (!newMenu) { + return false; + } + // initialiaze the new menu with the current mode + if (!newMenu->init()) { + DEBUG_LOGF("Failed to initialize %s menu", menuList[m_selection].menuName); + // if the menu failed to init, don't open it + delete newMenu; + return false; + } + if (m_pCurMenu) { + delete m_pCurMenu; + } + // assign the new menu + m_pCurMenu = newMenu; + // and the menus are open just in case + // clear all the leds + Leds::clearAll(); + m_menuState = MENU_STATE_IN_MENU; + return true; +} + +void Menus::showSelection(RGBColor colval) +{ + // blink the tip led white for 150ms when the short + // click threshold has been surpassed + if (g_pButton->isPressed() && + g_pButton->holdDuration() > SHORT_CLICK_THRESHOLD_TICKS && + g_pButton->holdDuration() < (SHORT_CLICK_THRESHOLD_TICKS + MS_TO_TICKS(250))) { + Leds::setAll(colval); + } +} + +bool Menus::checkOpen() +{ + return m_menuState != MENU_STATE_NOT_OPEN && g_pButton->releaseTime() > m_openTime; +} + +bool Menus::checkInMenu() +{ + return m_menuState == MENU_STATE_IN_MENU; +} + +Menu *Menus::curMenu() +{ + return m_pCurMenu; +} + +MenuEntryID Menus::curMenuID() +{ + return (MenuEntryID)m_selection; +} + +void Menus::closeCurMenu() +{ + // delete the currently open menu object + if (m_pCurMenu) { + delete m_pCurMenu; + m_pCurMenu = nullptr; + } + m_menuState = MENU_STATE_NOT_OPEN; + m_selection = 0; + // clear the leds + Leds::clearAll(); +} diff --git a/VortexEngine/tests/runtests.sh b/VortexEngine/tests/runtests.sh old mode 100644 new mode 100755