diff --git a/.github/workflows/duo_build.yml b/.github/workflows/duo_build.yml index 0a5635170c..f62e507f7e 100644 --- a/.github/workflows/duo_build.yml +++ b/.github/workflows/duo_build.yml @@ -58,19 +58,19 @@ 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 + #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: @@ -82,15 +82,15 @@ jobs: 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: diff --git a/Makefile b/Makefile index 1c279a53ef..7d10cd3d51 100644 --- a/Makefile +++ b/Makefile @@ -8,13 +8,13 @@ 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 -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..1fe8c19c7e 100644 --- a/VortexEngine/VortexLib/Makefile +++ b/VortexEngine/VortexLib/Makefile @@ -86,11 +86,11 @@ else SRC = $(shell find ../src/ -type f -name '*.cpp') VortexLib.cpp endif -# object files are source files with .c replaced with .o +# object files are source files with .cpp replaced with .o OBJS=\ $(SRC:.cpp=.o) \ -# dependency files are source files with .c replaced with .d +# dependency files are source files with .cpp replaced with .d DFILES=\ $(SRC:.cpp=.d) \ diff --git a/VortexEngine/appmain.c b/VortexEngine/appmain.c new file mode 100644 index 0000000000..1063ff10c4 --- /dev/null +++ b/VortexEngine/appmain.c @@ -0,0 +1,7 @@ + +#include "src/c_types.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..afe31aee7b --- /dev/null +++ b/VortexEngine/src/Buttons/Button.c @@ -0,0 +1,201 @@ + + +#include "../c_types.h" +#ifdef VORTEX_LIB +#include "VortexLib.h" +#endif + +#ifdef VORTEX_EMBEDDED +#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/Buttons.c b/VortexEngine/src/Buttons/Buttons.c new file mode 100644 index 0000000000..f380fc891f --- /dev/null +++ b/VortexEngine/src/Buttons/Buttons.c @@ -0,0 +1,33 @@ + + +#include "../c_types.h" +#ifdef VORTEX_LIB +#include "VortexLib.h" +#else +#endif + +Button *g_pButton = NULL; +static Button s_button; + +bool Buttons_init(uint8_t pin) +{ + g_pButton = &s_button; + Button_init(g_pButton); + return Button_initPin(g_pButton, pin); +} + +void Buttons_cleanup(void) +{ + if (g_pButton) { + Button_cleanup(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/Colors/ColorTypes.c b/VortexEngine/src/Colors/ColorTypes.c new file mode 100644 index 0000000000..622a33d10b --- /dev/null +++ b/VortexEngine/src/Colors/ColorTypes.c @@ -0,0 +1,552 @@ + + +#include "../c_types.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/Colorset.c b/VortexEngine/src/Colors/Colorset.c new file mode 100644 index 0000000000..e3f5050fee --- /dev/null +++ b/VortexEngine/src/Colors/Colorset.c @@ -0,0 +1,467 @@ +#include + +#include "../c_types.h" +#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/Leds/LedStash.c b/VortexEngine/src/Leds/LedStash.c new file mode 100644 index 0000000000..606da6b058 --- /dev/null +++ b/VortexEngine/src/Leds/LedStash.c @@ -0,0 +1,31 @@ + +#include "../c_types.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/Leds.c b/VortexEngine/src/Leds/Leds.c new file mode 100644 index 0000000000..503e15e713 --- /dev/null +++ b/VortexEngine/src/Leds/Leds.c @@ -0,0 +1,485 @@ +#include + + + + +#include "../c_types.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/Log/Log.c b/VortexEngine/src/Log/Log.c new file mode 100644 index 0000000000..21902cc21f --- /dev/null +++ b/VortexEngine/src/Log/Log.c @@ -0,0 +1,50 @@ + + +#include +#include +#include + +#include "../c_types.h" +#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/Memory/Memory.c b/VortexEngine/src/Memory/Memory.c new file mode 100644 index 0000000000..4d3cc60c05 --- /dev/null +++ b/VortexEngine/src/Memory/Memory.c @@ -0,0 +1,110 @@ + +#include +#include + + +#include "../c_types.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/Menus/Menu.c b/VortexEngine/src/Menus/Menu.c new file mode 100644 index 0000000000..18a585c0eb --- /dev/null +++ b/VortexEngine/src/Menus/Menu.c @@ -0,0 +1,147 @@ + + +#include "../c_types.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) +{ + Mode_cleanup(&self->previewMode); +} + +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)) { + Mode_cleanup(&tmpMode); + return false; + } + Mode_cleanup(&tmpMode); + } + 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/MenuList/ColorSelect.c b/VortexEngine/src/Menus/MenuList/ColorSelect.c new file mode 100644 index 0000000000..235c005ad5 --- /dev/null +++ b/VortexEngine/src/Menus/MenuList/ColorSelect.c @@ -0,0 +1,264 @@ +#include + + + +#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 }; +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 = &g_menuWorkspace.colorSelect; + 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/EditorConnection.c b/VortexEngine/src/Menus/MenuList/EditorConnection.c new file mode 100644 index 0000000000..dbd634d245 --- /dev/null +++ b/VortexEngine/src/Menus/MenuList/EditorConnection.c @@ -0,0 +1,533 @@ + + +#include +#include + +#include "../../c_types.h" +#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 = &g_menuWorkspace.editorConnection; + 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/FactoryReset.c b/VortexEngine/src/Menus/MenuList/FactoryReset.c new file mode 100644 index 0000000000..143a0ae2c5 --- /dev/null +++ b/VortexEngine/src/Menus/MenuList/FactoryReset.c @@ -0,0 +1,135 @@ +#include + + + +#include "../../c_types.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 = &g_menuWorkspace.factoryReset; + 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/GlobalBrightness.c b/VortexEngine/src/Menus/MenuList/GlobalBrightness.c new file mode 100644 index 0000000000..0f7674f340 --- /dev/null +++ b/VortexEngine/src/Menus/MenuList/GlobalBrightness.c @@ -0,0 +1,193 @@ +#include + + + +#include "../../c_types.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 = &g_menuWorkspace.globalBrightness; + 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/ModeSharing.c b/VortexEngine/src/Menus/MenuList/ModeSharing.c new file mode 100644 index 0000000000..5a04250756 --- /dev/null +++ b/VortexEngine/src/Menus/MenuList/ModeSharing.c @@ -0,0 +1,168 @@ +#include + + +#include "../../c_types.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 = &g_menuWorkspace.modeSharing; + 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/PatternSelect.c b/VortexEngine/src/Menus/MenuList/PatternSelect.c new file mode 100644 index 0000000000..e11ee67381 --- /dev/null +++ b/VortexEngine/src/Menus/MenuList/PatternSelect.c @@ -0,0 +1,84 @@ +#include + + +#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 = &g_menuWorkspace.patternSelect; + 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/Randomizer.c b/VortexEngine/src/Menus/MenuList/Randomizer.c new file mode 100644 index 0000000000..c4b91b5723 --- /dev/null +++ b/VortexEngine/src/Menus/MenuList/Randomizer.c @@ -0,0 +1,351 @@ +#include + + + +#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 +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 = &g_menuWorkspace.randomizer; + 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_initStatic(&ledData, s_randLedStore, sizeof(s_randLedStore)); + 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/Menus.c b/VortexEngine/src/Menus/Menus.c new file mode 100644 index 0000000000..7650aea668 --- /dev/null +++ b/VortexEngine/src/Menus/Menus.c @@ -0,0 +1,238 @@ + + + +#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, + 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; + 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; + } + if (!newMenu->vtable->init(newMenu)) { + DEBUG_LOGF("Failed to initialize %s menu", menuList[m_selection].menuName); + newMenu->vtable->destroy(newMenu); + return false; + } + 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/Modes/DefaultModes.c b/VortexEngine/src/Modes/DefaultModes.c new file mode 100644 index 0000000000..3949f294f7 --- /dev/null +++ b/VortexEngine/src/Modes/DefaultModes.c @@ -0,0 +1,54 @@ + + +#include "../c_types.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/Mode.c b/VortexEngine/src/Modes/Mode.c new file mode 100644 index 0000000000..a454d674c4 --- /dev/null +++ b/VortexEngine/src/Modes/Mode.c @@ -0,0 +1,1025 @@ + + +#include "../c_types.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); + PatternBuilder_release(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]); + PatternBuilder_release(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]); + PatternBuilder_release(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/Modes.c b/VortexEngine/src/Modes/Modes.c new file mode 100644 index 0000000000..bff7dec150 --- /dev/null +++ b/VortexEngine/src/Modes/Modes.c @@ -0,0 +1,827 @@ + + +#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; +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) +{ +#if MODES_TEST == 1 + Mode_test(); + Modes_test(); + return true; +#endif + Modes_resetHeaderBuf(); + if (!Storage_read(0, &s_headerBuf) || !Modes_unserializeSaveHeader(&s_headerBuf)) { + Modes_globalFlags |= MODES_FLAG_NEW_FIRMWARE; + } + s_loaded = false; +#ifdef VORTEX_LIB + Modes_globalFlags |= MODES_FLAG_ADV_MENUS; +#endif + return true; +} + +void Modes_cleanup(void) +{ + Modes_unloadLive(); +} + +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_liveValid && !Modes_loadLive()) { + 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(); + } + Mode_play(&s_liveMode); +} + +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) +{ + Modes_resetHeaderBuf(); + if (!Modes_serializeSaveHeader(&s_headerBuf)) { + return false; + } + if (!Storage_write(0, &s_headerBuf)) { + return false; + } + return true; +} + +bool Modes_loadHeader(void) +{ + Modes_resetHeaderBuf(); + if (!Storage_read(0, &s_headerBuf) || !ByteStream_size(&s_headerBuf)) { + DEBUG_LOG("Empty buffer read from storage"); + return false; + } + Modes_clearModes(); + if (!Modes_unserializeSaveHeader(&s_headerBuf)) { + return false; + } + return true; +} + +bool Modes_saveStorage(void) +{ + DEBUG_LOG("Saving modes..."); + Modes_saveHeader(); + Modes_saveCurMode(); + DEBUG_LOGF("Serialized num modes: %u", s_numModes); + return true; +} + +bool Modes_loadStorage(void) +{ + Modes_resetHeaderBuf(); + if (!Storage_read(0, &s_headerBuf) || !ByteStream_size(&s_headerBuf)) { + DEBUG_LOG("Empty buffer read from storage"); + return false; + } + Modes_clearModes(); + if (!Modes_unserializeSaveHeader(&s_headerBuf)) { + return false; + } + uint8_t numModes = 0; + if (!ByteStream_unserialize8(&s_headerBuf, &numModes)) { + return false; + } + if (!numModes) { + DEBUG_LOG("Did not find any modes"); + return false; + } + if (numModes > MAX_MODES) { + numModes = MAX_MODES; + } + s_numModes = numModes; + if (Modes_oneClickModeEnabled()) { + Modes_switchToStartupMode(); + } + 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; + } + for (uint8_t i = 0; i < s_numModes; ++i) { + Mode tmpMode; + Modes_resetModeBuf(); + if (!Storage_read(i + 1, &s_modeBuf)) { + return false; + } + 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; + } + } + 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]); + bool result = Modes_addModeMode(&defMode); + Mode_cleanup(&defMode); + if (!result) { + 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)) { + Mode_cleanup(&tmpMode); + return false; + } + Mode_initMode(&tmpMode); + bool result = Modes_addModeMode(&tmpMode); + Mode_cleanup(&tmpMode); + return result; +} + +bool Modes_addModeFromBuffer(ByteStream *serializedMode) +{ +#if MAX_MODES != 0 + if (s_numModes >= MAX_MODES) { + return false; + } +#endif + 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; +} + +bool Modes_shiftCurMode(int32_t offset) +{ + uint32_t newPos = (uint32_t)((int32_t)s_curMode + offset); + if (!s_numModes || offset == 0 || newPos >= s_numModes) { + return false; + } + 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; + } + 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; +} + +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); + 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 + 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; +} + +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; + } + Modes_unloadLive(); + return Modes_curMode() != NULL; +} + +Mode *Modes_setCurMode(uint8_t index) +{ + if (!s_numModes) { + return NULL; + } + Leds_clearAll(); + Modes_unloadLive(); + int8_t newModeIdx = index % s_numModes; + 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; + } + 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_liveValid) { + if (!Modes_loadLive()) { + ERROR_LOG("Failed to initialize current mode"); + return NULL; + } + } + return &s_liveMode; +} + +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_curMode >= s_numModes) { + return; + } + 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_curMode = 0; + Leds_clearAll(); + return; + } + if (s_curMode >= s_numModes) { + s_curMode = s_numModes - 1; + } + Modes_loadLive(); +} + +void Modes_clearModes(void) +{ + Modes_unloadLive(); + s_numModes = 0; + s_curMode = 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; + } + Modes_resetHeaderBuf(); + if (!Storage_read(0, &s_headerBuf) || !ByteStream_size(&s_headerBuf)) { + 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(&s_headerBuf); + pHeader->globalFlags = Modes_globalFlags; + ByteStream_setCRCDirty(&s_headerBuf); + return Storage_write(0, &s_headerBuf); +} + +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 + +Mode *Modes_initCurMode(bool force) +{ + (void)force; + Modes_unloadLive(); + return Modes_curMode(); +} + +bool Modes_saveCurMode(void) +{ + if (!s_liveValid) { + return true; + } + Modes_resetModeBuf(); + if (!Mode_saveToBuffer(&s_liveMode, &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_curMode + 1, &s_modeBuf)) { + return false; + } + return true; +} + +#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/Patterns/Multi/BackStrobePattern.c b/VortexEngine/src/Patterns/Multi/BackStrobePattern.c new file mode 100644 index 0000000000..9c204e99a7 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/BackStrobePattern.c @@ -0,0 +1,15 @@ + +#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) { +#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..64bb9c3ca9 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/BouncePattern.c @@ -0,0 +1,15 @@ + +#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) { +#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..37aa09e644 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/CompoundPattern.c @@ -0,0 +1,14 @@ + +#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) { +#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..b959adf211 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/CrossDopsPattern.c @@ -0,0 +1,15 @@ + +#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) { +#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..f2351c71bb --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/DoubleStrobePattern.c @@ -0,0 +1,15 @@ + +#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) { +#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..b239d85550 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/DripMorphPattern.c @@ -0,0 +1,15 @@ + +#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) { +#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..10f883f971 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/DripPattern.c @@ -0,0 +1,15 @@ + +#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) { +#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..ebad12e6b5 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/FillPattern.c @@ -0,0 +1,15 @@ + +#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) { +#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..363abec04c --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/HueShiftPattern.c @@ -0,0 +1,15 @@ + +#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) { +#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..811990705f --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/LighthousePattern.c @@ -0,0 +1,15 @@ + +#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) { +#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..e6c449b344 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/MeteorPattern.c @@ -0,0 +1,15 @@ + +#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) { +#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..21a2037ac5 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/MultiLedPattern.c @@ -0,0 +1,31 @@ + + +#include "../../c_types.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/PulsishPattern.c b/VortexEngine/src/Patterns/Multi/PulsishPattern.c new file mode 100644 index 0000000000..79a18aa7e4 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/PulsishPattern.c @@ -0,0 +1,15 @@ + +#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) { +#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..d64eeb596c --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/Sequencer/ChaserPattern.c @@ -0,0 +1,16 @@ +#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) { +#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..81257dc12b --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/Sequencer/Sequence.c @@ -0,0 +1,80 @@ +#include "../../Patterns/Multi/Sequencer/Sequence.h" + +#include + +#include "../../../c_types.h" +#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..071e02a634 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/Sequencer/SequencedPattern.c @@ -0,0 +1,14 @@ + +#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) { +#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..ef5edd3b86 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/SnowballPattern.c @@ -0,0 +1,15 @@ + +#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) { +#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..1f5675c0b4 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/SparkleTracePattern.c @@ -0,0 +1,15 @@ + +#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) { +#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..9d9d915780 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/TheaterChasePattern.c @@ -0,0 +1,15 @@ + +#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) { +#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..0966eea024 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/VortexPattern.c @@ -0,0 +1,15 @@ + +#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) { +#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..09a6094fa6 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/VortexWipePattern.c @@ -0,0 +1,15 @@ + +#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) { +#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..27671e10e7 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/WarpPattern.c @@ -0,0 +1,15 @@ + +#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) { +#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..dade05cf57 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/WarpWormPattern.c @@ -0,0 +1,15 @@ + +#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) { +#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..9854978a84 --- /dev/null +++ b/VortexEngine/src/Patterns/Multi/ZigzagPattern.c @@ -0,0 +1,15 @@ + +#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) { +#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..7d18105076 --- /dev/null +++ b/VortexEngine/src/Patterns/Pattern.c @@ -0,0 +1,230 @@ + + +#include "../c_types.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/PatternArgs.c b/VortexEngine/src/Patterns/PatternArgs.c new file mode 100644 index 0000000000..191829c9ec --- /dev/null +++ b/VortexEngine/src/Patterns/PatternArgs.c @@ -0,0 +1,168 @@ + + +#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; + 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/PatternBuilder.c b/VortexEngine/src/Patterns/PatternBuilder.c new file mode 100644 index 0000000000..5559c58726 --- /dev/null +++ b/VortexEngine/src/Patterns/PatternBuilder.c @@ -0,0 +1,390 @@ + + + + +#include "../c_types.h" +#if VORTEX_SLIM == 0 + +#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 + 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); + PatternBuilder_release(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); + PatternBuilder_release(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 *)PatternBuilder_alloc(); + 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 *)PatternBuilder_alloc(); + if (!bp) return NULL; + BlendPattern_init(bp, args); + return &bp->base.base.base; + } + case PATTERN_SOLID: { + SolidPattern *sp = (SolidPattern *)PatternBuilder_alloc(); + 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/Single/BasicPattern.c b/VortexEngine/src/Patterns/Single/BasicPattern.c new file mode 100644 index 0000000000..fecbbe273c --- /dev/null +++ b/VortexEngine/src/Patterns/Single/BasicPattern.c @@ -0,0 +1,155 @@ + + +#include "../../c_types.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/BlendPattern.c b/VortexEngine/src/Patterns/Single/BlendPattern.c new file mode 100644 index 0000000000..8596fcf19b --- /dev/null +++ b/VortexEngine/src/Patterns/Single/BlendPattern.c @@ -0,0 +1,75 @@ + + +#include "../../c_types.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/SingleLedPattern.c b/VortexEngine/src/Patterns/Single/SingleLedPattern.c new file mode 100644 index 0000000000..e05d8f3b64 --- /dev/null +++ b/VortexEngine/src/Patterns/Single/SingleLedPattern.c @@ -0,0 +1,6 @@ + +#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 new file mode 100644 index 0000000000..e2d3ce3d65 --- /dev/null +++ b/VortexEngine/src/Patterns/Single/SolidPattern.c @@ -0,0 +1,33 @@ + +#include "../../c_types.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/Random/Random.c b/VortexEngine/src/Random/Random.c new file mode 100644 index 0000000000..0e86601886 --- /dev/null +++ b/VortexEngine/src/Random/Random.c @@ -0,0 +1,28 @@ + +#include "../c_types.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/Serial/BitStream.c b/VortexEngine/src/Serial/BitStream.c new file mode 100644 index 0000000000..6b7efe3855 --- /dev/null +++ b/VortexEngine/src/Serial/BitStream.c @@ -0,0 +1,154 @@ + + +#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; + 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 (size > sizeof(s_bitStreamStatic)) { + ERROR_OUT_OF_MEMORY(); + return false; + } + BitStream_initBuf(self, (uint8_t *)s_bitStreamStatic, size); + memset(self->buf, 0, size); + return true; +} + +void BitStream_destroy(BitStream *self) +{ + (void)self; +} + +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); + } +} + +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 new file mode 100644 index 0000000000..6fb2139be0 --- /dev/null +++ b/VortexEngine/src/Serial/ByteStream.c @@ -0,0 +1,574 @@ + + +#include + + +#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) +{ + 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) +{ + 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; + } + bool wasScratch = ByteStream_usesScratch(self); + ByteStream_clear(target); + target->pData = self->pData; + target->position = self->position; + target->capacity = self->capacity; + 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) +{ + if (!rawdata || size < sizeof(RawBuffer)) { + DEBUG_LOGF("Cannot rawInit: %p %u", (const void *)rawdata, (unsigned)size); + 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); + return true; +} + +bool ByteStream_init(ByteStream *self, uint32_t size, const uint8_t *buf) +{ + ByteStream_clear(self); + uint16_t capacity; + if (size) { + 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; + } + 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); + } + return true; +} + +void ByteStream_clear(ByteStream *self) +{ + if (ByteStream_usesScratch(self)) { + s_scratchBusy = false; + } + self->pData = NULL; + self->capacity = 0; + self->position = 0; +} + +bool ByteStream_shrink(ByteStream *self) +{ + if (!self->pData) { + return false; + } + self->capacity = (uint16_t)self->pData->size; + 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); + } + ERROR_OUT_OF_MEMORY(); + return false; +} + +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/Compression.c b/VortexEngine/src/Serial/Compression.c new file mode 100644 index 0000000000..ad1ab3b989 --- /dev/null +++ b/VortexEngine/src/Serial/Compression.c @@ -0,0 +1,1466 @@ + +#include "../c_types.h" +// This is a heavily stripped down version of lz4 + + +#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..0dee2bf35e --- /dev/null +++ b/VortexEngine/src/Serial/Serial.c @@ -0,0 +1,135 @@ + + + +#include "../c_types.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/Storage/Storage.c b/VortexEngine/src/Storage/Storage.c new file mode 100644 index 0000000000..7f52ae398e --- /dev/null +++ b/VortexEngine/src/Storage/Storage.c @@ -0,0 +1,263 @@ + +#include +#include + + +#include "../c_types.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_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); + 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/Time/TimeControl.c b/VortexEngine/src/Time/TimeControl.c new file mode 100644 index 0000000000..abc8e5c00b --- /dev/null +++ b/VortexEngine/src/Time/TimeControl.c @@ -0,0 +1,328 @@ + +#include + + + + +#include "../c_types.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/Timer.c b/VortexEngine/src/Time/Timer.c new file mode 100644 index 0000000000..8f6b2105a4 --- /dev/null +++ b/VortexEngine/src/Time/Timer.c @@ -0,0 +1,216 @@ + + + +#include "../c_types.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->numAlarms = 0; + self->curAlarm = 0; + self->startTime = 0; +#ifdef VORTEX_LIB + self->simStartTime = 0; +#endif +} + +void Timer_cleanup(Timer *self) +{ + (void)self; +} + +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) +{ + if (self->numAlarms >= TIMER_4_ALARMS) { + ERROR_OUT_OF_MEMORY(); + return -1; + } + 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) +{ + 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->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->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/VortexEngine.c b/VortexEngine/src/VortexEngine.c new file mode 100644 index 0000000000..a57e9d8e70 --- /dev/null +++ b/VortexEngine/src/VortexEngine.c @@ -0,0 +1,446 @@ + + +#include "c_types.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 +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/Wireless/IRReceiver.c b/VortexEngine/src/Wireless/IRReceiver.c new file mode 100644 index 0000000000..d1b82bb89b --- /dev/null +++ b/VortexEngine/src/Wireless/IRReceiver.c @@ -0,0 +1,167 @@ + +#include "../c_types.h" +#if IR_ENABLE_RECEIVER == 1 + + +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/IRSender.c b/VortexEngine/src/Wireless/IRSender.c new file mode 100644 index 0000000000..ea001693b6 --- /dev/null +++ b/VortexEngine/src/Wireless/IRSender.c @@ -0,0 +1,175 @@ + +#include "../c_types.h" +#if IR_ENABLE_SENDER == 1 + + +#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/VLReceiver.c b/VortexEngine/src/Wireless/VLReceiver.c new file mode 100644 index 0000000000..2e857c54f8 --- /dev/null +++ b/VortexEngine/src/Wireless/VLReceiver.c @@ -0,0 +1,276 @@ + +#include "../c_types.h" +#if VL_ENABLE_RECEIVER == 1 + + +#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) +{ + 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; + } + 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; +} + +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) { + 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/VLSender.c b/VortexEngine/src/Wireless/VLSender.c new file mode 100644 index 0000000000..fc28300664 --- /dev/null +++ b/VortexEngine/src/Wireless/VLSender.c @@ -0,0 +1,130 @@ + +#include "../c_types.h" +#if VL_ENABLE_SENDER == 1 + + +#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; + +static uint8_t s_vlSenderStore[88]; + +bool VLSender_init() +{ + return true; +} + +void VLSender_cleanup() +{ +} + +bool VLSender_loadMode(const Mode *targetMode) +{ + 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; + } + 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/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 diff --git a/VortexEngine/tests/runtests.sh b/VortexEngine/tests/runtests.sh old mode 100644 new mode 100755 diff --git a/avrsize.sh b/avrsize.sh old mode 100644 new mode 100755