0

CircuitPython Tone Detection
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

CircuitPython Tone Detection

by JSE98 on Fri May 15, 2020 6:58 pm

Hello,

I'm trying to do tone detection with a Feather M4 Express, PDM mic, and the ulab library, and would appreciate some help!

Below is the part of my code that sets up the mic object and creates a spectrogram from the recorded samples.

Code: Select all | TOGGLE FULL SIZE
# Mic setup
mic = audiobusio.PDMIn(board.D1, board.D12, sample_rate=16000, bit_depth=16)
num_samples = 256
samples_bit = array.array('H', [0] * (num_samples+3))

# Main loop
while True:
    mic.record(samples_bit, len(samples_bit))
    samples = np.array(samples_bit[3:])
    spectrum = extras.spectrogram(samples)
    print((spectrum,))
    time.sleep(0.1)

The below image is of the Mu Plotter with me humming into the mic, but not entirely sure what I'm looking at. Could someone please enlighten me a bit?

Spectrum.png
Spectrum.png (138.48 KiB) Viewed 25 times

How could I go about determining the primary frequency (assuming the tone is almost pure)?

Thank you,
Jack

JSE98
 
Posts: 9
Joined: Sat Apr 04, 2020 1:42 am

Re: CircuitPython Tone Detection

by siddacious on Fri May 15, 2020 9:04 pm

I'll flag down someone who can help. Stand by!

siddacious
 
Posts: 246
Joined: Fri Apr 21, 2017 3:09 pm

Re: CircuitPython Tone Detection

by JSE98 on Fri May 15, 2020 9:20 pm

Okay, thank you!

JSE98
 
Posts: 9
Joined: Sat Apr 04, 2020 1:42 am

Re: CircuitPython Tone Detection

by jepler on Fri May 15, 2020 9:49 pm

When you take the spectrogram() of a pure tone, you would see a sharp peak in the spectrogram, with the high number corresponding to the frequency, and low values everywhere else. However, I think the problem is that mu isn't showing you the data in a convenient way to interpret what is going on. What you need to see is all 256 numbers returned by spectrogram() in a single plot. What mu is showing you is just the first 3 numbers returned by spectrogram() each time. Those numbers on their own don't mean a whole lot.


This is what a spectrogram with a pure tone will look like when correctly visualized. You'll notice that at the far left there's a second peak which is much higher, so we'll want to ignore that.

frequency-peak.png
frequency-peak.png (14.17 KiB) Viewed 18 times


To make this plot, I printed the first 128 the values using
Code: Select all | TOGGLE FULL SIZE
print(list(spectrum[:128]))
and put them into a graphing program on my computer. Because of reasons that are obvious only to mathematicians, the first half of the values from spectrogram() go in ascending frequency order and the second half go in descending frequency order. We'll just ignore the second half, which is why we say take the slice [:128], meaning the values at index 0, 1, 2, ..., 127.

So now how do we find that in CircuitPython? ulab has a routine called numerical.argmax that will return the index of the biggest item. So my strategy is in several steps:
  • zero out the items 0 and 1 in the list, because we don't want them
  • use argmax to find the index of the biggest item
  • Use a little math to find out the frequency. I did not verify this, but I believe that to turn the index into the frequency, you multiply by the sample frequency and divide by the number of samples sent to spectrum(). However, this may not be right

Code: Select all | TOGGLE FULL SIZE
# Main loop
while True:
    mic.record(samples_bit, len(samples_bit))
    samples = np.array(samples_bit[3:])
    spectrum = extras.spectrogram(samples)
    spectrum = spectrum[:128]
    spectrum[0] = 0
    spectrum[1] = 0
    peak_idx = numerical.argmax(spectrum)
    peak_freq = peak_idx * 16000 / 256
    print((peak_idx, peak_freq, spectrum[peak_idx]))
    time.sleep(0.1)


Because peak_idx is always a whole number, the frequency is always a multiple of (16000/256) = 62.5Hz. To a point, you could get a better estimate by increasing the number of samples you record, up to a point. There may be other techniques for refining the number, but I don't know them offhand.

When I ran this code on my device (a CLUE with built-in mic) and whistled a clear note, I got a consistent peak_freq for each note of an ascending scale.
Code: Select all | TOGGLE FULL SIZE
(19, 1187.5, 294417.0)
(19, 1187.5, 245807.0)
(21, 1312.5, 174337.0)
(21, 1312.5, 273723.0)
(23, 1437.5, 51420.2)

I hope that this information helps get you going with your project. I'll keep an eye on this thread for responses.

jepler
 
Posts: 23
Joined: Mon Oct 28, 2013 4:16 pm

Re: CircuitPython Tone Detection

by JSE98 on Sat May 16, 2020 1:05 am

Wow, thank you so much for the thorough response! It makes much more sense now.

I went ahead and did a quick test myself by playing it a 1k sine tone and sure enough:

1k.png
1k.png (53.56 KiB) Viewed 16 times

Thank you,
Jack

JSE98
 
Posts: 9
Joined: Sat Apr 04, 2020 1:42 am

Please be positive and constructive with your questions and comments.