We'll be using CircuitPython for this project. Are you new to using CircuitPython? No worries, there is a full getting started guide here.

Adafruit suggests using the Mu editor to edit your code and have an interactive REPL in CircuitPython. You can learn about Mu and its installation in this tutorial.

The code is based on this guide on CircuitPython audio for the Circuit Playground Express.

A PDMIn object is created to read sound levels from the mic, which are stored in the array named samples. Those samples are then combined using root-mean-square to get a measure of the amount of energy in the sound (which corresponds to the overall volume) and mapped onto a logarithmic scale from 0 to 10. If that results in a value greater than or equal to the trigger threshold (defined by THRESHOLD) the ears perk up. If it's less than THRESHOLD, the ears relax.

Beyond the above, the code only perks up or relaxes the ears the first time the sound is louder or quieter than the threshold. The state variable ears_up tracks the state of the ears (True represents perked up). So the ears will perk up only when the sound is loud enough and the ears are relaxed. If the sound is quiet and the ears are perked up, they will relax.

Why the extra code? To manage a cool off period of a second after the state of the ears changes. The code sleeps for that time and doesn't check the sound. This helps avoid the ears fluttering when the sound level is around the threshold. It also keeps the ears perked up for a second which enhances the effect.

# SPDX-FileCopyrightText: 2018 Dave Astels for Adafruit Industries
#
# SPDX-License-Identifier: MIT

"""
Circuit Playground Express sounds activated ears.

Adafruit invests time and resources providing this open source code.
Please support Adafruit and open source hardware by purchasing
products from Adafruit!

Written by Dave Astels for Adafruit Industries
Copyright (c) 2018 Adafruit Industries
Licensed under the MIT license.

All text above must be included in any redistribution.
"""

import time
import math
import array
import board
import audiobusio
import pwmio
from adafruit_motor import servo
from adafruit_circuitplayground.express import cpx

# Exponential scaling factor.
# Should probably be in range -10 .. 10 to be reasonable.
CURVE = 2
SCALE_EXPONENT = math.pow(10, CURVE * -0.1)

# Number of samples to read at once.
NUM_SAMPLES = 90

# the trigger threshhold
THRESHOLD = 6
left_pwm = pwmio.PWMOut(board.A1, frequency=50)
right_pwm = pwmio.PWMOut(board.A2, frequency=50)

left_ear = servo.Servo(left_pwm)
right_ear = servo.Servo(right_pwm)

cpx.pixels.fill((0, 0, 0))
left_ear.angle = 0
right_ear.angle = 0

# Restrict value to be between floor and ceiling.

def constrain(value, floor, ceiling):
    return max(floor, min(value, ceiling))


def log_scale(input_value, input_min, input_max, output_min, output_max):
    normalized_input_value = (input_value - input_min) / \
                             (input_max - input_min)
    return output_min + \
        math.pow(normalized_input_value, SCALE_EXPONENT) \
        * (output_max - output_min)


# Remove DC bias before computing RMS.

def normalized_rms(values):
    minbuf = int(mean(values))
    samples_sum = sum(
        float(sample - minbuf) * (sample - minbuf)
        for sample in values
    )

    return math.sqrt(samples_sum / len(values))


def mean(values):
    return sum(values) / len(values)


mic = audiobusio.PDMIn(board.MICROPHONE_CLOCK, board.MICROPHONE_DATA,
                       sample_rate=16000, bit_depth=16)


# Record an initial sample to calibrate. Assume it's quiet when we start.
samples = array.array('H', [0] * NUM_SAMPLES)
mic.record(samples, len(samples))
# Set lowest level to expect, plus a little.
input_floor = normalized_rms(samples) + 10

# Corresponds to sensitivity: lower means ears perk up at lower volumes
# Adjust this as you see fit.
input_ceiling = input_floor + 750

ears_up = False

while True:
    samples_read = mic.record(samples, len(samples))
    if samples_read < NUM_SAMPLES:
        print("MISSING SAMPLES, only: {0}".format(samples_read))
    magnitude = normalized_rms(samples)
    # You might want to print this to see the values.
    # print(magnitude)

    # Compute scaled logarithmic reading in the range 0 to 10
    c = log_scale(constrain(magnitude, input_floor, input_ceiling),
                  input_floor, input_ceiling, 0, 10)


    if c >= THRESHOLD and not ears_up:
        ears_up = True
        left_ear.angle = 90
        right_ear.angle = 90
        time.sleep(1.0)
    elif c < THRESHOLD and ears_up:
        ears_up = False
        left_ear.angle = 0
        right_ear.angle = 0
        time.sleep(1.0)
    else:
        time.sleep(0.1)

This guide was first published on Aug 22, 2018. It was last updated on Apr 15, 2024.

This page (Code) was last updated on Apr 15, 2024.

Text editor powered by tinymce.