3
Hello. I am creating a program that allows the user to know which note is singing and its frequency, in real time. However, during the window update, the program stops working and only updates after a few seconds, happening several times throughout the program run. Code:
# -*- coding: cp1252 -*-
from numba import jit
import pyximport; pyximport.install()
import sys
from audiolazy import (tostream, AudioIO, freq2str, sHz, chunks, lowpass, envelope, pi, thub, Stream, maverage)
from numpy.fft import rfft
import matplotlib
import matplotlib.pyplot as plt
import ttk
import pyaudio
import pylab
import random
#-----------------------------------------------------------------------------#
#funcoes de calculo do pitch--------------------------------------------------#
#variaveis do pyaudio
f_a=440.0
chunk=2048
FORMAT=pyaudio.paInt16
CHANNELS=1
RATE=44100
RECORD_S=2.
WAVE_OUTPUT_NAME="sing.wav"
pa=pyaudio.PyAudio()
# pylab.ioff()
stream=pa.open(format=FORMAT,
channels=CHANNELS,
rate=RATE,
input=True,
frames_per_buffer=chunk)
#calcula a frequencia
def real_freq():
all_v=[]
all_data=""
x_array=numpy.array([],dtype=numpy.int16)
n_chunks=int(RECORD_S*RATE/chunk)
npts=n_chunks*chunk
for i in range(0, n_chunks):
try:
data=stream.read(chunk)
except:
data=stream.read(chunk)
all_v.append(data)
all_data += data
x=numpy.fromstring(data, dtype=numpy.int16)
numpy.append(x_array,x)
all_x=numpy.fromstring(all_data, dtype=numpy.int16)
#p2p = 2.*numpy.sqrt(2.*all_x.var())
z = abs(pylab.fft(all_x))
max_arg = numpy.argmax(z[0:npts/2])
#peak = z[max_arg]
freq = pylab.arange(npts)*1.*RATE/npts
fmax = freq[max_arg] + 0.01
fmax = round(fmax, 2)
return fmax
def limiter(sig, threshold=.1, size=256, env=envelope.rms, cutoff=pi/2048):
sig = thub(sig, 2)
return sig * Stream( 1. if el <= threshold else threshold / el for el in maverage(size)(env(sig, cutoff=cutoff)) )
@tostream
def dft_pitch(sig, size=2048, hop=None):
for blk in Stream(sig).blocks(size=size, hop=hop):
dft_data = rfft(blk)
idx, vmax = max(enumerate(dft_data), key=lambda el: abs(el[1]) / (2 * el[0] / size + 1))
yield 2 * pi * idx / size
def pitch_from_mic(upd_time_in_ms):
rate = 44100
s, Hz = sHz(rate)
api = sys.argv[1] if sys.argv[1:] else None # Choose API via command-line
chunks.size = 1 if api == "jack" else 16
with AudioIO(api) as recorder:
snd = recorder.record(rate=rate)
sndlow = lowpass(400 * Hz)(limiter(snd, cutoff=20 * Hz))
hop = int(upd_time_in_ms * 1e-3 * s)
for pitch in freq2str(dft_pitch(sndlow, size=2*hop, hop=hop) / Hz):
first_cut = pitch.find('+')
second_cut = pitch.find('-')
if ((first_cut != -1) and (second_cut == -1)):
yield pitch[0:first_cut]
elif ((first_cut == -1) and (second_cut != -1)):
yield pitch[0:second_cut]
#funcao que define a afinacao (semelhante com a funcao pitch_from_mic())
def pitch_tune(upd_time_in_ms):
#calcula o pitch
rate = 44100
s, Hz = sHz(rate)
api = sys.argv[1] if sys.argv[1:] else None # Choose API via command-line
chunks.size = 1 if api == "jack" else 16
with AudioIO(api) as recorder:
snd = recorder.record(rate=rate)
sndlow = lowpass(400 * Hz)(limiter(snd, cutoff=20 * Hz))
hop = int(upd_time_in_ms * 1e-3 * s)
#calculado o pitch, le o valor encontrado e retorna a afinacao
for tune in freq2str(dft_pitch(sndlow, size=2*hop, hop=hop) / Hz):
first_cut = tune.find('+')
second_cut = tune.find('-')
third_cut = tune.find('%')
if ((first_cut != -1) and (second_cut == -1)):
fourth_cut = tune[first_cut:third_cut]
flat_one = fourth_cut[1:]
flat_one = float(flat_one)
#le os valores e retorna a afinacao
if (flat_one <= 20):
yield 'Afinado'
elif ((flat_one > 20) and (flat_one <= 35)):
yield 'Lig. Desafinado'
elif (flat_one > 35):
yield 'Muito Desafinado'
elif ((first_cut == -1) and (second_cut != -1)):
fifth_cut = tune[second_cut:third_cut]
flat_two = fifth_cut[1:]
flat_two = float(flat_two)
#le os valores e retorna a afinacao
if (flat_two <= 20):
yield 'Afinado'
elif ((flat_two > 20) and (flat_two <= 35)):
yield 'Lig. Desafinado'
elif (flat_two > 35):
yield 'Muito Desafinado'
# ----------------------
#Parte grafica do codigo
# ----------------------
if __name__ == "__main__":
from Tkinter import *
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import threading
import re
matplotlib.use('TkAgg')
#comeca a janela do programa
root = Tk()
#define o tamanho da janela
root.minsize(1200,500)
root.maxsize(1200,500)
root.configure(background='#d9d9d9')
root.focus_force()
#titulo da janela (outer widget)
root.title("SingMeter")
#stringvars
pitch_value = StringVar()
note_value = StringVar()
no_tone_deaf = StringVar()
#-----Frames secundaria(outer widgets)
#frame do pitch
sing_frame = LabelFrame(root, text='Informacão quantitativa', width=400, height=400, background='#d9d9d9')
sing_frame.place(x = 10, y = 30)
#frame da nota
graph_frame = LabelFrame(root, text='Gráfico', width=700, height=400, background='#d9d9d9')
graph_frame.place(x = 450, y = 30)
#listas e dicionarios de frequencias e notas (torna a base de dados propia para leitura)
database_dict = {}
freq_list = []
#funcoes do grafico-------------------------------------------------------#
xAchse=pylab.arange(0,100,1)
yAchse=pylab.array([0]*100)
fig = pylab.figure(1)
ax = fig.add_subplot(111)
ax.grid(True)
ax.set_xlabel("Tempo")
ax.set_ylabel("Frequência")
ax.axis([0,100, 50, 2000])
line1=ax.plot(xAchse,yAchse,'-')
canvas = FigureCanvasTkAgg(fig, master = graph_frame)
canvas.show()
canvas.get_tk_widget().place(x = 100, y = 50)
canvas._tkcanvas.place(x = 100, y = 25)
values=[]
values = [0 for x in range(100)]
Ta=0.01
fa=1.0/Ta
fcos=3.5
Konstant=1
T0=1.0
T1=Konstant
freq_list = []
def SinwaveformGenerator():
global values,T1,Konstant,T0,wScale2
#ohmegaCos=arccos(T1)/Ta
#print "fcos=", ohmegaCos/(2*pi), "Hz"
Tnext=((Konstant*T1)*2)-T0
if (len(values)%100>70):
try:
values.append(random.random())
except:
raise
else:
values.append(Tnext)
T0=T1
T1=Tnext
root.after(int(wScale2['to'])-wScale2.get(),SinwaveformGenerator)
def RealtimePloter():
global values,wScale,wScale2
NumberSamples=min(len(values),wScale.get())
CurrentXAxis=pylab.arange(len(values)-NumberSamples,len(values),1)
line1[0].set_data(CurrentXAxis,pylab.array(values[-NumberSamples:]))
ax.axis([CurrentXAxis.min(),CurrentXAxis.max(),50, 2000])
canvas.draw()
root.after(25,RealtimePloter)
#canvas.draw()
#manager.show()
wScale = Scale(master=root,label="View Width:", from_=3, to=1000,sliderlength=30, orient=HORIZONTAL)
wScale2 = Scale(master=root,label="Generation Speed:", from_=1, to=200,sliderlength=30, orient=HORIZONTAL)
wScale.set(100)
wScale2.set(wScale2['to']-10)
#--------------------------------------------------------------------------
#fecha a janela
def _quit():
root.quit()
root.destroy()
#funcões com elementos (inner widgets) da janela
@jit
def entries_window(pitch, note, sing_state):
#exibidor do pitch
pitch_value.set(pitch)
show_pitch = ttk.Entry(sing_frame, textvariable = pitch_value, state = 'readonly')
show_pitch.place(x = 120, y = 58.5)
#exibidor da nota
note_value.set(note)
show_note = ttk.Entry(sing_frame, textvariable = note_value, state = 'readonly')
show_note.place(x = 120, y = 135)
#exibidor da performance das notas do cantor
no_tone_deaf.set(sing_state)
show_tone_deaf = ttk.Entry(sing_frame, textvariable = no_tone_deaf, state = 'readonly')
show_tone_deaf.place(x = 170, y = 211.5)
entries_window(None, None, None)
#@jit
def window():
#labels
#label PITCH
pitch_title = ttk.Label(sing_frame, text = "Pitch:", font = "Verdana 20", background='#d9d9d9')
pitch_title.place(x = 30, y = 50)
#label NOTE
note_title = ttk.Label(sing_frame, text = "Nota:", font = "Verdana 21", background='#d9d9d9')
note_title.place(x = 30, y = 126)
#label AFINACAO
tune_title = ttk.Label(sing_frame, text = "Afinação", font = "Verdana 21", background='#d9d9d9')
tune_title.place(x = 30, y = 202)
#------------------------
#botões
#botao SAIR
record = ttk.Button(sing_frame, text = 'Sair', command=_quit)
record.place(x = 70, y = 300)
"""#botao STOP
stop_button = ttk.Button(sing_frame, text = 'Parar')
stop_button.place(x = 160, y = 300)"""
window()
regex_note = re.compile(r"^([A-Gb#]*-?[0-9]*)([?+-]?)(.*?%?)$")
upd_time_in_ms = 200
# atualiza as funcoes
def upd_value():
pitches = iter(pitch_from_mic(upd_time_in_ms))
while not root.should_finish:
root.value = next(pitches)
#atualiza os valores da janela
#@jit
def upd_timer():
note_value.set("\n".join(regex_note.findall(root.value)[0]))
tunes = iter(pitch_tune(upd_time_in_ms))
tuning = next(tunes)
no_tone_deaf.set(str("\n".join(regex_note.findall(tuning)[0])))
pitch_value.set(real_freq())
root.after(upd_time_in_ms, upd_timer)
root.after(1, SinwaveformGenerator)
root.after(1 ,RealtimePloter)
# inicia as threads
root.should_finish = False
root.value = freq2str(0)
note_value.set(root.value)
root.upd_thread = threading.Thread(target=upd_value)
#acaba o programa e as threads
root.protocol("WM_DELETE_WINDOW", _quit)
root.after_idle(upd_timer)
root.upd_thread.start()
root.mainloop()
root.should_finish = True
root.upd_thread.join()
I tried to use the numba (from numba import jit
) and Cython to improve the performance of the program execution, but there were no significant improvements. I still tried to use Pypy, but due to its incompatibility with Numpy I could not execute the code. The functions pitch_from_mic(upd_time_in_ms)
and pitch_tune(upd_time_in_ms)
calculate the note in the same way (I could not put this whole part of the code in a single function, as it gave the following error: IOError: [Errno -9981] Input overflowed
). The function real_freq()
it takes some time to calculate the frequency, but if the other functions are not called, the code has an acceptable performance. I’m using Windows 7 and I have Python 2.7.8.
Which window? What is crashing?
– Pablo Almeida
The program stops displaying the frequency and note values and jams (and prevents me from closing the window). After a few seconds, the program goes back to normal, but it gets stuck again after a few more seconds.
– Henrique
It’s probably because you’re running the algorithms in the same Graphical Interface Thread.
– Pablo Almeida
But how can I separate them, bearing in mind that the algorithms need to be constantly run and that the window needs to be updated regularly?
– Henrique
I took a quick look at the code, there are functions for redundant pitch calculus, you practically do two equal things at the same time, not to mention using Fourier and only finding the maximum value of the magnitude will not give you reliable results depending on the type of signal analyzed, your analysis window tbm seems to be very small, this could be improved to achieve more robust results ....
– ederwander