-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.py
210 lines (188 loc) · 5.5 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# library import for required libraries
import numpy as np
import sounddevice as sd
import aubio
import time
from scipy.signal import savgol_filter
# tuning freq for each note in an object
# from https://pages.mtu.edu/~suits/notefreqs.html
tuning_frequencies = {
"C0": 16.35,
"C#0/Db0": 17.32,
"D0": 18.35,
"D#0/Eb0": 19.45,
"E0": 20.60,
"F0": 21.83,
"F#0/Gb0": 23.12,
"G0": 24.50,
"G#0/Ab0": 25.96,
"A0": 27.50,
"A#0/Bb0": 29.14,
"B0": 30.87,
"C1": 32.70,
"C#1/Db1": 34.65,
"D1": 36.71,
"D#1/Eb1": 38.89,
"E1": 41.20,
"F1": 43.65,
"F#1/Gb1": 46.25,
"G1": 49.00,
"G#1/Ab1": 51.91,
"A1": 55.00,
"A#1/Bb1": 58.27,
"B1": 61.74,
"C2": 65.41,
"C#2/Db2": 69.30,
"D2": 73.42,
"D#2/Eb2": 77.78,
"E2": 82.41,
"F2": 87.31,
"F#2/Gb2": 92.50,
"G2": 98.00,
"G#2/Ab2": 103.83,
"A2": 110.00,
"A#2/Bb2": 116.54,
"B2": 123.47,
"C3": 130.81,
"C#3/Db3": 138.59,
"D3": 146.83,
"D#3/Eb3": 155.56,
"E3": 164.81,
"F3": 174.61,
"F#3/Gb3": 185.00,
"G3": 196.00,
"G#3/Ab3": 207.65,
"A3": 220.00,
"A#3/Bb3": 233.08,
"B3": 246.94,
"C4": 261.63,
"C#4/Db4": 277.18,
"D4": 293.66,
"D#4/Eb4": 311.13,
"E4": 329.63,
"F4": 349.23,
"F#4/Gb4": 369.99,
"G4": 392.00,
"G#4/Ab4": 415.30,
"A4": 440.00,
"A#4/Bb4": 466.16,
"B4": 493.88,
"C5": 523.25,
"C#5/Db5": 554.37,
"D5": 587.33,
"D#5/Eb5": 622.25,
"E5": 659.25,
"F5": 698.46,
"F#5/Gb5": 739.99,
"G5": 783.99,
"G#5/Ab5": 830.61,
"A5": 880.00,
"A#5/Bb5": 932.33,
"B5": 987.77,
"C6": 1046.50,
"C#6/Db6": 1108.73,
"D6": 1174.66,
"D#6/Eb6": 1244.51,
"E6": 1318.51,
"F6": 1396.91,
"F#6/Gb6": 1479.98,
"G6": 1567.98,
"G#6/Ab6": 1661.22,
"A6": 1760.00,
"A#6/Bb6": 1864.66,
"B6": 1975.53,
"C7": 2093.00,
"C#7/Db7": 2217.46,
"D7": 2349.32,
"D#7/Eb7": 2489.02,
"E7": 2637.02,
"F7": 2793.83,
"F#7/Gb7": 2959.96,
"G7": 3135.96,
"G#7/Ab7": 3322.44,
"A7": 3520.00,
"A#7/Bb7": 3729.31,
"B7": 3951.07,
"C8": 4186.01,
"C#8/Db8": 4434.92,
"D8": 4698.63,
"D#8/Eb8": 4978.03,
"E8": 5274.04,
"F8": 5587.65,
"F#8/Gb8": 5919.91,
"G8": 6271.93,
"G#8/Ab8": 6644.88,
"A8": 7040.00,
"A#8/Bb8": 7458.62,
"B8": 7902.13
}
# to track cent diff and current note
min_cent_difference = float('inf')
min_cent_note_status = ""
# calc nearest freq and get cent diff
def find_nearest_tuning_frequency(frequency):
nearest_note = min(tuning_frequencies, key=lambda note: abs(tuning_frequencies[note] - frequency))
corrected_cent_difference = 1200 * np.log2(frequency / tuning_frequencies[nearest_note])
return nearest_note, corrected_cent_difference
# to decide if the note is sharp or flat depending on cent diff (by 5)
def get_note_status(cent_difference):
if cent_difference > 5:
return "make flat"
elif cent_difference < -5:
return "make sharp"
else:
return "in tune"
# callback for audio input, used for audio input stream constructor
# yes i know we don't use frames or callback_time but it crashes without
# it in the audio input stream constructor...
def audio_callback(indata, frames, callback_time, status):
global min_cent_difference
global min_cent_note_status
if status:
print(status)
samples = indata[:, 0]
# pitch detection things.. makes more accurate
pitch_o = aubio.pitch("yinfft", 2048, 2048, sample_rate)
pitch_o.set_unit("Hz")
pitch_o.set_tolerance(0.8)
pitch_sum = 0
num_frames = 0
# calc the average pitch in blocks
for i in range(0, len(samples), block_size):
pitch = pitch_o(samples[i:i+block_size])[0]
if pitch != 0:
pitch_sum += pitch
num_frames += 1
if num_frames > 0:
average_pitch = pitch_sum / num_frames
# find nearest freq and calc cent diff
nearest_note, cent_difference = find_nearest_tuning_frequency(average_pitch)
note_status = get_note_status(cent_difference)
print(f"freq: {average_pitch:.2f}hz, note: {nearest_note}, cent diff: {cent_difference:.2f} ({note_status})")
# update min cent diff
if cent_difference < min_cent_difference:
min_cent_difference = cent_difference
min_cent_note_status = note_status
cent_differences.append((cent_difference, time.time()))
# define audio settings
sample_rate = 44100
block_size = 2048
# store cent differences from the mic pick up
cent_differences = []
# define audio interval length
time_interval = 2.5
# add the start time into a var
start_time = time.time()
# start the audio input stream
with sd.InputStream(callback=audio_callback, channels=1, samplerate=sample_rate, blocksize=block_size):
print("violin tuner started, awaiting input")
try:
# run the audio input for 2.5 sec
while True:
if time.time() - start_time >= time_interval:
break
sd.sleep(100)
except KeyboardInterrupt:
pass
# output the lowest cent and if the general pick ups were sharp or flat
print(f"lowest cent: {min_cent_difference:.2f} ({min_cent_note_status})")