I wrote a spectrum analyzer for my ESP32 NodeMCU and it's fast and happy and pretty, but it's not accurate or a pretty as it should be because it is very noisy.

If you play a pure sine tone, you'll get a peak one one bar and then randomness to about half that bar's height on the other bars. Adjusting my noise floor can help, but doesn't solve it.

I figured the code might be of use to someone, particularly if we can make it work properly, so I'll share it here. Please take a look at the FFT and comment on what I might be doing wrong!

Thanks,

Dave

class Analyzer

{

private:

static const int SAMPLES = 512;

static const int SAMPLING_FREQUENCY = 32000; // Hz * 2, so max frequency measured is half the sampling frequency

static const int NOISE_CUTOFF = 1000;

arduinoFFT _FFT;

unsigned int _sampling_period_us;

unsigned long _microseconds;

byte _peak[MAX_BANDS] = { 0 };

double _vReal[SAMPLES];

double _vImaginary[SAMPLES];

unsigned long _newTime;

unsigned long _oldTime;

int dominant_value;

uint8_t _inputPin;

static int * BandCutoffTable(int bandCount)

{

if (bandCount == 8)

return cutOffs8Band;

if (bandCount == 16)

return cutOffs16Band;

if (bandCount == 32)

return cutOffs32Band;

Serial.println("Error: Bogus bandCount");

}

int BucketFrequency(int iBucket)

{

if (iBucket == 0)

return 0;

int iOffset = iBucket - 2;

return iOffset * (SAMPLING_FREQUENCY / 2) / SAMPLES;

}

public:

Analyzer(uint8_t inputPin)

{

_inputPin = inputPin;

_sampling_period_us = round(1000000 * (1.0 / SAMPLING_FREQUENCY));

}

byte * GetBandPeaks()

{

return _peak;

}

void SampleAudio(float deltaTime, int bandCount)

{

for (int i = 0; i < bandCount; i++)

{

_peak[i] = 0;

}

float expectedTime = SAMPLES * _sampling_period_us;

unsigned long startTime = micros();

for (int i = 0; i < SAMPLES; i++)

{

_newTime = micros();

_vReal[i] = analogRead(_inputPin);

_vImaginary[i] = 0;

while ((micros() - _newTime) < _sampling_period_us)

{

// BUGBUG busy waiting!

}

}

Serial.printf("Sampling took %d us and was expedcted to take %d", micros() - startTime, (unsigned long)expectedTime);

int * Bands = BandCutoffTable(bandCount);

_FFT.Windowing(_vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);

_FFT.Compute(_vReal, _vImaginary, SAMPLES, FFT_FORWARD);

_FFT.ComplexToMagnitude(_vReal, _vImaginary, SAMPLES);

// Magical noise filter from https://www.youtube.com/watch?v=5RmQJtE61zE

// The Goggles, they do nothing! So commented out for now.

// for (int i = 2; i < SAMPLES / 2; i++)

// _vReal[i] = sqrt(_vReal[i] * _vReal[i] + _vImaginary[i] * _vImaginary[i]);

double overallPeak = 0.0f;

for (int i = 2; i < SAMPLES / 2; i++)

if (_vReal[i] > overallPeak)

overallPeak = _vReal[i];

float amplitudeScaling = overallPeak / 256.0f; // We want all results to scale down to a byte

if (amplitudeScaling <= 32.0f) // Some reasonable "max" amplifiication for quiet areas

amplitudeScaling = 32.0f;

Serial.printf("Peak: %f, Scaling: %f\n", (float)overallPeak, amplitudeScaling);

for (int i = 2; i < SAMPLES / 2; i++)

{

if (_vReal[i] > NOISE_CUTOFF)

{

int freq = BucketFrequency(i);

int iBand = 0;

while (iBand < bandCount)

{

if (freq < Bands[iBand])

break;

iBand++;

}

if (iBand > bandCount)

iBand = bandCount;

float scaledValue = _vReal[i] * scalars16Band[iBand] / amplitudeScaling;

byte byteVal = scaledValue > 255 ? 255 : (byte) scaledValue;

if (byteVal > _peak[iBand])

{

_peak[iBand] = byteVal;

}

}

}

}

};