[SOLVED] Decibel meter peak hold LED issues

I am using a pre-made decibel meter with a 24 RGB LED Adafruit Neopixel ring and FastLED.

The code works, but the peak hold LED flickers with the value stored in timePeakHold.

Also the peak hold RGB LED drops quickly and the whole thing could react smoother, but maybe that depends on the noise input to the decibel meter's microphone (I am making a noise reactive device for an underground station), and there are only 24 RGB LEDs in the ring.

Any ideas how to solve the first problem (and maybe the two secondary problems) would be much appreciated.

ps: If needed, I could make a short video and put it in my Dropbox for a while.

#include "FastLED.h"

const byte pinData = 6;
const byte pinSEN0232 = A0;
const byte pinPotentiometerMin = A1;
const byte pinPotentiometerMax = A2;

const float EMA_a = 0.4;

const byte ledCount = 24;
const byte ledBrightness = 128;
struct CRGB ledRing[ledCount];
struct CRGB ledGradient[ledCount];
CHSV gradientHueStart = CHSV(96, 255, 100);
CHSV grandientHueEnd = CHSV(0, 255, 100);

long timePeakHold = 500;

long dBValue = 0;
int dBValueEMA = 0;
byte dBMin = 0;
byte dBMax = 0;

int ledPeak = 0;
int ledPeakPrevious = 0;
long timeLastPeak = 0;

void setup()
{
  FastLED.addLeds<NEOPIXEL, pinData>(ledRing, ledCount);

  FastLED.setBrightness(ledBrightness);

  fill_gradient(ledGradient, 0, gradientHueStart, 23, grandientHueEnd, SHORTEST_HUES);
}

void loop()
{
  readSEN0232();

  readPotentiometers();

  if (ledPeak > ledPeakPrevious)
  {
    ledPeakPrevious = ledPeak;

    timeLastPeak = millis();
  }

  if (millis() > (timeLastPeak + timePeakHold))
  {
    ledPeakPrevious = ledPeakPrevious - 1;
  }

  FastLED.clear();

  for ( byte i = 0; i <= ledPeak; i++)
  {
    ledRing[i] = ledGradient[i];
  }

  ledRing[ledPeakPrevious] = ledGradient[ledCount - 1];

  FastLED.show();
}

void readSEN0232()
{
  float voltageValue;
  
  voltageValue = analogRead(pinSEN0232) / 1024.0 * 5.0;
  dBValue = voltageValue * 50.0;

  dBValueEMA = int ((EMA_a * dBValue) + ((1 - EMA_a) * dBValueEMA)) + 0.5;

  ledPeak = constrain(map(dBValueEMA, dBMin, dBMax, 0, 23), 0, 23);
}

void readPotentiometers()
{
  dBMin = constrain(map(analogRead(pinPotentiometerMin), 0, 1023, 35, 60), 35, 60);
  dBMax = constrain(map(analogRead(pinPotentiometerMax), 0, 1023, 60, 130), 60, 130);
}

Is the input a steady tone, if so you should have a steady light, if not it will flicker.

If instead of

ledRing[ledPeakPrevious] = ledGradient[ledCount - 1];

or

ledRing[ledPeakPrevious] = CHSV(0, 255, 255);

I use

ledRing[ledPeakPrevious] = ledGradient[ledCount];

the peak hold LED does not flicker (albeit then the colour is different). Currently, it flickers at the interval of the time specified here

long timePeakHold = 500;

whether the noise is below the 35 dB threshold as well as in operation, so to speak.

ledGradient[ledCount]

This would be accessing an index outside the array because the last index is 23. That would result in accessing memory used by some other variable. I could see that causing flickering, so it is strange that this should appear to fix the flickering.

The word "flickering" is normally used to describe flashing which is rapid and random, like a candle flame. 500ms flashing would be slow and steady. Can you explain more what you mean by "flickering"?

Thanks, pulsing could be the better word, because it is regular, at the timePeakHold variable interval.

Ok, thanks.

Not sure what's wrong now, knowing that. If the music pulses at the same level, that led is going to make the led pulse also. For sure, I cannot explain what you described in post #3.

I found out why the peak hold LED flickers/pulses; this is due to

FastLED.clear();

with each loop() iteration.

So, the instant peak hold LED drop-down problem prevails. I made a very short video showing the problem how it does not fall back slowly. Maybe you have an idea about how to do that?

No, nothing wrong with using .clear(). But I think I might have figured it out.


  if (millis() > (timeLastPeak + timePeakHold))
  {
    ledPeakPrevious = ledPeakPrevious - 1;
    timeLastPeak = millis(); //add this line
  }

Also you might want to try reducing the value of EMA_a to 0.1 or 0.05. This will smooth the sound level more.

Thanks a lot for your help, that was it indeed; the current time needs being taken there, too.

I set EMA_a to 0.6, as the underground station's noise fluctuates a fair bit at certain times. I will now add an ESP32 to the set-up to transmit the raw decibel values to ThingSpeak every second.

I leave the code here below for others who need a min/max level-adjustable display of peaking sensor values, it actually reminds me of these "VU meters with peak hold" from 80s HiFi.

#include "FastLED.h"

const byte pinData = 6;
const byte pinSEN0232 = A0;
const byte pinPotentiometerMin = A1;
const byte pinPotentiometerMax = A2;

const float EMA_a = 0.6;

const byte ledCount = 24;
const byte ledBrightness = 192;
struct CRGB ledRing[ledCount];
struct CRGB ledGradient[ledCount];
CHSV gradientHueStart = CHSV(96, 255, 100);
CHSV grandientHueEnd = CHSV(0, 255, 100);

long timePeakHold = 50;

long dBValue = 0;
int dBValueEMA = 0;
byte dBMin = 0;
byte dBMax = 0;

int ledPeak = 0;
int ledPeakPrevious = 0;
long timeLastPeak = 0;

void setup()
{
  FastLED.addLeds<NEOPIXEL, pinData>(ledRing, ledCount);

  FastLED.setBrightness(ledBrightness);

  fill_gradient(ledGradient, 0, gradientHueStart, 23, grandientHueEnd, SHORTEST_HUES);
}

void loop()
{
  readSEN0232();

  readPotentiometers();

  if (ledPeak > ledPeakPrevious)
  {
    ledPeakPrevious = ledPeak;

    timeLastPeak = millis();
  }

  if (millis() > (timeLastPeak + timePeakHold))
  {
    ledPeakPrevious = ledPeakPrevious - 1;
    
    timeLastPeak = millis();
  }

  FastLED.clear();

  for ( byte i = 0; i <= ledPeak; i++)
  {
    ledRing[i] = ledGradient[i];
  }

  ledRing[ledPeakPrevious] = CHSV(0, 255, 255);

  FastLED.show();
}

void readSEN0232()
{
  float voltageValue;

  voltageValue = analogRead(pinSEN0232) / 1024.0 * 5.0;
  dBValue = voltageValue * 50.0;

  dBValueEMA = int ((EMA_a * dBValue) + ((1 - EMA_a) * dBValueEMA)) + 0.5;

  ledPeak = constrain(map(dBValueEMA, dBMin, dBMax, 0, 23), 0, 23);
}

void readPotentiometers()
{
  dBMin = constrain(map(analogRead(pinPotentiometerMin), 0, 1023, 35, 60), 35, 60);
  dBMax = constrain(map(analogRead(pinPotentiometerMax), 0, 1023, 60, 130), 60, 130);
}

After some days, I realised that this is a too rudimentary solution.

There is no peakHold and no peakDecay. For people to really notice, they'd like to have the peak LED remaining "on" for a fraction of time, then either dimming or "dropping down". This needs to be done with millis(), but not like above. Meanwhile, I found some similar threads, but they relate to "VU-meters" with lots of options and stereo, quite convoluted, which I cannot reverse engineer, so to speak.

I made a chart to try understanding this better. Maybe I should open a new thread?

Updated the chart, it had an error.

No, that is considered "cross-posting" and is against forum rules.

I don't think this is very different from your previous idea. The difference is that the peak indicator does not move/drop gradually. Instead, its position remains fixed and it's brightness drops gradually?

So when a new peak is detected, you would additionally need to set it's brightness to maximum:

if (ledPeak > ledPeakPrevious)
  {
    ledPeakPrevious = ledPeak;
    ledPeakBrightness = 255;
    timeLastPeak = millis();
  }

After the hold period, instead of moving/dropping the peak, reduce its brightness. If the brightness is now zero, only then lower the peak:

  if (millis() > (timeLastPeak + timePeakHold))
  {
    
    if (ledPeakBrightness > 20) {
      ledPeakBrightness -= 20;
      }
    else {
      ledPeakBrightness = 0;
      ledPeakPrevious = ledPeak;
      }
    timeLastPeak = millis();
  }

Finally, you light the peak led at the required brightness:

ledRing[ledPeakPrevious] = CHSV(0, 255, ledPeakBrightness);

Thanks, very much appreciated. I had this partial code now, with a decay flag, not tried

  if (newPeak >= previousPeak)
  {
    previousPeak = newPeak;
    timePeak = millis();
    decay = false;
  }

  else if (!decay && (millis() - timePeak >= intervalPeakHold))
  {
    timePeak += intervalPeakHold - intervalPeakDecay;
    decay = true;
  }

  else if (decay && (millis() - timePeak > intervalPeakDecay))
  {
    if (previousPeak > 0)
    {
      previousPeak --;
      timePeak += intervalPeakDecay;
    }
  }

but as soon as I can get to the equipment, I shall try out your suggestion first.

Why would you want to make things difficult for people by separating a related question from all its important background?

All right, thanks again PaulRB, it's good now. Making a chart also helped me to get my head around what happens when. I leave this final(?) version here for others. It's really similar to those "VU-meters" of old now, lol, but, oh well...

#include "FastLED.h"

const byte pinData = 6;
const byte pinSEN0232 = A0;
const byte pinPotentiometerMin = A1;
const byte pinPotentiometerMax = A2;

const float EMA_a = 0.7; // Lower is more laggy but smoother

const byte ledCount = 24;
const byte ledBrightness = 192;
struct CRGB ledRing[ledCount];
struct CRGB ledGradient[ledCount];
CHSV gradientHueStart = CHSV(96, 255, 64); // Darker at the bottom
CHSV grandientHueEnd = CHSV(0, 255, 192);

const int intervalPeakHold = 900;
const int intervalPeakDecay = 30;

long dBValue = 0;
int dBValueEMA = 0;
byte dBMin = 0;
byte dBMax = 0;

int newPeak = 0;
int previousPeak = 0;
unsigned long timePeak = 0;
byte brightnessPeak = 192;
bool decay = false;

void setup()
{
  FastLED.addLeds<NEOPIXEL, pinData>(ledRing, ledCount);

  FastLED.setBrightness(ledBrightness);

  fill_gradient(ledGradient, 0, gradientHueStart, 23, grandientHueEnd, SHORTEST_HUES);
}

void loop()
{
  readSEN0232();

  readPotentiometers();

  if (newPeak >= previousPeak)
  {
    previousPeak = newPeak;
    timePeak = millis();
    brightnessPeak = 192;
    decay = false;
  }

  else if (!decay && (millis() - timePeak >= intervalPeakHold))
  {
    timePeak += intervalPeakHold - intervalPeakDecay;
    decay = true;
  }

  else if (decay && (millis() - timePeak > intervalPeakDecay))
  {
    if (previousPeak > 0)
    {
      previousPeak --;

      if (brightnessPeak <= 0)
      {
        brightnessPeak = 0;
      }
      else
      {
      brightnessPeak -= 16;
      }
      timePeak += intervalPeakDecay;
    }
  }

  FastLED.clear();

  for ( byte i = 0; i <= newPeak; i++)
  {
    ledRing[i] = ledGradient[i];
  }

  ledRing[previousPeak] = CHSV(0, 255, brightnessPeak);

  FastLED.show();
}

void readSEN0232()
{
  float voltageValue;

  voltageValue = analogRead(pinSEN0232) / 1024.0 * 5.0;
  dBValue = voltageValue * 50.0;

  dBValueEMA = int ((EMA_a * dBValue) + ((1 - EMA_a) * dBValueEMA)) + 0.5;

  newPeak = constrain(map(dBValueEMA, dBMin, dBMax, 0, 23), 0, 23);
}

void readPotentiometers()
{
  dBMin = constrain(map(analogRead(pinPotentiometerMin), 0, 1023, 35, 60), 35, 60);
  dBMax = constrain(map(analogRead(pinPotentiometerMax), 0, 1023, 60, 130), 60, 130);
}