Audio localization via interferometry

harzer99

New member
Hello everyone,
I just got my first Teensy 4.1 and joined just the forum.
My project is to build a device that is able to locate an audio source, preferable mosquitos. For the moment I plan to use the Teensy as an audio interface. So I can use python for the signal analysis as this is the language I'm most familiar with.
So far I have mostly worked on increasing the sampling rate of the analog read. I am able to achieve a bit more then 40kHz with 4 channels at 10 bit resolution which correspons to the roughly 5µs it takes to perform an analogread(): https://forum.pjrc.com/threads/57683-Analog-read-on-Teensy-4-0-slower-(compared-to-Teensy-3-6)

The simplest audio localization via interferometry works via finding the minimum in the cross correlation between two microphones. This yields a time delay from which again you can calculate an angle. More on that:
https://en.wikipedia.org/wiki/Acoustic_location
https://www.youtube.com/watch?v=Z7X7lf6FdYY

For the moment I plan to use the Teensy effectively as an audio interface. So I can use python for the signal processing as this is the language I'm most familiar with. So far I have mostly worked on increasing the sampling rate of the analog read. I am able to achieve a bit more then 40kHz with 4 channels at 10 bit resolution which corresponds to the roughly 5µs it takes to perform an analogread(). Is there a way to parallelize analogread()? Unfortunately I don't know much about ADCs.

Currently I am using these microphones: https://www.adafruit.com/product/1063

I plan on updating this thread as I progress with the project. Tips are very appreciated. :)
 
Ditched the Python idea as it became clear that the python serial interface wasn't up to the task in terms of speed. So local implementation it was. Fortunately I stumbled across this code by kpc which only needed minor modifications from my end.
So far it generates reasonable values. Code:

Code:
/* This code is free to use */
/* Kire Pudsje, Feb. 2015 */

#include "arm_math.h"

#define FFTSIZE 4096

int16_t s1[FFTSIZE], s2[FFTSIZE];
int16_t fft_buf[FFTSIZE * 2];
int32_t ifft_buf[FFTSIZE * 2];
arm_cfft_radix4_instance_q15 fft_inst;
arm_cfft_radix4_instance_q31 ifft_inst;

void setup() {
  Serial.begin(9600);
  arm_cfft_radix4_init_q15(&fft_inst, FFTSIZE, 0, 1);
  arm_cfft_radix4_init_q31(&ifft_inst, FFTSIZE, 1, 1);
  analogReadAveraging(3);
}

void loop() {
  // generate two signals with random data, s2 is delayed s1
  elapsedMicros us; // start timing
  unsigned long timeBegin = micros();
  for (unsigned i = 0; i < FFTSIZE; i++) {
      s1[i] = analogRead(9)*32;
      s2[i] = analogRead(8)*32;
      //delay(0.05);
  }
  unsigned long timeEnd = micros();
  double herz = 1000000*FFTSIZE/(timeEnd - timeBegin);
  Serial.print("frequency: ");
  Serial.println(herz);
  
  // Copy samples interleaved to FFT buffer s1 in real part, s2 in complex part
  // ie. fftbuf = s1 + j * s2
  for (int i = 0; i < FFTSIZE; i++)
    ((int32_t *)fft_buf)[i] = (s2[i] << 16) | s1[i];

    
  timeBegin = micros();
  // Perform FFT
  arm_cfft_radix4_q15(&fft_inst, fft_buf);
    
  // Untangle fft of both signals and calculate conj(fft(s1)) * fft(s2)
  // Treat DC point special
  int16_t fs1_re, fs1_im, fs2_re, fs2_im;
  fs1_re = fft_buf[0]; // fs1_im = 0
  fs2_re = fft_buf[0]; // fs2_im = 0
  ifft_buf[0] = fs1_re * fs2_re;
  ifft_buf[1] = 0;
  // Then handle most other points
  int16_t *fft_fwd = fft_buf + 2;
  int16_t *fft_rev = fft_buf + 2 * (FFTSIZE - 1);
  for (size_t i = 2; i < FFTSIZE; i += 2, fft_fwd += 2, fft_rev -= 2) {
    fs1_re = (fft_fwd[0] + fft_rev[0]) >> 1;
    fs1_im = (fft_fwd[1] - fft_rev[1]) >> 1;
    fs2_re = (fft_rev[1] + fft_fwd[1]) >> 1;
    fs2_im = (fft_rev[0] - fft_fwd[0]) >> 1;
    // calculate conj(fft(s1)) * fft(s2)
    int32_t cfs1_fs2_re = (fs1_re * fs2_re + fs1_im * fs2_im);
    int32_t cfs1_fs2_im = (fs1_re * fs2_im - fs1_im * fs2_re);
    ifft_buf[i] = ifft_buf[2 * FFTSIZE - i] = cfs1_fs2_re;
    ifft_buf[i + 1] = cfs1_fs2_im;
    ifft_buf[2 * FFTSIZE - i + 1] = -cfs1_fs2_im;
  }
  // Finally handle Nyquist point
  fs1_re = fft_fwd[0]; // fs1_im = 0
  fs2_re = fft_fwd[1]; // fs2_im = 0
  ifft_buf[FFTSIZE] = fs1_re * fs2_re;
  ifft_buf[FFTSIZE + 1] = 0;

  // Perform inverse FFT
  arm_cfft_radix4_q31(&ifft_inst, ifft_buf);

  timeEnd = micros(); // end of timing
    
  // The output data is in every second element of ifft_buf

  // As an example, find the cross-cor peak
  // The startvalues and boundaries restrict the optimization only to delays that are reasonable for my setup
  int peak_offset = 0;
  int32_t peak_value = ifft_buf[0]; //32768
  for (size_t i = 1, j = 2; i < 200; i++, j += 2) {
    if (ifft_buf[j] > peak_value) {
      peak_value = ifft_buf[j];
      peak_offset = i;
    }
  }
  for (size_t i = 3800, j = 7600; i < FFTSIZE; i++, j += 2) {
    if (ifft_buf[j] > peak_value) {
      peak_value = ifft_buf[j];
      peak_offset = i;
    }
  }
  if (peak_offset > FFTSIZE / 2)
    peak_offset -= FFTSIZE;
    peak_offset = -peak_offset;

  Serial.println(sizeof(ifft_buf));
  Serial.print("Calculated offset: ");
  if(peak_offset <200 and peak_offset > -200)
    Serial.println(peak_offset); // delay implies negative
  else 
    Serial.println("no source detected");
  Serial.print("It took ");
  Serial.println(timeEnd - timeBegin);
  
  delay(500);
}
 
In my opinion, using the ADC on the teensy is greatly hobbling your potential performance. You really should use the Teens Audio Shield or, even better, digital microphones (ie, mics with the ADC already built in). Like, this product from Adafruit:

https://www.adafruit.com/product/3421

With these mics (or the teensy audio board), you'd use the Teens Audio Library to get the digitized audio data. It would be at 44.1kHz and, if you use those digital mics, it'll be super clean audio quality. Way better than using the Teensy ADCs.

(Note that Adafruit has two types of digital mics: I2S mics and PDM mics. You want I2S mics. PDM mics are good mics, but you need another piece of hardware to interface them to the Teensy. The I2S mics are the better choice because they can be connected directly with no extra hardware.)

Mosquitos aren't very loud, so getting super clean audio is crucial.

Chip
 
I agree with chipaudette, our project has had noise issues and channel cross-talk issues using the 4 onboard ADCs in a K60 design. We are "getting along OK" but have discovered a need to take great care with the analog signal chain to avoid surprises that have taught us some hard lessons. If we find ourselves updating the design, we will almost certainly use digital connections between the controller and the analog signal chains. That way we decouple analog conversion from the surprisingly noisy and cross-talk-sensitive controller.
 
Last edited:
Back
Top