Seems to work. I'm looking for tips on how to speed up the program.
Tried: Running without GUI (a little faster but not as I wanted)
Severity: terrible (one update per 400 ms, no wait time included) Plus very high CPU usage
Purpose of the program: Measure noise exposure over time using the mic
tkinter.ttk version:
import os, errno
import pyaudio
from scipy.signal import lfilter
import numpy
from tkinter import *
from threading import Thread
from tkinter.ttk import *
from tk_tools import *
import time
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
from tkinter import messagebox
import sys
from idlelib.tooltip import Hovertip
try:
from winreg import *
except:
reg_present=False
messagebox.askokcancel('Limited Features', "Registry not present. Dosimeter disabled. OK to continue, Cancel to quit.", icon='warning')
else:
reg_present=True
def get_resource_path(relative_path):
try:
# PyInstaller creates a temp folder and stores path in _MEIPASS
base_path = sys._MEIPASS
except Exception:
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
def toggle_dosi():
global dosi_enabled
dosi_enabled=enable_dosi.instate(['selected'])
def reset():
global start, dosimeter_times, runTime, x, y, plot1
dosimeter_times={'82dB':0, '85dB':0, '88dB':0, '91dB':0, '94dB':0, '97dB':0, '100dB':0, '103dB':0, '106dB':0, '109dB':0, '112dB':0, '115dB':0, '118dB':0}
x=[]
y=[]
runTime=0
win=Tk()
win.title('Decibel Meter v1.2 (c) sserver')
win.grid()
win.resizable(False, False)
if reg_present:
CreateKeyEx(OpenKey(HKEY_CURRENT_USER, 'Software', reserved=0, access=KEY_ALL_ACCESS), 'sserver\Decibel Meter', reserved=0)
try:
dosi_enabled=bool(QueryValueEx(OpenKey(OpenKey(OpenKey(HKEY_CURRENT_USER, 'Software', reserved=0, access=KEY_ALL_ACCESS), 'sserver', reserved=0, access=KEY_ALL_ACCESS), 'Decibel Meter', reserved=0, access=KEY_ALL_ACCESS), 'DosimeterEnabled')[0])
except OSError:
SetValueEx(OpenKey(OpenKey(OpenKey(HKEY_CURRENT_USER, 'Software', reserved=0, access=KEY_ALL_ACCESS), 'sserver', reserved=0, access=KEY_ALL_ACCESS), 'Decibel Meter', reserved=0, access=KEY_ALL_ACCESS), 'DosimeterEnabled', 0, REG_DWORD, 0)
dosi_enabled=False
messagebox.showwarning('Registry Error', 'Error reading settings. Resetting to default...')
else:
dosi_enabled=False
dosi_enabled_first=dosi_enabled
tabControl = Notebook(win)
root=Frame(tabControl)
runTime=0
sub=Frame(tabControl)
sub1=Frame(tabControl)
tabControl.add(root, text ='Meter')
measure=False
start=time.time()
tabControl.add(sub, text ='Dosimeter')
tabControl.add(sub1, text ='Recording')
led = Led(root, size=20)
led.grid(column=2, row=14)
Hovertip(led,'1 dB\nAll is OK\nThreshold of hearing')
tabControl.pack(expand = 1, fill ="both")
gaugedb=SevenSegmentDigits(root, digits=3, digit_color='#00ff00', background='black')
gaugedb.grid(column=1, row=1)
dosidb=SevenSegmentDigits(sub, digits=3, digit_color='#00ff00', background='black')
dosidb.grid(column=1, row=2)
graphdb=SevenSegmentDigits(sub1, digits=3, digit_color='#00ff00', background='black')
graphdb.grid(column=1, row=2)
Hovertip(gaugedb,"Current dBA level")
led0 = Led(root, size=20)
led0.grid(column=2, row=13)
Hovertip(led0,'10 dB\nAll is OK\nEquivalent to rustling leaves in the distance')
led1 = Led(root, size=20)
led1.grid(column=2, row=12)
Hovertip(led1,'20 dB\nAll is OK\nEquivalent to a background in a movie studio')
led2 = Led(root, size=20)
led2.grid(column=2, row=11)
Hovertip(led2,'30 dB\nAll is OK\nEquivalent to a quiet bedroom')
led3 = Led(root, size=20)
led3.grid(column=2, row=10)
Hovertip(led3,'40 dB\nAll is OK\nEquivalent to a whisper')
led4 = Led(root, size=20)
led4.grid(column=2, row=9)
Hovertip(led4,'50 dB\nAll is OK\nEquivalent to a quiet home')
led5 = Led(root, size=20)
led5.grid(column=2, row=8)
Hovertip(led5,'60 dB\nAll is OK\nEquivalent to a quiet street')
led6 = Led(root, size=20)
led6.grid(column=2, row=7)
Hovertip(led6,'70 dB\nAll is OK\nEquivalent to a normal conversation')
led7 = Led(root, size=20)
led7.grid(column=2, row=6)
Hovertip(led7,'80 dB\nA little loud, may cause hearing damage in sensitive people.\nEquivalent to loud singing')
led8 = Led(root, size=20)
led8.grid(column=2, row=5)
Hovertip(led8,'90 dB\nLoud; repeated and/or long term exposure at this level may damage hearing.\nEquivalent to a motorcycle')
led9 = Led(root, size=20)
led9.grid(column=2, row=4)
Hovertip(led9,'100 dB\nCritically loud, even short exposure to this level can damage hearing.\nEquivalent to a subway')
led10 = Led(root, size=20)
led10.grid(column=2, row=3)
Hovertip(led10,'110 dB\nDangerous, even short exposure to this level can damage hearing.\nEquivalent to a helicopter overheaad')
led11 = Led(root, size=20)
led11.grid(column=2, row=2)
win.iconbitmap(get_resource_path('snd.ico'))
Hovertip(led11,"120 dB\nDangerous, even short exposure to this level can damage hearing.\nYou might feel pain at this level.\nEquivalent to a rock concert")
Label(root, text='120').grid(column=1, row=2)
Label(sub, text='Instantaneous dBA level').grid(column=1, row=1)
Label(sub1, text='Instantaneous dBA level').grid(column=1, row=1)
style=Style(win)
style.configure("1.Horizontal.TProgressbar", background='green')
style.configure("2.Horizontal.TProgressbar", background='yellow')
style.configure("3.Horizontal.TProgressbar", background='red')
style = Style(root)
style.layout('text.Horizontal.TProgressbar',
[('Horizontal.Progressbar.trough',
{'children': [('Horizontal.Progressbar.pbar',
{'side': 'left', 'sticky': 'ns'})],
'sticky': 'nswe'}),
('Horizontal.Progressbar.label', {'sticky': ''})])
style.configure('text.Horizontal.TProgressbar', text='0 %')
db_levels=[82, 85, 88, 91, 94, 97, 100, 103, 106, 109, 112, 115, 118]
niosh_limits=[57600, 28800, 14400, 7200, 3600, 1800, 900, 450, 225, 120, 60, 30, 15]
db_levels.reverse()
niosh_limits.reverse()
enable_dosi=Checkbutton(sub, text='Enable Dosimeter (restart app to apply)', command=toggle_dosi)
enable_dosi.grid(column=1, row=3)
enable_dosi.state(['!alternate'])
def record_frame():
global recording, rec_start, db, f
time_trunc='%.1f' % (time.time()-rec_start)
f.write(time_trunc+','+str(int(db))+'\n')
if recording:
win.after(500, record_frame)
else:
f.close()
def record():
global recording, f, rec_start
if not recording:
rec.config(text='Stop')
rec_start=time.time()
recording=True
f=open(os.getenv('HOMEPATH')+'\\Music\\mic_levels.csv', 'w')
f.write('Seconds,Decibels\n')
record_frame()
else:
recording=False
rec.config(text='Record')
if not reg_present:
enable_dosi.config(state=DISABLED)
enable_dosi.state(['!alternate'])
if dosi_enabled:
enable_dosi.state(['selected'])
Label(sub, text='Dose:', width=40).grid(column=1, row=4)
dosebar=Progressbar(sub, maximum=100, mode='determinate', length=200, style='text.Horizontal.TProgressbar')
dosebar.grid(column=2, row=4)
timeLabel=Label(sub, text='0 sec', width=30)
timeLabel.grid(column=2, row=2)
pdose=Label(sub, text='Projected dose: 0 sec', width=40)
Button(sub, text='Reset', command=reset).grid(column=2, row=3)
Label(sub1, text='Recording to Music\\mic_levels.csv.\nMake sure the file does not exist, or it will be overwritten.').grid(column=1, row=3)
rec=Button(sub1, text='Record', command=record)
rec.grid(column=1, row=4)
for i in range(len(db_levels)):
db_level=db_levels[i]
exec("label_"+str(db_level)+"=Label(sub, width=40, text='"+str(db_level)+"dBA: 0/"+str(niosh_limits[i])+" sec')")
exec("label_"+str(db_level)+".grid(column=1, row="+str(i+5)+")")
exec("bar_"+str(db_level)+'=Progressbar(sub, length=200, style="1.Horizontal.TProgressbar", mode="determinate")')
exec("bar_"+str(db_level)+'.grid(column=2, row='+str(i+5)+')')
else:
Label(sub, text='Dosimeter is not enabled', width=60).grid(column=1, row=4)
Label(sub1, text='Dosimeter must be enabled', width=60).grid(column=1, row=4)
Label(root, text='-').grid(column=1, row=3)
Label(root, text='-').grid(column=1, row=5)
Label(root, text='-').grid(column=1, row=7)
Label(root, text='-').grid(column=1, row=9)
Label(root, text='-').grid(column=1, row=11)
Label(root, text='-').grid(column=1, row=13)
Label(root, text='100').grid(column=1, row=4)
Label(root, text='Danger').grid(column=3, row=4)
Label(root, text='80').grid(column=1, row=6)
Label(root, text='Loud').grid(column=3, row=6)
Label(root, text='60').grid(column=1, row=8)
Label(root, text='40').grid(column=1, row=10)
Label(root, text='20').grid(column=1, row=12)
Label(root, text='dBA').grid(column=1, row=14)
Label(root, text='OK').grid(column=3, row=14)
Label(root, text='Max').grid(column=3, row=0)
Label(root, text='dBA').grid(column=1, row=0)
Label(root, text='dB Offset').grid(column=2, row=0)
maxdb_display=SevenSegmentDigits(root, digits=3, digit_color='#00ff00', background='black')
maxdb_display.grid(column=3, row=1)
dosimeter_times={'82dB':0, '85dB':0, '88dB':0, '91dB':0, '94dB':0, '97dB':0, '100dB':0, '103dB':0, '106dB':0, '109dB':0, '112dB':0, '115dB':0, '118dB':0}
Hovertip(maxdb_display,"Max dBA level since program start")
CHUNKS = [4096, 9600]
CHUNK = CHUNKS[1]
FORMAT = pyaudio.paInt16
CHANNEL = 1
RATES = [44300, 48000]
RATE = RATES[1]
offset=StringVar()
offset.set('0')
spinbox=Spinbox(root, from_=-20, to=20, textvariable=offset, state='readonly', width=5)
spinbox.grid(column=2, row=1)
Hovertip(spinbox,"dB offset (Calibration)\nUse this if the meter is not accurate.\nUse a reliable reference meter (such as a dedicated SPL meter).")
appclosed=False
start_check=time.time()
from scipy.signal import bilinear
db=0
x=[]
y=[]
def change_color(index):
global db
bar=db_levels[index]
temp=dosimeter_times[str(bar)+'dB']/niosh_limits[index]
if temp>=0.9:
exec("bar_"+str(db_level)+".config(style='3.Horizontal.TProgressbar')")
elif temp>=0.5:
exec("bar_"+str(db_level)+".config(style='2.Horizontal.TProgressbar')")
else:
exec("bar_"+str(db_level)+".config(style='1.Horizontal.TProgressbar')")
def timer_dosi():
global runTime, y, x, start_check
runTime+=time.time()-start_check
if db>=82 and db<85:
dosimeter_times['82dB']+=time.time()-start_check
if db>=85 and db<88:
dosimeter_times['85dB']+=time.time()-start_check
if db>=88 and db<91:
dosimeter_times['88dB']+=time.time()-start_check
if db>=91 and db<94:
dosimeter_times['91dB']+=time.time()-start_check
if db>=94 and db<97:
dosimeter_times['94dB']+=time.time()-start_check
if db>=97 and db<100:
dosimeter_times['97dB']+=time.time()-start_check
if db>=100 and db<103:
dosimeter_times['100dB']+=time.time()-start_check
if db>=103 and db<106:
dosimeter_times['103dB']+=time.time()-start_check
if db>=106 and db<109:
dosimeter_times['106dB']+=time.time()-start_check
if db>=109 and db<112:
dosimeter_times['109dB']+=time.time()-start_check
if db>=112 and db<115:
dosimeter_times['112dB']+=time.time()-start_check
if db>=115 and db<118:
dosimeter_times['115dB']+=time.time()-start_check
if db>=118:
dosimeter_times['118dB']+=time.time()-start_check
start_check=time.time()
win.after(200, timer_dosi)
def close():
global appclosed
win.destroy()
if dosi_enabled:
SetValueEx(OpenKey(OpenKey(OpenKey(HKEY_CURRENT_USER, 'Software', reserved=0, access=KEY_ALL_ACCESS), 'sserver', reserved=0, access=KEY_ALL_ACCESS), 'Decibel Meter', reserved=0, access=KEY_ALL_ACCESS), 'DosimeterEnabled', 0, REG_DWORD, 1)
else:
SetValueEx(OpenKey(OpenKey(OpenKey(HKEY_CURRENT_USER, 'Software', reserved=0, access=KEY_ALL_ACCESS), 'sserver', reserved=0, access=KEY_ALL_ACCESS), 'Decibel Meter', reserved=0, access=KEY_ALL_ACCESS), 'DosimeterEnabled', 0, REG_DWORD, 0)
appclosed=True
stream.stop_stream()
stream.close()
pa.terminate()
def A_weighting(fs):
f1 = 20.598997
f2 = 107.65265
f3 = 737.86223
f4 = 12194.217
A1000 = 1.9997
NUMs = [(2*numpy.pi * f4)**2 * (10**(A1000/20)), 0, 0, 0, 0]
DENs = numpy.polymul([1, 4*numpy.pi * f4, (2*numpy.pi * f4)**2],
[1, 4*numpy.pi * f1, (2*numpy.pi * f1)**2])
DENs = numpy.polymul(numpy.polymul(DENs, [1, 2*numpy.pi * f3]),
[1, 2*numpy.pi * f2])
return bilinear(NUMs, DENs, fs)
NUMERATOR, DENOMINATOR = A_weighting(RATE)
def rms_flat(a):
return numpy.sqrt(numpy.mean(numpy.absolute(a)**2))
pa = pyaudio.PyAudio()
stream = pa.open(format = FORMAT,
channels = CHANNEL,
rate = RATE,
input = True,
frames_per_buffer = CHUNK)
max_decibel=0
def returnSum(myDict):
mylist = []
for i in myDict:
mylist.append(myDict[i])
final = sum(mylist)
return final
def listen(old=0, error_count=0, min_decibel=100):
global appclosed
global max_decibel
global measure
global db
global start
if not appclosed:
try:
try:
block = stream.read(CHUNK)
except IOError as e:
if not appclosed:
error_count += 1
messagebox.showerror("Error, ", " (%d) Error recording: %s" % (error_count, e))
else:
decoded_block = numpy.frombuffer(block, numpy.int16)
y = lfilter(NUMERATOR, DENOMINATOR, decoded_block)
new_decibel = 20*numpy.log10(rms_flat(y))+int(offset.get())
if runTime>0:
runt=runTime
else:
runt=0.1
if new_decibel<0:
new_decibel=0
old = new_decibel
db=new_decibel
gaugedb.set_value(str(int(float('{:.2f}'.format(new_decibel)))))
dosidb.set_value(str(int(float('{:.2f}'.format(new_decibel)))))
graphdb.set_value(str(int(float('{:.2f}'.format(new_decibel)))))
if new_decibel>max_decibel:
max_decibel=new_decibel
if int(db)>0:
led.to_green(on=True)
else:
led.to_grey(on=True)
maxdb_display.set_value(str(int(float(str(max_decibel)))))
for i in range(0, 12):
if int(new_decibel)>=(10*(i+1)):
if i>=9:
exec("led"+str(i)+".to_red(on=True)")
elif i>=7:
exec("led"+str(i)+".to_yellow(on=True)")
else:
exec("led"+str(i)+".to_green(on=True)")
else:
exec("led"+str(i)+".to_grey(on=True)")
if dosi_enabled_first:
percent=(returnSum(dosimeter_times)/runt)*100
dosebar['value']=float(percent)
timeLabel.config(text=str(int(runt))+' sec')
style.configure('text.Horizontal.TProgressbar',text='%.1f %%' % percent)
pdose.config(text='Projected dose: '+str(int((returnSum(dosimeter_times)*8)/runt))+' sec')
for i in range(len(db_levels)):
db_level=db_levels[i]
exec("label_"+str(db_level)+".config(text='"+str(db_level)+"dBA: "+str(int(dosimeter_times[str(db_level)+'dB']))+'/'+str(niosh_limits[i])+" sec')")
exec("bar_"+str(db_level)+"['value']="+str((dosimeter_times[str(db_level)+'dB']/niosh_limits[i])*100))
change_color(i)
win.after(20, listen)
except TclError:
pass
win.protocol('WM_DELETE_WINDOW', close)
if __name__ == '__main__':
if dosi_enabled:
timer_dosi()
listen()
win.mainloop()
customtkinter version (library at https://github.com/TomSchimansky/CustomTkinter):
import os, errno
import pyaudio
from scipy.signal import lfilter
import numpy
from tkinter import *
from threading import Thread
from tk_tools import *
from customtkinter import *
import time
from tkinter import messagebox
import sys
from idlelib.tooltip import Hovertip
set_appearance_mode("System") # Modes: system (default), light, dark
set_default_color_theme("blue")
try:
from winreg import *
except:
reg_present=False
messagebox.askokcancel('Limited Features', "Registry not present. Dosimeter disabled. OK to continue, Cancel to quit.", icon='warning')
else:
reg_present=True
def get_resource_path(relative_path):
try:
# PyInstaller creates a temp folder and stores path in _MEIPASS
base_path = sys._MEIPASS
except Exception:
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
def toggle_dosi():
global dosi_enabled
dosi_enabled=enable_dosi.get()
def reset():
global start, dosimeter_times, runTime, x, y, plot1
dosimeter_times={'82dB':0, '85dB':0, '88dB':0, '91dB':0, '94dB':0, '97dB':0, '100dB':0, '103dB':0, '106dB':0, '109dB':0, '112dB':0, '115dB':0, '118dB':0}
x=[]
y=[]
runTime=0
win=CTk()
win.title('Decibel Meter v1.2 (c) sserver')
win.grid()
win.resizable(False, False)
if reg_present:
CreateKeyEx(OpenKey(HKEY_CURRENT_USER, 'Software', reserved=0, access=KEY_ALL_ACCESS), 'sserver\Decibel Meter', reserved=0)
try:
dosi_enabled=bool(QueryValueEx(OpenKey(OpenKey(OpenKey(HKEY_CURRENT_USER, 'Software', reserved=0, access=KEY_ALL_ACCESS), 'sserver', reserved=0, access=KEY_ALL_ACCESS), 'Decibel Meter', reserved=0, access=KEY_ALL_ACCESS), 'DosimeterEnabled')[0])
except OSError:
SetValueEx(OpenKey(OpenKey(OpenKey(HKEY_CURRENT_USER, 'Software', reserved=0, access=KEY_ALL_ACCESS), 'sserver', reserved=0, access=KEY_ALL_ACCESS), 'Decibel Meter', reserved=0, access=KEY_ALL_ACCESS), 'DosimeterEnabled', 0, REG_DWORD, 0)
dosi_enabled=False
messagebox.showwarning('Registry Error', 'Error reading settings. Resetting to default...')
else:
dosi_enabled=False
dosi_enabled_first=dosi_enabled
runTime=0
measure=False
recording=False
start=time.time()
tabview =CTkTabview(win)
tabview.pack(padx=20, pady=20)
tabview.add("Meter") # add tab at the end
tabview.add("Dosimeter")
tabview.add("Recording")
root=tabview.tab("Meter")
sub=tabview.tab("Dosimeter")
sub1=tabview.tab("Recording")
led = Led(root, size=20)
led.grid(column=2, row=14)
Hovertip(led,'1 dB\nAll is OK\nThreshold of hearing')
gaugedb=SevenSegmentDigits(root, digits=3, digit_color='#00ff00', background='black')
gaugedb.grid(column=1, row=1)
dosidb=SevenSegmentDigits(sub, digits=3, digit_color='#00ff00', background='black')
dosidb.grid(column=1, row=2)
graphdb=SevenSegmentDigits(sub1, digits=3, digit_color='#00ff00', background='black')
graphdb.grid(column=1, row=2)
Hovertip(gaugedb,"Current dBA level")
led0 = Led(root, size=20)
led0.grid(column=2, row=13)
Hovertip(led0,'10 dB\nAll is OK\nEquivalent to rustling leaves in the distance')
led1 = Led(root, size=20)
led1.grid(column=2, row=12)
Hovertip(led1,'20 dB\nAll is OK\nEquivalent to a background in a movie studio')
led2 = Led(root, size=20)
led2.grid(column=2, row=11)
Hovertip(led2,'30 dB\nAll is OK\nEquivalent to a quiet bedroom')
led3 = Led(root, size=20)
led3.grid(column=2, row=10)
Hovertip(led3,'40 dB\nAll is OK\nEquivalent to a whisper')
led4 = Led(root, size=20)
led4.grid(column=2, row=9)
Hovertip(led4,'50 dB\nAll is OK\nEquivalent to a quiet home')
led5 = Led(root, size=20)
led5.grid(column=2, row=8)
Hovertip(led5,'60 dB\nAll is OK\nEquivalent to a quiet street')
led6 = Led(root, size=20)
led6.grid(column=2, row=7)
Hovertip(led6,'70 dB\nAll is OK\nEquivalent to a normal conversation')
led7 = Led(root, size=20)
led7.grid(column=2, row=6)
Hovertip(led7,'80 dB\nA little loud, may cause hearing damage in sensitive people.\nEquivalent to loud singing')
led8 = Led(root, size=20)
led8.grid(column=2, row=5)
Hovertip(led8,'90 dB\nLoud; repeated and/or long term exposure at this level may damage hearing.\nEquivalent to a motorcycle')
led9 = Led(root, size=20)
led9.grid(column=2, row=4)
Hovertip(led9,'100 dB\nCritically loud, even short exposure to this level can damage hearing.\nEquivalent to a subway')
led10 = Led(root, size=20)
led10.grid(column=2, row=3)
Hovertip(led10,'110 dB\nDangerous, even short exposure to this level can damage hearing.\nEquivalent to a helicopter overheaad')
led11 = Led(root, size=20)
led11.grid(column=2, row=2)
win.iconbitmap(get_resource_path('snd.ico'))
Hovertip(led11,"120 dB\nDangerous, even short exposure to this level can damage hearing.\nYou might feel pain at this level.\nEquivalent to a rock concert")
CTkLabel(sub, text='Instantaneous dBA level').grid(column=1, row=1)
CTkLabel(sub1, text='Instantaneous dBA level').grid(column=1, row=1)
db_levels=[82, 85, 88, 91, 94, 97, 100, 103, 106, 109, 112, 115, 118]
niosh_limits=[57600, 28800, 14400, 7200, 3600, 1800, 900, 450, 225, 120, 60, 30, 15]
db_levels.reverse()
niosh_limits.reverse()
enable_dosi=CTkSwitch(sub, text='Enable Dosimeter (restart app to apply)', command=toggle_dosi)
enable_dosi.grid(column=1, row=3)
def record_frame():
global recording, rec_start, db, f
time_trunc='%.1f' % (time.time()-rec_start)
f.write(time_trunc+','+str(int(db))+'\n')
if recording:
win.after(500, record_frame)
else:
f.close()
def record():
global recording, f, rec_start
if not recording:
rec.configure(text='Stop')
rec_start=time.time()
recording=True
f=open(os.getenv('HOMEPATH')+'\\Music\\mic_levels.csv', 'w')
f.write('Seconds,Decibels\n')
record_frame()
else:
recording=False
rec.configure(text='Record')
if not reg_present:
enable_dosi.configure(state=DISABLED)
if dosi_enabled:
enable_dosi.select()
doseLabel=CTkLabel(sub, text=r'Dose: 0.0%', width=40)
doseLabel.grid(column=1, row=4)
dosebar=CTkProgressBar(sub, mode='determinate')
dosebar.grid(column=2, row=4)
timeLabel=CTkLabel(sub, text='0 sec', width=30)
timeLabel.grid(column=2, row=2)
pdose=CTkLabel(sub, text='Projected dose: 0 sec', width=40)
CTkButton(sub, text='Reset', command=reset).grid(column=2, row=3)
CTkLabel(sub1, text='Recording to Music\\mic_levels.csv.\nMake sure the file does not exist, or it will be overwritten.').grid(column=1, row=3)
rec=CTkButton(sub1, text='Record', command=record)
rec.grid(column=1, row=4)
for i in range(len(db_levels)):
db_level=db_levels[i]
exec("label_"+str(db_level)+"=CTkLabel(sub, width=40, text='"+str(db_level)+"dBA: 0/"+str(niosh_limits[i])+" sec')")
exec("label_"+str(db_level)+".grid(column=1, row="+str(i+5)+")")
exec("bar_"+str(db_level)+'=CTkProgressBar(sub, mode="determinate")')
exec("bar_"+str(db_level)+'.grid(column=2, row='+str(i+5)+')')
else:
CTkLabel(sub, text='Dosimeter is not enabled', width=60).grid(column=1, row=4)
CTkLabel(sub1, text='Dosimeter must be enabled', width=60).grid(column=1, row=4)
CTkLabel(root, text='120').grid(column=1, row=2)
CTkLabel(root, text='100').grid(column=1, row=4)
CTkLabel(root, text='Danger').grid(column=3, row=4)
CTkLabel(root, text='80').grid(column=1, row=6)
CTkLabel(root, text='Loud').grid(column=3, row=6)
CTkLabel(root, text='60').grid(column=1, row=8)
CTkLabel(root, text='40').grid(column=1, row=10)
CTkLabel(root, text='20').grid(column=1, row=12)
CTkLabel(root, text='dB').grid(column=1, row=14)
CTkLabel(root, text='OK').grid(column=3, row=14)
CTkLabel(root, text='Max').grid(column=3, row=0)
CTkLabel(root, text='dBA').grid(column=1, row=0)
CTkLabel(root, text='-').grid(column=1, row=3)
CTkLabel(root, text='-').grid(column=1, row=5)
CTkLabel(root, text='-').grid(column=1, row=7)
CTkLabel(root, text='-').grid(column=1, row=9)
CTkLabel(root, text='-').grid(column=1, row=11)
CTkLabel(root, text='-').grid(column=1, row=13)
CTkLabel(root, text='dB Offset').grid(column=2, row=0)
maxdb_display=SevenSegmentDigits(root, digits=3, digit_color='#00ff00', background='black')
maxdb_display.grid(column=3, row=1)
dosimeter_times={'82dB':0, '85dB':0, '88dB':0, '91dB':0, '94dB':0, '97dB':0, '100dB':0, '103dB':0, '106dB':0, '109dB':0, '112dB':0, '115dB':0, '118dB':0}
Hovertip(maxdb_display,"Max dBA level since program start")
CHUNKS = [4096, 9600]
win.geometry('502x586')
CHUNK = CHUNKS[1]
FORMAT = pyaudio.paInt16
CHANNEL = 1
RATES = [44300, 48000]
RATE = RATES[1]
offset=StringVar()
offset.set('0')
spinbox=CTkOptionMenu(root, variable=offset, width=100, values=tuple(reversed(['-20','-19', '-18', '-17', '-16', '-15', '-14', '-13', '-12', '-11', '-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20'])), state='readonly')
spinbox.grid(column=2, row=1)
Hovertip(spinbox,"dB offset (Calibration)\nUse this if the meter is not accurate.\nUse a reliable reference meter (such as a dedicated SPL meter).")
appclosed=False
from scipy.signal import bilinear
start_check=time.time()
db=0
x=[]
y=[]
def timer_dosi():
global runTime, y, x, start_check
runTime+=time.time()-start_check
if db>=82 and db<85:
dosimeter_times['82dB']+=time.time()-start_check
if db>=85 and db<88:
dosimeter_times['85dB']+=time.time()-start_check
if db>=88 and db<91:
dosimeter_times['88dB']+=time.time()-start_check
if db>=91 and db<94:
dosimeter_times['91dB']+=time.time()-start_check
if db>=94 and db<97:
dosimeter_times['94dB']+=time.time()-start_check
if db>=97 and db<100:
dosimeter_times['97dB']+=time.time()-start_check
if db>=100 and db<103:
dosimeter_times['100dB']+=time.time()-start_check
if db>=103 and db<106:
dosimeter_times['103dB']+=time.time()-start_check
if db>=106 and db<109:
dosimeter_times['106dB']+=time.time()-start_check
if db>=109 and db<112:
dosimeter_times['109dB']+=time.time()-start_check
if db>=112 and db<115:
dosimeter_times['112dB']+=time.time()-start_check
if db>=115 and db<118:
dosimeter_times['115dB']+=time.time()-start_check
if db>=118:
dosimeter_times['118dB']+=time.time()-start_check
start_check=time.time()
win.after(200, timer_dosi)
def close():
global appclosed
win.destroy()
if dosi_enabled:
SetValueEx(OpenKey(OpenKey(OpenKey(HKEY_CURRENT_USER, 'Software', reserved=0, access=KEY_ALL_ACCESS), 'sserver', reserved=0, access=KEY_ALL_ACCESS), 'Decibel Meter', reserved=0, access=KEY_ALL_ACCESS), 'DosimeterEnabled', 0, REG_DWORD, 1)
else:
SetValueEx(OpenKey(OpenKey(OpenKey(HKEY_CURRENT_USER, 'Software', reserved=0, access=KEY_ALL_ACCESS), 'sserver', reserved=0, access=KEY_ALL_ACCESS), 'Decibel Meter', reserved=0, access=KEY_ALL_ACCESS), 'DosimeterEnabled', 0, REG_DWORD, 0)
appclosed=True
stream.stop_stream()
stream.close()
pa.terminate()
def A_weighting(fs):
f1 = 20.598997
f2 = 107.65265
f3 = 737.86223
f4 = 12194.217
A1000 = 1.9997
NUMs = [(2*numpy.pi * f4)**2 * (10**(A1000/20)), 0, 0, 0, 0]
DENs = numpy.polymul([1, 4*numpy.pi * f4, (2*numpy.pi * f4)**2],
[1, 4*numpy.pi * f1, (2*numpy.pi * f1)**2])
DENs = numpy.polymul(numpy.polymul(DENs, [1, 2*numpy.pi * f3]),
[1, 2*numpy.pi * f2])
return bilinear(NUMs, DENs, fs)
NUMERATOR, DENOMINATOR = A_weighting(RATE)
def rms_flat(a):
return numpy.sqrt(numpy.mean(numpy.absolute(a)**2))
pa = pyaudio.PyAudio()
stream = pa.open(format = FORMAT,
channels = CHANNEL,
rate = RATE,
input = True,
frames_per_buffer = CHUNK)
max_decibel=0
def returnSum(myDict):
mylist = []
for i in myDict:
mylist.append(myDict[i])
final = sum(mylist)
return final
def listen(old=0, error_count=0, min_decibel=100):
global appclosed
global max_decibel
global measure
global db
global start
if not appclosed:
try:
try:
block = stream.read(CHUNK)
except IOError as e:
if not appclosed:
error_count += 1
messagebox.showerror("Error, ", " (%d) Error recording: %s" % (error_count, e))
else:
decoded_block = numpy.frombuffer(block, numpy.int16)
y = lfilter(NUMERATOR, DENOMINATOR, decoded_block)
new_decibel = 20*numpy.log10(rms_flat(y))+int(offset.get())
if runTime>0:
runt=runTime
else:
runt=0.1
if new_decibel<0:
new_decibel=0
old = new_decibel
db=new_decibel
gaugedb.set_value(str(int(float('{:.2f}'.format(new_decibel)))))
dosidb.set_value(str(int(float('{:.2f}'.format(new_decibel)))))
graphdb.set_value(str(int(float('{:.2f}'.format(new_decibel)))))
if new_decibel>max_decibel:
max_decibel=new_decibel
if int(db)>0:
led.to_green(on=True)
else:
led.to_grey(on=True)
maxdb_display.set_value(str(int(float(str(max_decibel)))))
for i in range(0, 12):
if int(new_decibel)>=(10*(i+1)):
if i>=9:
exec("led"+str(i)+".to_red(on=True)")
elif i>=7:
exec("led"+str(i)+".to_yellow(on=True)")
else:
exec("led"+str(i)+".to_green(on=True)")
else:
exec("led"+str(i)+".to_grey(on=True)")
if dosi_enabled_first:
dosebar.set(returnSum(dosimeter_times)/runt)
pdose.configure(text='Projected dose: '+str(int((returnSum(dosimeter_times)*8)/runt))+' sec')
timeLabel.configure(text=str(int(runt))+' sec')
doseLabel.configure(text='Dose: '+str(round(returnSum(dosimeter_times)/runt*1000)/10)+'%')
for i in range(len(db_levels)):
db_level=db_levels[i]
exec("label_"+str(db_level)+".configure(text='"+str(db_level)+"dBA: "+str(int(dosimeter_times[str(db_level)+'dB']))+'/'+str(niosh_limits[i])+" sec')")
exec("bar_"+str(db_level)+".set("+str((dosimeter_times[str(db_level)+'dB']/niosh_limits[i]))+')')
win.after(20, listen)
except TclError:
pass
win.protocol('WM_DELETE_WINDOW', close)
if __name__ == '__main__':
if dosi_enabled:
timer_dosi()
listen()
win.mainloop()
1 Answer 1
Your code need major refactoring.
In order to investigate your performance issue, you need to be able to profile execution, identify the slow parts, work on them independently...
As is, this is not possible, the whole code is so intertwined and convoluted that following the logic is borderline impossible, and I'm not willing to poor in that much effort into understanding what's going on.
First, I'd suggest reading PEP 8 – Style Guide for Python Code and PEP 257 – Docstring Conventions and keeping that in mind when writing code. This won't help on performance, but would at least make the code readable enough to be able to get helpful feedback on your issues.
Here is a list on things to improve in order to make optimizing your logic reasonably doable:
Clean up your imports
Star imports
You import *
from multiple modules, including non-standard ones. This means that when functions/classes from these modules are used down the line, you can't tell where it came from and what it's supposed to do.
This can also lead to inadvertently overwriting class or function definitions from the modules with your own definitions or between different modules.
Duplicate imports
You import *
from tkinter
, then import messagebox
from the same module, which is useless.
Unused imports
You import things you don't use, including Thread
and things from matplotlib
. Remove those.
Let your code breathe
There isn't a single blank line in your whole code, making it hard to see where functions start and end and what are the logical blocks.
Adding spaces around operators (such as =
, <
, >=
, ...) also helps with readability.
Code layout
You mix executed statements with function definitions, making the execution order hard to follow. Some imports are added right in the middle of the code. Also, most code always gets executed but a small portion is behind a if __name__ == '__main__'
guard, for reasons I can't understand.
You should have all of your imports at the start, then all constants definitions, then function definitions, then executed code, preferably all behind an if __name__ == '__main__'
guard.
Document your code
Use docstrings to document what your functions do, what arguments are expected and what do they return.
Avoid globals
Globals make the execution order hard to follow, and introduce risks for bugs.
Too broad exception catching
While some of your try/except
blocks do catch a specific error and handle it, others catch all exceptions, hiding potential issues. For example, you should only catch ImportError
s when trying to import winreg
.
Don't repeat yourself
There is a lot of duplicate code, especially for initializing GUI widgets. Use loops to simplify the code.
Separate presentation from logic
It looks like your main logic happens in the listen
function. However, there are many dependencies to the GUI in that function, making it impossible to test and profile your logic to find bugs or bottlenecks.
list
and iterate through thelist
to call the function using the current element as arguments. Or better, since each command has nothing to do with each other logically and causally, they don't need to be called sequentially. You can usemultiprocessing.pool.ThreadPool.starmap_async
to do it. \$\endgroup\$dict
, or use multithreading. And don't use elif ladder to check if a number is a certain range, it is a linear search and inefficient. You can usebisect.bisect
to do it. Also, don't usetime.time
for this purpose, it is imprecise, usetime.perf_counter
. \$\endgroup\$