Translating sound into movement, using FFT

Status
Not open for further replies.

Heinrich

Member
Hi,

I am a beginner.
After thoroughly reading relevant threads in this forum I still have a lot of questions regarding a project I am doing right now (part of a MA thesis in Design)
I am trying to get following setup to work and have a couple of issues/questions i ran into using the Teensy Audio Library:

Hardware:
Teensy 3.5
MAX4466 Microphone
LiquidCystal Display (as a substitute before using steppermotors with TMC2100 SilentStepStick, and Nema17)
Potentiometer
IMG_6203.jpg


Description:
I would like to separate frequencies of different surroundings and use the values of the FFT after conversion into dB values to control (for now) one Steppermotor. My thought is to use a potentiometer to scroll through the FFT bands and select a desired one to see whats is going on there. I started with the SpectrumAnalyzerBasic from the Audio library and made some changes since I don't own an AudioShield (yet :))

Issues:
0. Is it possible in general to drive a stepper motor and run FFT together from one Teensy? If so, does it need to be sequentially or can i do the fft while the stepper is running (Accelstepper and TeensyStep seem to support imediate return)

1. I don't quite understand how to translate the raw FFT values into db. This thread seems to have a solution:
Code:
float db(float n, float r) {
  if (n <= 0) return r-96;  // or whatever you consider to be "off"
  return r+log10f(n) * 20.0f;
}
but to me as a novice it is not clear which values are n,r and f.

2. When adding the potentiometer
Code:
sel = map(analogRead(14), 0, 1023, 0, 15);
  Serial.println(sel);
the Arduino IDE compiles and uploads it but nothing is printed to the Serial monitor and it seems the microphone and the display stop picking up and displaying the sound. It somehow works if putting this snippet at the end of the loop but then the mapping function wont work, I even got very high values (around 65000) when turning the Pot. I have tested the same setup in a separate sketch where it functioned flawlessly.

3. If it is possible to get the Pot to work together with the rest of the code, is there a more elegant solution than this to select the desired band from the array "levels":
Code:
if (sel == 0) {
serial.print(level[0], 3);
};
else if (sel == 1) {
serial.print(level[1], 3);
};
else if (sel == 2) {
serial.print(level[2], 3);
};
else if (sel == 3) {
serial.print(level[3], 3);
};
...

4. I got the original code to work with the MAX4466 (following this guideline). The pot is turned all way ccw but even then the general sound level has to be very high for the mic to pic up anything. This
Code:
 // configure the mixer to equally add left & right
  mixer1.gain(0, 10);
seems to influence it a bit but eventually (with higher numbers) the first two bands will raise much more whilst being inert. Is there a better Mic you could recommend? (The teensy audio shield is ordered:))

I'm aware there are a lot of open questions in this project but I thought I'll give it a try here anyway...any help/criticism/feedback and support is highly appreciated!

Cheers /h

here is the full code for now:
Code:
#include <LiquidCrystal.h>
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioInputAnalog         adc1;           //xy=349.0000114440918,284.00000858306885
AudioMixer4              mixer1;         //xy=504.0000114440918,303.0000114440918
AudioAnalyzeFFT1024      fft1024;      //xy=659.0000228881836,302.0000319480896
AudioConnection          patchCord1(adc1, 0, mixer1, 0);
AudioConnection          patchCord2(mixer1, fft1024);
// GUItool: end automatically generated code

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

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

// 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];
//int   shown[8];
//int   shown[5];

int sel; //to choose the  displayed band

// Use the LiquidCrystal library to display the spectrum

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
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);
  //fft1024.windowFunction(AudioWindowHanning1024);
  //fft1024.windowFunction(AudioWindowFlattop1024);

  //turn on the LCD and define the custom characters
  lcd.begin(16, 2);
  lcd.print("freqSpec");
  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, 10);

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

}


void loop() {

  sel = map(analogRead(14), 0, 1023, 0, 15);
  Serial.println(sel);

  if (fft1024.available()) {
    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);
    /*
      level[0] =  fft1024.read(0);
      level[1] =  fft1024.read(1, 4);
      level[2] =  fft1024.read(5, 24);
      level[3] =  fft1024.read(25, 113);
      level[4] =  fft1024.read(114, 511);
    */
    /*
      level[0] =  fft1024.read(0);
      level[1] =  fft1024.read(1, 2);
      level[2] =  fft1024.read(3, 7);
      level[3] =  fft1024.read(8, 18);
      level[4] =  fft1024.read(19, 43);
      level[5] =  fft1024.read(44, 100);
      level[6] =  fft1024.read(101, 227);
      level[7] =  fft1024.read(228, 511);
    */

    // 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], 3);

      // conversion from FFT data to display bars should be
      // exponentially scaled.  HOW?
      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());

    //delay(100);
  }
}
Code:
 
Last edited:
Everything's easier with the Audio Shield.

Got it to work with PWM and dc motors.

The only thing that's left from the issues above is the correct translation of the FFT values into db...
 
You can't use AudioInputAnalog and analogRead() in the same program. Both try to access the same hardware, causing a conflict which crashes your program.
 
You can't use AudioInputAnalog and analogRead() in the same program. Both try to access the same hardware, causing a conflict which crashes your program.

Thanks. I eventually found the threads discussing this ADC conflict. The AudioShield was indeed the magic wand that helped solving the issues.
 
This conflict is a confusing gotcha that comes up occasionally.

I wonder if some sort of script could analyze the compiler's symbol table output. Maybe it could show a warning in Arduino that your program contains use of features which are known to conflict?

I'd really like to find a way to save people from the frustration of known conflicts that don't give any warnings or errors, but simply crash their program.
 
Jep. As a newbie it seems tricky to get an overview and search for the right terms when you don't know the right terms already. But the information was actually already there- if i would have read the description for the adc input in the gui properly.
Maybe one way to support a smooth start with the Shield and the library could be to have some sort of frequently updated wiki including all relevant issues and solutions, that have been discussed here in this forum accompanied by the video tutorial and pdf that is already available? I'm thinking of some sort of notation tree for example that leads after a few klicks to the right thread here in the forum...
 
Status
Not open for further replies.
Back
Top