So what about musical notes? The ones with letters, like ABCDEFG. Every Good Bird Does Fly. And all that.
Determining notes is essentially just a matter of mapping the computed frequency to specific notes. This mapping is well known and you can read more about it here:
Here's the complete code that includes note detection:
# 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
# octave = 1 2 3 4 5 6 7 8
NOTES = { "C" : (33, 65, 131, 262, 523, 1047, 2093, 4186),
"D" : (37, 73, 147, 294, 587, 1175, 2349, 4699),
"E" : (41, 82, 165, 330, 659, 1319, 2637, 5274),
"F" : (44, 87, 175, 349, 698, 1397, 2794, 5588),
"G" : (49, 98, 196, 392, 785, 1568, 3136, 6272),
"A" : (55, 110, 220, 440, 880, 1760, 3520, 7040),
"B" : (62, 123, 247, 494, 988, 1976, 3951, 7902)}
#----------------------------------------------------
# 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))
# Find corresponding note
for note in NOTES:
for octave, note_freq in enumerate(NOTES[note]):
if note_freq * 0.97 <= freq <= note_freq * 1.03:
print("-"*10)
print("NOTE = {}{}".format(note, octave + 1))
print("-"*10)
time.sleep(DELAY)
This also largely the same as our original code. The main new item is a table that maps NOTES at various OCTAVES to their corresponding frequency:
# octave = 1 2 3 4 5 6 7 8
NOTES = { "C" : (33, 65, 131, 262, 523, 1047, 2093, 4186),
"D" : (37, 73, 147, 294, 587, 1175, 2349, 4699),
"E" : (41, 82, 165, 330, 659, 1319, 2637, 5274),
"F" : (44, 87, 175, 349, 698, 1397, 2794, 5588),
"G" : (49, 98, 196, 392, 785, 1568, 3136, 6272),
"A" : (55, 110, 220, 440, 880, 1760, 3520, 7040),
"B" : (62, 123, 247, 494, 988, 1976, 3951, 7902)}
And then once we have our frequency estimate, we just figure out where it is located in this table (coded to be plus or minus 3%):
# Find corresponding note
for note in NOTES:
for octave, note_freq in enumerate(NOTES[note]):
if note_freq * 0.97 <= freq <= note_freq * 1.03:
print("-"*10)
print("NOTE = {}{}".format(note, octave + 1))
print("-"*10)
The simple sketch above does not have entries for sharp and flat notes. So we'll test just the primary notes. To bring up a table of pre-defined musical note frequencies, click this button:
And then, with the above code running, try different notes. Here's C4:
Here's A4:
Here's E6:
Pretty good. As you get lower in octave (the number, like 4 and 6 above), you'll probably find it doesn't work quite as well. That's mainly due to the absolute frequency differences between the notes getting smaller in the lower octaves. And since our technique isn't super accurate, it has trouble distinguishing between consecutive note frequencies. It should get pretty close though.
Page last edited January 22, 2025
Text editor powered by tinymce.