Measuring and comparing average audio reading

I'm still on a mission to create my project that will detect audio anomalies in machinery for early warning of bearing failure. I've included the code that I have admittedly had 'external help' with and am still very much a beginner. In my code the initial idea was to:
1.) Take a 5 second "average" reading of the audio using an Esp32 with a max4466 mic and save that pattern for reference.
2.) Run a 'live' audio analysis with smoothing to try and measure only constant audio, not intermittent sounds, and then display the difference between the two, so that I can detect 'new' sounds developing.
I have concluded that having two different types of measuring only complicates matters and I would like to change the code so that I do a 5 second average and save the first pattern, and then keep doing the exact same reading and compare it with the first to show any increases of an frequency. In time I will change the amount of time that the readings are done once I get an idea of how well it is working.
I know it's a long bit of code, with possibly many mistakes, all I'm asking is if someone could have a look at the first 5 second reading that is taken and tell me whether that will actually return an average reading over the first 5 seconds of monitoring so I know that I can proceed from there.

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <arduinoFFT.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

#define SAMPLES 256
#define SAMPLING_FREQUENCY 32000  // Placeholder, actual sampling rate will be measured
#define SCALING_FACTOR 0.0001
#define AVERAGE_TIME 5000
#define CONSECUTIVE_DETECTIONS_REQUIRED 3
#define MIN_FREQUENCY 3500  // Minimum frequency to consider (500 Hz)
#define LED_PIN 23
#define POTENTIOMETER_PIN 13

arduinoFFT FFT = arduinoFFT();

double vReal[SAMPLES];
double vImag[SAMPLES];
double initialFFT[SAMPLES / 2];
double liveFFT[SAMPLES / 2];
double previousFFT[SAMPLES / 2] = {0};  // Buffer for previous FFT results

unsigned long lastHighestFrequencyUpdate = 0;
const unsigned long HIGHEST_FREQUENCY_UPDATE_INTERVAL = 1000;

double highestFrequency = -1;
bool frequencyUpdated = false;

int consecutiveDetections = 0;

unsigned long frequencyStartTime = 0;
unsigned long constantFrequencyTime = 2000;  // 2 seconds for constant detection

int detectionThreshold = 250;  // Default threshold value

void setup() {
  Serial.begin(115200);

  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { 
    Serial.println(F("SSD1306 allocation failed"));
    for (;;);
  }
  display.setRotation(0); // Rotate the display upside down
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.println(F("Initializing..."));
  display.display();

  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
  pinMode(POTENTIOMETER_PIN, INPUT);  // Set potentiometer pin as input

  // Measure actual sampling frequency
  unsigned long actualSamplingFrequency = measureSamplingFrequency();

  // Capture and average the initial FFT data over 5 seconds
  captureFFT(initialFFT, true, actualSamplingFrequency);

  display.clearDisplay();
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.print("Freq: ");
  display.display();
}

void loop() {
  detectionThreshold = analogRead(POTENTIOMETER_PIN);  // Read value from potentiometer
  detectionThreshold = map(detectionThreshold, 0, 4095, 10, 1000);  // Map value to threshold range

 // Round the threshold to the nearest multiple of 10
  detectionThreshold = (detectionThreshold / 10) * 10;
  unsigned long actualSamplingFrequency = measureSamplingFrequency();
  captureFFT(liveFFT, false, actualSamplingFrequency);
  displayFFT(liveFFT);

  double newHighestFrequency = getFrequencyOfHighestSpike(liveFFT, actualSamplingFrequency);

  if (newHighestFrequency > MIN_FREQUENCY) {
    if (millis() - lastHighestFrequencyUpdate >= HIGHEST_FREQUENCY_UPDATE_INTERVAL) {
      if (newHighestFrequency == highestFrequency) {
        consecutiveDetections++;
      } else {
        highestFrequency = newHighestFrequency;
        consecutiveDetections = 1;
      }

      if (consecutiveDetections >= CONSECUTIVE_DETECTIONS_REQUIRED) {
        if (millis() - frequencyStartTime >= constantFrequencyTime) {
          digitalWrite(LED_PIN, HIGH);
        }
      } else {
        frequencyStartTime = millis();
        digitalWrite(LED_PIN, LOW);
      }

      lastHighestFrequencyUpdate = millis();
    }
  } else {
    digitalWrite(LED_PIN, LOW);
    consecutiveDetections = 0;
    highestFrequency = -1;
  }

  displayFrequency(newHighestFrequency);
  delay(100);
}

unsigned long measureSamplingFrequency() {
    unsigned long startMicros = micros();
    for (int i = 0; i < SAMPLES; i++) {
        vReal[i] = analogRead(34);  // Reading microphone data
    }
    unsigned long elapsedMicros = micros() - startMicros;
    double actualSamplingRate = (1000000.0 / (elapsedMicros / SAMPLES));
    Serial.print("Actual Sampling Rate: ");
    Serial.println(actualSamplingRate);
    return actualSamplingRate;
}

void captureFFT(double* fftResults, bool initial, unsigned long samplingFrequency) {
    double maxFrequencies[SAMPLES / 2] = {0};
    unsigned long startTime = millis();
    double previousAverage[SAMPLES / 2] = {0}; // Buffer for smoothing

    if (initial) {
        int count = 0;
        while (millis() - startTime < AVERAGE_TIME) {
            for (int i = 0; i < SAMPLES; i++) {
                vReal[i] = analogRead(34);
                vImag[i] = 0;
            }

            FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
            FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
            FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);

            for (int i = 2; i < SAMPLES / 2; i++) {
                maxFrequencies[i] += vReal[i];
            }

            count++;
        }

        for (int i = 0; i < SAMPLES / 2; i++) {
            double averageValue = maxFrequencies[i] / count;
            fftResults[i] = (previousAverage[i] * 0.9) + (averageValue * 0.1);
            previousAverage[i] = fftResults[i];
        }
    } else {
        for (int i = 0; i < SAMPLES; i++) {
            vReal[i] = analogRead(34);
            vImag[i] = 0;
        }

        FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
        FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
        FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);

        for (int i = 2; i < SAMPLES / 2; i++) {
            fftResults[i] = (previousFFT[i] * 0.9) + (vReal[i] * 0.1);
            previousFFT[i] = fftResults[i];
        }
    }

    // Debugging output for FFT results
    debugFFTResults(fftResults, samplingFrequency);
}

double getFrequencyOfHighestSpike(double* fftData, unsigned long samplingFrequency) {
  int highestIndex = 2;
  double highestValue = 0;

  for (int i = 2; i < SAMPLES / 2; i++) {
    double frequency = (i * samplingFrequency) / SAMPLES;
    if (frequency > MIN_FREQUENCY) {
      double value = fftData[i];
      if (value > highestValue && value > detectionThreshold) {  // Use variable detectionThreshold
        highestValue = value;
        highestIndex = i;
      }
    }
  }

  double frequency = highestIndex * ((double)samplingFrequency / (double)SAMPLES);
  return frequency;
}

void displayFFT(double* fftData) {
  display.clearDisplay();
  for (int i = 2; i < SAMPLES / 2; i++) {
    double normValue = (fftData[i] - initialFFT[i]) * SCALING_FACTOR;
    int barHeight = normValue * (SCREEN_HEIGHT / 2);

    if (barHeight > SCREEN_HEIGHT / 2) barHeight = SCREEN_HEIGHT / 2;

    display.drawLine(i * (SCREEN_WIDTH / (SAMPLES / 2)), SCREEN_HEIGHT, i * (SCREEN_WIDTH / (SAMPLES / 2)), SCREEN_HEIGHT - barHeight, SSD1306_WHITE);
  }
  display.display();
}
void displayFrequency(double frequency) {
  display.clearDisplay(); // Clear the display to prevent overlapping text
  display.setTextSize(1);

  // Display threshold value at the top
  display.setCursor(0, 0);
  display.print("T:");
  display.print(detectionThreshold);

  // Display frequency value on the same line
  display.setCursor(40, 0);
  if (frequency > MIN_FREQUENCY) {
    display.print("Frq:");
    display.print(frequency);
    display.print(" Hz");
  } else {
    display.print("Freq: No Freq");
  }

  // Calculate the arrow position based on the detectionThreshold
  int arrowPosition = map(detectionThreshold, 10, 1000, SCREEN_HEIGHT - 10, 0);

  // Draw the arrow on the right-hand side of the display
  display.setCursor(SCREEN_WIDTH - 8, arrowPosition);
  display.print("<"); // Draw arrow

  display.display();
}


void debugFFTResults(double* fftData, unsigned long samplingFrequency) {
  for (int i = 2; i < SAMPLES / 2; i++) {
    double frequency = (i * samplingFrequency) / SAMPLES;
    Serial.print("Frequency: ");
    Serial.print(frequency);
    Serial.print(" Hz, Magnitude: ");
    Serial.println(fftData[i]);
  }
}

Cross post and continuation of these threads:

Ok, I've added it to the first post but don't seem to have the ability to delete this post if you'd like to do that please.

Check this link and see if it helps, it is not Arduino based. Bearing Fault Detection Vibration Analysis - How To Measure Vibration Frequency

I know of injection molding machines that are monitored by vibration signatures while operating. Any change, they phone home and a tech is sent.

You can pick up vibraations with a piezo disk, for Arduino they should be full-wave rectified.

Thanks for the replies - some of our more expensive machinery has vibration analysis but I want to make an audio monitor because there are about 20 motors in the area and several conveyors with water splashing everywhere - I don't want to connect up vibration devices to all of the machinery.

Sounds like the right decision to me. Talk the the boss and each time you prevent a production shutdown put half of it in a fund that will pay for the better equipment in time.

There are 3 lines to the infeed - the smallest is worth $30 000/hr and the largest, $50 000/hr. If I can get this to work properly it will be a valuable tool. Imagine knowing a week before a bearing will fail and being able to do repairs during normal downtime rather than a breakdown :slight_smile:

When you talk to the boss set up some metric that will make the measurement consistant that way it becomes hard to say it would not have failed. Be sure you both agree.

I am doing this independently of the company - they have their doubts. Since we have a hand-held ultra-sonic device for checking bearings I will try that when I think my device is alerting us to impending failure - hopefully that will confirm it. Eventually I will connect it to wifi and get it to email me alerts.

I assume your factory has people doing regular maintenance on the equipment. Unless the bearings are sealed, do they get regular grease?

Yes, to everything - but sometimes things do go wrong. As an example occasionally an over-zealous new worker might water blast where they shouldn't and introduce faults.

Hmm. How will your equipment handle water spray, etc? Is this some type of mining operation?

It's a vegetable factory.

So: can anyone answer my original question? That's the main purpose of this discussion :slight_smile:

Why do you not actually test the program and verify the average is what you are getting?

How will I know I'm getting the average?
I was hoping someone here would know about the maths involved and coding.

Display the values and use a hand calculator!

Of 256 samples over 5 seconds of averaging?

How long have you been computer programming? Testing is a basic part of programming.