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, errnoimport pyaudiofrom scipy.signal import lfilterimport numpyfrom tkinter import *from threading import Threadfrom tkinter.ttk import *from tk_tools import *import timefrom matplotlib.figure import Figurefrom matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)from tkinter import messageboximport sysfrom idlelib.tooltip import Hovertiptry: 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=Truedef 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=0win=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=Falsedosi_enabled_first=dosi_enabledtabControl = Notebook(win)root=Frame(tabControl)runTime=0sub=Frame(tabControl)sub1=Frame(tabControl)tabControl.add(root, text ='Meter')measure=Falsestart=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.paInt16CHANNEL = 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=Falsestart_check=time.time()from scipy.signal import bilineardb=0x=[]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=0def returnSum(myDict): mylist = [] for i in myDict: mylist.append(myDict[i]) final = sum(mylist) return finaldef 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: passwin.protocol('WM_DELETE_WINDOW', close)if __name__ == '__main__': if dosi_enabled: timer_dosi() listen() win.mainloop()customtkinter version (library athttps://github.com/TomSchimansky/CustomTkinter):
import os, errnoimport pyaudiofrom scipy.signal import lfilterimport numpyfrom tkinter import *from threading import Threadfrom tk_tools import *from customtkinter import *import timefrom tkinter import messageboximport sysfrom idlelib.tooltip import Hovertipset_appearance_mode("System") # Modes: system (default), light, darkset_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=Truedef 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=0win=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=Falsedosi_enabled_first=dosi_enabledrunTime=0measure=Falserecording=Falsestart=time.time()tabview =CTkTabview(win)tabview.pack(padx=20, pady=20)tabview.add("Meter") # add tab at the endtabview.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.paInt16CHANNEL = 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=Falsefrom scipy.signal import bilinearstart_check=time.time()db=0x=[]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=0def returnSum(myDict): mylist = [] for i in myDict: mylist.append(myDict[i]) final = sum(mylist) return finaldef 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: passwin.protocol('WM_DELETE_WINDOW', close)if __name__ == '__main__': if dosi_enabled: timer_dosi() listen() win.mainloop()- 1\$\begingroup\$Your script is way too long so I didn't bother to figure out how it works. But at a glance you called the same functions repeatedly using a regular pattern of arguments, don't do this. You can just store the arguments in a
listand iterate through thelistto 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_asyncto do it.\$\endgroup\$Ξένη Γήινος– Ξένη Γήινος2023-04-14 03:21:05 +00:00CommentedApr 14, 2023 at 3:21 - \$\begingroup\$After I looked your code more closely, you should not make independent variables for the GUI elements that are constructed similarly. Rather, you should use a for loop to create them and keep track of them in a
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.bisectto do it. Also, don't usetime.timefor this purpose, it is imprecise, usetime.perf_counter.\$\endgroup\$Ξένη Γήινος– Ξένη Γήινος2023-04-14 03:33:56 +00:00CommentedApr 14, 2023 at 3:33 - 2\$\begingroup\$That question lacks a lot of context? What does the program do? Why do you want to speed it up? Does it lag? If so, how bad is it? Did you try to profile it? Did you try to run the logic without the GUI or the GUI without the logic?\$\endgroup\$gazoh– gazoh2023-04-14 15:38:30 +00:00CommentedApr 14, 2023 at 15:38
- \$\begingroup\$@gazoh Added more context.\$\endgroup\$user35849– user358492023-04-16 22:50:18 +00:00CommentedApr 16, 2023 at 22:50
- 1\$\begingroup\$I recommend that you read through:Python Sound Visualizer,Python Decibel Meter\$\endgroup\$Reinderien– Reinderien2023-04-16 22:56:22 +00:00CommentedApr 16, 2023 at 22:56
1 Answer1
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 readingPEP 8 – Style Guide for Python Code andPEP 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
Youimport * 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
Youimport * fromtkinter, thenimport messagebox from the same module, which is useless.
Unused imports
You import things you don't use, includingThread and things frommatplotlib. 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 aif __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 anif __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 yourtry/except blocks do catch a specific error and handle it, others catch all exceptions, hiding potential issues. For example, you should only catchImportErrors when trying to importwinreg.
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 thelisten 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.
You mustlog in to answer this question.
Explore related questions
See similar questions with these tags.