Audioreactive usermod.

This commit is contained in:
Blaz Kristan 2022-06-08 21:14:01 +02:00
parent 9db872db56
commit 184ff7a3b3
7 changed files with 1776 additions and 57 deletions

File diff suppressed because it is too large Load Diff

View 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
};
}
};

View 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)_

View File

@ -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";

View File

@ -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

View File

@ -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);

View File

@ -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;
}