This repository has been archived on 2025-04-28. You can view files and clone it, but cannot push or open issues or pull requests.
computers-sound-music-portf.../code/tuner/main.py
2024-11-15 17:26:41 -08:00

88 lines
2.3 KiB
Python

import string
from typing import Any
from scipy.io import wavfile as wav
import numpy as np
import sounddevice as sd
import math
import sys
print("Tuner exercise")
SAMPLE_RATE = 44100
CHUNK = 2048
QUIET_THRESHOLD = 0.01
A4 = 440
DEBUG = True
NOTES = "A B♭/A♯ B C D♭/C♯ D E♭/D♯ E F F♯/G♭ G A♭/G♯".split()
# Get the note and octave using known note forumla
def calculate_note(frequency):
if frequency > 0:
note_num = round(math.log2(frequency / A4) * 12)
octave = math.floor(4 + note_num / 12)
rel_note = note_num % 12
note = NOTES[rel_note]
return f"{note}{octave}"
return 0
# Attempt to get the dominant frequency by windowing our data sample, and taking an FFT
# In theory, the highest magnitude frequency of the FFT result should reflect what the human is trying to play in the signal
def dominant_frequency(data, samplerate):
windowed_data = data * np.hanning(len(data))
fft_result = np.fft.rfft(windowed_data)
fft_magnitude = np.abs(fft_result)
freqs = np.fft.rfftfreq(len(windowed_data), 1 / samplerate)
peak_idx = np.argmax(fft_magnitude)
dominant_freq = freqs[peak_idx]
return dominant_freq
def calculate_rms(data):
return np.sqrt(np.mean(data**2))
def audio_callback(indata: np.ndarray, frames: int, time: Any, status: Any):
if status:
print(f"Status: {status}")
rms = calculate_rms(indata)
# Ignore low noise to prevent spammy-ness and keep last audible result shown
if rms > QUIET_THRESHOLD:
mono_data = np.mean(indata, axis=1)
frequency = dominant_frequency(mono_data, SAMPLE_RATE)
out = f"\r{calculate_note(frequency)}"
if DEBUG: out += f" Dominant Frequency: {frequency:.2f} Hz (RMS: {rms:.3f})"
sys.stdout.write(out)
sys.stdout.flush()
else:
pass
if DEBUG:
print(sd.query_devices())
try:
stream = sd.InputStream(
device=15,
callback=audio_callback,
samplerate=SAMPLE_RATE,
channels=1,
blocksize=CHUNK,
)
stream.start()
if DEBUG:
print("channels? ", stream.channels)
print("device? ", stream.device)
print("active? ", stream.active)
while True:
pass
except KeyboardInterrupt:
print("Audio recording stopped.")
finally:
print("Stream closed.")