Skip to content

Instantly share code, notes, and snippets.

@jul
Last active October 29, 2024 13:20
Show Gist options
  • Save jul/ae03e76173c73265c9ed45f06ba2b1c3 to your computer and use it in GitHub Desktop.
Save jul/ae03e76173c73265c9ed45f06ba2b1c3 to your computer and use it in GitHub Desktop.
a better guitar tuner in python
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()
@jul
Copy link
Author

jul commented Oct 28, 2024

tuner2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment