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

Tone Test

Here is a nice website that provides a pure tone generator we can use for testing:

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.

This guide was first published on Oct 22, 2019. It was last updated on Oct 22, 2019.

This page (Frequency Basics) was last updated on Mar 20, 2023.

Text editor powered by tinymce.