Spectrum display - 20 bands

Status
Not open for further replies.

scalesr1

Member
Hello Forum,

I only learnt about Teensy a couple of hours ago and am quite excited about the prospects of using it for a project I have in mind.

I want to build an audio spectrum display split over 20 channels.

For the display I will be using IN-13 Nixie Tubes - driving these is not an issue - I can use a PWM output (which I have already done using Arduino) and it works just fine.

I would like to divide the audio spectrum in to around 20 bands and drive each display with the output from each band.

I can see that the bigger Teensy (3.6?) has sufficient PWM outputs - or I should use multiple Teensy 3.2 in case processing power was an issue.

I would just like to know if the processing power of the Teensy platform is up to this particular task (it may well exceed it many times but I have no idea!).

I have watched a Teensy Audio Library instructional video and it looks like it might just do it. I now have a loose grip on bins, hanning (?) windows and spectral leakage!

Can anyone advise if I am on the right track?

If there are any threads that talk about this or projects that have done this already then all pointers gleefully accepted.

In my simplistic approach I think I am looking to do this sort of thing in the main loop (assuming that the audio source is being fed to a suitable input on the Teensy)
(Please forgive my shoddy attempt at pseudo code)

{
Call whatever it is that performs the Fast Fourier Analysis

for channel=1 to 20

Select the right bin (or bins) according to the channel I want to drive

Set PWM_Output(channel) to the value for that channel

next channel
}

Thank you for looking.
 
It is certainly possible and Teensy 3.6 would probably be overkill except for the PWM outputs. Using a T3.2 + a PWM driver (or just whatever duino you have hand via serial) could get the number of pins you want. If you have already looked at the video next step is installing Arduino and Teensyduino and looking at the audio examples, especially those under analysis which include the basic FFT and the prebuilt spectrum examples (that use different hardware but may be worth stealing persistence and similar from). Using the Audio library is a bit more involved then your psuedo code since it is designed to be non blocking but the examples have the process there.
 
Thank you very much for this.
Arduino IDE already here, Teensyduino downloading as we speak and Teensy 3.5 (for the 5V aspect) ordered.
I shall look at the sample sketches - by 'different hardware' I am guessing that you mean the different display technologies such as LED Matrix for example.
In the first instance I was thinking that it would sample the audio, produce the various levels and set the PWM outputs accordingly.
A second step might be to do this via an array and then display the array (or have it display via an interrupt) which would allow for some extra processing for persistence etc.
Thank you again for the confirmation.
 
Start with the audio lib FFT example. Or look at File > Examples > OctoWS2811 > SpectrumAnalyzer for an example that gives a 60 band display on 1920 LEDs. It's basically the FFT example with some post-processing. Some of that code should be pretty useful for your project.

For this sort of project, you'll probably do fine by just checking whether the FFT has new data available once per loop(). It's pretty easy to add rendering by just adding arrays for remembering what you did on the last data set, then draw again when you get new FFT data. If using the 1024 point FFT, you'll get new data at a consistent 86 Hz rate which works great for a very responsive display.

I'd recommend resisting the urge to dive into interrupts. They make everything more complicated. Just avoid adding any delays. If you add buttons, use the Bounce library to read them. Don't go down the path of digitalRead with delays.
 
Yes, the examples are outputing to serial and various flavours of LED. One thing to look at while wiating for your teensy to ship is what your input will look like. The audio library offers various options to get your audio in, but all of them will involve some hardware to impedance match and buffer the signal and the easy option of straight into an ADC pin is pretty noisy unless very carefully engineered. Can also be helpful to have an audio out built into the design so you can listen to what you got when trying to fault find code vs hardware problems.
 
Start with the audio lib FFT example. Or look at File > Examples > OctoWS2811 > SpectrumAnalyzer for an example that gives a 60 band display on 1920 LEDs. It's basically the FFT example with some post-processing. Some of that code should be pretty useful for your project.

For this sort of project, you'll probably do fine by just checking whether the FFT has new data available once per loop(). It's pretty easy to add rendering by just adding arrays for remembering what you did on the last data set, then draw again when you get new FFT data. If using the 1024 point FFT, you'll get new data at a consistent 86 Hz rate which works great for a very responsive display.

I'd recommend resisting the urge to dive into interrupts. They make everything more complicated. Just avoid adding any delays. If you add buttons, use the Bounce library to read them. Don't go down the path of digitalRead with delays.

That is super helpful. I have installed the software and have taken a look at the examples. Teensies has been ordered - all hugely exciting - this has to be better than building and fine tuning twenty individual band pass filters!

Thank you for your help and support.
 
Progress has been made - I am now in possession of a Teensy 3.5 and I have loaded the SpectrumAnalyzer example and seen the output on the Serial Monitor.
I want to re-work the code for 20 channels, that all looks straight forward though I can see that I will need to re-distribute the bins across 20 channels in a logarithmic style as in the example.
Am I right in thinking that there are a total of 447 bins which I need to spread across my proposed 20 bands in some logarithmic style?
Can you point me to any reference material for this so I can get a better understanding of how it all might work?
 
Take a look at the SpectrumAnalyzer example in the OctoWS2811 library.


I believe that is what I am looking at and I was hoping to use some of that code as the basis for my project. I can see that the example is using fft 1024 and assigning the bins to each of 60 bands. The distribution of the bins appears to be logarithmic.

I have totalled up the bins and there seem to be 447 - is it actually the case that there are 1024 returned from the FFT1024 function and the example is only looking at the lower 447?
 
Take a look at the SpectrumAnalyzer example in the OctoWS2811 library.

Hello Paul,

what is the proper way to switch input from I2S to USB? I would like to adapt the SpectrumAnalyzer example for USB input. The FFT code works as expected using Teensy 3.6, but the output audio is highly distorted.
It happens with other FFT examples as well. What am I missing?
Any suggestion will be appreciated.

Thank you very much and best regards
Enzo (Italy)

Code:
#include <LiquidCrystal.h>
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>


// GUItool: begin automatically generated code
AudioInputUSB            usb1;           //xy=145,206
AudioMixer4              mixer1;         //xy=381,263
AudioOutputI2S           i2s2;           //xy=438,168
AudioAnalyzeFFT1024      fft1024;        //xy=562,277
AudioConnection          patchCord1(usb1, 0, i2s2, 0);
AudioConnection          patchCord2(usb1, 0, mixer1, 0);
AudioConnection          patchCord3(usb1, 1, i2s2, 1);
AudioConnection          patchCord4(usb1, 1, mixer1, 1);
AudioConnection          patchCord5(mixer1, fft1024);
AudioControlSGTL5000     audioShield;    //xy=412,361
// GUItool: end automatically generated code


//const int myInput = AUDIO_INPUT_LINEIN;
//const int myInput = AUDIO_INPUT_MIC;


// The scale sets how much sound is needed in each frequency range to
// show all 8 bars.  Higher numbers are more sensitive.
float scale = 60.0;

// An array to hold the 16 frequency bands
float level[16];

// This array holds the on-screen levels.  When the signal drops quickly,
// these are used to lower the on-screen level 1 bar per update, which
// looks more pleasing to corresponds to human sound perception.
int   shown[16];



// Use the LiquidCrystal library to display the spectrum
//
LiquidCrystal lcd(0, 1, 2, 3, 4, 5);
byte bar1[8] = {0,0,0,0,0,0,0,255};
byte bar2[8] = {0,0,0,0,0,0,255,255};        // 8 bar graph
byte bar3[8] = {0,0,0,0,0,255,255,255};      // custom
byte bar4[8] = {0,0,0,0,255,255,255,255};    // characters
byte bar5[8] = {0,0,0,255,255,255,255,255};
byte bar6[8] = {0,0,255,255,255,255,255,255};
byte bar7[8] = {0,255,255,255,255,255,255,255};
byte bar8[8] = {255,255,255,255,255,255,255,255};


void setup() {
  // Audio requires memory to work.
  AudioMemory(12);

  // Enable the audio shield and set the output volume.
  audioShield.enable();
//  audioShield.inputSelect(myInput);
  audioShield.volume(0.5);

  // turn on the LCD and define the custom characters
  lcd.begin(16, 2);
  lcd.print("Audio Spectrum");
  lcd.createChar(0, bar1);
  lcd.createChar(1, bar2);
  lcd.createChar(2, bar3);
  lcd.createChar(3, bar4);
  lcd.createChar(4, bar5);
  lcd.createChar(5, bar6);
  lcd.createChar(6, bar7);
  lcd.createChar(7, bar8);

  // configure the mixer to equally add left & right
  mixer1.gain(0, 0.5);
  mixer1.gain(1, 0.5);

  // pin 21 will select rapid vs animated display
  pinMode(21, INPUT_PULLUP);
}


void loop() {
  if (fft1024.available()) {
    // 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);
    // See this conversation to change this to more or less than 16 log-scaled bands?
    // https://forum.pjrc.com/threads/32677-Is-there-a-logarithmic-function-for-FFT-bin-selection-for-any-given-of-bands

    // if you have the volume pot soldered to your audio shield
    // uncomment this line to make it adjust the full scale signal
    //scale = 8.0 + analogRead(A1) / 5.0;

    // begin drawing at the first character on the 2nd row
    lcd.setCursor(0, 1);
 
    for (int i=0; i<16; i++) {
      Serial.print(level[i]);

      // TODO: conversion from FFT data to display bars should be
      // exponentially scaled.  But how keep it a simple example?
      int val = level[i] * scale;
      if (val > 8) val = 8;

      if (val >= shown[i]) {
        shown[i] = val;
      } else {
        if (shown[i] > 0) shown[i] = shown[i] - 1;
        val = shown[i];
      }

      //Serial.print(shown[i]);
      Serial.print(" ");

      // print each custom digit
      if (shown[i] == 0) {
        lcd.write(' ');
      } else {
        lcd.write(shown[i] - 1);
      }
    }
    Serial.print(" cpu:");
    Serial.println(AudioProcessorUsageMax());
  }
}
 

Attachments

  • I2S-to-USB.jpg
    I2S-to-USB.jpg
    19.6 KB · Views: 117
Status
Not open for further replies.
Back
Top