Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 8 of 8

Thread: Translating sound into movement, using FFT

  1. #1
    Junior Member Heinrich's Avatar
    Join Date
    Mar 2018
    Location
    Stockholm
    Posts
    5

    Translating sound into movement, using FFT

    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
    Click image for larger version. 

Name:	IMG_6203.jpg 
Views:	50 
Size:	128.8 KB 
ID:	13166


    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[i]":
    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);
      }
    }
    Last edited by Heinrich; 03-04-2018 at 04:19 PM.

  2. #2
    Junior Member Heinrich's Avatar
    Join Date
    Mar 2018
    Location
    Stockholm
    Posts
    5
    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...

  3. #3
    Senior Member+ Theremingenieur's Avatar
    Join Date
    Feb 2014
    Location
    Colmar, France
    Posts
    2,583
    dB = 20 x log10(FFT value)

  4. #4
    Junior Member Heinrich's Avatar
    Join Date
    Mar 2018
    Location
    Stockholm
    Posts
    5
    Perfect! Thanks a lot!

  5. #5
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    22,123
    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.

  6. #6
    Junior Member Heinrich's Avatar
    Join Date
    Mar 2018
    Location
    Stockholm
    Posts
    5
    Quote Originally Posted by PaulStoffregen View Post
    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.

  7. #7
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    22,123
    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.

  8. #8
    Junior Member Heinrich's Avatar
    Join Date
    Mar 2018
    Location
    Stockholm
    Posts
    5
    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...

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •