Spectrum Analyzer

Status
Not open for further replies.

Frank B

Senior Member
Hi,

since i have some cpu-cycles left, i want to add a nice fft-display (with ILI9341) to my project.
In the SpectrumAnalyzerBasic example is following "table":

Code:
    // read the 512 FFT frequencies into 16 levels
    // music is heard in octaves, but the FFT data
    // is linear, so for the higher octaves, read
    // many FFT bins together.
    level[0] =  fft1024.read(0);
    level[1] =  fft1024.read(1);
    level[2] =  fft1024.read(2, 3);
    level[3] =  fft1024.read(4, 6);
    level[4] =  fft1024.read(7, 10);
    level[5] =  fft1024.read(11, 15);
    level[6] =  fft1024.read(16, 22);
    level[7] =  fft1024.read(23, 32);
    level[8] =  fft1024.read(33, 46);
    level[9] =  fft1024.read(47, 66);
    level[10] = fft1024.read(67, 93);
    level[11] = fft1024.read(94, 131);
    level[12] = fft1024.read(132, 184);
    level[13] = fft1024.read(185, 257);
    level[14] = fft1024.read(258, 359);
    level[15] = fft1024.read(360, 511);

I want to use fft256, but i'm not still sure how many "levels" (see code) i want, so my Question is:
How do you calc the values for L[x]=fft256.read(n,m) for a number "y" of "levels", resp. what's the formula/algorithm ?
 
Hi,
I want to use fft256, but i'm not still sure how many "levels" (see code) i want, so my Question is:
How do you calc the values for L[x]=fft256.read(n,m) for a number "y" of "levels", resp. what's the formula/algorithm ?

The formula for third octave would be (pseudo matlab)
Code:
2.^([ii;ii+1]/3)
with
Code:
ii=0..N

if you change 3 to 1.77 (16/ld(512)) you get roughly the bands from the example you showed

However, if one analyses the bandwidth more carefully, one realizes that the relative bandwidth is not constant but decreases with frequency.
edit: obviously, one must convert the frequency bins to integer and decide on bin overlap.
 
Last edited:
How do you calc the values for L[x]=fft256.read(n,m) for a number "y" of "levels", resp. what's the formula/algorithm ?

When I wrote the example, I create this perl script to make up the numbers.

Code:
#! /usr/bin/perl

$e = 1.3915;

$sum = 0;
$count = 0;

for ($i=0; $i < 16; $i++) {
        printf "%2d  ", $i;
        $n = $e ** $i;
        printf "%6.2f  ", $n;

        $d = int($n + 0.5);
        printf "%3d  ", $d;

        $sum += $n;
        printf "%6.2f  ", $sum;

        printf "%3d ", $count;
        $count += $d - 1;
        printf "%3d ", $count;
        $count++;

        print "\n";
}

If you run this, you'll see it ends at 501. I simply rounded the last one up. So far, nobody's ever noticed they're not perfectly scaled.....
 
Does it look like this?
0 1.00 1 1.00 0 0
1 1.39 1 2.39 1 1
2 1.94 2 4.33 2 3
3 2.69 3 7.02 4 6
4 3.75 4 10.77 7 10
5 5.22 5 15.99 11 15
6 7.26 7 23.25 16 22
7 10.10 10 33.35 23 32
8 14.06 14 47.41 33 46
9 19.56 20 66.96 47 66
10 27.22 27 94.18 67 93
11 37.87 38 132.05 94 131
12 52.70 53 184.75 132 184
13 73.33 73 258.08 185 257
14 102.04 102 360.12 258 359
15 141.99 142 502.11 360 501

Code:
/*
// https://forum.pjrc.com/threads/28430-Spectrum-Analyzer?p=71187&viewfull=1#post71187
// How do you calc the values for L[x]=fft256.read(n,m) for a number "y" of "levels", resp. what's the formula/algorithm ?
// When I wrote the example, I create this perl script to make up the numbers.
*/

void setup() {
  // put your setup code here, to run once:
  while (!Serial && (millis() <= 6000));


  float e = 1.3915;

  float sum = 0;
  float n = 0;
  int count = 0;
  int d = 0;

  for (int i = 0; i < 16; i++) {
    Serial.printf("%2d  ", i);
    // n = e ** i;
    n = pow(e, i);
    Serial.printf( "%6.2f  ", n);

    d = int(n + 0.5);
    Serial.printf( "%3d  ", d);

    sum += n;
    Serial.printf( "%6.2f  ", sum);

    Serial.printf( "%3d ", count);
    count += d - 1;
    Serial.printf( "%3d ", count);
    count++;

    Serial.print( "\n");
  }

}

void loop() {
  // put your main code here, to run repeatedly:


}
 
Paul - Correct - I was thinking of that manual method outside Audio in that post. Combined with an old item kpc gave to calculate the frequency of a peak value from the Audio magnitudes.
 
Great, thank you all.

I made a little excelsheet with WMXZ's formula, that's the easiest way to adjust the numbers.

Edit:
With 21 Bars (FFT256) it gives:
Code:
1	2
2	2
2	3
3	4
4	5
5	6
6	7
7	9
9	11
11	13
13	16
16	20
20	25
25	31
31	38
38	46
46	57
57	69
69	85
85	104
104	128
Looks good, thanks.
 
Last edited:
You might want to reduce the top value with one. Now you have two overlapping bins. Eg. at the end, bin nr 104 will appear in the last and one-but last value.

I think you forgot the scaling factor. log10 is just the log-base-10. It does not include the multiplication factor of 10.
Also people in general are always confused when to take 10*log10 or 20*log10. The easiest is to remember that dB always refers to power. Since power is proportional to voltage squared, in this case you need to take 20.f * log10f(value).
Probably also need a special case for a value of 0, since log(0) is undefined.
 
Works :)


Teensy decoding 128kBps mp3-webstream + fft256

Sorry for the soundquality, i had only headphones connected
 
Hello,

I just join this thread because I also like to do some FFT with a configurable number of bands. I'm using FFT256 so my user is able to chose his number of bands between 8 to 128 (in powers of 2). Thus, I also need some code to calculate the offsets for the .read(x, y) function.

First question:
Is someone having code that lets me pre-calculate the offsets during setup() and store them in an array?

Second question:
The example in the first post of this thread uses some values where no band is used twice. But some later reply is showing some offsets like this:
1 2
2 2
2 3
...

Now band 2 is in the sum of the first, second and third result. Is this good or wrong?

Kind regards,

Kukulkan
 
wrt the second question. This is why I said previously
You might want to reduce the top value with one. Now you have two overlapping bins. Eg. at the end, bin nr 104 will appear in the last and one-but last value.

Even when doing this, you will find that you have to use the same band twice at the low end. You simply do not have enough resolution on a log scale, to show separate bins. You can
1: Leave partly ovelapping bins at the low end
2: Use an FFT with more points
3: Cheat a little at the low end. Use a linear scale there, until the logarithmic steps are large enough.
4: Perform some resampling/interpolation scheme at the low end (sinc?).
Probably more options.
 
I realized that more than 18..20 steps make not much sense for a "pretty" display with fft256, so 16 is perhaps the best choice.
 
Frank - is that the ILI9341 SD Card working (doesn't seem all soldered up) or another one? I'd like to see your display bar write code if you wanted to share - it looks good.
 
Thanks for the answers. I think I will manually calculate several options. Maybe only 32 and 64 bands, manually tweaked.
 
Code:
inline 
bool fftDisplay(void)
{      
      const int displayFreq = 24; //max Hertz
      const int displayPeriod = 1000 / displayFreq;
      const int decay = 15; //speed of bars going lower
      const int nBars = sizeof(fftOctTab) / 2 ;
      
      const int barWidth = 16;
      const int barGap = 2;
      
      const int posX = (320 / 2) - (nBars * barWidth / 2);
      const int posY = 239;
      const int minHeight = 2;
      const int maxHeight = 100;
      
      static uint16_t bar = 0;     
      static uint16_t oldData[nBars];
      static uint32_t oldMillis = millis();
      
      if (bar == 0) {
        uint32_t t = millis();
        if (t - oldMillis <= displayPeriod) return false;
        oldMillis = t;
      }
      
      float n;
      int16_t val;
      uint16_t oldval;       
      n = myFFT.read(fftOctTab[bar * 2], fftOctTab[bar * 2 + 1]);      
      val = log10f(n) * 60 + 150;

      oldval = oldData[bar];       
      if (val < oldval - decay ) val = oldval - decay;
      if (val < minHeight) val = minHeight;
      else 
      if (val > maxHeight) val = maxHeight;

      oldData[bar] = val;     
      //draw only the nessesary portions - the difference to the old bar.
      if (val < oldval)
        tft.fillRect(posX + bar * barWidth, posY - oldval, barWidth - barGap, oldval - val, ILI9341_BLACK);
      else if (val > oldval) {
         int y;
         int x = posX + bar * barWidth;
         for (y = posY - val; y < posY - oldval; y++)
           if (y & 6) 
             tft.drawFastHLine(x, y,  barWidth - barGap , ILI9341_BLUE);
//           else 
//             tft.drawFastHLine(x, y,  barWidth - barGap , ILI9341_RED);
        // tft.fillRect(posX + bar * barWidth, posY - val, barWidth - barGap,  val - oldval , ILI9341_BLUE);
         }
      else {} //nothing to do

      if (++bar >= nBars) bar = 0;
      return true;
}
with
Code:
const uint8_t fftOctTab[] = {
//0,	0, 
1,	1,
2,	2,
3,	3,
4,	4,
5,	5,
6,      7,
8,       9,
10,	11,
12,	14,
14,	16,
17,	20,
21,	25,
26,	31,
32,	38,
39,	46,
47,	57,
58,	69,
70,	85,
86, 127
//86,	103,
//104,	127
};
This table is not perfect and should be optimized a bit - or simply use pauls' value from the example.
Simply call fftDisplay() in your loop() often enough. Every call draws only one bar - this is needed, because drawing alls bars at once take too much time. Nevertheless, it's quite fast. But there is room for optimizations - the "drawFastHLine" in a loop for example could be exchanged with a optimzed version. The version above looks like this:
(This version is with a smaller "decay" value. If you want it "faster" use values between 12..20 like in the code above)

If you dont want the black grid, remove the "if (y & 6)" and evemtually use the "fillrect" version some lines later (instead of the "for..".
 
Last edited:
Variant with specialized "gridFillRect" which is a bit faster than the loop above (less spi-transmitted bytes):
Code:
class myILI9341_t3:  public ILI9341_t3
{
public:
      myILI9341_t3(uint8_t _CS, uint8_t _DC, uint8_t _RST = 255, uint8_t _MOSI=11, uint8_t _SCLK=13, uint8_t _MISO=12): ILI9341_t3(_CS,_DC,_RST,_MOSI,_SCLK,_MISO) {};

      inline void gridFillRect(int16_t x, int16_t y, const int16_t w, const int16_t h, const uint16_t color)
      {
	SPI.beginTransaction(SPISettings(30000000, MSBFIRST, SPI_MODE0));
        int m = y+h-1;
	setAddr(x, y, x+w-1, m);
	writecommand[B]_last[/B](ILI9341_RAMWR);

	for(int i=y; i<=m; i++) {
               if ( i & 6 ) {
		  for(x=w; x>1; x--) 
			writedata16_cont(color);
		  writedata16_last(color);
               } else {
		  for(x=w; x>1; x--) 
			writedata16_cont(textbgcolor);
		  writedata16_last(textbgcolor);
               }

	}
	SPI.endTransaction();
     }  
};


inline 
bool fftDisplay(void)
{      
      const int displayFreq = 24; //max Hertz
      const int displayPeriod = 1000 / displayFreq;
      const int decay = 15; //speed of bars going lower
      const int nBars = sizeof(fftOctTab) / 2 ;
      
      const int barWidth = 16;
      const int barGap = 2;
      
      const int posX = (320 / 2) - (nBars * barWidth / 2);
      const int posY = 239;
      const int minHeight = 2;
      const int maxHeight = 100;
      
      static uint8_t bar = 0;     
      static uint8_t oldData[nBars];
      static uint32_t oldMillis = millis();
      
      if (bar == 0) {
        uint32_t t = millis();
        if (t - oldMillis <= displayPeriod) return false;
        oldMillis = t;
      }
      
      float n;
      int16_t val;
      uint16_t oldval;       
      n = myFFT.read(fftOctTab[bar * 2], fftOctTab[bar * 2 + 1]);      
      val = log10f(n) * 60 + 150;
//      in = n * 400.0;

      oldval = oldData[bar];       
      if (val < oldval - decay ) val = oldval - decay;
      if (val < minHeight) val = minHeight;
      else 
      if (val > maxHeight) val = maxHeight;

      oldData[bar] = val;     
      //draw only the nessesary portions - the difference to the old bar.
      if (val < oldval)
        tft.fillRect(posX + bar * barWidth, posY - oldval, barWidth - barGap, oldval - val, ILI9341_BLACK);
      else if (val > oldval) {
/*         int y;
         int x = posX + bar * barWidth;
         for (y = posY - val; y < posY - oldval; y++)
           if (y & 6) 
             tft.drawFastHLine(x, y,  barWidth - barGap , ILI9341_BLUE);
//           else 
//             tft.drawFastHLine(x, y,  barWidth - barGap , ILI9341_RED);
*/
         tft.gridFillRect(posX + bar * barWidth, posY - val, barWidth - barGap,  val - oldval , ILI9341_BLUE);
         }
      else {} //nothing to do

      if (++bar >= nBars) bar = 0;
      return true;
}
(You have to remove the "private" keyword in the class in ILI9341_t3.h)
 
Is there a place where we can see the source code? Or do we load the SpectrumAnalyzerBasic and add this code?

I appreciate any help
 
Sound distorted using NeoMatrix on teensy audio adapter

Hi,

I'm using the teensy audio adapter with teensy 3.1

I'm having some sound output issues , the sound output from the audio board is totally distorted when I apply the NeoMatrix library
Is the NeoMatrix slowing down the audio processing ??

Here is the codes, https://pastebin.com/bh41SNEb

I can confirmed no sound distortion without applying the NeoMatrix library

Any suggestion to solve this ?



https://github.com/adafruit/Adafruit_NeoMatrix

I'm using a 8 x 32 ws2812 with NeoMatrix library ...

8x32 audio by stanley_seow, on Flickr
 
Status
Not open for further replies.
Back
Top