diff --git a/espmusicmouse/host_driver/host_driver.py b/espmusicmouse/host_driver/host_driver.py index cdb82ff..dcf1740 100644 --- a/espmusicmouse/host_driver/host_driver.py +++ b/espmusicmouse/host_driver/host_driver.py @@ -35,12 +35,15 @@ led_ring_effect_to_message_id = { EffectCircularConfig: 2, EffectRandomTwoColorInterpolationConfig: 3, EffectSwipeAndChange: 4, + EffectReverseSwipe: 5, } mouse_led_effect_to_message_id = { - EffectStaticConfig: 5, - EffectCircularConfig: 6, - EffectRandomTwoColorInterpolationConfig: 7 + EffectStaticConfig: 6, + EffectCircularConfig: 7, + EffectRandomTwoColorInterpolationConfig: 8, + EffectSwipeAndChange: 9, + EffectReverseSwipe: 10, } diff --git a/espmusicmouse/host_driver/led_cmds.py b/espmusicmouse/host_driver/led_cmds.py index 43520a1..a232218 100644 --- a/espmusicmouse/host_driver/led_cmds.py +++ b/espmusicmouse/host_driver/led_cmds.py @@ -67,7 +67,7 @@ class EffectStaticConfig: class EffectAlexaSwipeConfig: primary_color_width: float = 20 # in degrees transition_width: float = 30 # in degrees - swipe_speed: float = 3 * 360 # in degrees per second + swipe_speed: float = 2 * 360 # in degrees per second bell_curve_width_in_leds: float = 3 start_position: float = 180 # in degrees forward: bool = True @@ -124,4 +124,17 @@ class EffectSwipeAndChange: return self.swipe.as_bytes() + self.change.as_bytes() def __repr__(self) -> str: - return f"Swipe and Change: \n {str(self.swipe)}\n {str(self.change)}" \ No newline at end of file + return f"Swipe and Change: \n {str(self.swipe)}\n {str(self.change)}" + + +@dataclass +class EffectReverseSwipe: + swipeSpeed: float = 2 * 360 + bellCurveWidthInLeds: float = 3 + startPosition: float = 180 + + def as_bytes(self) -> bytes: + return struct.pack(" str: + return f"Reverse swipe, speed {self.swipeSpeed}, width in leds {self.bellCurveWidthInLeds}, start position {self.startPosition}" \ No newline at end of file diff --git a/espmusicmouse/host_driver/main.py b/espmusicmouse/host_driver/main.py index f31b7e4..7a6139a 100644 --- a/espmusicmouse/host_driver/main.py +++ b/espmusicmouse/host_driver/main.py @@ -2,10 +2,11 @@ import asyncio import serial_asyncio from led_cmds import (ColorRGBW, ColorHSV, EffectStaticConfig, EffectRandomTwoColorInterpolationConfig, EffectAlexaSwipeConfig, - EffectSwipeAndChange) + EffectSwipeAndChange, EffectReverseSwipe) from host_driver import MusicMouseProtocol, RfidTokenRead, RotaryEncoderEvent, ButtonEvent, TouchButton, TouchButtonPress, TouchButtonRelease from player import AudioPlayer from glob import glob +from copy import deepcopy import os MUSIC_FOLDER = "/home/martin/code/musicmouse/espmusicmouse/host_driver/music" @@ -75,12 +76,14 @@ def on_rfid(protocol, tagid): if tagid == bytes.fromhex("0000000000"): # Off if audio_player.is_playing(): - ring_eff = EffectAlexaSwipeConfig() - ring_eff.forward = False + ring_eff = EffectReverseSwipe() + mouse_eff = EffectReverseSwipe() + mouse_eff.startPosition = 6 / 45 * 360 else: ring_eff = EffectStaticConfig(ColorRGBW(0, 0, 0, 0)) + mouse_eff = EffectStaticConfig(ColorRGBW(0, 0, 0, 0)) protocol.led_ring_effect(ring_eff) - protocol.mouse_led_effect(EffectStaticConfig(ColorRGBW(0, 0, 0, 0))) + protocol.mouse_led_effect(mouse_eff) audio_player.pause() last_figure = current_figure @@ -96,6 +99,8 @@ def on_rfid(protocol, tagid): protocol.led_ring_effect(ring_eff) mouse_eff = EffectStaticConfig(ccfg.background) + mouse_eff = deepcopy(ring_eff) + mouse_eff.swipe.start_position = 6 / 45 * 360 protocol.mouse_led_effect(mouse_eff) if figure in playlists: @@ -127,8 +132,17 @@ def on_firmware_msg(protocol: MusicMouseProtocol, message): *mouse_leds[message.touch_button])) elif isinstance(message, TouchButtonRelease): ccfg = color_cfg[current_figure] - color = ccfg.background if audio_player.is_playing() else ColorRGBW(0, 0, 0, 0) - protocol.mouse_led_effect(EffectStaticConfig(color, *mouse_leds[message.touch_button])) + eff_change = EffectRandomTwoColorInterpolationConfig() + eff_static = EffectStaticConfig(ColorRGBW(0, 0, 0, 0), *mouse_leds[message.touch_button]) + if audio_player.is_playing(): + eff_static.color = ccfg.primary + protocol.mouse_led_effect(eff_static) + + if audio_player.is_playing(): + eff_change.color1 = ccfg.primary + eff_change.color2 = ccfg.secondary + eff_change.start_with_existing = True + protocol.mouse_led_effect(eff_change) loop = asyncio.get_event_loop() diff --git a/espmusicmouse/lib/ledtl/effects/AlexaSwipe.h b/espmusicmouse/lib/ledtl/effects/AlexaSwipe.h index f063e9e..29169f7 100644 --- a/espmusicmouse/lib/ledtl/effects/AlexaSwipe.h +++ b/espmusicmouse/lib/ledtl/effects/AlexaSwipe.h @@ -55,7 +55,7 @@ public: } } - bool finished() { return finished_; } + bool finished() const { return finished_; } int operator()() { @@ -90,7 +90,7 @@ public: private: void getParams(float x, float &interpFac, float &brightness) { - brightness = (x < bellCurveWidth_) ? bellCurveApproximation(bellCurveWidth_ / 2 - x, invBellCurveWidth_) : 1.0f; + brightness = stepFunction(x, bellCurveWidth_, invBellCurveWidth_); if (x < primaryColorWidth_) interpFac = 0.0f; else if (x > primaryColorWidth_ + transitionWidth_) diff --git a/espmusicmouse/lib/ledtl/effects/Common.h b/espmusicmouse/lib/ledtl/effects/Common.h index 9c3ec2b..a294101 100644 --- a/espmusicmouse/lib/ledtl/effects/Common.h +++ b/espmusicmouse/lib/ledtl/effects/Common.h @@ -7,6 +7,7 @@ enum class EffectId ALEXA_SWIPE, RANDOM_TWO_COLOR_INTERPOLATION, SWIPE_AND_CHANGE, // combination of ALEXA_SWIPE and RANDOM_TWO_COLOR_INTERPOLATION + REVERSE_SWIPE, }; template diff --git a/espmusicmouse/lib/ledtl/effects/ReverseSwipe.h b/espmusicmouse/lib/ledtl/effects/ReverseSwipe.h new file mode 100644 index 0000000..7c5ad3c --- /dev/null +++ b/espmusicmouse/lib/ledtl/effects/ReverseSwipe.h @@ -0,0 +1,112 @@ + +#pragma once + +#include "effects/Common.h" +#include "helpers/BellCurve.h" + +#pragma pack(push, 1) +struct EffectReverseSwipeConfig +{ + float swipeSpeed; // in degrees per second + float bellCurveWidthInLeds; + float startPosition; +}; +#pragma pack(pop) + +template +class EffectReverseSwipe +{ + +public: + static constexpr auto NUM_LEDS = numLeds(); + static constexpr int DELAY_MS = 10; + + using ConfigType = EffectReverseSwipeConfig; + + EffectReverseSwipe(const EffectReverseSwipeConfig &cfg, TLedStrip &ledStrip) + : ledStrip_(ledStrip), + currentPosition_(float(NUM_LEDS) / 2 + cfg.bellCurveWidthInLeds), + bellCurveWidth_(cfg.bellCurveWidthInLeds), + invBellCurveWidth_(1.0f / cfg.bellCurveWidthInLeds), + speed_(cfg.swipeSpeed / 360 / 1000 * NUM_LEDS * DELAY_MS), + startPosition_(cfg.startPosition / 360.0f * NUM_LEDS), + finished_(false) + { + for (int i = 0; i < NUM_LEDS; ++i) + state_[i] = getLedRGBW(ledStrip_, i); + } + + bool finished() const { return finished_; } + + int operator()() + { + if (finished_) + return 60000; + + const auto width = std::min(int(currentPosition_ + 1), int(NUM_LEDS / 2) + 1); + + { + float brightness = stepFunction(currentPosition_, bellCurveWidth_, invBellCurveWidth_); + ColorRGBW &prevC = state_[ledStrip_.normalizeIdx(startPosition_)]; + setLedRGBW(ledStrip_, startPosition_, prevC * brightness); + } + + for (int i = 1; i < width; ++i) + { + const float x = currentPosition_ - float(i); + if (x > 0.0f) + { + const int led1 = startPosition_ + i; + const int led2 = startPosition_ - i; + float brightness = stepFunction(x, bellCurveWidth_, invBellCurveWidth_); + ColorRGBW &prevC1 = state_[ledStrip_.normalizeIdx(led1)]; + ColorRGBW &prevC2 = state_[ledStrip_.normalizeIdx(led2)]; + + setLedRGBW(ledStrip_, led1, prevC1 * brightness); + setLedRGBW(ledStrip_, led2, prevC2 * brightness); + } + } + + currentPosition_ -= speed_; + if (currentPosition_ < 0) + { + finished_ = true; + clear(ledStrip_); + } + + return DELAY_MS; + } + +private: + TLedStrip &ledStrip_; + float currentPosition_; + + float bellCurveWidth_; + float invBellCurveWidth_; + float speed_; + + int startPosition_; + + bool finished_; + + ColorRGBW state_[NUM_LEDS]; +}; + +// Traits +template <> +struct EffectIdToConfig +{ + using type = EffectReverseSwipeConfig; +}; + +template <> +struct EffectConfigToId +{ + static constexpr auto id = EffectId::REVERSE_SWIPE; +}; + +template +struct EffectIdToClass +{ + using type = EffectReverseSwipe; +}; \ No newline at end of file diff --git a/espmusicmouse/lib/ledtl/helpers/BellCurve.h b/espmusicmouse/lib/ledtl/helpers/BellCurve.h index fcfbc36..f18d7e8 100644 --- a/espmusicmouse/lib/ledtl/helpers/BellCurve.h +++ b/espmusicmouse/lib/ledtl/helpers/BellCurve.h @@ -16,4 +16,20 @@ static inline float bellCurveApproximation(float x, float inverseWidth) const auto res = 1.0f + 0.27606958941084f * x3 - 0.80213917882168f * x2; return res < 0.0f ? 0.0f : res; +} + +// Function start at 0 (x=0) and goes smoothly up to 1 and arrives 1 at x=width +// inverse width has to be 1 / width (should be cached outside) +static inline float stepFunction(float x, float width, float inverseWidth) +{ + if (x < 0.0f) + return 0.0f; + if (x >= width) + return 1.0f; + + auto bellInvWidth = inverseWidth * 0.5f; + auto nx = (-x + width) * bellInvWidth * 4; + auto x2 = nx * nx; + auto x3 = x2 * nx; + return 1 + 0.2760695894 * x3 - 0.8021391 * x2; } \ No newline at end of file diff --git a/espmusicmouse/src/Messages.h b/espmusicmouse/src/Messages.h index b2aef9f..6987427 100644 --- a/espmusicmouse/src/Messages.h +++ b/espmusicmouse/src/Messages.h @@ -1,6 +1,7 @@ #include "effects/Circular.h" #include "effects/Static.h" #include "effects/AlexaSwipe.h" +#include "effects/ReverseSwipe.h" #include "effects/RandomTwoColorInterpolation.h" #include "effects/SwipeAndChange.h" @@ -101,10 +102,12 @@ enum class MessageHostToFw : uint8_t LED_WHEEL_EFFECT_CIRCULAR = 2, LED_WHEEL_EFFECT_RANDOM_TWO_COLOR_INTERPOLATION = 3, LED_WHEEL_EFFECT_SWIPE_AND_CHANGE = 4, - MOUSE_LED_EFFECT_STATIC = 5, - MOUSE_LED_EFFECT_CIRCULAR = 6, - MOUSE_LED_EFFECT_RANDOM_TWO_COLOR_INTERPOLATION = 7, - + LED_WHEEL_EFFECT_REVERSE_SWIPE = 5, + MOUSE_LED_EFFECT_STATIC = 6, + MOUSE_LED_EFFECT_CIRCULAR = 7, + MOUSE_LED_EFFECT_RANDOM_TWO_COLOR_INTERPOLATION = 8, + MOUSE_LED_EFFECT_SWIPE_AND_CHANGE = 9, + MOUSE_LED_EFFECT_REVERSE_SWIPE = 10, }; template <> @@ -201,6 +204,12 @@ inline void handleIncomingMessagesFromHost(LedTask1 *ledTaskCircle, LedTask2 *le auto cfg = reinterpret_cast(msgBuffer); ledTaskCircle->startEffect(*cfg); } + else if (msgType == MessageHostToFw::LED_WHEEL_EFFECT_REVERSE_SWIPE) + { + auto cfg = reinterpret_cast(msgBuffer); + ledTaskCircle->startEffect(*cfg); + } + // else if (msgType == MessageHostToFw::MOUSE_LED_EFFECT_STATIC) { auto cfg = reinterpret_cast(msgBuffer); @@ -216,6 +225,16 @@ inline void handleIncomingMessagesFromHost(LedTask1 *ledTaskCircle, LedTask2 *le auto cfg = reinterpret_cast(msgBuffer); ledTaskMouse->startEffect(*cfg); } + else if (msgType == MessageHostToFw::MOUSE_LED_EFFECT_SWIPE_AND_CHANGE) + { + auto cfg = reinterpret_cast(msgBuffer); + ledTaskMouse->startEffect(*cfg); + } + else if (msgType == MessageHostToFw::MOUSE_LED_EFFECT_REVERSE_SWIPE) + { + auto cfg = reinterpret_cast(msgBuffer); + ledTaskMouse->startEffect(*cfg); + } else Serial.println("Unknown message type"); } diff --git a/espmusicmouse/src/TaskLed.h b/espmusicmouse/src/TaskLed.h index b78b499..2bd1c5d 100644 --- a/espmusicmouse/src/TaskLed.h +++ b/espmusicmouse/src/TaskLed.h @@ -80,6 +80,7 @@ void _led_task_func(void *params) else if (dispatchEffectId(id, effectFunction, ledStrip, msgBuffer, effectStorage)) {} else if (dispatchEffectId(id, effectFunction, ledStrip, msgBuffer, effectStorage)) {} else if (dispatchEffectId(id, effectFunction, ledStrip, msgBuffer, effectStorage)) {} + else if (dispatchEffectId(id, effectFunction, ledStrip, msgBuffer, effectStorage)) {} // clang-format on timeoutMsForEffect = 0; diff --git a/espmusicmouse/src/host_test.cpp b/espmusicmouse/src/host_test.cpp index a6d3b28..6c45286 100644 --- a/espmusicmouse/src/host_test.cpp +++ b/espmusicmouse/src/host_test.cpp @@ -2,25 +2,105 @@ #include "containers/LedStripRGBW.h" #include "effects/AlexaSwipe.h" +#include "effects/ReverseSwipe.h" +#include "helpers/ColorConversions.h" #include #include +#include template -void printVec(const std::vector &vec) +std::ostream &operator<<(std::ostream &os, const std::vector &vec) { - std::cout << "["; + os << "["; for (const auto &e : vec) - std::cout << e << ","; - std::cout << "]\n"; + { + if (std::is_same::value) + os << int(e) << ","; + else + os << e << ","; + } + os << "]"; + return os; +} + +template +std::ostream &operator<<(std::ostream &os, const std::vector> &vec) +{ + os << "["; + for (const auto &e : vec) + os << e << ","; + os << "]"; + return os; +} + +template +void effectToFile(const std::string &filename, TEffect &effect, LedStripRGBW &strip, int calls = 100) +{ + std::vector> vr(calls); + std::vector> vg(calls); + std::vector> vb(calls); + + std::vector> vh(calls); + std::vector> vs(calls); + std::vector> vv(calls); + + for (int time = 0; time < calls; ++time) + { + effect(); + vr[time].resize(NLeds); + vg[time].resize(NLeds); + vb[time].resize(NLeds); + + vh[time].resize(NLeds); + vs[time].resize(NLeds); + vv[time].resize(NLeds); + + for (int i = 0; i < NLeds; ++i) + { + uint8_t r, g, b, w; + strip.getRGBW(i, r, g, b, w); + vr[time][i] = r; + vg[time][i] = g; + vb[time][i] = b; + + auto hsv = rgb2hsv(ColorRGBW{r, g, b, w}); + vh[time][i] = hsv.h; + vs[time][i] = hsv.s; + vv[time][i] = hsv.v; + } + } + + std::ofstream fs(filename.c_str()); + fs << "r = " << vr << "\n"; + fs << "g = " << vg << "\n"; + fs << "b = " << vb << "\n"; + fs << "h = " << vh << "\n"; + fs << "s = " << vs << "\n"; + fs << "v = " << vv << "\n"; } int main(int argc, char **argv) { - auto cfg = EffectAlexaSwipeConfig{20, 20, 90, 5, 180, ColorRGBW{255, 0, 0, 0}, ColorRGBW{0, 0, 255, 0}}; - LedStripRGBW<51> strip; + { + auto cfg = EffectAlexaSwipeConfig{20.f, 20.f, 90.f, 5.f, 180, true, ColorRGBW{255, 0, 0, 0}, ColorRGBW{255, 0, 0, 0}}; + LedStripRGBW<51> strip; - EffectAlexaSwipe effect(cfg, strip); + EffectAlexaSwipe effect(cfg, strip); + effectToFile("swipe.py", effect, strip, 200); + } + + { + auto cfg = EffectReverseSwipeConfig{360.f, 5.f, 180}; + LedStripRGBW<51> strip; + for (int i = 0; i < strip.numLeds(); ++i) + setLedRGBW(strip, i, ColorRGBW{255, 255, 255, 0}); + + EffectReverseSwipe effect(cfg, strip); + effectToFile("reverse_swipe.py", effect, strip, 200); + } + + /* effect.currentPosition_ = 150; const auto numLeds = strip.numLeds() / 2; @@ -35,6 +115,6 @@ int main(int argc, char **argv) printVec(interpolation); effect(); - + */ return 0; } \ No newline at end of file