# TES-1350A Sound Level Meter with Python
目的:將分貝計連上電腦,獲取即時數據,數值需要與分貝計上顯示的一致。
## Environment
- [TES-1350A Sound Level Meter](https://www.tes.com.tw/product_detail.asp?seq=64)
- [USB Audio Interface / USB 音效卡 / UGREEN USB to Audio Jack Sound Card Adapter](https://www.amazon.com/UGREEN-External-Headphone-Microphone-Desktops/dp/B01N905VOY?th=1)
- 3.5mm audio plug
- Macbook Air M2
## TES-1350A
- [英文說明書](https://d17bck4wpaw2mg.cloudfront.net/att/a/2/c/4/a2c463/tes1350a-manual.pdf)
### 摘要
> Time weighting: FAST(125mS), SLOW(1 sec.)
> Level ranges: Lo: 35~100dB and Hi: 65~130dB
> Accuracy: ± 2.0dB (under reference conditions)
> Calibration control can be adjusted clockwise or counterclockwise to standard 94.0dB.
> AC output: 0.65 Vrms at FS (full scale), output impedance approx. 600 Ω
> DC output: 10mV / dB, output impedance approx. 100 Ω
> Calibration: Electrical calibration with the internal oscillator (1KHz sine wave)

### 使用參數
- low range: Lo: 35 ~ 100dB
- RESPONSE: F: fast respnse
- FUNCT: A: A-weighting
## 使用 pyaudio
- 輸出結果已經很接近分貝計上顯示的數值,但輸出的數字大概要再減個 2~4 dB 才會更接近。
```python=
import numpy as np
import pyaudio
import datetime
import math
# Constants from the TES-1350A manual
V_fs = 0.65 # Full scale voltage in Vrms
db_fs = 100 # Corresponding dB level at full scale in low range
# Calculate the reference voltage for 94 dB SPL
V_94 = V_fs * 10 ** ((94 - db_fs) / 20)
def voltage_to_db_spl(v_rms, v_ref):
return 20 * np.log10(v_rms / v_ref) + 94
def callback(indata, frames, time, status):
indata = np.frombuffer(indata, dtype=np.int16) # Convert to 16-bit integer
max_int_value = 2**15 # 32768 for 16-bit PCM
# Scale the integer data to voltage
voltage_data = indata / max_int_value * V_fs
squared_data = np.square(voltage_data)
mean_squared_data = np.mean(squared_data)
rms_voltage = np.sqrt(mean_squared_data)
# Calculate the RMS voltage of the input signal
print(f"RMS Voltage: {rms_voltage:.6f} V", flush=True)
# Convert the RMS voltage to decibels (A-weighted)
db_spl = voltage_to_db_spl(rms_voltage, V_94)
log_entry = f"{datetime.datetime.now()} - {db_spl:.2f} dB(A)\n"
print(log_entry, end='')
device_index = 2 # Replace with the actual device index
duration = 1 # Replace with the actual duration in seconds
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 44100
CHUNK = 1024
audio = pyaudio.PyAudio()
stream = audio.open(format=FORMAT, channels=CHANNELS,
rate=RATE, input=True,
input_device_index=device_index,
frames_per_buffer=CHUNK)
print("Recording...")
for _ in range(0, int(RATE / CHUNK * duration)):
indata = stream.read(CHUNK)
callback(indata, CHUNK, None, None)
print("Finished recording.")
stream.stop_stream()
stream.close()
audio.terminate()
```
## ChatGPT 對程式碼的解釋
### Information Used:
1. **Full Scale Voltage (V_fs)**: 0.65 Vrms as specified in the manual, which corresponds to the full-scale dB level (100 dB) on the meter.
2. **Full Scale dB Level (db_fs)**: 100 dB is the full-scale level for the low range.
3. **Calibration Reference (94 dB SPL)**: The device can be calibrated to 94 dB SPL, a standard reference point.
### Steps and Calculations:
1. **Convert Raw Audio Data to Integers**:
- The audio data is captured in 16-bit PCM format, which ranges from -32768 to 32767. This raw data is converted to integers using `np.frombuffer(indata, dtype=np.int16)`.
2. **Scale the Integer Data to Voltage**:
- The raw integer values are scaled to voltages based on the full-scale voltage (0.65 Vrms). The formula used is:
$$
\text{voltage_data} = \frac{\text{indata}}{\text{max_int_value}} \times V_{fs}
$$
- Here, `max_int_value` is \(2^{15} = 32768\), the maximum value for 16-bit PCM data.
3. **Calculate RMS Voltage**:
- The Root Mean Square (RMS) voltage is calculated from the scaled voltage data:
$$
\text{squared_data} = \text{voltage_data}^2
$$
$$
\text{mean_squared_data} = \text{mean}(\text{squared_data})
$$
$$
\text{rms_voltage} = \sqrt{\text{mean_squared_data}}
$$
- This gives the RMS voltage of the input signal.
4. **Reference Voltage Calculation**:
- Calculate the reference voltage \(V_{94}\) corresponding to 94 dB SPL:
$$
V_{94} = V_{fs} \times 10^{\frac{94 - db_{fs}}{20}}
$$
- This uses the relationship between voltage and dB SPL, where \(V_{fs}\) is the full-scale voltage and \(db_{fs}\) is the full-scale dB level.
5. **Convert RMS Voltage to dB SPL**:
- The formula to convert the RMS voltage to dB SPL (A-weighted) is:
$$
\text{dB SPL} = 20 \times \log_{10}\left(\frac{\text{rms_voltage}}{V_{94}}\right) + 94
$$
- This formula compares the measured RMS voltage to the reference voltage \(V_{94}\) and adds the calibration level (94 dB SPL).
This ensures that the audio signal is correctly processed, scaled, and converted to dB SPL (A-weighted), matching the expected readings from your sound level meter.
## 使用 sounddevice
:::warning
失敗,轉換出錯誤數值,程式碼有問題。
:::
### 伏特轉換分貝
```python=
import numpy as np
import sounddevice as sd
import datetime
# Constants from the TES-1350A manual
V_fs = 0.65 # Full scale voltage in Vrms
db_fs = 100 # Corresponding dB level at full scale in Hi range
def voltage_to_db(v_rms, v_fs, db_fs):
return (v_rms / v_fs) * db_fs
def callback(indata, frames, time, status):
# Flatten the array to a single dimension
flat_indata = indata.flatten()
# Calculate the RMS voltage of the input signal
rms_voltage = np.sqrt(np.mean(flat_indata**2))
print(f"RMS Voltage: {rms_voltage:.6f} V", flush=True)
# Convert the RMS voltage to decibels
db = voltage_to_db(rms_voltage, V_fs, db_fs)
log_entry = f"{datetime.datetime.now()} - {db:.2f} dB\n"
print(log_entry, end='')
device_index = 2 # Replace with the actual device index
duration = 1 # Replace with the actual duration in seconds
with sd.InputStream(callback=callback, device=device_index):
sd.sleep(duration * 1000)
```
Output
```
RMS Voltage: 0.000063 V
2024-07-04 19:58:00.784316 - 0.01 dB
RMS Voltage: 0.000065 V
2024-07-04 19:58:00.868526 - 0.01 dB
RMS Voltage: 0.000062 V
2024-07-04 19:58:00.953830 - 0.01 dB
```
### Raw data
```python=
import sounddevice as sd
import numpy as np
import datetime
def log_decibels(duration, device_index=2, filename='raw_output_signals.txt'):
with open(filename, 'w') as f:
def callback(indata, frames, time, status):
log_entry = f"{datetime.datetime.now()} - {indata}\n"
print(log_entry, end='')
f.write(log_entry)
with sd.InputStream(callback=callback, device=device_index):
sd.sleep(duration * 1000)
if __name__ == "__main__":
log_decibels(1)
```
Output
```
2024-07-05 14:08:30.426940 - [[-0.00125122]
[-0.0010376 ]
[-0.00128174]
...
[ 0.00146484]
[ 0.00088501]
[ 0.00115967]]
2024-07-05 14:08:30.512188 - [[ 0.00149536]
[ 0.00097656]
[ 0.00018311]
...
[-0.00112915]
[-0.00057983]
[-0.00057983]]
2024-07-05 14:08:30.597561 - [[-2.7465820e-04]
[ 3.0517578e-05]
[-1.8310547e-04]
...
[ 6.1340332e-03]
[ 8.3312988e-03]
[ 7.7819824e-03]]
```
## Reference
- [錄音筆記-數位音量標準、 dBFS、dBTP、LUFS](https://corychu.medium.com/%E9%8C%84%E9%9F%B3%E7%AD%86%E8%A8%98-%E6%95%B8%E4%BD%8D%E9%9F%B3%E9%87%8F%E6%A8%99%E6%BA%96-dbfs-dbtp-lufs-c47ca4646b7f)
- Github repo
- https://github.com/johnson86tw/noise-monitoring-pi
- https://github.com/johnson86tw/noise-monitoring