Python code

import array
import audiobusio
import board
import math
import time

from adafruit_circuitplayground import cp

# Define color of the peak pixel.
PEAK_COLOR = (80, 0, 255)

# Exponential scaling factor.
# Should probably be in range -10 .. 10 to be reasonable.
# CURVE is used to make
# - the readings more or less sensitive and
# - the display more or less jumpy.
CURVE = 10
SCALE_EXPONENT = math.pow(10, CURVE * -0.1)

# Define the number of samples that should be read at once.
NUM_SAMPLES = 160

# Return a normalized root mean square (RMS) average.
# Calculate the RMS value to measure the average loudness of a sound waveform.
def normalized_rms(samples):
    mean = sum(samples) / len(samples)
    return math.sqrt(sum((sample - mean) * (sample - mean) for sample in samples) / len(samples))

# Configure the microphone object and the samples variable.
mic = audiobusio.PDMIn(board.MICROPHONE_CLOCK, board.MICROPHONE_DATA, sample_rate = 16000, bit_depth = 16)
samples = array.array('H', [0] * NUM_SAMPLES)

# Initialize the mic object. Assume that it is quiet when started.
mic.record(samples, len(samples))

# Set the lowest expected level, plus a slight increase.
input_floor = normalized_rms(samples) + 10
# OR
# Use a fixed floor
#input_floor = 50

# Lower sensitivity number means more sensitive; more NeoPixels will light up with less sound.
# If the meter seems too sensitive, the sensitivity number can be adjusted.
sensitivity = 500
input_ceiling = input_floor + sensitivity

peak = 0

# Loop forever
while True:
    # Sound samples are taken by the mic object.
    mic.record(samples, len(samples))

    # Use the normalized RMS to calculate the average of a set of samples, namely the magnitude of this set of samples.
    magnitude = normalized_rms(samples)

    # Compute scaled logarithmic reading in the range 0 to 10,
    # as 10 NeoPixels are built into the Circuit Playground Express board.
    constrain = max(input_floor, min(magnitude, input_ceiling))
    scale = math.pow((constrain - input_floor) / (input_ceiling - input_floor), SCALE_EXPONENT) * 10

    # The pixels below the scaled and interpolated magnitude are lit up.
    cp.pixels.fill((0, 0, 0))

    for p in range(10):
        if p < scale:
            cp.pixels[p] = (200, p * (255 // 10), 0)
        # Light up the peak pixel and animate it slowly dropping.
        if scale >= peak:
            peak = min(scale, 9)
        elif peak > 0:
            peak = peak - 1
        if peak > 0:
            cp.pixels[int(peak)] = PEAK_COLOR

    # Depending on the switch, write to
    # * the serial console (if True, i.e. left), or to
    # * the CircuitPython storage (if False, i.e. right).
    if cp.switch:
        print("The magnitude of the set of samples is", magnitude)
        print( (magnitude, ) )
    else:
        f = open("magnitude.csv", "a")
        f.write(repr(magnitude) + "\n")
        f.close()
import storage

from adafruit_circuitplayground import cp

storage.remount("/", readonly=cp.switch)