0
\$\begingroup\$

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()
\$\endgroup\$
6
  • 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 list and iterate through the list 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 use multiprocessing.pool.ThreadPool.starmap_async to do it. \$\endgroup\$ Apr 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 use bisect.bisect to do it. Also, don't use time.time for this purpose, it is imprecise, use time.perf_counter. \$\endgroup\$ Apr 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
    Apr 14, 2023 at 15:38
  • \$\begingroup\$ @gazoh Added more context. \$\endgroup\$
    – user35849
    Apr 16, 2023 at 22:50
  • 1
    \$\begingroup\$ I recommend that you read through: Python Sound Visualizer, Python Decibel Meter \$\endgroup\$
    – Reinderien
    Apr 16, 2023 at 22:56

1 Answer 1

2
\$\begingroup\$

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 ImportErrors 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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.