| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101 |
- import os
- import sys
- import time
- import socket
- import struct
- import numpy as np
- # Network settings
- UDP_IP = os.environ.get("UDP_IP", "239.0.0.1")
- UDP_PORT = int(os.environ.get("UDP_PORT", "11988"))
- # Audio stream constants
- SAMPLE_RATE = 44100
- CHUNK_SIZE = 1024
- CHUNK_DURATION = CHUNK_SIZE / SAMPLE_RATE
- FRAME_BYTES = CHUNK_SIZE * 4
- MAX_SILENT_FRAMES = 43 # ~1 second of silence at 43.06 FPS
- # DSP tuning environment overrides
- FREQ_MIN = float(os.environ.get("FREQ_MIN", "100.0"))
- FREQ_MAX = float(os.environ.get("FREQ_MAX", "8000.0"))
- GAIN_MULT = float(os.environ.get("GAIN_MULT", "3500.0"))
- SILENCE_THRESHOLD = float(os.environ.get("SILENCE_THRESHOLD", "0.5"))
- sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
- sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
- # Compute 16 logarithmic frequency bands
- FREQ_EDGES = np.logspace(np.log10(FREQ_MIN), np.log10(FREQ_MAX), 17)
- fft_freqs = np.fft.rfftfreq(CHUNK_SIZE, 1.0 / SAMPLE_RATE)
- bins_idx = []
- for i in range(16):
- low = FREQ_EDGES[i]
- high = FREQ_EDGES[i + 1]
- idx = np.where((fft_freqs >= low) & (fft_freqs < high) & (fft_freqs > 0))[0]
- if len(idx) == 0:
- non_zero_bins = np.where(fft_freqs > 0)[0]
- closest = non_zero_bins[np.argmin(np.abs(fft_freqs[non_zero_bins] - low))]
- idx = [closest]
- bins_idx.append(idx)
- sample_smth = 0.0
- clock_target = time.perf_counter()
- silence_frames = 0
- # Canonical 44-byte WLED AudioReactive V2 struct layout
- STRUCT_FMT_V2 = "<6s2xffB3x16sd"
- while True:
- raw_data = sys.stdin.buffer.read(FRAME_BYTES)
- if not raw_data or len(raw_data) < FRAME_BYTES:
- break
- # Pacing at real-time audio speed to avoid CPU runaway
- clock_target += CHUNK_DURATION
- delay = clock_target - time.perf_counter()
- if delay > 0:
- time.sleep(delay)
- elif delay < -0.2:
- clock_target = time.perf_counter()
- audio = np.frombuffer(raw_data, dtype=np.int16).astype(np.float32)
- left = audio[0::2]
- right = audio[1::2]
- mono = (left + right) / (2.0 * 32768.0)
- raw_mag = float(np.max(np.abs(mono)) * 255.0)
- sample_smth = 0.7 * sample_smth + 0.3 * raw_mag
- sample_peak = 1 if raw_mag > (sample_smth * 1.5 + 20) else 0
- if raw_mag < SILENCE_THRESHOLD:
- silence_frames += 1
- else:
- silence_frames = 0
- if silence_frames > MAX_SILENT_FRAMES:
- continue
- windowed = mono * np.hanning(CHUNK_SIZE)
- fft_vals = np.abs(np.fft.rfft(windowed)) / (CHUNK_SIZE / 2)
- fft_result = bytearray(16)
- for i in range(16):
- energy = float(np.mean(fft_vals[bins_idx[i]])) * GAIN_MULT
- fft_result[i] = min(255, int(np.clip(energy, 0, 255)))
- payload = struct.pack(
- STRUCT_FMT_V2,
- b"00002\x00",
- float(raw_mag),
- float(sample_smth),
- sample_peak,
- bytes(fft_result),
- float(raw_mag)
- )
- try:
- sock.sendto(payload, (UDP_IP, UDP_PORT))
- except Exception:
- pass
|