Feature complete... and better!
This commit is contained in:
@@ -141,9 +141,9 @@ static uint8_t fftResult[NUM_GEQ_CHANNELS]= {0}; // Our calculated freq. chann
|
||||
static float fftCalc[NUM_GEQ_CHANNELS] = {0.0f}; // Try and normalize fftBin values to a max of 4096, so that 4096/16 = 256. (also used by dynamics limiter)
|
||||
static float fftAvg[NUM_GEQ_CHANNELS] = {0.0f}; // Calculated frequency channel results, with smoothing (used if dynamics limiter is ON)
|
||||
|
||||
unsigned int zeroCrossingCount = 0;
|
||||
unsigned int energy = 0;
|
||||
unsigned int lowFreqencyContent = 0;
|
||||
uint_fast32_t zeroCrossingCount = 0;
|
||||
uint_fast32_t energy = 0;
|
||||
uint_fast32_t lowFreqencyContent = 0;
|
||||
|
||||
// TODO: probably best not used by receive nodes
|
||||
static float agcSensitivity = 128; // AGC sensitivity estimation, based on agc gain (multAgc). calculated by getSensitivity(). range 0..255
|
||||
@@ -498,6 +498,21 @@ void FFTcode(void * parameter)
|
||||
|
||||
// get a fresh batch of samples from I2S
|
||||
if (audioSource) audioSource->getSamples(vReal, samplesFFT);
|
||||
|
||||
// WLED-MM/TroyHacks: Calculate zero crossings
|
||||
//
|
||||
zeroCrossingCount = 0;
|
||||
energy = 0;
|
||||
|
||||
for (int i=0; i < samplesFFT; i++) {
|
||||
if (i < (samplesFFT)-2) {
|
||||
if((vReal[i] >= 0 && vReal[i+1] < 0) || (vReal[i+1] < 0 && vReal[i+1] >= 0)) {
|
||||
zeroCrossingCount++;
|
||||
}
|
||||
}
|
||||
// WLED-MM/TroyHacks: Calculate energy
|
||||
energy += (vReal[i] * vReal[i])/10000000;
|
||||
}
|
||||
|
||||
#if defined(WLED_DEBUG) || defined(SR_DEBUG)|| defined(SR_STATS)
|
||||
// debug info in case that stack usage changes
|
||||
@@ -549,24 +564,13 @@ void FFTcode(void * parameter)
|
||||
|
||||
// find highest sample in the batch
|
||||
float maxSample = 0.0f; // max sample from FFT batch
|
||||
zeroCrossingCount = 0;
|
||||
for (int i=0; i < samplesFFT; i++) {
|
||||
// set imaginary parts to 0
|
||||
vImag[i] = 0;
|
||||
// pick our our current mic sample - we take the max value from all samples that go into FFT
|
||||
if ((vReal[i] <= (INT16_MAX - 1024)) && (vReal[i] >= (INT16_MIN + 1024))) //skip extreme values - normally these are artefacts
|
||||
if (fabsf((float)vReal[i]) > maxSample) maxSample = fabsf((float)vReal[i]);
|
||||
|
||||
// WLED-MM/TroyHacks: Calculate zero crossings
|
||||
if (i < samplesFFT-2) {
|
||||
if (vReal[i] * vReal[i+1] < 0) {
|
||||
zeroCrossingCount++;
|
||||
}
|
||||
}
|
||||
// WLED-MM/TroyHacks: Calculate energy
|
||||
energy += vReal[i] * vReal[i];
|
||||
}
|
||||
energy /= 100000; // WLED-MM/TroyHacks: scale this down becasue we're gonna make it bigger later
|
||||
|
||||
// release highest sample to volume reactive effects early - not strictly necessary here - could also be done at the end of the function
|
||||
// early release allows the filters (getSample() and agcAvg()) to work with fresh values - we will have matching gain and noise gate values when we want to process the FFT results.
|
||||
@@ -641,6 +645,17 @@ void FFTcode(void * parameter)
|
||||
#endif
|
||||
FFT_MajorPeak = constrain(FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects
|
||||
FFT_MajPeakSmth = FFT_MajPeakSmth + 0.42 * (FFT_MajorPeak - FFT_MajPeakSmth); // I like this "swooping peak" look
|
||||
|
||||
// WLED-MM/TroyHacks: Calculate Low-Frequency Content
|
||||
//
|
||||
lowFreqencyContent = fftAddAvg(2,4)/1000;
|
||||
|
||||
// USER_PRINT("ZCR = ");
|
||||
// USER_PRINT(zeroCrossingCount);
|
||||
// USER_PRINT(" Energy = ");
|
||||
// USER_PRINT(" LFC = ");
|
||||
// USER_PRINT(lowFreqencyContent);
|
||||
// USER_PRINTLN();
|
||||
|
||||
} else { // skip second run --> clear fft results, keep peaks
|
||||
memset(vReal, 0, sizeof(vReal));
|
||||
@@ -662,8 +677,6 @@ void FFTcode(void * parameter)
|
||||
vReal[i] = t / 16.0f; // Reduce magnitude. Want end result to be scaled linear and ~4096 max.
|
||||
} // for()
|
||||
|
||||
lowFreqencyContent = fftAddAvg(1,4); // WLED-MM/TroyHacks: Calculate Low-Frequency Content
|
||||
|
||||
// mapping of FFT result bins to frequency channels
|
||||
//if (fabsf(sampleAvg) > 0.25f) { // noise gate open
|
||||
if (fabsf(volumeSmth) > 0.25f) { // noise gate open
|
||||
@@ -1738,7 +1751,7 @@ class AudioReactive : public Usermod {
|
||||
// usermod exchangeable data
|
||||
// we will assign all usermod exportable data here as pointers to original variables or arrays and allocate memory for pointers
|
||||
um_data = new um_data_t;
|
||||
um_data->u_size = 11;
|
||||
um_data->u_size = 13;
|
||||
um_data->u_type = new um_types_t[um_data->u_size];
|
||||
um_data->u_data = new void*[um_data->u_size];
|
||||
um_data->u_data[0] = &volumeSmth; //*used (New)
|
||||
@@ -1764,10 +1777,12 @@ class AudioReactive : public Usermod {
|
||||
um_data->u_type[9] = UMT_FLOAT;
|
||||
um_data->u_data[10] = &agcSensitivity; // used (New)
|
||||
um_data->u_type[10] = UMT_FLOAT;
|
||||
unsigned int* extra[3] = {&zeroCrossingCount, &energy, &lowFreqencyContent};
|
||||
um_data->u_data[11] = extra; //
|
||||
um_data->u_type[11] = UMT_INT16_ARR;
|
||||
|
||||
um_data->u_data[11] = &zeroCrossingCount;
|
||||
um_data->u_type[11] = UMT_UINT32;
|
||||
um_data->u_data[12] = &energy;
|
||||
um_data->u_type[12] = UMT_UINT32;
|
||||
um_data->u_data[13] = &lowFreqencyContent;
|
||||
um_data->u_type[13] = UMT_UINT32;
|
||||
#else
|
||||
// ESP8266
|
||||
// See https://github.com/MoonModules/WLED/pull/60#issuecomment-1666972133 for explanation of these alternative sources of data
|
||||
|
||||
@@ -13,28 +13,32 @@ class AutoPlaylistUsermod : public Usermod {
|
||||
int timeout = 60;
|
||||
bool autoChange = false;
|
||||
byte lastAutoPlaylist = 0;
|
||||
int change_timer = millis();
|
||||
|
||||
int avg_long_energy = 10000;
|
||||
int avg_long_lfc = 1000;
|
||||
int avg_long_zcr = 100;
|
||||
uint_fast32_t avg_long_energy = 250;
|
||||
uint_fast32_t avg_long_lfc = 1000;
|
||||
uint_fast32_t avg_long_zcr = 500;
|
||||
|
||||
int avg_short_energy = 10000;
|
||||
int avg_short_lfc = 1000;
|
||||
int avg_short_zcr = 100;
|
||||
uint_fast32_t avg_short_energy = 250;
|
||||
uint_fast32_t avg_short_lfc = 1000;
|
||||
uint_fast32_t avg_short_zcr = 500;
|
||||
|
||||
int vector_energy = 0;
|
||||
int vector_lfc = 0;
|
||||
int vector_zcr = 0;
|
||||
uint_fast32_t vector_energy = 0;
|
||||
uint_fast32_t vector_lfc = 0;
|
||||
uint_fast32_t vector_zcr = 0;
|
||||
|
||||
uint_fast32_t distance = 0;
|
||||
|
||||
// uint_fast64_t squared_distance = 0;
|
||||
|
||||
int squared_distance = 0;
|
||||
int lastchange = millis();
|
||||
|
||||
int last_beat_interval = millis();
|
||||
int change_threshold = 10;
|
||||
int change_threshold = 50;
|
||||
|
||||
int change_lockout = 100; // never change below this numnber of millis. I think of this more like a debounce, but opinions may vary.
|
||||
int ideal_change_min = 2000; // ideally change patterns no less than this number of millis
|
||||
int ideal_change_max = 5000; // ideally change patterns no more than this number of millis
|
||||
int change_lockout = 1000; // never change below this numnber of millis. I think of this more like a debounce, but opinions may vary.
|
||||
int ideal_change_min = 10000; // ideally change patterns no less than this number of millis
|
||||
int ideal_change_max = 20000; // ideally change patterns no more than this number of millis
|
||||
|
||||
std::vector<int> autoChangeIds;
|
||||
|
||||
@@ -43,10 +47,15 @@ class AutoPlaylistUsermod : public Usermod {
|
||||
static const char _musicPlaylist[];
|
||||
static const char _timeout[];
|
||||
static const char _autoChange[];
|
||||
static const char _change_lockout[];
|
||||
static const char _ideal_change_min[];
|
||||
static const char _ideal_change_max[];
|
||||
|
||||
public:
|
||||
|
||||
AutoPlaylistUsermod(bool enabled):Usermod("AutoPlaylist", enabled) {}
|
||||
AutoPlaylistUsermod(bool enabled):Usermod("AutoPlaylist", enabled) {
|
||||
// noop
|
||||
}
|
||||
|
||||
// gets called once at boot. Do all initialization that doesn't depend on
|
||||
// network here
|
||||
@@ -56,47 +65,62 @@ class AutoPlaylistUsermod : public Usermod {
|
||||
|
||||
// gets called every time WiFi is (re-)connected. Initialize own network
|
||||
// interfaces here
|
||||
void connected() {}
|
||||
void connected() {
|
||||
// noop
|
||||
}
|
||||
|
||||
void change(um_data_t *um_data) {
|
||||
|
||||
int *extra = (int*) um_data->u_data[11];
|
||||
|
||||
unsigned int zcr = extra[0];
|
||||
int energy = extra[1];
|
||||
int lfc = extra[2]; // getFFTFromRange(um_data, 2 , 4);
|
||||
|
||||
USER_PRINTF("zcr = %d, energy = %d, lfc = %d\n",zcr,energy,lfc);
|
||||
uint_fast32_t zcr = *(uint_fast32_t*)um_data->u_data[11];
|
||||
uint_fast32_t energy = *(uint_fast32_t*)um_data->u_data[12];
|
||||
uint_fast32_t lfc = *(uint_fast32_t*)um_data->u_data[13];
|
||||
|
||||
// WLED-MM/TroyHacks: Calculate the long- and short-running averages
|
||||
// and the squared_distance for the vector.
|
||||
|
||||
avg_long_energy = avg_long_energy * 0.99 + energy * 0.01;
|
||||
avg_long_lfc = avg_long_lfc * 0.99 + lfc * 0.01;
|
||||
avg_long_zcr = avg_long_zcr * 0.99 + zcr * 0.01;
|
||||
if (volumeSmth > 0.1) {
|
||||
|
||||
avg_short_energy = avg_short_energy * 0.9 + energy * 0.1;
|
||||
avg_short_lfc = avg_short_lfc * 0.9 + lfc * 0.1;
|
||||
avg_short_zcr = avg_short_zcr * 0.9 + zcr * 0.1;
|
||||
avg_long_energy = avg_long_energy * 0.99 + energy * 0.01;
|
||||
avg_long_lfc = avg_long_lfc * 0.99 + lfc * 0.01;
|
||||
avg_long_zcr = avg_long_zcr * 0.99 + zcr * 0.01;
|
||||
|
||||
energy = 0;
|
||||
lfc = 0;
|
||||
zcr = 0;
|
||||
avg_short_energy = avg_short_energy * 0.9 + energy * 0.1;
|
||||
avg_short_lfc = avg_short_lfc * 0.9 + lfc * 0.1;
|
||||
avg_short_zcr = avg_short_zcr * 0.9 + zcr * 0.1;
|
||||
|
||||
// allegedly this is faster than pow(whatever,2)
|
||||
vector_lfc = (avg_short_lfc-avg_long_lfc)*(avg_short_lfc-avg_long_lfc);
|
||||
vector_energy = (avg_short_energy-avg_long_energy)*(avg_short_energy-avg_long_energy);
|
||||
vector_zcr = (avg_short_zcr-avg_long_zcr)*(avg_short_zcr-avg_long_zcr);
|
||||
// allegedly this is faster than pow(whatever,2)
|
||||
vector_lfc = (avg_short_lfc-avg_long_lfc)*(avg_short_lfc-avg_long_lfc);
|
||||
vector_energy = (avg_short_energy-avg_long_energy)*(avg_short_energy-avg_long_energy);
|
||||
vector_zcr = (avg_short_zcr-avg_long_zcr)*(avg_short_zcr-avg_long_zcr);
|
||||
|
||||
squared_distance = vector_lfc + vector_energy + vector_zcr;
|
||||
}
|
||||
|
||||
distance = vector_lfc + vector_energy + vector_zcr;
|
||||
// USER_PRINTF("squared_distance = %d\n", squared_distance * squared_distance / 10000000);
|
||||
|
||||
squared_distance = squared_distance * squared_distance / 10000000; // shorten just because it's a big number
|
||||
// squared_distance = distance * distance;
|
||||
|
||||
int change_interval = millis()-lastchange;
|
||||
|
||||
// if (millis() > change_timer + 100) {
|
||||
// // if (change_interval > ideal_change_max) {
|
||||
// // USER_PRINTF("Increasing sensitivity to: %d\n",change_threshold++);
|
||||
// // }
|
||||
// USER_PRINT("\tDistance: ");
|
||||
// USER_PRINT(distance);
|
||||
// USER_PRINT("\tv_lfc: ");
|
||||
// USER_PRINT(vector_lfc);
|
||||
// USER_PRINT("\tv_energy: ");
|
||||
// USER_PRINT(vector_energy);
|
||||
// USER_PRINT("\tv_zcr: ");
|
||||
// USER_PRINTLN(vector_zcr);
|
||||
|
||||
// change_timer = millis();
|
||||
// }
|
||||
|
||||
// WLED-MM/TroyHacks - Change pattern testing
|
||||
//
|
||||
int change_interval = millis()-lastchange;
|
||||
if (squared_distance <= change_threshold && change_interval > change_lockout) {
|
||||
if (distance <= change_threshold && change_interval > change_lockout && volumeSmth > 0.1) {
|
||||
if (change_interval > ideal_change_max) {
|
||||
change_threshold += 1;
|
||||
} else if (change_interval < ideal_change_min) {
|
||||
@@ -122,19 +146,16 @@ class AutoPlaylistUsermod : public Usermod {
|
||||
while (currentPreset == newpreset);
|
||||
|
||||
applyPreset(newpreset);
|
||||
USER_PRINTF("*** CHANGE! Squared distance = %d - change interval was %d ms - next change min is %d\n",squared_distance, change_interval, change_threshold);
|
||||
USER_PRINT("*** CHANGE! Vector distance = ");
|
||||
USER_PRINT(distance);
|
||||
USER_PRINT(" - change interval was ");
|
||||
USER_PRINT(change_interval);
|
||||
USER_PRINT("ms - next change min is ");
|
||||
USER_PRINTLN(change_threshold);
|
||||
lastchange = millis();
|
||||
}
|
||||
}
|
||||
|
||||
// // static float fftAddAvgLin(int from, int to) {
|
||||
// float result = 0.0f;
|
||||
// for (int i = from; i <= to; i++) {
|
||||
// result += vReal[i];
|
||||
// }
|
||||
// return result / float(to - from + 1);
|
||||
// }
|
||||
|
||||
uint8_t getFFTFromRange(um_data_t *data, uint8_t from, uint8_t to) {
|
||||
uint8_t *fftResult = (uint8_t*) data->u_data[2];
|
||||
uint16_t result = 0;
|
||||
@@ -268,6 +289,9 @@ class AutoPlaylistUsermod : public Usermod {
|
||||
top[FPSTR(_ambientPlaylist)] = ambientPlaylist; // usermodparam
|
||||
top[FPSTR(_musicPlaylist)] = musicPlaylist; // usermodparam
|
||||
top[FPSTR(_autoChange)] = autoChange;
|
||||
top[FPSTR(_change_lockout)] = change_lockout;
|
||||
top[FPSTR(_ideal_change_min)] = ideal_change_min;
|
||||
top[FPSTR(_ideal_change_max)] = ideal_change_max;
|
||||
lastAutoPlaylist = 0;
|
||||
DEBUG_PRINTLN(F("AutoPlaylist config saved."));
|
||||
}
|
||||
@@ -296,6 +320,9 @@ class AutoPlaylistUsermod : public Usermod {
|
||||
getJsonValue(top[_ambientPlaylist], ambientPlaylist);
|
||||
getJsonValue(top[_musicPlaylist], musicPlaylist);
|
||||
getJsonValue(top[_autoChange], autoChange);
|
||||
getJsonValue(top[_change_lockout], change_lockout);
|
||||
getJsonValue(top[_ideal_change_min], ideal_change_min);
|
||||
getJsonValue(top[_ideal_change_max], ideal_change_max);
|
||||
|
||||
DEBUG_PRINTLN(F(" config (re)loaded."));
|
||||
|
||||
@@ -324,8 +351,11 @@ class AutoPlaylistUsermod : public Usermod {
|
||||
|
||||
};
|
||||
|
||||
const char AutoPlaylistUsermod::_enabled[] PROGMEM = "enabled";
|
||||
const char AutoPlaylistUsermod::_ambientPlaylist[] PROGMEM = "ambientPlaylist";
|
||||
const char AutoPlaylistUsermod::_musicPlaylist[] PROGMEM = "musicPlaylist";
|
||||
const char AutoPlaylistUsermod::_timeout[] PROGMEM = "timeout";
|
||||
const char AutoPlaylistUsermod::_autoChange[] PROGMEM = "autoChange";
|
||||
const char AutoPlaylistUsermod::_enabled[] PROGMEM = "enabled";
|
||||
const char AutoPlaylistUsermod::_ambientPlaylist[] PROGMEM = "ambientPlaylist";
|
||||
const char AutoPlaylistUsermod::_musicPlaylist[] PROGMEM = "musicPlaylist";
|
||||
const char AutoPlaylistUsermod::_timeout[] PROGMEM = "timeout";
|
||||
const char AutoPlaylistUsermod::_autoChange[] PROGMEM = "autoChange";
|
||||
const char AutoPlaylistUsermod::_change_lockout[] PROGMEM = "change_lockout";
|
||||
const char AutoPlaylistUsermod::_ideal_change_min[] PROGMEM = "ideal_change_min";
|
||||
const char AutoPlaylistUsermod::_ideal_change_max[] PROGMEM = "ideal_change_max";
|
||||
|
||||
Reference in New Issue
Block a user