首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >巨蟒分贝计-准确吗?

巨蟒分贝计-准确吗?
EN

Code Review用户
提问于 2022-09-25 19:01:03
回答 1查看 3.2K关注 0票数 9
代码语言:javascript
复制
import os, errno
import pyaudio
from scipy.signal import lfilter
import numpy
from tkinter import *
from tkinter.ttk import *
from tk_tools import *
from tkinter import messagebox
root=Tk()
root.title('Decibel Meter')
root.grid()
gaugedb = RotaryScale(root, max_value=120.0, unit=' dBA')
gaugedb.grid(column=1, row=1)
led = Led(root, size=50)
led.grid(column=3, row=1)
led.to_red(on=False)
Label(root, text='Too Loud').grid(column=3, row=0)
Label(root, text='Max').grid(column=2, row=0)
Label(root, text='Calibration (dB)').grid(column=4, row=0)
maxdb_display=SevenSegmentDigits(root, digits=3, digit_color='#00ff00', background='black')
maxdb_display.grid(column=2, row=1)
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')
spinbox.grid(column=4, row=1)
appclosed=False
from scipy.signal import bilinear
def close():
 global appclosed
 root.destroy()
 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)
def update_max_if_new_is_larger_than_max(new, max):
 if new > max:
     return new
 else:
     return max
def listen(old=0, error_count=0, min_decibel=100, max_decibel=0):
 global appclosed
 while True:
     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.fromstring(block, numpy.int16)
             y = lfilter(NUMERATOR, DENOMINATOR, decoded_block)
             new_decibel = 20*numpy.log10(rms_flat(y))+int(offset.get())
             old = new_decibel
             gaugedb.set_value(float('{:.2f}'.format(new_decibel)))
             max_decibel = update_max_if_new_is_larger_than_max(new_decibel, max_decibel)
             maxdb_display.set_value(str(int(float(str(max_decibel)))))
             if new_decibel>85:
                 led.to_red(on=True)
             else:
                 led.to_red(on=False)
         root.update()
     except TclError:
         break
root.protocol('WM_DELETE_WINDOW', close)
listen()

这是一个精确的dBA表吗?我的代码工作正常,但我想检查它是否真的从麦克风反射周围的声音水平。

EN

回答 1

Code Review用户

发布于 2022-09-25 22:32:10

将您的代码缩进符合PEP8 8的IDE或linter;它现在是一个完美的混乱。

将全局代码移动到函数或类中。这里有两个很好的类用例-一个用于GUI,一个用于音频处理器。

offset不能是StringVar,而必须是IntVar --除其他原因外,这将消除int(offset.get())中的演员角色。不要让它没有名字,也不要让它成为孤儿;它的父对象需要成为根对象。

bilinear导入向上移动以加入其他导入。

您的导入应该避免import *;这会使全局名称空间变得巨大,而它不需要是那样的。传统上,numpy被别名为np

考虑编写上下文管理器以关闭音频流。

numpy.absolute(a)**2只是a**2,对吧?

删除update_max_if_new_is_larger_than_max。这只是对内置max()的一个调用。

而不是

代码语言:javascript
复制
         if new_decibel>85:
             led.to_red(on=True)
         else:
             led.to_red(on=False)

只需将布尔表达式移动到单个调用的参数并删除if

添加PEP484类型提示。

A_weighting中的列表转换为不可变的元组。

听一听告诉您的警告:您对np.fromstring的使用需要替换为np.frombuffer

str(int(float(str(max_decibel))))只是..。雄伟的。使用格式字符串代替。

正如@Seb评论,44300几乎可以肯定是44100。

polymul已弃用。使用Polynomial代替。

rms_flat相当的是更集成的,而且可能更快

代码语言:javascript
复制
np.linalg.norm(a) / np.sqrt(len(a))

它基于这个linalg来源,进一步简化为一个自点积:

代码语言:javascript
复制
np.sqrt(a.dot(a) / len(a))

建议

代码语言:javascript
复制
import tkinter as tk
import numpy as np
import pyaudio
import tk_tools
from numpy.polynomial import Polynomial
from scipy.signal import bilinear, lfilter

CHUNKS = [4096, 9600]
CHUNK = CHUNKS[1]
FORMAT = pyaudio.paInt16
CHANNEL = 1
RATES = [44100, 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 = Polynomial(((2*np.pi * f4)**2 * 10**(a1000 / 20), 0,0,0,0))
    dens = (
        Polynomial((1, 4*np.pi * f4, (2*np.pi * f4)**2)) *
        Polynomial((1, 4*np.pi * f1, (2*np.pi * f1)**2)) *
        Polynomial((1, 2*np.pi * f3)) *
        Polynomial((1, 2*np.pi * f2))
    )
    return bilinear(nums.coef, dens.coef, fs)


def rms_flat(a: np.ndarray) -> float:
    return np.sqrt(a.dot(a) / len(a))


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, 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) -> None:
        self.app_closed = True

    def run(self) -> None:
        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()

输出

布局

你的布局需要一点爱。既然量规文本在底部,为什么不把所有标签放在底部呢?增加一些填充,以便于阅读,并增加一些调整大小的理智。不幸的是,除了缺少变量支持之外,tk_tools小部件似乎有错误的布局行为,因为它们忽略了sticky调整大小的请求;但是哦,好吧:

代码语言:javascript
复制
import tkinter as tk
import numpy as np
import pyaudio
import tk_tools
from numpy.polynomial import Polynomial
from scipy.signal import bilinear, lfilter

CHUNKS = [4096, 9600]
CHUNK = CHUNKS[1]
FORMAT = pyaudio.paInt16
CHANNEL = 1
RATES = [44100, 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 = Polynomial(((2*np.pi * f4)**2 * 10**(a1000 / 20), 0,0,0,0))
    dens = (
        Polynomial((1, 4*np.pi * f4, (2*np.pi * f4)**2)) *
        Polynomial((1, 4*np.pi * f1, (2*np.pi * f1)**2)) *
        Polynomial((1, 2*np.pi * f3)) *
        Polynomial((1, 2*np.pi * f2))
    )
    return bilinear(nums.coef, dens.coef, fs)


def rms_flat(a: np.ndarray) -> float:
    return np.sqrt(a.dot(a) / len(a))


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.grid_rowconfigure(index=0, weight=1)
        root.grid_rowconfigure(index=1, weight=1)
        root.grid_columnconfigure(index=0, weight=1)
        root.grid_columnconfigure(index=3, weight=1)
        root.protocol('WM_DELETE_WINDOW', self.close)
        self.app_closed = False

        self.gaugedb = tk_tools.RotaryScale(root, max_value=120, unit=' dBA')
        # This control does not respect resizing via tk.NSEW.
        self.gaugedb.grid(row=0, column=0, rowspan=2, sticky=tk.E)

        self.maxdb_display = tk_tools.SevenSegmentDigits(root, digits=3, digit_color='#00ff00', background='black')
        self.maxdb_display.grid(row=0, column=1, sticky=tk.S, padx=5)
        tk.Label(root, text='Max').grid(row=1, column=1, sticky=tk.N, padx=5)

        self.led = tk_tools.Led(root, size=50)
        self.led.to_red(on=False)
        self.led.grid(row=0, column=2, sticky=tk.S, padx=5)
        tk.Label(root, text='Too Loud').grid(row=1, column=2, sticky=tk.N, padx=5)

        self.offset = tk.IntVar(root, value=0, name='offset')
        spinbox = tk.Spinbox(root, from_=-20, to=20, textvariable=self.offset, state='readonly', width=12)
        spinbox.grid(row=0, column=3, sticky=tk.SW, padx=5)
        tk.Label(root, text='Calibration (dB)').grid(row=1, column=3, sticky=tk.NW, padx=5)

    def close(self) -> None:
        self.app_closed = True

    def run(self) -> None:
        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()
票数 31
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/279980

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档