OK, let's actually implement the "mean crossing" technique and see if it works. We'll start simple and just print the computed results to the serial monitor. Here's the complete code:
# SPDX-FileCopyrightText: 2019 Carter Nelson for Adafruit Industries
#
# SPDX-License-Identifier: MIT
import time
import array
import board
import audiobusio
#---| User Configuration |---------------------------
SAMPLERATE = 16000
SAMPLES = 1024
THRESHOLD = 100
MIN_DELTAS = 5
DELAY = 0.2
#----------------------------------------------------
# Create a buffer to record into
samples = array.array('H', [0] * SAMPLES)
# Setup the mic input
mic = audiobusio.PDMIn(board.MICROPHONE_CLOCK,
board.MICROPHONE_DATA,
sample_rate=SAMPLERATE,
bit_depth=16)
while True:
# Get raw mic data
mic.record(samples, SAMPLES)
# Compute DC offset (mean) and threshold level
mean = int(sum(samples) / len(samples) + 0.5)
threshold = mean + THRESHOLD
# Compute deltas between mean crossing points
# (this bit by Dan Halbert)
deltas = []
last_xing_point = None
crossed_threshold = False
for i in range(SAMPLES-1):
sample = samples[i]
if sample > threshold:
crossed_threshold = True
if crossed_threshold and sample < mean:
if last_xing_point:
deltas.append(i - last_xing_point)
last_xing_point = i
crossed_threshold = False
# Try again if not enough deltas
if len(deltas) < MIN_DELTAS:
continue
# Average the deltas
mean = sum(deltas) / len(deltas)
# Compute frequency
freq = SAMPLERATE / mean
print("crossings: {} mean: {} freq: {} ".format(len(deltas), mean, freq))
time.sleep(DELAY)
The part of the code that does the main work for the mean crossing estimation of frequency is this:
# Compute deltas between mean crossing points
# (this bit by Dan Halbert)
deltas = []
last_xing_point = None
crossed_threshold = False
for i in range(SAMPLES-1):
sample = samples[i]
if sample > threshold:
crossed_threshold = True
if crossed_threshold and sample < mean:
if last_xing_point:
deltas.append(i - last_xing_point)
last_xing_point = i
crossed_threshold = False
Compare that to the explanation in the previous section. You can see how it is looping over the entire mic SAMPLES, checking for crossed_threshold and storing all of the deltas.
Once that loop is complete, the actual frequency computation is straight forward. The deltas are averaged:
# Average the deltas
mean = sum(deltas) / len(deltas)
And the frequency (freq) is then just:
# Compute frequency
freq = SAMPLERATE / mean
Run the basic frequency example program above and watch the output on the serial monitor. Then use the website to generate some tones of various frequencies. Like this:
Here 190 is registering at about ~188:
Here 446 is registering at about ~442:
Here 1500 is registering at about ~1487:
The results are not exact, but pretty close. Not too bad for a technique this simple.
Now let's add some fun NeoPixel response based on the estimated frequency.
Page last edited January 22, 2025
Text editor powered by tinymce.