scaling detected frequency range in spectrum analyzer

Status
Not open for further replies.
I am trying to figure out how to set the min and max of a frequency range in a spectrum analyzer.

I came across this link (which was in the SpectrumAnalyzerBasic example)
https://forum.pjrc.com/threads/32677-Is-there-a-logarithmic-function-for-FFT-bin-selection-for-any-given-of-bands

which showed me how to scale the number of bands to fit the number of LEDs on my strip.
I have only one strip, but id like to display each band not by height (using a grid) but rather by brightness, which i have more or less.
I would like the strip to have set colors for pixels from red through the rainbow back to almost red, which I also have.

But I would like the beginning of the strip to show at roughly 40-60 Hz, and the end of the strip to be roughly around 1200 Hz, but it seems the stuff in the link goes much higher, so 1200Hz is a little past halfway down the strip.

I can't figure out where exactly the frequencies get set in each bin. I'm honestly not even sure I understand the bins and such, but I think I do.

I have searched the forums and maybe it is out there, if it is, a link from someone who knows where to find it would be appreciated, but I cant seem to find it.

I had it working quite nicely using a spectrum analyzer i found around here that uses the ADC in on pin 16 with the recommended circuit found in the audio system design tool without using the audio shield.

(i was also able to set the min and max decibals to display led brightness with the analog way, which would be nice to know how to do the shield line in way, but it does an OK job at it as it is, so not as big an issue.)

But I am now trying to do it with the audio shield and with using the line in on the shield.

My real problem is that going the original analog way, I get this weird audio noise in the line out to the amp ONLY when the teensy is set to read the audio (if i put a switch that when switched says dont listen to the analog audio in, there is no noise).
Going the digital line in on the audio shield cleans up all that noise reading and also not reading that audio line in.

I really was pretty happy with my previous original route, but I am trying the shield with this alternative route because I don't know how to fix the noise through the circuit, if that is something that can be done, and I would also be ok with a suggested circuit to fix the noise, instead of using the audio shield with line level in.

Here is my original old spectrum code, using ADC on pin 16, no audio shield... and you can see in the SpectrumSetup() where i scale the frequency range
Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SerialFlash.h>
#include <Adafruit_NeoPixel.h>

const int bodyLEDs = 66;
int BodyPin = 1;

////////////////////////////////////////////////////////////////////////////////
// CONIFIGURATION 
// These values can be changed to alter the behavior of the spectrum display.
////////////////////////////////////////////////////////////////////////////////

int SAMPLE_RATE_HZ = 9000;          // Sample rate of the audio in hertz.

float intensityCutoff = 0.00;

float SPECTRUM_MIN_DB = 50.0;          // Audio intensity (in decibels) that maps to low LED brightness.
float SPECTRUM_MAX_DB = 63.0;          // Audio intensity (in decibels) that maps to high LED brightness.

const int FFT_SIZE = 256;              // Size of the FFT.  Realistically can only be at most 256 
                                       // without running out of memory for buffers and other state.
const int AUDIO_INPUT_PIN = 16;        // Input ADC pin for audio data.   
const int ANALOG_READ_RESOLUTION = 10; // Bits of resolution for the ADC.                        
const int ANALOG_READ_AVERAGING = 8;  // Number of samples to average with each ADC reading.   

const int MAX_CHARS = 65;              // Max size of the input command buffer

IntervalTimer samplingTimer;
float samples[FFT_SIZE*2];
float magnitudes[FFT_SIZE];
int sampleCounter = 0;
Adafruit_NeoPixel bodyStrip(bodyLEDs, BodyPin, NEO_GRB + NEO_KHZ800);
char commandBuffer[MAX_CHARS];
float frequencyWindow[bodyLEDs+1];
float frequencyWindowTemp[bodyLEDs+1];
float hues[bodyLEDs];

int i, f;


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

  // Set up ADC and audio input.
  pinMode(AUDIO_INPUT_PIN, INPUT);
  analogReadResolution(ANALOG_READ_RESOLUTION);
  analogReadAveraging(ANALOG_READ_AVERAGING);

  // Clear the input command buffer
  memset(commandBuffer, 0, sizeof(commandBuffer));

  // Initialize spectrum display
  spectrumSetup();
  // Begin sampling audio
  samplingBegin();
  
  bodyStrip.begin();
  bodyStrip.show();

}


void loop() {
///////////////////////// SET THE BODY LED MODE /////////////////////////////
  if (samplingIsDone()) {
    // Run FFT on sample data.
    arm_cfft_radix4_instance_f32 fft_inst;
    arm_cfft_radix4_init_f32(&fft_inst, FFT_SIZE, 0, 1);
    arm_cfft_radix4_f32(&fft_inst, samples);
    // Calculate magnitude of complex numbers output by the FFT.
    arm_cmplx_mag_f32(samples, magnitudes, FFT_SIZE);
    spectrumLoop();
    // Restart audio sampling.
    samplingBegin();
  }
  // Parse any pending commands.
  parserLoop();

}

////////////////////////////////////////////////////////////////////////////////
// UTILITY FUNCTIONS
////////////////////////////////////////////////////////////////////////////////

// Compute the average magnitude of a target frequency window vs. all other frequencies.
void windowMean(float* magnitudes, int lowBin, int highBin, float* windowMean, float* otherMean) {
    *windowMean = 0;
    *otherMean = 0;
    // Notice the first magnitude bin is skipped because it represents the
    // average power of the signal.
    for (f = 1; f < FFT_SIZE/2; ++f) {
      if (f >= lowBin && f <= highBin) {
        *windowMean += magnitudes[f];
      }else{
        *otherMean += magnitudes[f];
      }
    }
    *windowMean /= (highBin - lowBin) + 1;
    *otherMean /= (FFT_SIZE / 2 - (highBin - lowBin));
}

// Convert a frequency to the appropriate FFT bin it will fall within.
int frequencyToBin(float frequency) {
  float binFrequency = float(SAMPLE_RATE_HZ) / float(FFT_SIZE);
  return int(frequency / binFrequency);
}

// Convert from HSV values (in floating point 0 to 1.0) to RGB colors usable by neo pixel functions.
uint32_t pixelHSVtoRGBColor(float hue, float saturation, float value) {
  // Implemented from algorithm at http://en.wikipedia.org/wiki/HSL_and_HSV#From_HSV
  float chroma = value * saturation;
  float h1 = float(hue)/60.0;
  float x = chroma*(1.0-fabs(fmod(h1, 2.0)-1.0));
  float r = 0;
  float g = 0;
  float b = 0;
  if (h1 < 1.0) {
    r = chroma;
    g = x;
  }
  else if (h1 < 2.0) {
    r = x;
    g = chroma;
  }
  else if (h1 < 3.0) {
    g = chroma;
    b = x;
  }
  else if (h1 < 4.0) {
    g = x;
    b = chroma;
  }
  else if (h1 < 5.0) {
    r = x;
    b = chroma;
  }
  else // h1 <= 6.0
  {
    r = chroma;
    b = x;
  }
  float m = value - chroma;
  r += m;
  g += m;
  b += m;
  return bodyStrip.Color(int(255*r), int(255*g), int(255*b));
}

////////////////////////////////////////////////////////////////////////////////
// SPECTRUM DISPLAY FUNCTIONS
///////////////////////////////////////////////////////////////////////////////
int highestFreq = 1220;
int lowestFreq = 60;

void spectrumSetup() {
  // Set the frequency window values by evenly dividing the possible frequency
  // spectrum across the number of neo pixels.
  for (int i = 0; i < bodyLEDs+1; ++i) {
    frequencyWindow[i] = ((float)i/bodyLEDs*(highestFreq-lowestFreq))+lowestFreq;
    //Serial.print("frequency window ");Serial.print(i);Serial.print(" = ");Serial.println(frequencyWindow[i]);
  }
  // Evenly spread hues across all pixels.
  for (int i = 0; i < bodyLEDs; ++i) {
    hues[i] = 360.0*(float(i)/float(bodyLEDs-1));
  }
}

void spectrumLoop() {
  // Update each LED based on the intensity of the audio 
  // in the associated frequency window.
  float intensity, otherMean;
  for (int i = 0; i < bodyLEDs; ++i) {
    windowMean(magnitudes, 
               frequencyToBin(frequencyWindow[i]),
               frequencyToBin(frequencyWindow[i+1]),
               &intensity,
               &otherMean);
    // Convert intensity to decibels.
    intensity = 20.0*log10(intensity);
    // Scale the intensity and clamp between 0 and 1.0.
    intensity -= SPECTRUM_MIN_DB;
    intensity = intensity < 0.0 ? 0.0 : intensity;
    intensity /= (SPECTRUM_MAX_DB-SPECTRUM_MIN_DB);
    intensity = intensity > 1.0 ? 1.0 : intensity;
    bodyStrip.setPixelColor(i, pixelHSVtoRGBColor(hues[i], 1.0, intensity));
  }
  bodyStrip.show();
}


////////////////////////////////////////////////////////////////////////////////
// SAMPLING FUNCTIONS
////////////////////////////////////////////////////////////////////////////////

void samplingCallback() {
  // Read from the ADC and store the sample data
  samples[sampleCounter] = (float32_t)analogRead(AUDIO_INPUT_PIN);
  // Complex FFT functions require a coefficient for the imaginary part of the input.
  // Since we only have real data, set this coefficient to zero.
  samples[sampleCounter+1] = 0.0;
  // Update sample buffer position and stop after the buffer is filled
  sampleCounter += 2;
  if (sampleCounter >= FFT_SIZE*2) {
    samplingTimer.end();
  }
}

void samplingBegin() {
  // Reset sample buffer position and start callback at necessary rate.
  sampleCounter = 0;
  samplingTimer.begin(samplingCallback, 1000000/SAMPLE_RATE_HZ);
}

boolean samplingIsDone() {
  return sampleCounter >= FFT_SIZE*2;
}

void parserLoop() {
  // Process any incoming characters from the serial port
  while (Serial.available() > 0) {
    char c = Serial.read();
    // Add any characters that aren't the end of a command (semicolon) to the input buffer.
    if (c != ';') {
      c = toupper(c);
      strncat(commandBuffer, &c, 1);
    }
    else
    {
      // Clear the input buffer
      memset(commandBuffer, 0, sizeof(commandBuffer));
    }
  }
}

Here is the new spectrum code using the audio shield and line level in... and I just can't find anything that will print me frequencies for each bin (which i can do on the old code version) and I cant figure out what to edit to scale the frequencies.
Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SerialFlash.h>
#include <Adafruit_NeoPixel.h>

// GUItool: begin automatically generated code
AudioInputI2S            i2s1;           //xy=599,400
AudioAnalyzeFFT1024      fft1024;        //xy=789,397
AudioConnection          patchCord1(i2s1, 0, fft1024, 0);
AudioControlSGTL5000     sgtl5000_1;     //xy=785,485
// GUItool: end automatically generated code

const int bodyLEDs = 66;
int BodyPin = 1;

const int FFT_SIZE = 512;      

Adafruit_NeoPixel bodyStrip(bodyLEDs, BodyPin, NEO_GRB + NEO_KHZ800);

float hues[bodyLEDs];
int i;

int fftBins[bodyLEDs];
float level[bodyLEDs];
float shown[bodyLEDs];

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

  AudioMemory(12);

  sgtl5000_1.enable();  // Enable the audio shield
  sgtl5000_1.inputSelect(AUDIO_INPUT_LINEIN);
  sgtl5000_1.volume(0.5);

  getFFTBins();
  spectrumSetup();

  bodyStrip.begin();
  bodyStrip.show();

}


void loop() {
///////////////////////// SET THE BODY LED MODE /////////////////////////////


  if (fft1024.available()) {
    for (i = 0; i < bodyLEDs; i++) {
      if (i < bodyLEDs - 1) {
        level[i] = fft1024.read(fftBins[i], fftBins[i + 1] - 1);
      } else {
        level[i] = fft1024.read(fftBins[i], 511);
      }
    }

    float intensity = 0;
    for (i = 0; i < bodyLEDs; i++) {
      intensity = level[i] * 33;//scale;
      if (intensity > 1) intensity = 0;
      shown[i]=0;
      if (intensity >= 0.2) {
        shown[i] = intensity;
      }
      shown[i] = (intensity-0.2)*1.25;
      if(shown[i]<0){shown[i]=0;}

      bodyStrip.setPixelColor(i, pixelHSVtoRGBColor((float)(((int)hues[i])%360), 1.0, shown[i]));
    }
    bodyStrip.show();
  }

}

////////////////////////////////////////////////////////////////////////////////
// UTILITY FUNCTIONS
////////////////////////////////////////////////////////////////////////////////


// Convert from HSV values (in floating point 0 to 1.0) to RGB colors usable by neo pixel functions.
uint32_t pixelHSVtoRGBColor(float hue, float saturation, float value) {
  // Implemented from algorithm at http://en.wikipedia.org/wiki/HSL_and_HSV#From_HSV
  float chroma = value * saturation;
  float h1 = float(hue)/60.0;
  float x = chroma*(1.0-fabs(fmod(h1, 2.0)-1.0));
  float r = 0;
  float g = 0;
  float b = 0;
  if (h1 < 1.0) {
    r = chroma;
    g = x;
  }
  else if (h1 < 2.0) {
    r = x;
    g = chroma;
  }
  else if (h1 < 3.0) {
    g = chroma;
    b = x;
  }
  else if (h1 < 4.0) {
    g = x;
    b = chroma;
  }
  else if (h1 < 5.0) {
    r = x;
    b = chroma;
  }
  else // h1 <= 6.0
  {
    r = chroma;
    b = x;
  }
  float m = value - chroma;
  r += m;
  g += m;
  b += m;
  return bodyStrip.Color(int(255*r), int(255*g), int(255*b));
}

void spectrumSetup() {
  for (i = 0; i < bodyLEDs; ++i) {
    hues[i] = 360.0*(float(i)/float(bodyLEDs));
  }
}

void getFFTBins() {
  float e, n;
  int b, bands, bins, count = 0, d;
  while (!Serial && (millis() <= 6000));  // Wait for Serial interface

  bands = bodyLEDs;                          // Frequency bands; (Adjust to desired value)
  bins = FFT_SIZE;                             // FFT bins; (Adjust to desired value)

  e = FindE(bands, bins);                 // Find calculated E value
  if (e) {                                // If a value was returned continue
    //Serial.printf("E = %4.4f\n", e);      // Print calculated E value
    for (b = 0; b < bands; b++) {         // Test and print the bins from the calculated E
      //Serial.print("band ");Serial.printf("%2d ",b);
      n = pow(e, b);
      d = int(n + 0.5);
      //Serial.printf( "%4d ", count);      // Print low bin
      fftBins[b] = count;
      count += d - 1;
      //Serial.printf( "%4d\n", count);     // Print high bin
      ++count;
    }
  }
  else
  {
    //Serial.println("Error\n");            // Error, something happened
  }
}

float FindE(int bands, int bins) {
  float increment = 0.1, eTest, n;
  int b, count, d;

  for (eTest = 1; eTest < bins; eTest += increment) {     // Find E through brute force calculations
    count = 0;
    for (b = 0; b < bands; b++) {                         // Calculate full log values
      n = pow(eTest, b);
      d = int(n + 0.5);
      count += d;
    }
    if (count > bins) {     // We calculated over our last bin
      eTest -= increment;   // Revert back to previous calculation increment
      increment /= 10.0;    // Get a finer detailed calculation & increment a decimal point lower
    }
    else if (count == bins)   // We found the correct E
      return eTest;       // Return calculated E
    if (increment < 0.0000001)        // Ran out of calculations. Return previous E. Last bin will be lower than (bins-1)
      return (eTest - increment);
  }
  return 0;                 // Return error 0
}

And here is a video of both side by side reading the same signal (and also the audio sound i get out of the amp at the end)
the bottom copper covered strip is the old code, the top white strip is the new code, u can see how they scale differently.


any help is much appreciated. I been bangin my head on this for days
 
Status
Not open for further replies.
Back
Top