Indent your code with a PEP8-compliant IDE or linter; it's a perfect mess right now.
Move your global code into functions and maybe classes. There are two good use cases for classes here - one for a GUI and one for an audio processor.
offset must not be a StringVar, but instead an IntVar - among other reasons this will obviate the cast in int(offset.get()). Do not leave it nameless and do not leave it orphaned; its parent needs to be the root object.
Move your import of bilinear up to join your other imports.
Your imports should avoid import *; that makes a vast swamp out of the global namespace and it doesn't need to be like that. Traditionally numpy is aliased to np.
Consider writing a context manager to close off your audio stream.
numpy.absolute(a)**2 is just a**2, right?
Delete update_max_if_new_is_larger_than_max. This is just a call to the built-in max().
Rather than
if new_decibel>85:
led.to_red(on=True)
else:
led.to_red(on=False)
just move the boolean expression to the argument of a single call and delete the if.
Add PEP484 typehints.
Convert your lists in A-weighting into immutable tuples.
Listen to the warnings being told to you: your use of np.fromstring needs to be replaced with np.frombuffer.
str(int(float(str(max_decibel)))) is just... majestic. Use a formatting string instead.
Suggested
import tkinter as tk
import numpy as np
import pyaudio
import tk_tools
from scipy.signal import bilinear, lfilter
CHUNKS = [4096, 9600]
CHUNK = CHUNKS[1]
FORMAT = pyaudio.paInt16
CHANNEL = 1
RATES = [44300, 48000]
RATE = RATES[1]
def A_weighting(fs: float) -> tuple[np.ndarray, np.ndarray]:
f1 = 20.598997
f2 = 107.65265
f3 = 737.86223
f4 = 12194.217
A1000 = 1.9997
NUMs = ((2*np.pi * f4)**2 * (10 ** (A1000 / 20)), 0, 0, 0, 0)
DENs = np.polymul((1, 4*np.pi * f4, (2*np.pi * f4)**2),
(1, 4*np.pi * f1, (2*np.pi * f1)**2))
DENs = np.polymul(np.polymul(DENs, (1, 2*np.pi * f3)),
(1, 2*np.pi * f2))
return bilinear(NUMs, DENs, fs)
def rms_flat(a: np.ndarray) -> float:
return np.sqrt(np.mean(a**2))
class Meter:
def __init__(self) -> None:
self.pa = pyaudio.PyAudio()
self.stream = self.pa.open(
format=FORMAT,
channels=CHANNEL,
rate=RATE,
input=True,
frames_per_buffer=CHUNK,
)
self.numerator, self.denominator = A_weighting(RATE)
self.max_decibel = 0
def __enter__(self) -> 'Meter':
return self
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
self.stream.stop_stream()
self.stream.close()
self.pa.terminate()
def listen(self, offset: int) -> float:
block = self.stream.read(CHUNK)
decoded_block = np.frombuffer(block, dtype=np.int16)
y = lfilter(self.numerator, self.denominator, decoded_block)
new_decibel = 20*np.log10(rms_flat(y)) + offset
self.max_decibel = max(self.max_decibel, new_decibel)
return new_decibel
class GUI:
def __init__(self, meter: Meter) -> None:
self.meter = meter
self.root = root = tk.Tk()
root.title('Decibel Meter')
root.grid()
root.protocol('WM_DELETE_WINDOW', self.close)
self.app_closed = False
self.gaugedb = tk_tools.RotaryScale(root, max_value=120.0, unit=' dBA')
self.gaugedb.grid(column=1, row=1)
self.led = tk_tools.Led(root, size=50)
self.led.grid(column=3, row=1)
self.led.to_red(on=False)
tk.Label(root, text='Too Loud').grid(column=3, row=0)
tk.Label(root, text='Max').grid(column=2, row=0)
tk.Label(root, text='Calibration (dB)').grid(column=4, row=0)
self.maxdb_display = tk_tools.SevenSegmentDigits(root, digits=3, digit_color='#00ff00', background='black')
self.maxdb_display.grid(column=2, row=1)
self.offset = tk.IntVar(root, value=0, name='offset')
spinbox = tk.Spinbox(root, from_=-20, to=20, textvariable=self.offset, state='readonly')
spinbox.grid(column=4, row=1)
def close(self):
self.app_closed = True
def run(self):
while not self.app_closed:
new_decibel = self.meter.listen(self.offset.get())
self.update(new_decibel, self.meter.max_decibel)
self.root.update()
def update(self, new_decibel: float, max_decibel: float) -> None:
self.gaugedb.set_value(np.around(new_decibel, 1))
self.maxdb_display.set_value(f'{max_decibel:.1f}')
self.led.to_red(on=new_decibel > 85)
def main() -> None:
with Meter() as meter:
gui = GUI(meter)
gui.run()
if __name__ == '__main__':
main()