Spectrum Analyser Help

I am very new to the Arduino community and I am building a project for my church, But I’m having trouble with the code. Hope you guys can help.

I am building a 7 band audio spectrum analyzer using (7) 1 meter NeoPixel strips, Arduino and SparkFun’s spectrum shield. I have done all the wiring and built the towers and I have successfully uploaded a test pattern. All the LED’s work great. But now comes the hard part, (for me anyway) making them move to music. I am trying to find an Arduino sketch that will work with the equipment I’m using and look something like this.http://www.youtube.com/watch?v=JFKPlNoBpfk

I have found several project files that are similar to what I need, but they all seem use to code that is for older LED tech (2 pins for data not 1 like mine). I just don’t know enough to modify those codes or write one from scratch. So I was hoping some one out there has already done this with the new WS2812 NeoPixel LED’s and a spectrum shield.

Any help or guidance would be greatly appreciated.

How are you wiring the Neopixels ? As 1 long strip or 7 short strips ? How many RGB LEDs in each short strip ? Are they the 400 kHz type or the 800 kHz type ?

Hey Mee_n_Mac,

The strips are wired as 1 long strip with each strip mounted on a 2x4 tower and then daisy chained together with custom made extension cables sending pwr and data out of the top of one strip and into the bottom of the next. There are 30 LED’s per strip. Im not sure about the frequency of the strip (400khz or 800khz) but they are this exact strip. http://www.adafruit.com/products/1460#Technical_Details. I do know that they are using the WS2812B chip.

Any help you could give on this would be awesome.

I’ve never used the WS2811/WS2812B LEDs so I’m not sure how much help I can be there. Because of their fast timing they are a PITA to use, short of staying w/someone’s library done in assembly. IIRC the SA Shield is stereo and output 7 pairs of analog voltages to be sampled by the Arduino with each tick of it’s clock. The question is how much processing can be done for each frequency before the next analog sample or whether you have to wait for all 7 bands to do anything. I’ll have to refresh my memory on the timing of the SA Shield. Somewhere I may have so code to start with.

Are you planning to display the R channel data, L channel data or average them ? I’d also plan on some ‘volumn’ control or some AGC control. Also the vid showed the use of 3 ‘colors’; off, red and the top LED as blue. Would your scheme be that simple wrt color range and intensity ?

I plan on feeding the shield with a feed from an AUX send off our church soundboard, so volume control and line level strength should not be to much of a problem. It will probably be just a mono feed for the left channel. As far as “color scheme” goes, my thought was to use ; off, green about 3/4 up the strip ,then a few red at the top.

One last question … would you be willing to rewire your strips from 1 long strip into 7 shorter, separate strips, each strip having it’s own data pin … if that made things easier ? I believe that approach was used for Adafruit’s glowy wig that used Neopixels. The more you can copy an existing, working design, the better off you’ll be. And 7 strips for 7 bands is logical.

The main loop() would have a for loop doing the ‘pseudo code’ below.

for(int band = 1, band <=7; band++){
  bitbang strobe low
  wait settling time
  do A/D conversion (L, R, L+R)  
  bitbang strobe high
  cut noise floor (?)
  rescale (?)
  if(linear)
    mag = map(read, 0, 1023, 0, 30);
  if(log)
    mag = map(read, 0, ???, 0, 30);
  colormap pixels; off, color1, color2
  transform pixel data into output format
  clock out 30 pixels
  delay
}

I guess I could rewire the strips. When I built the strip I guess I was going off other designs I had seen where they had just devided one long strip into 7 bands. I assumed they just addressed each band of the SA to start at a specific led in the strip. For example band 1 at 0 band 2 at 31 band 3 at 62 and so on. But then again I was guessing.

samme71:
I guess I could rewire the strips. When I built the strip I guess I was going off other designs I had seen where they had just devided one long strip into 7 bands. I assumed they just addressed each band of the SA to start at a specific led in the strip. For example band 1 at 0 band 2 at 31 band 3 at 62 and so on. But then again I was guessing.

That's how it's been done. Tell you what, leave it as is and we'll add a last step to the above pseudo code. I was concerned about running out of SRAM to store all the intermediate calculations. But you only have 210 LEDs so the memory used to store the bitstream to be sent is only 630 bytes. With perhaps another 630+30+90 bytes used (and reused) for each strip's intermediate calculations, it still should be safely under the 2k you have.

Here’s an old program w/some, not all, of your new stuff added. It has relics you may, or may not, want to utilize. What I haven’t figured out is the format of the array needed to store all the LED data and how best to populate it on an as-you-go basis. See if you can help figure that out. See …

http://learn.adafruit.com/adafruit-neop … no-library

#include <math.h>
#include <Adafruit_NeoPixel.h>

// declare the constants
const boolean logMode = false;          //enable or disable log mode display
const boolean debug = false;            //enable or disable debug measurements and printouts
const byte analogPinL = 0;              //left channel analog data from shield
const byte analogPinR = 1;              //right channel analog data from shield
const byte strobePin = 4;               //data strobe for shield
const byte resetPin = 7;                //reset strobe for shield
const byte dataPin = 5;                 //data pin for Neopixels
const int noise[] = {
  0, 0, 0, 0, 0, 0, 0};                 //set this to magnitude of noise from shield
const float gain[] = {
  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0};    //gain for each band
const unsigned long loop_dlay = 1;

enum audio
{
  STEREO,
  MONO,
  RIGHT,
  LEFT
};

Adafruit_NeoPixel strip = Adafruit_NeoPixel(60, dataPin, NEO_GRB + NEO_KHZ800);

// declare the variables
int spectrumReadR;                    //R magnitude from shield
int spectrumReadL;                    //L magnitude from shield
int upperLimit = 1023;                //upper limit on magnitude allowed
int audio = MONO;                     //set audio mode to mono, combine R&L channels
int mag = 0;
int numLEDs = 0;
float fl_mag = 0.0;
unsigned long time1 = 0;              //variable for benchmarking

void setup() {
  Serial.begin(9600);
  // initialize the digital pins as an outputs.
  pinMode(resetPin, OUTPUT);
  pinMode(strobePin, OUTPUT);
  pinMode(dataPin, OUTPUT);

  // set the initial states
  digitalWrite(resetPin, LOW);
  digitalWrite(strobePin, LOW);
  digitalWrite(dataPin, LOW);

  //initialize the Neopixels
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'

  //Initialize spectrum analyzer
  digitalWrite(resetPin,HIGH);
  delayMicroseconds(5);
  digitalWrite(strobePin,HIGH);
  delayMicroseconds(50);              // strobe PW > 18 usec min
  digitalWrite(strobePin,LOW);
  delayMicroseconds(50);
  digitalWrite(resetPin,LOW);
  delayMicroseconds(5);               
  digitalWrite(strobePin,HIGH);
  delayMicroseconds(100);              // allow reset to strobe falling > 72 usec min
}

void loop() {
  //time1 = micros();                  //start time for benchmarking
  //now do the calculations for each freq band
  for(byte band = 1; band <= 7; band++){
    //bring strobe low to output data
    digitalWrite(strobePin,LOW);
    delayMicroseconds(40);              // allow 36 usec min for output to settle
    // data is now available on analog pins 0 and 1
    spectrumReadR = analogRead(analogPinR);
    spectrumReadL = analogRead(analogPinL);

    //now set strobe back to high
    digitalWrite(strobePin, HIGH);

    //combine L/R data as dictated by setting
    switch (audio) {
    case MONO:
      // this averages the L & R readings
      mag = spectrumReadL/2 + spectrumReadR/2;
      break;
    case RIGHT:
      // this sets the magnitude = R data
      mag = spectrumReadR;
      break;
    case LEFT:
      //this sets the magnitude = L data
      mag = spectrumReadL;
      break;
    default:
      //this sets the magnitude = L data
      mag = spectrumReadL;
      break;
    }

    //now remove "noise" and rescale
    mag = max(0,(mag - noise[band-1]));
    fl_mag = gain[band-1]*float(mag);

    //remap signal magnitude in number of LEDs to be on
    // take log if mode is set
    if(logMode){
      //convert to 20log of input
      fl_mag = 20.0*log10(fl_mag);
      numLEDs = map(fl_mag, 0, 60, 0, 30);
    }
    else{
      numLEDs = map(fl_mag, 0, 1024, 0, 30);
    }

    //now store color of this bands LEDs into the larger array
    //LEDs <= numLEDs - 2 = color1, LEDs > numLEDs = off, remaining top 2 LEDs = color2
    //TBD

    //delay if needed
    //delayMicroseconds(??);
  }

  //now clock out all 7 bands of data to single Neopuxel array
  //strip.show();
  
  //delay to slow loop rate ~30 Hz
  delay(loop_dlay);
}

After looking at the link I posted it (seemed) fairly straight forward. So ignore the code above and give this an actual try. See if it does anything. Note the pin to connect to the Neopixels is pin 5, though you can change that. Rather than me explain the options, why don’t you review the code and see what questions you have.

baseline, ver 0.0

#include <math.h>
#include <Adafruit_NeoPixel.h>

// declare the constants
const boolean logMode = false;          //enable or disable log mode display
const boolean debug = false;            //enable or disable debug measurements and printouts
const byte analogPinL = 0;              //left channel analog data from shield
const byte analogPinR = 1;              //right channel analog data from shield
const byte strobePin = 4;               //data strobe for shield
const byte resetPin = 7;                //reset strobe for shield
const byte dataPin = 5;                 //data pin for Neopixels
const byte numBand = 30;                //number of LEDs for each freq band
const byte numTop = 2;                  //number of LEDs to have top color
const int noise[] = {
  0, 0, 0, 0, 0, 0, 0};                 //set this to magnitude of noise from shield
const float gain[] = {
  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0};   //gain for each band
const unsigned long loop_dlay = 1;      //loop delay to slow down display update rate

Adafruit_NeoPixel strip = Adafruit_NeoPixel(7*numBand, dataPin, NEO_GRB + NEO_KHZ800);

uint32_t off = strip.Color(0, 0, 0);
uint32_t base = strip.Color(0, 255, 0);
uint32_t top = strip.Color(255, 0, 0);

enum audio
{
  MONO,
  RIGHT,
  LEFT
};

// declare the variables
int spectrumReadR;                    //R magnitude from shield
int spectrumReadL;                    //L magnitude from shield
int audio = MONO;                     //set audio mode to mono, combine R&L channels
int mag = 0;                          //the magnitude of a freq band
int numON = 0;                        //the number of LEDs on in a freq band
float fl_mag = 0.0;                   //floating point mag after noise removal and scaling

void setup() {
  Serial.begin(9600);
  // initialize the digital pins as an outputs.
  pinMode(resetPin, OUTPUT);
  pinMode(strobePin, OUTPUT);
  pinMode(dataPin, OUTPUT);

  // set the initial states
  digitalWrite(resetPin, LOW);
  digitalWrite(strobePin, LOW);
  digitalWrite(dataPin, LOW);

  //initialize the Neopixels
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'

  //Initialize spectrum analyzer
  digitalWrite(resetPin,HIGH);
  delayMicroseconds(5);
  digitalWrite(strobePin,HIGH);
  delayMicroseconds(50);              // strobe PW > 18 usec min
  digitalWrite(strobePin,LOW);
  delayMicroseconds(50);              //reset PW > 100 usec min
  digitalWrite(resetPin,LOW);
  delayMicroseconds(5);               
  digitalWrite(strobePin,HIGH);
  delayMicroseconds(100);             // allow reset to strobe falling > 72 usec min
}

void loop() {
  //now do the calculations for each freq band
  for(byte band = 1; band <= 7; band++){
    //bring strobe low to output data
    digitalWrite(strobePin,LOW);
    delayMicroseconds(40);              // allow 36 usec min for output to settle
    // data is now available on analog pins 0 and 1
    spectrumReadR = analogRead(analogPinR);
    spectrumReadL = analogRead(analogPinL);
    if(debug){
      Serial.print("Right chan reads ");
      Serial.print(spectrumReadR);
      Serial.print(" for band ");
      Serial.println(band);
      Serial.print("Left chan reads ");
      Serial.print(spectrumReadL);
      Serial.print(" for band ");
      Serial.println(band);
    }

    //now set strobe back to high
    digitalWrite(strobePin, HIGH);

    //combine L/R data as dictated by setting
    switch (audio) {
    case MONO:
      // this averages the L & R readings
      mag = (spectrumReadL + spectrumReadR)/2;
      break;
    case RIGHT:
      // this sets the magnitude = R data
      mag = spectrumReadR;
      break;
    case LEFT:
      //this sets the magnitude = L data
      mag = spectrumReadL;
      break;
    default:
      //this sets the magnitude = L data
      mag = spectrumReadL;
      break;
    }
    if(debug){
      Serial.print("Magnitude is ");
      Serial.println(mag);
    }
    
    //now remove "noise" and rescale
    mag = max(0,(mag - noise[band-1]));
    fl_mag = gain[band-1]*float(mag);
    if(debug){
      Serial.print("Magnitude after noise removal is ");
      Serial.println(fl_mag);
    }
    
    //remap signal magnitude in number of LEDs to be on
    // take log if mode is set
    if(logMode){
      //convert to 20log of input
      fl_mag = 20.0*log10(fl_mag);
      numON = map(fl_mag, 0, 60, 0, 30);
    }
    else{
      numON = map(fl_mag, 0, 1024, 0, 30);
    }
    if(debug){
      Serial.print("Number of LEDs on is ");
      Serial.print(numON);
      Serial.print(" for band ");
      Serial.println(band);
    }
    
    //now store color of this bands LEDs into the larger array
    for(byte i = 0; i < numBand; i++){
      if(i < (numON - numTop - 1)){
        strip.setPixelColor(i + numBand*(band-1), base);    //set LEDs to base color
      }
      else if(i >= numON){
        strip.setPixelColor(i + numBand*(band-1), off);     //set LEDs off
      }
      else{
        strip.setPixelColor(i + numBand*(band-1), top);     //set LEDs to top color
      }
    }
  }
  if(debug){
    Serial.println("  ");
  }
  //now clock out all 7 bands of data to single Neopixel array
  strip.show();
  //delay to slow loop rate ~30 Hz
  delay(loop_dlay);
}

It compiles for me, see if it works for you.

Thanks for all your hard work so far. I’ll give this code a try and see what happens.

FWIW don’t be too surprised if it doesn’t work. I’ve been benchmarking, timing the various portions of code, and while some portions where “hmmmm”, one portion is “WTH, this can’t be”. To elaborate … the code repeats the same loop() over and over, updating the LEDs each time. If it runs too fast the lowest frequency band display will act oddly (to the eye). So I wanted to make sure this didn’t happen by measuring times and adding in a delay if/as needed.

One such time is how long it takes to clock all the data out and into your 210 LEDs. Given a fixed 800kHz clock and 24 bits/LED, this time should be a dead repeatable 6.3 msec. Yet it measures, more often than not, only 1.216 msec, w/a time of 0.2 msec every 5 or 7 loops. WTH ?!? So something is clearly amiss.

I find this … annoying. :evil:

FWIW I think I understand what was happening above. The assembly level code needs to run w/o interruption to make the proper waveform to convey 1’s and 0’s to the WS2812 IC. So interrupts are disabled and the timer I used doesn’t increment. Thus the measured times are short. The only question is why I measured any time.

If my other measured timings are correct then your display (if working) is being updated about 90x per second. Faster than good for the 63 Hz band. My guess 30x per second is still plenty fast and so the loop delay should be increased to about 22 or 23 msec.

const unsigned long loop_dlay = 22; //loop delay to slow down display update rate

Hey Mee_n_Mac,

First let me say YOU ARE THE MAN! ( or woman which ever the case may be, lol. ) I was able upload your code and make the timing changes you suggested, and guess what? Check this out. https://vimeo.com/82587406. It seems to work great. This was just an entail test, but I have sense been able to make some eq adjustments and everything is working better than expected. The only minor thing I noticed was that even with no audio coming in, I was getting the first couple of LEDs on each strip lighting up. I’m guessing this is “noise” from the sound system. Please feel free to offer other improvements or tweaks you think I need to make. I will be putting the finishing touches on the construction of the towers next week, adding some diffusion to the front and cleaning up the wiring, but I am more than thrilled. I really think this will have a huge impact on our Sunday service in a few weeks. I can’t say enough how grateful I am for all your help with this. I will post an update in a few week with the final version up and running. :clap: Here’s to you! Thanks again.

I have to say I’m fairly amazed it’s all working so well. I mean it should but usually there’s something forgotten or …

As for noise … that was a common complaint with that shield. One (imperfect) compensation was to subtract out most of the “noise” and then increase the gain a bit to compensate. As you can see there’s separate controls for each band. One thing to try is the log mode of display. This will compress the range somewhat, it may or may not, look better to your eye. Hopefully you can see how to change the color arrangements and the L/R mixing, should you want to…

I’ll look over the code once more to find any potential gotchas (I have one in mind) but otherwise I’m pretty pleased with it (if I do say so myself). You may want to consider if you want any button type controls to adjust things or change settings w/o having to recode, recompile and U/L. Say a prayer for my wife, Ellen, lost to breast cancer and we’ll call it even. Have a Merry Christmas and Happy New Year. :mrgreen:

ps - The Neopixels are running at 100% luminosity. They appear to be quite bright. How are they in the daylight (just your opinion) ? You can reduce their output by scaling down, in proportion, the 255 in the color definitions. Saves some electricity too.