Initial Commit
This commit is contained in:
+136
@@ -0,0 +1,136 @@
|
|||||||
|
import time
|
||||||
|
import numpy as np
|
||||||
|
import sounddevice as sd
|
||||||
|
import pyloudnorm as pyln
|
||||||
|
import paho.mqtt.client as mqtt
|
||||||
|
|
||||||
|
# MQTT
|
||||||
|
MQTT_HOST = "10.40.0.2"
|
||||||
|
MQTT_PORT = 1883
|
||||||
|
|
||||||
|
TOPIC_M = "stream/audio/lufs/momentary"
|
||||||
|
TOPIC_S = "stream/audio/lufs/shortterm"
|
||||||
|
TOPIC_I = "stream/audio/lufs/integrated"
|
||||||
|
DEVICE = "default"
|
||||||
|
|
||||||
|
# Audio
|
||||||
|
RATE = 48000
|
||||||
|
CHANNELS = 2
|
||||||
|
|
||||||
|
def find_scarlett():
|
||||||
|
for i, d in enumerate(sd.query_devices()):
|
||||||
|
name = d["name"].lower()
|
||||||
|
if "scarlett" in name and d["max_input_channels"] >= 2:
|
||||||
|
return i
|
||||||
|
raise RuntimeError("Scarlett stereo input not found")
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
M_WINDOW = 0.4
|
||||||
|
S_WINDOW = 3.0
|
||||||
|
UPDATE = 0.25
|
||||||
|
|
||||||
|
meter = pyln.Meter(RATE)
|
||||||
|
|
||||||
|
integrated_buffer = np.array([], dtype=np.float32)
|
||||||
|
shortterm_buffer = np.array([], dtype=np.float32)
|
||||||
|
|
||||||
|
RESET_TOPIC = "stream/audio/lufs/reset"
|
||||||
|
|
||||||
|
reset_requested = False
|
||||||
|
|
||||||
|
|
||||||
|
def on_message(client, userdata, msg):
|
||||||
|
global reset_requested
|
||||||
|
|
||||||
|
if msg.topic == RESET_TOPIC:
|
||||||
|
print("Resetting integrated LUFS")
|
||||||
|
reset_requested = True
|
||||||
|
|
||||||
|
client = mqtt.Client()
|
||||||
|
|
||||||
|
client.on_message = on_message
|
||||||
|
|
||||||
|
client.connect(MQTT_HOST, MQTT_PORT)
|
||||||
|
|
||||||
|
client.subscribe(RESET_TOPIC)
|
||||||
|
|
||||||
|
client.loop_start()
|
||||||
|
|
||||||
|
print("LUFS → MQTT running")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
|
||||||
|
try:
|
||||||
|
samples = sd.rec(
|
||||||
|
int(UPDATE * RATE),
|
||||||
|
samplerate=RATE,
|
||||||
|
channels=CHANNELS,
|
||||||
|
device=DEVICE,
|
||||||
|
dtype="float32",
|
||||||
|
)
|
||||||
|
sd.wait()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Audio route changed / device unavailable: {e}")
|
||||||
|
time.sleep(2)
|
||||||
|
continue
|
||||||
|
|
||||||
|
mono = samples.flatten()
|
||||||
|
|
||||||
|
if reset_requested:
|
||||||
|
integrated_buffer = np.array([], dtype=np.float32)
|
||||||
|
reset_requested = False
|
||||||
|
|
||||||
|
integrated_buffer = np.concatenate(
|
||||||
|
[integrated_buffer, mono]
|
||||||
|
)[-RATE * 60 * 120:]
|
||||||
|
|
||||||
|
shortterm_buffer = np.concatenate(
|
||||||
|
[shortterm_buffer, mono]
|
||||||
|
)[-int(S_WINDOW * RATE):]
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
m_samples = shortterm_buffer[
|
||||||
|
-int(M_WINDOW * RATE):
|
||||||
|
]
|
||||||
|
|
||||||
|
momentary = meter.integrated_loudness(
|
||||||
|
m_samples
|
||||||
|
)
|
||||||
|
|
||||||
|
shortterm = meter.integrated_loudness(
|
||||||
|
shortterm_buffer
|
||||||
|
)
|
||||||
|
|
||||||
|
integrated = meter.integrated_loudness(
|
||||||
|
integrated_buffer
|
||||||
|
)
|
||||||
|
|
||||||
|
client.publish(
|
||||||
|
TOPIC_M,
|
||||||
|
f"{momentary:.1f}",
|
||||||
|
retain=True
|
||||||
|
)
|
||||||
|
|
||||||
|
client.publish(
|
||||||
|
TOPIC_S,
|
||||||
|
f"{shortterm:.1f}",
|
||||||
|
retain=True
|
||||||
|
)
|
||||||
|
|
||||||
|
client.publish(
|
||||||
|
TOPIC_I,
|
||||||
|
f"{integrated:.1f}",
|
||||||
|
retain=True
|
||||||
|
)
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"\rM {momentary:.1f} "
|
||||||
|
f"S {shortterm:.1f} "
|
||||||
|
f"I {integrated:.1f}",
|
||||||
|
end=""
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# LUFS MQTT Viewer
|
||||||
|
|
||||||
|
A simple script to broadcast a sound sources LUFS over MQTT
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
cffi==2.0.0
|
||||||
|
numpy==2.4.6
|
||||||
|
paho-mqtt==2.1.0
|
||||||
|
pycparser==3.0
|
||||||
|
pyloudnorm==0.2.0
|
||||||
|
scipy==1.17.1
|
||||||
|
sounddevice==0.5.5
|
||||||
Reference in New Issue
Block a user