Alexa swipe effect

This commit is contained in:
Martin Bauer 2021-11-19 17:22:09 +01:00
parent 484db9bdfa
commit aa76900244
20 changed files with 342 additions and 894 deletions

View File

@ -2,4 +2,4 @@
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
.vscode/ipch

View File

@ -1,56 +0,0 @@
#include "LedControl.h"
class LedAnimation
{
public:
/// Sets leds in the strip and returns number of milliseconds to wait until it is called again
virtual int operator()(LedStrip &leds) = 0;
};
class SweepCircularAnimation : public LedAnimation
{
public:
SweepCircularAnimation(const ColorRGB &color, int delayMs = 100, int numLedsHalfWidth = 3, float brightnessFallOff = 0.8)
: color_(color), delayMs_(delayMs), numLedsHalfWidth_(numLedsHalfWidth), brightnessFallOff_(brightnessFallOff), currentCenter_(0)
{
}
int operator()(LedStrip &leds) override
{
for (int i = 0; i < leds.numLeds(); ++i)
leds.setColor(i, 0, 0, 0, 0);
leds.setColor(currentCenter_, color_);
ColorRGB currColor = color_;
for (int i = 1; i <= numLedsHalfWidth_; ++i)
{
currColor.r = uint8_t(float(currColor.r) * brightnessFallOff_);
currColor.g = uint8_t(float(currColor.g) * brightnessFallOff_);
currColor.b = uint8_t(float(currColor.b) * brightnessFallOff_);
leds.setColor(leds.normalizeLedIdx(currentCenter_ - i), currColor);
leds.setColor(leds.normalizeLedIdx(currentCenter_ + i), currColor);
}
currentCenter_ = leds.normalizeLedIdx(currentCenter_ + 1);
return delayMs_;
}
private:
// parameters
ColorRGB color_;
int delayMs_;
int numLedsHalfWidth_; // number of leds on to the left and right of center
float brightnessFallOff_;
// state
int currentCenter_;
};
// strategy:
// use queue to send animation pointers over
// task calls animate function and waits for new even in queue with timeout of next due call
//
void setAnimation(LedAnimation *animation)
{
}

View File

@ -1,146 +0,0 @@
#include "LedControl.h"
#include "driver/rmt.h"
#include "esp32_digital_led_lib.h"
#include "Arduino.h"
#define HSV_SECTION_6 (0x20)
#define HSV_SECTION_3 (0x40)
static ColorRGB hsv2rgb(const ColorHSV &hsv)
{
// Convert hue, saturation and brightness ( HSV/HSB ) to RGB
// "Dimming" is used on saturation and brightness to make
// the output more visually linear.
// Apply dimming curves
uint8_t value = hsv.v;
uint8_t saturation = hsv.s;
// The brightness floor is minimum number that all of
// R, G, and B will be set to.
uint8_t invsat = 255 - saturation;
uint8_t brightness_floor = (value * invsat) / 256;
// The color amplitude is the maximum amount of R, G, and B
// that will be added on top of the brightness_floor to
// create the specific hue desired.
uint8_t color_amplitude = value - brightness_floor;
// Figure out which section of the hue wheel we're in,
// and how far offset we are withing that section
uint8_t section = hsv.h / HSV_SECTION_3; // 0..2
uint8_t offset = hsv.h % HSV_SECTION_3; // 0..63
uint8_t rampup = offset; // 0..63
uint8_t rampdown = (HSV_SECTION_3 - 1) - offset; // 63..0
// We now scale rampup and rampdown to a 0-255 range -- at least
// in theory, but here's where architecture-specific decsions
// come in to play:
// To scale them up to 0-255, we'd want to multiply by 4.
// But in the very next step, we multiply the ramps by other
// values and then divide the resulting product by 256.
// So which is faster?
// ((ramp * 4) * othervalue) / 256
// or
// ((ramp ) * othervalue) / 64
// It depends on your processor architecture.
// On 8-bit AVR, the "/ 256" is just a one-cycle register move,
// but the "/ 64" might be a multicycle shift process. So on AVR
// it's faster do multiply the ramp values by four, and then
// divide by 256.
// On ARM, the "/ 256" and "/ 64" are one cycle each, so it's
// faster to NOT multiply the ramp values by four, and just to
// divide the resulting product by 64 (instead of 256).
// Moral of the story: trust your profiler, not your insticts.
// Since there's an AVR assembly version elsewhere, we'll
// assume what we're on an architecture where any number of
// bit shifts has roughly the same cost, and we'll remove the
// redundant math at the source level:
// // scale up to 255 range
// //rampup *= 4; // 0..252
// //rampdown *= 4; // 0..252
// compute color-amplitude-scaled-down versions of rampup and rampdown
uint8_t rampup_amp_adj = (rampup * color_amplitude) / (256 / 4);
uint8_t rampdown_amp_adj = (rampdown * color_amplitude) / (256 / 4);
// add brightness_floor offset to everything
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)
return ColorRGB{brightness_floor, rampdown_adj_with_floor, rampup_adj_with_floor};
else
return ColorRGB{rampup_adj_with_floor, brightness_floor, rampdown_adj_with_floor};
}
else
return ColorRGB{rampdown_adj_with_floor, rampup_adj_with_floor, brightness_floor};
}
LedStrip::LedStrip(int numLeds, int pin)
: numLeds_(numLeds), pin_(pin)
{
cfg = {.rmtChannel = 0, .gpioNum = pin, .ledType = LED_SK6812W_V1, .brightLimit = 24, .numPixels = numLeds};
strands[0] = &cfg;
}
void LedStrip::begin()
{
digitalLeds_initDriver();
gpioSetup(cfg.gpioNum, OUTPUT, LOW);
int rc = digitalLeds_addStrands(strands, 1);
if (rc)
Serial.println("LEDs: Error during init");
}
void LedStrip::clear()
{
digitalLeds_resetPixels(strands, 1);
}
int LedStrip::normalizeLedIdx(int i)
{
while (i < 0)
i += numLeds_;
while (i >= numLeds_)
i -= numLeds_;
return i;
}
void LedStrip::setColor(int led, int r, int g, int b, int w)
{
strands[0]->pixels[led] = pixelFromRGBW(r, g, b, w);
}
void LedStrip::transmit()
{
digitalLeds_drawPixels(strands, 1);
}
void LedStrip::setAll(int r, int g, int b, int w)
{
for (int i = 0; i < numLeds_; ++i)
setColor(i, r, g, b, w);
}
void LedStrip::setRange(int begin, int end, int r, int g, int b, int w)
{
for (int i = begin; i < min(end, numLeds_); ++i)
setColor(i, r, g, b, w);
}
void LedStrip::setColor(int led, const ColorRGB &color)
{
setColor(led, color.r, color.g, color.b, 0);
}
void LedStrip::setColor(int led, const ColorHSV &color)
{
setColor(led, hsv2rgb(color));
}

View File

@ -1,37 +0,0 @@
#pragma once
#include "esp32_digital_led_lib.h"
struct ColorRGB
{
uint8_t r, g, b;
};
struct ColorHSV
{
uint8_t h, s, v;
};
class LedStrip
{
public:
LedStrip(int numLeds, int pin);
void begin();
void setColor(int led, int r, int g, int b, int w);
void setColor(int led, const ColorRGB &color);
void setColor(int led, const ColorHSV &color);
void transmit();
void clear();
void setAll(int r, int g, int b, int w);
void setRange(int begin, int end, int r, int g, int b, int w);
int numLeds() const { return numLeds_; }
int normalizeLedIdx(int i);
private:
strand_t cfg;
strand_t *strands[1];
int numLeds_;
int pin_;
};

View File

@ -1,472 +0,0 @@
/*
* Library for driving digital RGB(W) LEDs using the ESP32's RMT peripheral
*
* Modifications Copyright (c) 2017-2019 Martin F. Falatic
*
* Portions modified using FastLED's ClocklessController as a reference
* Copyright (c) 2018 Samuel Z. Guyer
* Copyright (c) 2017 Thomas Basler
*
* Based on public domain code created 19 Nov 2016 by Chris Osborn <fozztexx@fozztexx.com>
* http://insentricity.com
*
*/
/*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "esp32_digital_led_lib.h"
#ifdef __cplusplus
extern "C" {
#endif
#if defined(ARDUINO)
#include "esp32-hal.h"
#include "esp_intr.h"
#include "driver/gpio.h"
#include "driver/rmt.h"
#include "driver/periph_ctrl.h"
#include "freertos/semphr.h"
#include "soc/rmt_struct.h"
#elif defined(ESP_PLATFORM)
#include <esp_intr.h>
#include <driver/gpio.h>
#include <driver/rmt.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <soc/dport_reg.h>
#include <soc/gpio_sig_map.h>
#include <soc/rmt_struct.h>
#include <stdio.h>
#include <string.h> // memset, memcpy, etc. live here!
#endif
#ifdef __cplusplus
}
#endif
#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))
#if DEBUG_ESP32_DIGITAL_LED_LIB
extern char * digitalLeds_debugBuffer;
extern int digitalLeds_debugBufferSz;
#endif
static DRAM_ATTR const uint16_t MAX_PULSES = 32; // A channel has a 64 "pulse" buffer - we use half per pass
static DRAM_ATTR const uint16_t DIVIDER = 4; // 8 still seems to work, but timings become marginal
static DRAM_ATTR const double RMT_DURATION_NS = 12.5; // Minimum time of a single RMT duration based on clock ns
// Considering the RMT_INT_RAW_REG (raw int status) and RMT_INT_ST_REG (masked int status) registers (each 32-bit):
// Where op = {raw, st, ena, clr} and n = {0..7}
// Every three bits = RMT.int_<op>.ch<n>_tx_end, RMT.int_<op>.ch<n>_rx_end, RMT.int_<op>.ch<n>_err
// The final 8 bits are RMT.int_<op>.ch<n>_tx_thr_event
// LUT for mapping bits in RMT.int_<op>.ch<n>_tx_thr_event
static DRAM_ATTR const uint32_t tx_thr_event_offsets [] = {
static_cast<uint32_t>(1) << (24 + 0),
static_cast<uint32_t>(1) << (24 + 1),
static_cast<uint32_t>(1) << (24 + 2),
static_cast<uint32_t>(1) << (24 + 3),
static_cast<uint32_t>(1) << (24 + 4),
static_cast<uint32_t>(1) << (24 + 5),
static_cast<uint32_t>(1) << (24 + 6),
static_cast<uint32_t>(1) << (24 + 7),
};
// LUT for mapping bits in RMT.int_<op>.ch<n>_tx_end
static DRAM_ATTR const uint32_t tx_end_offsets [] = {
static_cast<uint32_t>(1) << (0 + 0) * 3,
static_cast<uint32_t>(1) << (0 + 1) * 3,
static_cast<uint32_t>(1) << (0 + 2) * 3,
static_cast<uint32_t>(1) << (0 + 3) * 3,
static_cast<uint32_t>(1) << (0 + 4) * 3,
static_cast<uint32_t>(1) << (0 + 5) * 3,
static_cast<uint32_t>(1) << (0 + 6) * 3,
static_cast<uint32_t>(1) << (0 + 7) * 3,
};
typedef union {
struct {
uint32_t duration0:15;
uint32_t level0:1;
uint32_t duration1:15;
uint32_t level1:1;
};
uint32_t val;
} rmtPulsePair;
typedef struct {
uint8_t * buf_data;
uint16_t buf_pos, buf_len, buf_half, buf_isDirty;
rmtPulsePair pulsePairMap[2];
bool isProcessing;
} digitalLeds_stateData;
double randDouble()
{
return double(esp_random()>>16) / (UINT16_MAX + 1);
}
pixelColor_t adjustByUniformFactor(pixelColor_t * color, double adjFactor) {
color->r = uint8_t(color->r * (1.0 - adjFactor));
color->g = uint8_t(color->g * (1.0 - adjFactor));
color->b = uint8_t(color->b * (1.0 - adjFactor));
color->w = uint8_t(color->w * (1.0 - adjFactor));
return *color;
}
const static int MAX_RMT_CHANNELS = 8;
static strand_t * strandDataPtrs[MAX_RMT_CHANNELS] = {nullptr}; // Indexed by RMT channel
// Forward declarations of local functions
static void copyHalfBlockToRmt(strand_t * pStrand);
static void rmtInterruptHandler(void *arg);
static xSemaphoreHandle gRmtSem = nullptr;
static intr_handle_t gRmtIntrHandle = nullptr;
static int gToProcess = 0;
#if defined(ARDUINO) && ARDUINO >= 100
void espPinMode(int pinNum, int pinDir) {
// Enable GPIO32 or 33 as output. Device-dependent
// (only works if these aren't used for external XTAL).
// https://esp32.com/viewtopic.php?t=9151#p38282
if (pinNum == 32 || pinNum == 33) {
uint64_t gpioBitMask = (pinNum == 32) ? 1ULL<<GPIO_NUM_32 : 1ULL<<GPIO_NUM_33;
gpio_mode_t gpioMode = (pinDir == OUTPUT) ? GPIO_MODE_OUTPUT : GPIO_MODE_INPUT;
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = gpioMode;
io_conf.pin_bit_mask = gpioBitMask;
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
gpio_config(&io_conf);
}
else {
pinMode(pinNum, pinDir);
}
}
#endif
void gpioSetup(int gpioNum, int gpioMode, int gpioVal) {
#if defined(ARDUINO) && ARDUINO >= 100
espPinMode(gpioNum, gpioMode);
digitalWrite (gpioNum, gpioVal);
#elif defined(ESP_PLATFORM)
gpio_num_t gpioNumNative = static_cast<gpio_num_t>(gpioNum);
gpio_mode_t gpioModeNative = static_cast<gpio_mode_t>(gpioMode);
gpio_pad_select_gpio(gpioNumNative);
gpio_set_direction(gpioNumNative, gpioModeNative);
gpio_set_level(gpioNumNative, gpioVal);
#endif
}
int digitalLeds_initDriver()
{
#if DEBUG_ESP32_DIGITAL_LED_LIB
snprintf(digitalLeds_debugBuffer, digitalLeds_debugBufferSz, "digitalLeds_initDriver\n");
#endif
esp_err_t rc = ESP_OK;
if (gRmtIntrHandle == nullptr) { // Only on first run
// Sem is created here
gRmtSem = xSemaphoreCreateBinary();
xSemaphoreGive(gRmtSem);
rc = esp_intr_alloc(ETS_RMT_INTR_SOURCE, 0, rmtInterruptHandler, nullptr, &gRmtIntrHandle);
}
return rc;
}
int digitalLeds_addStrands(strand_t * strands [], int numStrands)
{
for (int i = 0; i < numStrands; i++) {
int rmtChannel = strands[i]->rmtChannel;
strand_t * pStrand = strands[i];
strandDataPtrs[rmtChannel] = pStrand;
ledParams_t ledParams = ledParamsAll[pStrand->ledType];
pStrand->pixels = static_cast<pixelColor_t*>(malloc(pStrand->numPixels * sizeof(pixelColor_t)));
if (pStrand->pixels == nullptr) {
return -1;
}
pStrand->_stateVars = static_cast<digitalLeds_stateData*>(malloc(sizeof(digitalLeds_stateData)));
if (pStrand->_stateVars == nullptr) {
return -2;
}
digitalLeds_stateData * pState = static_cast<digitalLeds_stateData*>(pStrand->_stateVars);
pState->buf_len = (pStrand->numPixels * ledParams.bytesPerPixel);
pState->buf_data = static_cast<uint8_t*>(malloc(pState->buf_len));
if (pState->buf_data == nullptr) {
return -3;
}
// RMT configuration for transmission
rmt_config_t rmt_tx;
rmt_tx.channel = static_cast<rmt_channel_t>(rmtChannel);
rmt_tx.gpio_num = static_cast<gpio_num_t>(pStrand->gpioNum);
rmt_tx.rmt_mode = RMT_MODE_TX;
rmt_tx.mem_block_num = 1;
rmt_tx.clk_div = DIVIDER;
rmt_tx.tx_config.loop_en = false;
rmt_tx.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW;
rmt_tx.tx_config.carrier_en = false;
rmt_tx.tx_config.idle_level = RMT_IDLE_LEVEL_LOW;
rmt_tx.tx_config.idle_output_en = true;
rmt_config(&rmt_tx);
// RMT config for transmitting a '0' bit val to this LED strand
pState->pulsePairMap[0].level0 = 1;
pState->pulsePairMap[0].level1 = 0;
pState->pulsePairMap[0].duration0 = ledParams.T0H / (RMT_DURATION_NS * DIVIDER);
pState->pulsePairMap[0].duration1 = ledParams.T0L / (RMT_DURATION_NS * DIVIDER);
// RMT config for transmitting a '0' bit val to this LED strand
pState->pulsePairMap[1].level0 = 1;
pState->pulsePairMap[1].level1 = 0;
pState->pulsePairMap[1].duration0 = ledParams.T1H / (RMT_DURATION_NS * DIVIDER);
pState->pulsePairMap[1].duration1 = ledParams.T1L / (RMT_DURATION_NS * DIVIDER);
pState->isProcessing = false;
// Set interrupts
rmt_set_tx_thr_intr_en(static_cast<rmt_channel_t>(rmtChannel), true, MAX_PULSES); // sets rmt_set_tx_wrap_en and RMT.tx_lim_ch<n>.limit
}
digitalLeds_resetPixels(strands, numStrands);
return 0;
}
int digitalLeds_removeStrands(strand_t * strands [], int numStrands)
{
digitalLeds_resetPixels(strands, numStrands);
for (int i = 0; i < numStrands; i++) {
int rmtChannel = strands[i]->rmtChannel;
strand_t * pStrand = strandDataPtrs[rmtChannel];
if (pStrand) {
strandDataPtrs[rmtChannel] = nullptr;
}
}
return 0;
}
int digitalLeds_resetPixels(strand_t * strands [], int numStrands)
{
// TODO: The input is strands for convenience - the point is to get indicies of strands to draw
// Could just pass the channel numbers, but would it be slower to construct that list?
for (int i = 0; i < numStrands; i++) {
int rmtChannel = strands[i]->rmtChannel;
strand_t * pStrand = strandDataPtrs[rmtChannel];
memset(pStrand->pixels, 0, pStrand->numPixels * sizeof(pixelColor_t));
}
digitalLeds_drawPixels(strands, numStrands);
return 0;
}
int IRAM_ATTR digitalLeds_drawPixels(strand_t * strands [], int numStrands)
{
// TODO: The input is strands for convenience - the point is to get indicies of strands to draw
// Could just pass the channel numbers, but would it be slower to construct that list?
if (numStrands == 0) {
return 0;
}
gToProcess = numStrands;
xSemaphoreTake(gRmtSem, portMAX_DELAY);
for (int i = 0; i < numStrands; i++) {
int rmtChannel = strands[i]->rmtChannel;
strand_t * pStrand = strandDataPtrs[rmtChannel];
digitalLeds_stateData * pState = static_cast<digitalLeds_stateData*>(pStrand->_stateVars);
ledParams_t ledParams = ledParamsAll[pStrand->ledType];
pState->isProcessing = true;
// Pack pixels into transmission buffer
if (ledParams.bytesPerPixel == 3) {
for (uint16_t i = 0; i < pStrand->numPixels; i++) {
// Color order is translated from RGB to GRB
pState->buf_data[0 + i * 3] = pStrand->pixels[i].g;
pState->buf_data[1 + i * 3] = pStrand->pixels[i].r;
pState->buf_data[2 + i * 3] = pStrand->pixels[i].b;
}
}
else if (ledParams.bytesPerPixel == 4) {
for (uint16_t i = 0; i < pStrand->numPixels; i++) {
// Color order is translated from RGBW to GRBW
pState->buf_data[0 + i * 4] = pStrand->pixels[i].g;
pState->buf_data[1 + i * 4] = pStrand->pixels[i].r;
pState->buf_data[2 + i * 4] = pStrand->pixels[i].b;
pState->buf_data[3 + i * 4] = pStrand->pixels[i].w;
}
}
else {
return -1;
}
pState->buf_pos = 0;
pState->buf_half = 0;
rmt_set_tx_intr_en(static_cast<rmt_channel_t>(rmtChannel), true);
copyHalfBlockToRmt(pStrand);
if (pState->buf_pos < pState->buf_len) { // Fill the other half of the buffer block
copyHalfBlockToRmt(pStrand);
}
// Starts RMT, which will end up giving us the semaphore back
// Immediately starts transmitting
rmt_set_tx_intr_en(static_cast<rmt_channel_t>(rmtChannel), true);
rmt_tx_start(static_cast<rmt_channel_t>(rmtChannel), true);
}
// Give back semaphore after drawing is done
xSemaphoreTake(gRmtSem, portMAX_DELAY);
xSemaphoreGive(gRmtSem);
return 0;
}
static IRAM_ATTR void copyHalfBlockToRmt(strand_t * pStrand)
{
// This fills half an RMT block
// When wraparound is happening, we want to keep the inactive half of the RMT block filled
digitalLeds_stateData * pState = static_cast<digitalLeds_stateData*>(pStrand->_stateVars);
ledParams_t ledParams = ledParamsAll[pStrand->ledType];
uint16_t i, j, offset, len, byteval;
offset = pState->buf_half * MAX_PULSES;
pState->buf_half = !pState->buf_half;
len = pState->buf_len - pState->buf_pos;
if (len > (MAX_PULSES / 8))
len = (MAX_PULSES / 8);
if (!len) {
if (!pState->buf_isDirty) {
return;
}
// Clear the channel's data block and return
for (i = 0; i < MAX_PULSES; i++) {
RMTMEM.chan[pStrand->rmtChannel].data32[i + offset].val = 0;
}
pState->buf_isDirty = 0;
return;
}
pState->buf_isDirty = 1;
for (i = 0; i < len; i++) {
byteval = pState->buf_data[i + pState->buf_pos];
// Shift bits out, MSB first, setting RMTMEM.chan[n].data32[x] to
// the rmtPulsePair value corresponding to the buffered bit value
for (j = 0; j < 8; j++, byteval <<= 1) {
int bitval = (byteval >> 7) & 0x01;
int data32_idx = i * 8 + offset + j;
RMTMEM.chan[pStrand->rmtChannel].data32[data32_idx].val = pState->pulsePairMap[bitval].val;
}
// Handle the reset bit by stretching duration1 for the final bit in the stream
if (i + pState->buf_pos == pState->buf_len - 1) {
RMTMEM.chan[pStrand->rmtChannel].data32[i * 8 + offset + 7].duration1 =
ledParams.TRS / (RMT_DURATION_NS * DIVIDER);
}
}
// Clear the remainder of the channel's data not set above
for (i *= 8; i < MAX_PULSES; i++) {
RMTMEM.chan[pStrand->rmtChannel].data32[i + offset].val = 0;
}
pState->buf_pos += len;
return;
}
static IRAM_ATTR void rmtInterruptHandler(void *arg)
{
portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
for (int rmtChannel = 0; rmtChannel < MAX_RMT_CHANNELS; rmtChannel++) {
strand_t * pStrand = strandDataPtrs[rmtChannel];
if (pStrand == nullptr) {
continue;
}
digitalLeds_stateData * pState = static_cast<digitalLeds_stateData*>(pStrand->_stateVars);
if (!pState->isProcessing) {
continue;
}
if (RMT.int_st.val & tx_thr_event_offsets[rmtChannel]) {
// We got an RMT.int_st.ch<n>_tx_thr_event interrupt because RMT.tx_lim_ch<n>.limit was crossed
RMT.int_clr.val |= tx_thr_event_offsets[rmtChannel]; // set RMT.int_clr.ch<n>_tx_thr_event (reset interrupt bit)
copyHalfBlockToRmt(pStrand);
}
else if (RMT.int_st.val & tx_end_offsets[rmtChannel]) {
// We got an RMT.int_st.ch<n>_tx_end interrupt with a zero-length entry which means we're done
RMT.int_clr.val |= tx_end_offsets[rmtChannel]; // set RMT.int_clr.ch<n>_tx_end (reset interrupt bit)
//gpio_matrix_out(static_cast<gpio_num_t>(pStrand->gpioNum), 0x100, 0, 0); // only useful if rmt_config keeps getting called
pState->isProcessing = false;
gToProcess--;
if (gToProcess == 0) {
xSemaphoreGiveFromISR(gRmtSem, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken == pdTRUE) { // Perform cleanup if we're all done
portYIELD_FROM_ISR();
}
}
}
}
return;
}
//**************************************************************************//

View File

@ -1,155 +0,0 @@
/*
* Library for driving digital RGB(W) LEDs using the ESP32's RMT peripheral
*
* Modifications Copyright (c) 2017-2019 Martin F. Falatic
*
* Portions modified using FastLED's ClocklessController as a reference
* Copyright (c) 2018 Samuel Z. Guyer
* Copyright (c) 2017 Thomas Basler
*
* Based on public domain code created 19 Nov 2016 by Chris Osborn <fozztexx@fozztexx.com>
* http://insentricity.com
*
*/
/*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef ESP32_DIGITAL_LED_LIB_H
#define ESP32_DIGITAL_LED_LIB_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#define DEBUG_ESP32_DIGITAL_LED_LIB 0
typedef union {
struct __attribute__ ((packed)) {
uint8_t b, g, r, w; // Little-endian ordered
};
uint32_t raw32;
} pixelColor_t;
inline pixelColor_t pixelFromRGB(uint8_t r, uint8_t g, uint8_t b)
{
pixelColor_t v;
v.r = r;
v.g = g;
v.b = b;
v.w = 0;
return v;
}
inline pixelColor_t pixelFromRGBhex(uint8_t r, uint8_t g, uint8_t b)
{
pixelColor_t v;
v.r = r;
v.g = g;
v.b = b;
v.w = 0;
return v;
}
inline pixelColor_t pixelFromRGBW(uint8_t r, uint8_t g, uint8_t b, uint8_t w)
{
pixelColor_t v;
v.r = r;
v.g = g;
v.b = b;
v.w = w;
return v;
}
inline pixelColor_t pixelFromRGBWhex(uint8_t r, uint8_t g, uint8_t b, uint8_t w)
{
// The value is of the form 0xWWRRGGBB
pixelColor_t v;
v.r = r;
v.g = g;
v.b = b;
v.w = w;
return v;
}
typedef struct {
int rmtChannel;
int gpioNum;
int ledType;
int brightLimit;
int numPixels;
pixelColor_t * pixels;
void * _stateVars;
} strand_t;
typedef struct {
int bytesPerPixel;
uint32_t T0H;
uint32_t T1H;
uint32_t T0L;
uint32_t T1L;
uint32_t TRS;
} ledParams_t;
enum led_types {
LED_WS2812_V1,
LED_WS2812B_V1,
LED_WS2812B_V2,
LED_WS2812B_V3,
LED_WS2813_V1,
LED_WS2813_V2,
LED_WS2813_V3,
LED_SK6812_V1,
LED_SK6812W_V1,
};
const ledParams_t ledParamsAll[] = { // Still must match order of `led_types`
[LED_WS2812_V1] = { .bytesPerPixel = 3, .T0H = 350, .T1H = 700, .T0L = 800, .T1L = 600, .TRS = 50000}, // Various
[LED_WS2812B_V1] = { .bytesPerPixel = 3, .T0H = 350, .T1H = 900, .T0L = 900, .T1L = 350, .TRS = 50000}, // Older datasheet
[LED_WS2812B_V2] = { .bytesPerPixel = 3, .T0H = 400, .T1H = 850, .T0L = 850, .T1L = 400, .TRS = 50000}, // 2016 datasheet
[LED_WS2812B_V3] = { .bytesPerPixel = 3, .T0H = 450, .T1H = 850, .T0L = 850, .T1L = 450, .TRS = 50000}, // cplcpu test
[LED_WS2813_V1] = { .bytesPerPixel = 3, .T0H = 350, .T1H = 800, .T0L = 350, .T1L = 350, .TRS = 300000}, // Older datasheet
[LED_WS2813_V2] = { .bytesPerPixel = 3, .T0H = 270, .T1H = 800, .T0L = 800, .T1L = 270, .TRS = 300000}, // 2016 datasheet
[LED_WS2813_V3] = { .bytesPerPixel = 3, .T0H = 270, .T1H = 630, .T0L = 630, .T1L = 270, .TRS = 300000}, // 2017-05 WS datasheet
[LED_SK6812_V1] = { .bytesPerPixel = 3, .T0H = 300, .T1H = 600, .T0L = 900, .T1L = 600, .TRS = 80000}, // Various, all consistent
[LED_SK6812W_V1] = { .bytesPerPixel = 4, .T0H = 300, .T1H = 600, .T0L = 900, .T1L = 600, .TRS = 80000}, // Various, all consistent
};
extern void espPinMode(int pinNum, int pinDir);
extern void gpioSetup(int gpioNum, int gpioMode, int gpioVal);
extern double randDouble();
extern pixelColor_t adjustByUniformFactor(pixelColor_t * color, double adjFactor);
extern int digitalLeds_initDriver();
extern int digitalLeds_addStrands(strand_t * strands [], int numStrands);
extern int digitalLeds_removeStrands(strand_t * strands [], int numStrands);
extern int digitalLeds_drawPixels(strand_t * strands [], int numStrands);
extern int digitalLeds_resetPixels(strand_t * strands [], int numStrands);
#ifdef __cplusplus
}
#endif
#endif /* ESP32_DIGITAL_LED_LIB_H */
//**************************************************************************//

View File

@ -1,3 +1,5 @@
#ifndef PLATFORM_NATIVE
#include "drivers/Esp32DriverRGBW.h"
#include <driver/gpio.h>
@ -103,3 +105,5 @@ bool Esp32DriverRGBW::waitForTransmissionToFinish(int waitMs)
else
return false;
}
#endif

View File

@ -3,7 +3,6 @@
#include "helpers/ColorRGBW.h"
#include "Arduino.h" // TODO
#include <cstdint>
template <int TNumLeds>
@ -12,16 +11,19 @@ class LedStripRGBW
public:
static constexpr int NUM_LEDS = TNumLeds;
static constexpr int normalizeIdx(int idx)
{
return (idx < 0) ? (idx + NUM_LEDS) : (idx >= NUM_LEDS ? idx - NUM_LEDS : idx);
}
void set(int idx, uint8_t r, uint8_t g, uint8_t b, uint8_t w)
{
// green: 0
// red: 8
// blue: 16
// white: 24
if (idx < 0 || idx >= NUM_LEDS)
Serial.printf("Out of bounds idx %i\n", idx);
else
data_[idx] = (g << 0) | (r << 8) | (b << 16) | (w << 24);
idx = normalizeIdx(idx);
data_[idx] = (g << 0) | (r << 8) | (b << 16) | (w << 24);
}
const uint32_t *rawData() const { return data_; }

View File

@ -1,5 +1,7 @@
#pragma once
#ifndef PLATFORM_NATIVE
#include "containers/LedStripRGBW.h"
class Esp32DriverRGBW
@ -17,3 +19,5 @@ private:
int rmtChannel_;
bool transmitting_;
};
#endif

View File

@ -0,0 +1,142 @@
#pragma once
#include "effects/Common.h"
#include "helpers/ColorRGBW.h"
#include "helpers/ColorHSV.h"
#include "helpers/ColorConversions.h"
#include "helpers/BellCurve.h"
struct EffectAlexaSwipeConfig
{
float primaryColorWidth; // in degrees
float transitionWidth;
float swipeSpeed; // in degrees per second
float bellCurveWidthInLeds;
float startPosition;
ColorRGBW primaryColor;
ColorRGBW secondaryColor;
};
template <typename TLedStrip>
class EffectAlexaSwipe
{
public:
static constexpr auto NUM_LEDS = numLeds<TLedStrip>();
static constexpr int DELAY_MS = 10;
using ConfigType = EffectAlexaSwipeConfig;
EffectAlexaSwipe(const EffectAlexaSwipeConfig &cfg, TLedStrip &ledStrip)
: ledStrip_(ledStrip),
currentPosition_(0),
transitionWidth_(cfg.transitionWidth / 360.0f * NUM_LEDS),
invTransitionWidth_(1.0f / transitionWidth_),
primaryColorWidth_(cfg.primaryColorWidth / 360.0f * NUM_LEDS + 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),
primaryColor_(rgb2hsv(cfg.primaryColor)),
secondaryColor_(rgb2hsv(cfg.secondaryColor))
{
//#ifndef PLATFORM_NATIVE
// Serial.printf("Primary color %f, %f, %f\n", primaryColor_.h, primaryColor_.s, primaryColor_.v);
// Serial.printf("Secondary color %f, %f, %f\n", secondaryColor_.h, secondaryColor_.s, secondaryColor_.v);
//#endif
}
int operator()()
{
clear(ledStrip_);
const auto width = std::min(int(currentPosition_ + 1), int(NUM_LEDS / 2));
setLedRGBW(ledStrip_, startPosition_, getColor(currentPosition_));
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;
const ColorRGBW color = getColor(x);
//#ifndef PLATFORM_NATIVE
// Serial.printf("Setting %d and %d to %d, %d, %d\n", led1, led2, color.r, color.g, color.b);
//#endif
setLedRGBW(ledStrip_, led1, color);
setLedRGBW(ledStrip_, led2, color);
}
}
currentPosition_ += speed_;
currentPosition_ = std::min(currentPosition_, float(NUM_LEDS) / 2.0f + bellCurveWidth_ / 2);
return DELAY_MS;
}
//private:
void getParams(float x, float &interpFac, float &brightness)
{
brightness = (x < bellCurveWidth_) ? bellCurveApproximation(bellCurveWidth_ / 2 - x, invBellCurveWidth_) : 1.0f;
if (x < primaryColorWidth_)
interpFac = 0.0f;
else if (x > primaryColorWidth_ + transitionWidth_)
interpFac = 1.0f;
else
interpFac = (x - primaryColorWidth_) * invTransitionWidth_;
}
// x is positive distance from running front
ColorRGBW getColor(float x)
{
float interpFac;
float brightness;
getParams(x, interpFac, brightness);
ColorHSV result{
interpFac * secondaryColor_.h + (1.0f - interpFac) * primaryColor_.h,
interpFac * secondaryColor_.s + (1.0f - interpFac) * primaryColor_.s,
interpFac * secondaryColor_.v + (1.0f - interpFac) * primaryColor_.v};
result.v *= brightness;
const auto converted = hsv2rgb(result);
//#ifndef PLATFORM_NATIVE
// Serial.printf("Coord %f, interp %f, bright %f, h %f, s %f, v %f, r %d, g %d, b %d\n", x,
// interpFac, brightness, result.h, result.s, result.v, converted.r,
// converted.g, converted.b);
//#endif
return converted;
}
TLedStrip &ledStrip_;
// in number of leds
float currentPosition_;
float transitionWidth_;
float invTransitionWidth_;
float primaryColorWidth_;
float bellCurveWidth_;
float invBellCurveWidth_;
float speed_;
int startPosition_;
const ColorHSV primaryColor_;
const ColorHSV secondaryColor_;
};
// Traits
template <>
struct EffectIdToConfig<EffectId::ALEXA_SWIPE>
{
using type = EffectAlexaSwipeConfig;
};
template <>
struct EffectConfigToId<EffectAlexaSwipeConfig>
{
static constexpr auto id = EffectId::ALEXA_SWIPE;
};
template <typename TLedStrip>
struct EffectIdToClass<EffectId::ALEXA_SWIPE, TLedStrip>
{
using type = EffectAlexaSwipe<TLedStrip>;
};

View File

@ -28,11 +28,6 @@ public:
{
}
static constexpr int normalizeIdx(int idx)
{
return (idx < 0) ? (idx + NUM_LEDS) : (idx >= NUM_LEDS ? idx - NUM_LEDS : idx);
}
int operator()()
{
int startLed = int(currentPosition_);
@ -41,20 +36,20 @@ public:
clear(ledStrip_);
// center
setLedRGBW(ledStrip_, normalizeIdx(startLed),
setLedRGBW(ledStrip_, startLed,
config_.color * bellCurveApproximation(distDown, invWidth_));
// down
for (int i = 1; i < widthInLeds_ / 2 + 1; ++i)
{
setLedRGBW(ledStrip_, normalizeIdx(startLed - i),
setLedRGBW(ledStrip_, startLed - i,
config_.color * bellCurveApproximation(distDown + i, invWidth_));
}
// up
for (int i = 1; i < widthInLeds_ / 2 + 1; ++i)
{
setLedRGBW(ledStrip_, normalizeIdx(startLed + i),
setLedRGBW(ledStrip_, startLed + i,
config_.color * bellCurveApproximation(distUp + i - 1, invWidth_));
}
@ -62,7 +57,6 @@ public:
if (currentPosition_ > NUM_LEDS)
currentPosition_ -= NUM_LEDS;
//Serial.printf("Current pos %f led %d width %d\n", currentPosition_, normalizeIdx(startLed), widthInLeds_ / 2 + 1);
return DELAY_MS;
}

View File

@ -4,9 +4,7 @@ enum class EffectId
{
STATIC,
CIRCULAR,
CIRCLE_WAVE,
COLOR_FADE,
RAINBOW_FADE,
ALEXA_SWIPE,
};
template <EffectId id>

View File

@ -1,3 +1,5 @@
#pragma once
#include <cstdint>
static inline float bellCurveApproximation(float x, float inverseWidth)

View File

@ -0,0 +1,114 @@
#include "helpers/ColorHSV.h"
#include "helpers/ColorRGBW.h"
#include <cstdint>
#include <cmath>
#include <algorithm>
// https://stackoverflow.com/questions/3018313/algorithm-to-convert-rgb-to-hsv-and-hsv-to-rgb-in-range-0-255-for-both
inline ColorHSV rgb2hsv(const ColorRGBW &in)
{
ColorHSV out;
const float r = (float)(in.r) / 255.0f;
const float g = (float)(in.g) / 255.0f;
const float b = (float)(in.b) / 255.0f;
const float min = std::min(r, std::min(g, b));
const float max = std::max(r, std::max(g, b));
out.v = max; // v
const float delta = max - min;
if (delta < 0.00001f)
{
out.s = 0;
out.h = 0; // undefined, maybe nan?
return out;
}
if (max > 0.0f)
{ // NOTE: if Max is == 0, this divide would cause a crash
out.s = (delta / max); // s
}
else
{
// if max is 0, then r = g = b = 0
// s = 0, h is undefined
out.s = 0.0f;
out.h = 0.0f; //NAN; // its now undefined
return out;
}
if (r >= max) // > is bogus, just keeps compilor happy
out.h = (g - b) / delta; // between yellow & magenta
else if (g >= max)
out.h = 2.0f + (b - r) / delta; // between cyan & yellow
else
out.h = 4.0f + (r - g) / delta; // between magenta & cyan
out.h *= 60.0f; // degrees
if (out.h < 0.0f)
out.h += 360.0f;
return out;
}
ColorRGBW hsv2rgb(const ColorHSV &in)
{
int i;
ColorRGBW out;
out.w = 0;
if (in.s <= 0.0f)
{ // < is bogus, just shuts up warnings
out.r = (uint8_t)(in.v * 255.0f);
out.g = (uint8_t)(in.v * 255.0f);
out.b = (uint8_t)(in.v * 255.0f);
return out;
}
float hh = in.h;
if (hh >= 360.0f)
hh = 0.0f;
hh /= 60.0f;
i = (long)hh;
auto ff = hh - i;
float p = in.v * (1.0f - in.s);
float q = in.v * (1.0f - (in.s * ff));
float t = in.v * (1.0f - (in.s * (1.0f - ff)));
switch (i)
{
case 0:
out.r = (uint8_t)(in.v * 255.0f);
out.g = (uint8_t)(t * 255.0f);
out.b = (uint8_t)(p * 255.0f);
break;
case 1:
out.r = (uint8_t)(q * 255.0f);
out.g = (uint8_t)(in.v * 255.0f);
out.b = (uint8_t)(p * 255.0f);
break;
case 2:
out.r = (uint8_t)(p * 255.0f);
out.g = (uint8_t)(in.v * 255.0f);
out.b = (uint8_t)(t * 255.0f);
break;
case 3:
out.r = (uint8_t)(p * 255.0f);
out.g = (uint8_t)(q * 255.0f);
out.b = (uint8_t)(in.v * 255.0f);
break;
case 4:
out.r = (uint8_t)(t * 255.0f);
out.g = (uint8_t)(p * 255.0f);
out.b = (uint8_t)(in.v * 255.0f);
break;
case 5:
default:
out.r = (uint8_t)(in.v * 255.0f);
out.g = (uint8_t)(p * 255.0f);
out.b = (uint8_t)(q * 255.0f);
break;
}
return out;
}

View File

@ -0,0 +1,8 @@
#pragma once
#include <cstdint>
struct ColorHSV
{
float h, s, v;
};

View File

@ -27,5 +27,11 @@ framework = arduino
monitor_port = /dev/ttyUSB0
upload_port = /dev/ttyUSB0
monitor_speed = 115200
src_filter = +<*> -<host_test.cpp>
lib_deps =
miguelbalboa/MFRC522
[env:native]
platform=native
src_filter = +<*> -<main.cpp>
build_flags=-Ilib/ledtl -g -DPLATFORM_NATIVE

View File

@ -75,8 +75,9 @@ void _led_task_func(void *params)
TLedStrip &ledStrip = *(task->ledStrip_);
// clang-format off
if (dispatchEffectId<EffectId::CIRCULAR>(id, effectFunction, ledStrip, msgBuffer, effectStorage)) { Serial.println("Parsed circular");}
else if (dispatchEffectId<EffectId::STATIC >(id, effectFunction, ledStrip, msgBuffer, effectStorage)) { Serial.println("Parsed static");}
if (dispatchEffectId<EffectId::CIRCULAR >(id, effectFunction, ledStrip, msgBuffer, effectStorage)) { Serial.println("Parsed circular");}
else if (dispatchEffectId<EffectId::STATIC >(id, effectFunction, ledStrip, msgBuffer, effectStorage)) { Serial.println("Parsed static");}
else if (dispatchEffectId<EffectId::ALEXA_SWIPE>(id, effectFunction, ledStrip, msgBuffer, effectStorage)) { Serial.println("Alexa swipe");}
// clang-format on
timeoutMsForEffect = 0;
@ -99,7 +100,7 @@ void LedTask<TLedStrip>::begin(TLedStrip &strip, Esp32DriverRGBW &driver)
ledStrip_ = &strip;
driver_ = &driver;
xTaskCreate(_led_task_func<TLedStrip>, "led task", MAX_EFFECT_CLASS_SIZE + MAX_EFFECT_CONFIG_SIZE + 2048,
xTaskCreate(_led_task_func<TLedStrip>, "led task", MAX_EFFECT_CLASS_SIZE + MAX_EFFECT_CONFIG_SIZE + 2048 * 2,
(void *)(this), 1, nullptr);
}

View File

@ -0,0 +1,40 @@
#include "containers/LedStripRGBW.h"
#include "effects/AlexaSwipe.h"
#include <iostream>
#include <vector>
template <typename T>
void printVec(const std::vector<T> &vec)
{
std::cout << "[";
for (const auto &e : vec)
std::cout << e << ",";
std::cout << "]\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;
EffectAlexaSwipe<decltype(strip)> effect(cfg, strip);
effect.currentPosition_ = 150;
const auto numLeds = strip.numLeds() / 2;
std::vector<float> brightness(numLeds, 0);
std::vector<float> interpolation(numLeds, 0);
for (int i = 0; i < numLeds; ++i)
effect.getParams(float(i), interpolation[i], brightness[i]);
printVec(brightness);
printVec(interpolation);
effect();
return 0;
}

View File

@ -2,14 +2,13 @@
#include "rc522.h"
#include "SPI.h"
#include <MFRC522.h>
#include "LedControl.h"
#include "rotary_encoder.h"
#include "LedAnimation.h"
#include "containers/LedStripRGBW.h"
#include "drivers/Esp32DriverRGBW.h"
#include "effects/Circular.h"
#include "effects/Static.h"
#include "effects/AlexaSwipe.h"
#include "TaskLed.h"
@ -34,12 +33,13 @@ void tag_handler(uint8_t *sn)
if (sn[4] == 0x30)
{
Serial.println("Fuchs");
ledTask.startEffect(EffectCircularConfig{2 * 360, 180, ColorRGBW{0, 0, 255, 0}});
//ledTask.startEffect(EffectCircularConfig{2 * 360, 180, ColorRGBW{0, 0, 255, 0}});
ledTask.startEffect(EffectAlexaSwipeConfig{20, 30, 3 * 360, 3, 180, ColorRGBW{0, 255, 0, 0}, ColorRGBW{0, 0, 255, 0}});
}
if (sn[4] == 0xf0)
{
Serial.println("Eule");
ledTask.startEffect(EffectCircularConfig{180, 180, ColorRGBW{0, 0, 0, 128}});
ledTask.startEffect(EffectCircularConfig{360, 180, ColorRGBW{0, 0, 255, 0}});
}
}
else
@ -94,7 +94,6 @@ void setup()
bool btn2state = true;
SweepCircularAnimation animation(ColorRGB{0, 0, 255}, 100, 15, 0.7);
void loop()
{
/*

View File

@ -1,5 +1,5 @@
- play wav file
- read & parse wav file
- play wav file [ok]
- read & parse wav file [ok]
- stop wave file in the middle, wait 2 secs and continue
- fade in/out
- mix second wave file on top (some effect)