Last active
October 29, 2024 13:20
-
-
Save jul/ae03e76173c73265c9ed45f06ba2b1c3 to your computer and use it in GitHub Desktop.
a better guitar tuner in python
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import pyaudio | |
import matplotlib.pyplot as plt | |
import matplotlib.animation as animation | |
import numpy as np | |
import time | |
from sys import argv | |
A = 440.0 | |
try: | |
A=float(argv[1]) | |
except IndexError: | |
pass | |
form_1 = pyaudio.paInt16 # 16-bit resolution | |
chans = 1 # 1 channel | |
samp_rate = 44100 # 44.1kHz sampling rate | |
chunk = 44100//2# .5 seconds of sampling for 1Hz accuracy | |
audio = pyaudio.PyAudio() # create pyaudio instantiation | |
# create pyaudio stream | |
stream = audio.open( | |
format = form_1,rate = samp_rate,channels = chans, | |
input = True , frames_per_buffer=chunk | |
) | |
fig = plt.figure(figsize=(13,8)) | |
ax = fig.add_subplot(111) | |
plt.grid(True) | |
def compute_freq(ref, half_tones): | |
return [ 1.0*ref*(2**((half_tones+12*i )/12)) for i in range(-4,4) ] | |
print(compute_freq(A,0)) | |
note_2_freq = dict( | |
E = compute_freq(A,-5), | |
A = compute_freq(A, 0), | |
D = compute_freq(A, 5), | |
G = compute_freq(A,-2), | |
B = compute_freq(A, 2), | |
) | |
resolution = samp_rate/(2*chunk) | |
def closest_to(freq): | |
res = dict() | |
for note, freqs in note_2_freq.items(): | |
res[note]=max(freqs) | |
for f in freqs: | |
res[note]= min(res[note], abs(freq -f)) | |
note,diff_freq = sorted(res.items(), key = lambda item : item[1])[0] | |
for f in note_2_freq[note]: | |
if abs(freq-f) == diff_freq: | |
return "%s %s %2.1f %d" % ( | |
note, | |
abs(freq - f ) < resolution and "=" or | |
( freq > f and "+" or "-"), | |
abs(freq-f), | |
freq | |
) | |
def init_func(): | |
plt.rcParams['font.size']=18 | |
plt.xlabel('Frequency [Hz]') | |
plt.ylabel('Amplitude [Arbitry Unit]') | |
plt.grid(True) | |
ax.set_xscale('log') | |
ax.set_yscale('log') | |
ax.set_xticks( note_2_freq["E"] + note_2_freq["A"]+ | |
note_2_freq["D"]+ note_2_freq["G"]+ | |
note_2_freq["B"] , | |
labels = ( | |
[ "E" ] * len(note_2_freq["E"]) + | |
[ "A" ] * len(note_2_freq["A"]) + | |
[ "D" ] * len(note_2_freq["D"]) + | |
[ "G" ] * len(note_2_freq["G"]) + | |
[ "B" ] * len(note_2_freq["B"]) | |
) | |
) | |
ax.set_xlim(40, 4000) | |
return ax | |
def data_gen(): | |
stream.start_stream() | |
data = np.frombuffer(stream.read(chunk),dtype=np.int16) | |
stream.stop_stream() | |
yield data | |
i=0 | |
def animate(data): | |
global i | |
i+=1 | |
ax.cla() | |
init_func() | |
# compute FFT parameters | |
f_vec = samp_rate*np.arange(chunk/2)/chunk # frequency vector based on window | |
# size and sample rate | |
mic_low_freq = 50 # low frequency response of the mic | |
low_freq_loc = np.argmin(np.abs(f_vec-mic_low_freq)) | |
fft_data = (np.abs(np.fft.fft(data))[0:int(np.floor(chunk/2))])/chunk | |
fft_data[1:] = 2*fft_data[1:] | |
plt.plot(f_vec,fft_data) | |
max_loc = np.argmax(fft_data[low_freq_loc:])+low_freq_loc | |
# max frequency resolution | |
plt.annotate(r'$\Delta f_{max}$: %2.1f Hz, A = %2.1f Hz' % ( | |
resolution, A), xy=(0.7,0.92), xycoords='figure fraction' | |
) | |
ax.set_ylim([0,2*np.max(fft_data)]) | |
# annotate peak frequency | |
annot = ax.annotate( | |
'Freq: %s'%(closest_to(f_vec[max_loc])), | |
xy=(f_vec[max_loc], fft_data[max_loc]),\ | |
xycoords='data', xytext=(0,30), textcoords='offset points', | |
arrowprops=dict(arrowstyle="->"), | |
ha='center',va='bottom') | |
#fig.savefig('full_figure-%04d.png' % i) | |
return ax, | |
ani = animation.FuncAnimation( | |
fig, animate, data_gen, init_func, interval=.15, | |
cache_frame_data=False, repeat=True, blit=False | |
) | |
plt.show() |
Author
jul
commented
Oct 28, 2024
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment