Audioreactive usermod.
This commit is contained in:
parent
9db872db56
commit
184ff7a3b3
1164
usermods/audioreactive/audio_reactive.h
Normal file
1164
usermods/audioreactive/audio_reactive.h
Normal file
File diff suppressed because it is too large
Load Diff
473
usermods/audioreactive/audio_source.h
Normal file
473
usermods/audioreactive/audio_source.h
Normal file
@ -0,0 +1,473 @@
|
||||
#pragma once
|
||||
|
||||
#include <Wire.h>
|
||||
#include "wled.h"
|
||||
#include <driver/i2s.h>
|
||||
|
||||
/* ToDo: remove. ES7243 is controlled via compiler defines
|
||||
Until this configuration is moved to the webinterface
|
||||
*/
|
||||
|
||||
// data type requested from the I2S driver - currently we always use 32bit
|
||||
//#define I2S_USE_16BIT_SAMPLES // (experimental) define this to request 16bit - more efficient but possibly less compatible
|
||||
#ifdef I2S_USE_16BIT_SAMPLES
|
||||
#define I2S_SAMPLE_RESOLUTION I2S_BITS_PER_SAMPLE_16BIT
|
||||
#define I2S_datatype int16_t
|
||||
#undef I2S_SAMPLE_DOWNSCALE_TO_16BIT
|
||||
#else
|
||||
#define I2S_SAMPLE_RESOLUTION I2S_BITS_PER_SAMPLE_32BIT
|
||||
#define I2S_datatype int32_t
|
||||
#define I2S_SAMPLE_DOWNSCALE_TO_16BIT
|
||||
#endif
|
||||
|
||||
#ifndef MCLK_PIN
|
||||
int mclkPin = 0;
|
||||
#else
|
||||
int mclkPin = MLCK_PIN;
|
||||
#endif
|
||||
|
||||
#ifndef ES7243_ADDR
|
||||
int addr_ES7243 = 0x13;
|
||||
#else
|
||||
int addr_ES7243 = ES7243_ADDR;
|
||||
#endif
|
||||
|
||||
#ifndef ES7243_SDAPIN
|
||||
int pin_ES7243_SDA = 18;
|
||||
#else
|
||||
int pin_ES7243_SDA = ES7243_SDAPIN;
|
||||
#endif
|
||||
|
||||
#ifndef ES7243_SDAPIN
|
||||
int pin_ES7243_SCL = 23;
|
||||
#else
|
||||
int pin_ES7243_SCL = ES7243_SCLPIN;
|
||||
#endif
|
||||
|
||||
/* Interface class
|
||||
AudioSource serves as base class for all microphone types
|
||||
This enables accessing all microphones with one single interface
|
||||
which simplifies the caller code
|
||||
*/
|
||||
class AudioSource {
|
||||
public:
|
||||
/* All public methods are virtual, so they can be overridden
|
||||
Everything but the destructor is also removed, to make sure each mic
|
||||
Implementation provides its version of this function
|
||||
*/
|
||||
virtual ~AudioSource() {};
|
||||
|
||||
/* Initialize
|
||||
This function needs to take care of anything that needs to be done
|
||||
before samples can be obtained from the microphone.
|
||||
*/
|
||||
virtual void initialize() = 0;
|
||||
|
||||
/* Deinitialize
|
||||
Release all resources and deactivate any functionality that is used
|
||||
by this microphone
|
||||
*/
|
||||
virtual void deinitialize() = 0;
|
||||
|
||||
/* getSamples
|
||||
Read num_samples from the microphone, and store them in the provided
|
||||
buffer
|
||||
*/
|
||||
virtual void getSamples(double *buffer, uint16_t num_samples) = 0;
|
||||
|
||||
/* Get an up-to-date sample without DC offset */
|
||||
virtual int getSampleWithoutDCOffset() = 0;
|
||||
|
||||
protected:
|
||||
// Private constructor, to make sure it is not callable except from derived classes
|
||||
AudioSource(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) : _sampleRate(sampleRate), _blockSize(blockSize), _sampleNoDCOffset(0), _dcOffset(0.0f), _shift(lshift), _mask(mask), _initialized(false) {};
|
||||
|
||||
int _sampleRate; /* Microphone sampling rate */
|
||||
int _blockSize; /* I2S block size */
|
||||
volatile int _sampleNoDCOffset; /* Up-to-date sample without DCOffset */
|
||||
float _dcOffset; /* Rolling average DC offset */
|
||||
int16_t _shift; /* Shift obtained samples to the right (positive) or left(negative) by this amount */
|
||||
uint32_t _mask; /* Bitmask for sample data after shifting. Bitmask 0X0FFF means that we need to convert 12bit ADC samples from unsigned to signed*/
|
||||
bool _initialized; /* Gets set to true if initialization is successful */
|
||||
};
|
||||
|
||||
/* Basic I2S microphone source
|
||||
All functions are marked virtual, so derived classes can replace them
|
||||
*/
|
||||
class I2SSource : public AudioSource {
|
||||
public:
|
||||
I2SSource(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) :
|
||||
AudioSource(sampleRate, blockSize, lshift, mask) {
|
||||
_config = {
|
||||
.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX),
|
||||
.sample_rate = _sampleRate,
|
||||
.bits_per_sample = I2S_SAMPLE_RESOLUTION,
|
||||
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
|
||||
.communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
|
||||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
|
||||
.dma_buf_count = 8,
|
||||
.dma_buf_len = _blockSize
|
||||
};
|
||||
|
||||
_pinConfig = {
|
||||
.bck_io_num = i2sckPin,
|
||||
.ws_io_num = i2swsPin,
|
||||
.data_out_num = I2S_PIN_NO_CHANGE,
|
||||
.data_in_num = i2ssdPin
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
virtual void initialize() {
|
||||
|
||||
if (!pinManager.allocatePin(i2swsPin, true, PinOwner::DigitalMic) ||
|
||||
!pinManager.allocatePin(i2ssdPin, true, PinOwner::DigitalMic)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// i2ssckPin needs special treatment, since it might be unused on PDM mics
|
||||
if (i2sckPin != -1) {
|
||||
if (!pinManager.allocatePin(i2sckPin, true, PinOwner::DigitalMic))
|
||||
return;
|
||||
}
|
||||
|
||||
esp_err_t err = i2s_driver_install(I2S_NUM_0, &_config, 0, nullptr);
|
||||
if (err != ESP_OK) {
|
||||
Serial.printf("Failed to install i2s driver: %d\n", err);
|
||||
return;
|
||||
}
|
||||
|
||||
err = i2s_set_pin(I2S_NUM_0, &_pinConfig);
|
||||
if (err != ESP_OK) {
|
||||
Serial.printf("Failed to set i2s pin config: %d\n", err);
|
||||
return;
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
virtual void deinitialize() {
|
||||
_initialized = false;
|
||||
esp_err_t err = i2s_driver_uninstall(I2S_NUM_0);
|
||||
if (err != ESP_OK) {
|
||||
Serial.printf("Failed to uninstall i2s driver: %d\n", err);
|
||||
return;
|
||||
}
|
||||
pinManager.deallocatePin(i2swsPin, PinOwner::DigitalMic);
|
||||
pinManager.deallocatePin(i2ssdPin, PinOwner::DigitalMic);
|
||||
// i2ssckPin needs special treatment, since it might be unused on PDM mics
|
||||
if (i2sckPin != -1) {
|
||||
pinManager.deallocatePin(i2sckPin, PinOwner::DigitalMic);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void getSamples(double *buffer, uint16_t num_samples) {
|
||||
if(_initialized) {
|
||||
esp_err_t err;
|
||||
size_t bytes_read = 0; /* Counter variable to check if we actually got enough data */
|
||||
I2S_datatype newSamples[num_samples]; /* Intermediary sample storage */
|
||||
|
||||
// Reset dc offset
|
||||
_dcOffset = 0.0f;
|
||||
|
||||
err = i2s_read(I2S_NUM_0, (void *)newSamples, sizeof(newSamples), &bytes_read, portMAX_DELAY);
|
||||
if ((err != ESP_OK)){
|
||||
Serial.printf("Failed to get samples: %d\n", err);
|
||||
return;
|
||||
}
|
||||
|
||||
// For correct operation, we need to read exactly sizeof(samples) bytes from i2s
|
||||
if(bytes_read != sizeof(newSamples)) {
|
||||
Serial.printf("Failed to get enough samples: wanted: %d read: %d\n", sizeof(newSamples), bytes_read);
|
||||
return;
|
||||
}
|
||||
|
||||
// Store samples in sample buffer and update DC offset
|
||||
for (int i = 0; i < num_samples; i++) {
|
||||
// pre-shift samples down to 16bit
|
||||
#ifdef I2S_SAMPLE_DOWNSCALE_TO_16BIT
|
||||
if (_shift != 0)
|
||||
newSamples[i] >>= 16;
|
||||
#endif
|
||||
double currSample = 0.0;
|
||||
if(_shift > 0)
|
||||
currSample = (double) (newSamples[i] >> _shift);
|
||||
else {
|
||||
if(_shift < 0)
|
||||
currSample = (double) (newSamples[i] << (- _shift)); // need to "pump up" 12bit ADC to full 16bit as delivered by other digital mics
|
||||
else
|
||||
#ifdef I2S_SAMPLE_DOWNSCALE_TO_16BIT
|
||||
currSample = (double) newSamples[i] / 65536.0; // _shift == 0 -> use the chance to keep lower 16bits
|
||||
#else
|
||||
currSample = (double) newSamples[i];
|
||||
#endif
|
||||
}
|
||||
buffer[i] = currSample;
|
||||
_dcOffset = ((_dcOffset * 31) + currSample) / 32;
|
||||
}
|
||||
|
||||
// Update no-DC sample
|
||||
_sampleNoDCOffset = buffer[num_samples - 1] - _dcOffset;
|
||||
}
|
||||
}
|
||||
|
||||
virtual int getSampleWithoutDCOffset() {
|
||||
return _sampleNoDCOffset;
|
||||
}
|
||||
|
||||
protected:
|
||||
i2s_config_t _config;
|
||||
i2s_pin_config_t _pinConfig;
|
||||
};
|
||||
|
||||
/* I2S microphone with master clock
|
||||
Our version of the IDF does not support setting master clock
|
||||
routing via the provided API, so we have to do it by hand
|
||||
*/
|
||||
class I2SSourceWithMasterClock : public I2SSource {
|
||||
public:
|
||||
I2SSourceWithMasterClock(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) :
|
||||
I2SSource(sampleRate, blockSize, lshift, mask) {
|
||||
};
|
||||
|
||||
virtual void initialize() {
|
||||
// Reserve the master clock pin
|
||||
if(!pinManager.allocatePin(mclkPin, true, PinOwner::DigitalMic)) {
|
||||
return;
|
||||
}
|
||||
_routeMclk();
|
||||
I2SSource::initialize();
|
||||
|
||||
}
|
||||
|
||||
virtual void deinitialize() {
|
||||
// Release the master clock pin
|
||||
pinManager.deallocatePin(mclkPin, PinOwner::DigitalMic);
|
||||
I2SSource::deinitialize();
|
||||
}
|
||||
protected:
|
||||
void _routeMclk() {
|
||||
/* Enable the mclk routing depending on the selected mclk pin
|
||||
Only I2S_NUM_0 is supported
|
||||
*/
|
||||
if (mclkPin == GPIO_NUM_0) {
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);
|
||||
WRITE_PERI_REG(PIN_CTRL,0xFFF0);
|
||||
} else if (mclkPin == GPIO_NUM_1) {
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD_CLK_OUT3);
|
||||
WRITE_PERI_REG(PIN_CTRL, 0xF0F0);
|
||||
} else {
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD_CLK_OUT2);
|
||||
WRITE_PERI_REG(PIN_CTRL, 0xFF00);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* ES7243 Microphone
|
||||
This is an I2S microphone that requires ininitialization over
|
||||
I2C before I2S data can be received
|
||||
*/
|
||||
class ES7243 : public I2SSourceWithMasterClock {
|
||||
|
||||
private:
|
||||
// I2C initialization functions for ES7243
|
||||
void _es7243I2cBegin() {
|
||||
Wire.begin(pin_ES7243_SDA, pin_ES7243_SCL, 100000U);
|
||||
}
|
||||
|
||||
void _es7243I2cWrite(uint8_t reg, uint8_t val) {
|
||||
Wire.beginTransmission(addr_ES7243);
|
||||
Wire.write((uint8_t)reg);
|
||||
Wire.write((uint8_t)val);
|
||||
Wire.endTransmission();
|
||||
}
|
||||
|
||||
void _es7243InitAdc() {
|
||||
_es7243I2cBegin();
|
||||
_es7243I2cWrite(0x00, 0x01);
|
||||
_es7243I2cWrite(0x06, 0x00);
|
||||
_es7243I2cWrite(0x05, 0x1B);
|
||||
_es7243I2cWrite(0x01, 0x00); // 0x00 for 24 bit to match INMP441 - not sure if this needs adjustment to get 16bit samples from I2S
|
||||
_es7243I2cWrite(0x08, 0x43);
|
||||
_es7243I2cWrite(0x05, 0x13);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
ES7243(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) :
|
||||
I2SSourceWithMasterClock(sampleRate, blockSize, lshift, mask) {
|
||||
_config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT;
|
||||
};
|
||||
void initialize() {
|
||||
// Reserve SDA and SCL pins of the I2C interface
|
||||
if (!pinManager.allocatePin(pin_ES7243_SDA, true, PinOwner::DigitalMic) ||
|
||||
!pinManager.allocatePin(pin_ES7243_SCL, true, PinOwner::DigitalMic)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First route mclk, then configure ADC over I2C, then configure I2S
|
||||
_es7243InitAdc();
|
||||
I2SSourceWithMasterClock::initialize();
|
||||
}
|
||||
|
||||
void deinitialize() {
|
||||
// Release SDA and SCL pins of the I2C interface
|
||||
pinManager.deallocatePin(pin_ES7243_SDA, PinOwner::DigitalMic);
|
||||
pinManager.deallocatePin(pin_ES7243_SCL, PinOwner::DigitalMic);
|
||||
I2SSourceWithMasterClock::deinitialize();
|
||||
}
|
||||
};
|
||||
|
||||
/* ADC over I2S Microphone
|
||||
This microphone is an ADC pin sampled via the I2S interval
|
||||
This allows to use the I2S API to obtain ADC samples with high sample rates
|
||||
without the need of manual timing of the samples
|
||||
*/
|
||||
class I2SAdcSource : public I2SSource {
|
||||
public:
|
||||
I2SAdcSource(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) :
|
||||
I2SSource(sampleRate, blockSize, lshift, mask){
|
||||
_config = {
|
||||
.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN),
|
||||
.sample_rate = _sampleRate,
|
||||
.bits_per_sample = I2S_SAMPLE_RESOLUTION,
|
||||
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
|
||||
.communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
|
||||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL2,
|
||||
.dma_buf_count = 8,
|
||||
.dma_buf_len = _blockSize
|
||||
};
|
||||
}
|
||||
|
||||
void initialize() {
|
||||
|
||||
if(!pinManager.allocatePin(audioPin, false, PinOwner::AnalogMic)) {
|
||||
return;
|
||||
}
|
||||
// Determine Analog channel. Only Channels on ADC1 are supported
|
||||
int8_t channel = digitalPinToAnalogChannel(audioPin);
|
||||
if (channel > 9) {
|
||||
Serial.printf("Incompatible GPIO used for audio in: %d\n", audioPin);
|
||||
return;
|
||||
} else {
|
||||
adc_gpio_init(ADC_UNIT_1, adc_channel_t(channel));
|
||||
}
|
||||
|
||||
// Install Driver
|
||||
esp_err_t err = i2s_driver_install(I2S_NUM_0, &_config, 0, nullptr);
|
||||
if (err != ESP_OK) {
|
||||
Serial.printf("Failed to install i2s driver: %d\n", err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Enable I2S mode of ADC
|
||||
err = i2s_set_adc_mode(ADC_UNIT_1, adc1_channel_t(channel));
|
||||
if (err != ESP_OK) {
|
||||
Serial.printf("Failed to set i2s adc mode: %d\n", err);
|
||||
return;
|
||||
|
||||
}
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
// according to docs from espressif, the ADC needs to be started explicitly
|
||||
// fingers crossed
|
||||
err = i2s_adc_enable(I2S_NUM_0);
|
||||
if (err != ESP_OK) {
|
||||
Serial.printf("Failed to enable i2s adc: %d\n", err);
|
||||
//return;
|
||||
}
|
||||
#endif
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
void getSamples(double *buffer, uint16_t num_samples) {
|
||||
|
||||
/* Enable ADC. This has to be enabled and disabled directly before and
|
||||
after sampling, otherwise Wifi dies
|
||||
*/
|
||||
if (_initialized) {
|
||||
#if !defined(ARDUINO_ARCH_ESP32)
|
||||
// old code - works for me without enable/disable, at least on ESP32.
|
||||
esp_err_t err = i2s_adc_enable(I2S_NUM_0);
|
||||
//esp_err_t err = i2s_start(I2S_NUM_0);
|
||||
if (err != ESP_OK) {
|
||||
Serial.printf("Failed to enable i2s adc: %d\n", err);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
I2SSource::getSamples(buffer, num_samples);
|
||||
|
||||
#if !defined(ARDUINO_ARCH_ESP32)
|
||||
// old code - works for me without enable/disable, at least on ESP32.
|
||||
err = i2s_adc_disable(I2S_NUM_0);
|
||||
//err = i2s_stop(I2S_NUM_0);
|
||||
if (err != ESP_OK) {
|
||||
Serial.printf("Failed to disable i2s adc: %d\n", err);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void deinitialize() {
|
||||
pinManager.deallocatePin(audioPin, PinOwner::AnalogMic);
|
||||
_initialized = false;
|
||||
esp_err_t err;
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
// according to docs from espressif, the ADC needs to be stopped explicitly
|
||||
// fingers crossed
|
||||
err = i2s_adc_disable(I2S_NUM_0);
|
||||
if (err != ESP_OK) {
|
||||
Serial.printf("Failed to disable i2s adc: %d\n", err);
|
||||
//return;
|
||||
}
|
||||
#endif
|
||||
err = i2s_driver_uninstall(I2S_NUM_0);
|
||||
if (err != ESP_OK) {
|
||||
Serial.printf("Failed to uninstall i2s driver: %d\n", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* SPH0645 Microphone
|
||||
This is an I2S microphone with some timing quirks that need
|
||||
special consideration.
|
||||
*/
|
||||
class SPH0654 : public I2SSource {
|
||||
|
||||
public:
|
||||
SPH0654(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) :
|
||||
I2SSource(sampleRate, blockSize, lshift, mask){}
|
||||
|
||||
void initialize() {
|
||||
I2SSource::initialize();
|
||||
REG_SET_BIT(I2S_TIMING_REG(I2S_NUM_0), BIT(9));
|
||||
REG_SET_BIT(I2S_CONF_REG(I2S_NUM_0), I2S_RX_MSB_SHIFT);
|
||||
}
|
||||
};
|
||||
|
||||
/* I2S PDM Microphone
|
||||
This is an I2S PDM microphone, these microphones only use a clock and
|
||||
data line, to make it simpler to debug, use the WS pin as CLK and SD
|
||||
pin as DATA
|
||||
*/
|
||||
|
||||
class I2SPdmSource : public I2SSource {
|
||||
|
||||
public:
|
||||
I2SPdmSource(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) :
|
||||
I2SSource(sampleRate, blockSize, lshift, mask) {
|
||||
|
||||
_config.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM); // Change mode to pdm
|
||||
|
||||
_pinConfig = {
|
||||
.bck_io_num = I2S_PIN_NO_CHANGE, // bck is unused in PDM mics
|
||||
.ws_io_num = i2swsPin, // clk pin for PDM mic
|
||||
.data_out_num = I2S_PIN_NO_CHANGE,
|
||||
.data_in_num = i2ssdPin
|
||||
};
|
||||
}
|
||||
};
|
10
usermods/audioreactive/readme.md
Normal file
10
usermods/audioreactive/readme.md
Normal file
@ -0,0 +1,10 @@
|
||||
# Usermods API v2 example usermod
|
||||
|
||||
In this usermod file you can find the documentation on how to take advantage of the new version 2 usermods!
|
||||
|
||||
## Installation
|
||||
|
||||
Copy `usermod_v2_example.h` to the wled00 directory.
|
||||
Uncomment the corresponding lines in `usermods_list.cpp` and compile!
|
||||
_(You shouldn't need to actually install this, it does nothing useful)_
|
||||
|
121
wled00/FX.cpp
121
wled00/FX.cpp
@ -26,6 +26,7 @@
|
||||
|
||||
#include "FX.h"
|
||||
#include "wled.h"
|
||||
#include "fcn_declare.h"
|
||||
|
||||
#define IBN 5100
|
||||
#define PALETTE_SOLID_WRAP (paletteBlend == 1 || paletteBlend == 3)
|
||||
@ -1053,7 +1054,7 @@ static const char *_data_FX_MODE_CANDY_CANE PROGMEM = "Candy Cane@!,Width;;";
|
||||
uint16_t WS2812FX::mode_halloween(void) {
|
||||
return running(PURPLE, ORANGE);
|
||||
}
|
||||
static const char *_data_FX_MODE_HALLOWEEN PROGMEM = "Halloween";
|
||||
static const char *_data_FX_MODE_HALLOWEEN PROGMEM = "Halloween@!,Width;;";
|
||||
|
||||
|
||||
/*
|
||||
@ -1140,7 +1141,7 @@ uint16_t WS2812FX::larson_scanner(bool dual) {
|
||||
*/
|
||||
uint16_t WS2812FX::mode_comet(void) {
|
||||
uint16_t counter = now * ((SEGMENT.speed >>2) +1);
|
||||
uint16_t index = counter * SEGLEN >> 16;
|
||||
uint16_t index = (counter * SEGLEN) >> 16;
|
||||
if (SEGENV.call == 0) SEGENV.aux0 = index;
|
||||
|
||||
fade_out(SEGMENT.intensity);
|
||||
@ -1159,7 +1160,7 @@ uint16_t WS2812FX::mode_comet(void) {
|
||||
|
||||
return FRAMETIME;
|
||||
}
|
||||
static const char *_data_FX_MODE_COMET PROGMEM = "Lighthouse";
|
||||
static const char *_data_FX_MODE_COMET PROGMEM = "Lighthouse@!,Fade rate;!,!,!;!";
|
||||
|
||||
|
||||
/*
|
||||
@ -1594,7 +1595,7 @@ uint16_t WS2812FX::mode_tricolor_wipe(void)
|
||||
|
||||
return FRAMETIME;
|
||||
}
|
||||
static const char *_data_FX_MODE_TRICOLOR_WIPE PROGMEM = "Tri Wipe@!,Width;1,2,3;0";
|
||||
static const char *_data_FX_MODE_TRICOLOR_WIPE PROGMEM = "Tri Wipe@!,;1,2,3;0";
|
||||
|
||||
|
||||
/*
|
||||
@ -5517,6 +5518,41 @@ static const char *_data_FX_MODE_WAVERLY PROGMEM = "2D Waverly@Fade rate,Sensiti
|
||||
/////////////////////////
|
||||
// 2D Akemi //
|
||||
/////////////////////////
|
||||
static uint8_t akemi[] PROGMEM = {
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,2,2,3,3,3,3,3,3,2,2,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,2,3,3,0,0,0,0,0,0,3,3,2,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,2,3,0,0,0,6,5,5,4,0,0,0,3,2,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,2,3,0,0,6,6,5,5,5,5,4,4,0,0,3,2,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,2,3,0,6,5,5,5,5,5,5,5,5,4,0,3,2,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,2,3,0,6,5,5,5,5,5,5,5,5,5,5,4,0,3,2,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,3,2,0,6,5,5,5,5,5,5,5,5,5,5,4,0,2,3,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,3,2,3,6,5,5,7,7,5,5,5,5,7,7,5,5,4,3,2,3,0,0,0,0,0,0,
|
||||
0,0,0,0,0,2,3,1,3,6,5,1,7,7,7,5,5,1,7,7,7,5,4,3,1,3,2,0,0,0,0,0,
|
||||
0,0,0,0,0,8,3,1,3,6,5,1,7,7,7,5,5,1,7,7,7,5,4,3,1,3,8,9,0,0,0,0,
|
||||
0,0,0,0,0,8,3,1,3,6,5,5,1,1,5,5,5,5,1,1,5,5,4,3,1,3,8,0,0,0,0,0,
|
||||
0,0,0,0,0,2,3,1,3,6,5,5,5,5,5,5,5,5,5,5,5,5,4,3,1,3,2,0,0,0,0,0,
|
||||
0,0,0,0,0,0,3,2,3,6,5,5,5,5,5,5,5,5,5,5,5,5,4,3,2,3,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,6,5,5,5,5,5,7,7,5,5,5,5,5,4,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,6,5,5,5,5,5,5,5,5,5,5,5,5,4,0,0,0,0,0,0,0,0,0,
|
||||
1,0,0,0,0,0,0,0,0,6,5,5,5,5,5,5,5,5,5,5,5,5,4,0,0,0,0,0,0,0,0,2,
|
||||
0,2,2,2,0,0,0,0,0,6,5,5,5,5,5,5,5,5,5,5,5,5,4,0,0,0,0,0,2,2,2,0,
|
||||
0,0,0,3,2,0,0,0,6,5,4,4,4,4,4,4,4,4,4,4,4,4,4,4,0,0,0,2,2,0,0,0,
|
||||
0,0,0,3,2,0,0,0,6,5,5,5,5,5,5,5,5,5,5,5,5,5,5,4,0,0,0,2,3,0,0,0,
|
||||
0,0,0,0,3,2,0,0,0,0,3,3,0,3,3,0,0,3,3,0,3,3,0,0,0,0,2,2,0,0,0,0,
|
||||
0,0,0,0,3,2,0,0,0,0,3,2,0,3,2,0,0,3,2,0,3,2,0,0,0,0,2,3,0,0,0,0,
|
||||
0,0,0,0,0,3,2,0,0,3,2,0,0,3,2,0,0,3,2,0,0,3,2,0,0,2,3,0,0,0,0,0,
|
||||
0,0,0,0,0,3,2,2,2,2,0,0,0,3,2,0,0,3,2,0,0,0,3,2,2,2,3,0,0,0,0,0,
|
||||
0,0,0,0,0,0,3,3,3,0,0,0,0,3,2,0,0,3,2,0,0,0,0,3,3,3,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
};
|
||||
|
||||
uint16_t WS2812FX::mode_2DAkemi(void) {
|
||||
if (!isMatrix) return mode_static(); // not a 2D set-up
|
||||
|
||||
@ -5526,51 +5562,25 @@ uint16_t WS2812FX::mode_2DAkemi(void) {
|
||||
uint16_t counter = (now * ((SEGMENT.speed >> 2) +2)) & 0xFFFF;
|
||||
counter = counter >> 8;
|
||||
|
||||
//Akemi
|
||||
uint8_t akemi[32][32]={
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,2,2,3,3,3,3,3,3,2,2,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,2,3,3,0,0,0,0,0,0,3,3,2,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,2,3,0,0,0,6,5,5,4,0,0,0,3,2,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,2,3,0,0,6,6,5,5,5,5,4,4,0,0,3,2,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,2,3,0,6,5,5,5,5,5,5,5,5,4,0,3,2,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,2,3,0,6,5,5,5,5,5,5,5,5,5,5,4,0,3,2,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,3,2,0,6,5,5,5,5,5,5,5,5,5,5,4,0,2,3,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,3,2,3,6,5,5,7,7,5,5,5,5,7,7,5,5,4,3,2,3,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,2,3,1,3,6,5,1,7,7,7,5,5,1,7,7,7,5,4,3,1,3,2,0,0,0,0,0},
|
||||
{0,0,0,0,0,8,3,1,3,6,5,1,7,7,7,5,5,1,7,7,7,5,4,3,1,3,8,9,0,0,0,0},
|
||||
{0,0,0,0,0,8,3,1,3,6,5,5,1,1,5,5,5,5,1,1,5,5,4,3,1,3,8,0,0,0,0,0},
|
||||
{0,0,0,0,0,2,3,1,3,6,5,5,5,5,5,5,5,5,5,5,5,5,4,3,1,3,2,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,3,2,3,6,5,5,5,5,5,5,5,5,5,5,5,5,4,3,2,3,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,6,5,5,5,5,5,7,7,5,5,5,5,5,4,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,6,5,5,5,5,5,5,5,5,5,5,5,5,4,0,0,0,0,0,0,0,0,0},
|
||||
{1,0,0,0,0,0,0,0,0,6,5,5,5,5,5,5,5,5,5,5,5,5,4,0,0,0,0,0,0,0,0,2},
|
||||
{0,2,2,2,0,0,0,0,0,6,5,5,5,5,5,5,5,5,5,5,5,5,4,0,0,0,0,0,2,2,2,0},
|
||||
{0,0,0,3,2,0,0,0,6,5,4,4,4,4,4,4,4,4,4,4,4,4,4,4,0,0,0,2,2,0,0,0},
|
||||
{0,0,0,3,2,0,0,0,6,5,5,5,5,5,5,5,5,5,5,5,5,5,5,4,0,0,0,2,3,0,0,0},
|
||||
{0,0,0,0,3,2,0,0,0,0,3,3,0,3,3,0,0,3,3,0,3,3,0,0,0,0,2,2,0,0,0,0},
|
||||
{0,0,0,0,3,2,0,0,0,0,3,2,0,3,2,0,0,3,2,0,3,2,0,0,0,0,2,3,0,0,0,0},
|
||||
{0,0,0,0,0,3,2,0,0,3,2,0,0,3,2,0,0,3,2,0,0,3,2,0,0,2,3,0,0,0,0,0},
|
||||
{0,0,0,0,0,3,2,2,2,2,0,0,0,3,2,0,0,3,2,0,0,0,3,2,2,2,3,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,3,3,3,0,0,0,0,3,2,0,0,3,2,0,0,0,0,3,3,3,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
};
|
||||
const float lightFactor = 0.15f;
|
||||
const float normalFactor = 0.4f;
|
||||
float base = 0.0f;
|
||||
uint8_t *fftResult = nullptr;
|
||||
|
||||
um_data_t *um_data;
|
||||
if (usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) {
|
||||
fftResult = um_data->ub8_data[1];
|
||||
base = fftResult[0]/255.0f;
|
||||
}
|
||||
|
||||
//draw and color Akemi
|
||||
for (uint16_t y=0; y < rows; y++) for (uint16_t x=0; x < cols; x++) {
|
||||
CRGB color = BLACK;
|
||||
CRGB faceColor = color_wheel(counter);
|
||||
CRGB armsAndLegsColor = SEGCOLOR(1) > 0 ? SEGCOLOR(1) : 0xFFE0A0; //default warmish white 0xABA8FF; //0xFF52e5;//
|
||||
CRGB color;
|
||||
CRGB soundColor = ORANGE;
|
||||
float lightFactor = 0.15;
|
||||
float normalFactor = 0.4;
|
||||
float base = 0.0; //fftResult[0]/255.0;
|
||||
switch (akemi[(y * 32)/rows][(x * 32)/cols]) {
|
||||
CRGB faceColor = color_wheel(counter);
|
||||
CRGB armsAndLegsColor = SEGCOLOR(1) > 0 ? SEGCOLOR(1) : 0xFFE0A0; //default warmish white 0xABA8FF; //0xFF52e5;//
|
||||
uint8_t ak = pgm_read_byte_near(akemi + ((y * 32)/rows) * 32 + (x * 32)/cols); // akemi[(y * 32)/rows][(x * 32)/cols]
|
||||
switch (ak) {
|
||||
case 0: color = BLACK; break;
|
||||
case 3: armsAndLegsColor.r *= lightFactor; armsAndLegsColor.g *= lightFactor; armsAndLegsColor.b *= lightFactor; color = armsAndLegsColor; break; //light arms and legs 0x9B9B9B
|
||||
case 2: armsAndLegsColor.r *= normalFactor; armsAndLegsColor.g *= normalFactor; armsAndLegsColor.b *= normalFactor; color = armsAndLegsColor; break; //normal arms and legs 0x888888
|
||||
@ -5583,7 +5593,7 @@ uint16_t WS2812FX::mode_2DAkemi(void) {
|
||||
default: color = BLACK;
|
||||
}
|
||||
|
||||
if (SEGMENT.intensity > 128 /*&& fftResult[0] > 128*/) { //dance if base is high
|
||||
if (SEGMENT.intensity > 128 && fftResult && fftResult[0] > 128) { //dance if base is high
|
||||
setPixelColorXY(x, 0, BLACK);
|
||||
setPixelColorXY(x, y+1, color);
|
||||
} else
|
||||
@ -5591,18 +5601,19 @@ uint16_t WS2812FX::mode_2DAkemi(void) {
|
||||
}
|
||||
|
||||
//add geq left and right
|
||||
/*
|
||||
for (uint16_t x=0; x < cols/8; x++) {
|
||||
uint16_t band = x * cols/8;
|
||||
uint16_t barHeight = map(fftResult[band], 0, 255, 0, 17*rows/32);
|
||||
CRGB color = color_from_palette((band * 35), false, PALETTE_SOLID_WRAP, 0);
|
||||
if (um_data && fftResult) {
|
||||
for (uint16_t x=0; x < cols/8; x++) {
|
||||
uint16_t band = x * cols/8;
|
||||
uint16_t barHeight = map(fftResult[band], 0, 255, 0, 17*rows/32);
|
||||
CRGB color = color_from_palette((band * 35), false, PALETTE_SOLID_WRAP, 0);
|
||||
|
||||
for (uint16_t y=0; y < barHeight; y++) {
|
||||
setPixelColorXY(x, rows/2-y, color);
|
||||
setPixelColorXY(cols-1-x, rows/2-y, color);
|
||||
for (uint16_t y=0; y < barHeight; y++) {
|
||||
setPixelColorXY(x, rows/2-y, color);
|
||||
setPixelColorXY(cols-1-x, rows/2-y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
return FRAMETIME;
|
||||
} // mode_2DAkemi
|
||||
static const char *_data_FX_MODE_AKEMI PROGMEM = "2D Akemi@Color speed,Dance;Head palette,Arms & Legs,Eyes & Mouth;Face palette";
|
||||
|
@ -76,6 +76,7 @@
|
||||
#define USERMOD_ID_WORDCLOCK 27 //Usermod "usermod_v2_word_clock.h"
|
||||
#define USERMOD_ID_MY9291 28 //Usermod "usermod_MY9291.h"
|
||||
#define USERMOD_ID_SI7021_MQTT_HA 29 //Usermod "usermod_si7021_mqtt_ha.h"
|
||||
#define USERMOD_ID_AUDIOREACTIVE 30 //Usermod "audioreactive.h"
|
||||
|
||||
//Access point behavior
|
||||
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
|
||||
|
@ -216,11 +216,64 @@ int getSignalQuality(int rssi);
|
||||
void WiFiEvent(WiFiEvent_t event);
|
||||
|
||||
//um_manager.cpp
|
||||
typedef struct UM_Exchange_Data {
|
||||
size_t ub8_size; // size of ub8_data
|
||||
uint8_t **ub8_data; // array of pointers to bytes (pointer can point to an array of bytes, depends on the usermod)
|
||||
size_t uw16_size; // size of uw16_data
|
||||
uint16_t **uw16_data; // array of pointers to unsigned words
|
||||
size_t uw32_size; // size of uw32_data
|
||||
uint32_t **uw32_data; // array of pointers to unsigned long words
|
||||
size_t ui32_size; // size of uw32_data
|
||||
int32_t **ui32_data; // array of pointers to long words
|
||||
size_t uf4_size; // size of ubf4_data
|
||||
float **uf4_data; // array of pointers to floats
|
||||
size_t uf8_size; // size of ubf4_data
|
||||
double **uf8_data; // array of pointers to doubles
|
||||
/*
|
||||
uint8_t ub1, ub2, ub3, ub4; // 4 byte values
|
||||
uint16_t ui1, ui2, *aui1, *aui2, *aui3; // 2 word values and 3 pointers to word arrays/values
|
||||
int16_t ui3, ui4, *aui4, *aui5, *aui6; // 2 signed word values and 3 pointers to signed word arrays/values
|
||||
uint32_t ul1, ul2; // 2 long word values
|
||||
float uf1, uf2, uf3, *auf1, *auf2; // 3 float values and 2 pointers to float arrays/values
|
||||
*/
|
||||
UM_Exchange_Data() {
|
||||
ub8_size = 0;
|
||||
uw16_size = 0;
|
||||
uw32_size = 0;
|
||||
ui32_size = 0;
|
||||
uf4_size = 0;
|
||||
uf8_size = 0;
|
||||
/*
|
||||
ub1 = ub2 = ub3 = ub4 = 0;
|
||||
ui1 = ui2 = ui3 = ui4 = 0;
|
||||
ul1 = ul2 = 0;
|
||||
uf1 = uf2 = uf3 = 0.0f;
|
||||
aui1 = aui2 = aui3 = nullptr;
|
||||
aui4 = aui5 = aui6 = nullptr;
|
||||
auf1 = auf2 = nullptr;
|
||||
*/
|
||||
}
|
||||
~UM_Exchange_Data() {
|
||||
if (ub8_size && ub8_data ) delete[] ub8_data;
|
||||
if (uw16_size && uw16_data) delete[] uw16_data;
|
||||
if (uw32_size && uw32_data) delete[] uw32_data;
|
||||
if (ui32_size && ui32_data) delete[] ui32_data;
|
||||
if (uf4_size && uf4_data ) delete[] uf4_data;
|
||||
if (uf8_size && uf8_data ) delete[] uf8_data;
|
||||
}
|
||||
} um_data_t;
|
||||
const unsigned int um_data_size = sizeof(um_data_t); // about 64 bytes
|
||||
|
||||
class Usermod {
|
||||
protected:
|
||||
um_data_t *um_data; // um_data should be allocated using new in (derived) Usermod's setup() or constructor
|
||||
public:
|
||||
Usermod() { um_data = nullptr; }
|
||||
virtual ~Usermod() { if (um_data) delete um_data; }
|
||||
virtual void loop() {}
|
||||
virtual void handleOverlayDraw() {}
|
||||
virtual bool handleButton(uint8_t b) { return false; }
|
||||
virtual bool getUMData(um_data_t **data) { if (data) *data = nullptr; return false; };
|
||||
virtual void setup() {}
|
||||
virtual void connected() {}
|
||||
virtual void addToJsonState(JsonObject& obj) {}
|
||||
@ -242,6 +295,7 @@ class UsermodManager {
|
||||
void loop();
|
||||
void handleOverlayDraw();
|
||||
bool handleButton(uint8_t b);
|
||||
bool getUMData(um_data_t **um_data, uint8_t mod_id = USERMOD_ID_RESERVED); // USERMOD_ID_RESERVED will poll all usermods
|
||||
void setup();
|
||||
void connected();
|
||||
void addToJsonState(JsonObject& obj);
|
||||
|
@ -13,6 +13,13 @@ bool UsermodManager::handleButton(uint8_t b) {
|
||||
}
|
||||
return overrideIO;
|
||||
}
|
||||
bool UsermodManager::getUMData(um_data_t **data, uint8_t mod_id) {
|
||||
for (byte i = 0; i < numMods; i++) {
|
||||
if (mod_id > 0 && ums[i]->getId() != mod_id) continue; // only get data form requested usermod if provided
|
||||
if (ums[i]->getUMData(data)) return true; // if usermod does provide data return immediately (only one usermod can povide data at one time)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void UsermodManager::setup() { for (byte i = 0; i < numMods; i++) ums[i]->setup(); }
|
||||
void UsermodManager::connected() { for (byte i = 0; i < numMods; i++) ums[i]->connected(); }
|
||||
@ -49,8 +56,7 @@ Usermod* UsermodManager::lookup(uint16_t mod_id) {
|
||||
bool UsermodManager::add(Usermod* um)
|
||||
{
|
||||
if (numMods >= WLED_MAX_USERMODS || um == nullptr) return false;
|
||||
ums[numMods] = um;
|
||||
numMods++;
|
||||
ums[numMods++] = um;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user