Codebase review: stability, performance, usability, and i18n fixes
Stability: - Fix race condition: set _is_running before create_task in target processors - Await probe task after cancel in wled_target_processor - Replace raw fetch() with fetchWithAuth() across devices, kc-targets, pattern-templates - Add try/catch to showTestTemplateModal in streams.js - Wrap blocking I/O in asyncio.to_thread (picture_targets, system restore) - Fix dashboardStopAll to filter only running targets with ok guard Performance: - Vectorize fire effect spark loop with numpy in effect_stream - Vectorize FFT band binning with cumulative sum in analysis.py - Rewrite pixel_processor with vectorized numpy (accept ndarray or list) - Add httpx.AsyncClient connection pooling with lock in wled_provider - Optimize _send_pixels_http to avoid np.hstack allocation in wled_client - Mutate chart arrays in-place in dashboard, perf-charts, targets - Merge dashboard 2-batch fetch into single Promise.all - Hoist frame_time outside loop in mapped_stream Usability: - Fix health check interval load/save in device settings - Swap confirm modal button classes (No=secondary, Yes=danger) - Add aria-modal to audio/value source editors, fix close button aria-labels - Add modal footer close button to settings modal - Add dedicated calibration LED count validation error keys i18n: - Replace ~50 hardcoded English strings with t() calls across 12 JS files - Add 50 new keys to en.json, ru.json, zh.json - Localize inline toasts in index.html with window.t fallback - Add data-i18n to command palette footer - Add localization policy to CLAUDE.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -99,6 +99,12 @@ class AudioAnalyzer:
|
||||
self._spectrum_buf_right = np.zeros(NUM_BANDS, dtype=np.float32)
|
||||
self._sq_buf = np.empty(chunk_size, dtype=np.float32)
|
||||
|
||||
# Pre-compute band start/end arrays and widths for vectorized binning
|
||||
self._band_starts = np.array([s for s, _ in self._bands], dtype=np.intp)
|
||||
self._band_ends = np.array([e for _, e in self._bands], dtype=np.intp)
|
||||
self._band_widths = (self._band_ends - self._band_starts).astype(np.float32)
|
||||
self._band_widths[self._band_widths == 0] = 1.0 # avoid divide-by-zero
|
||||
|
||||
# Pre-allocated channel buffers for stereo
|
||||
self._left_buf = np.empty(chunk_size, dtype=np.float32)
|
||||
self._right_buf = np.empty(chunk_size, dtype=np.float32)
|
||||
@@ -205,11 +211,15 @@ class AudioAnalyzer:
|
||||
fft_mag = np.abs(np.fft.rfft(self._fft_windowed))
|
||||
fft_mag *= (1.0 / chunk_size)
|
||||
fft_len = len(fft_mag)
|
||||
for b, (s, e) in enumerate(self._bands):
|
||||
if s < fft_len and e <= fft_len:
|
||||
buf[b] = float(np.mean(fft_mag[s:e]))
|
||||
else:
|
||||
buf[b] = 0.0
|
||||
# Vectorized band binning using cumulative sum
|
||||
valid = (self._band_starts < fft_len) & (self._band_ends <= fft_len) & (self._band_ends > 0)
|
||||
buf[:] = 0.0
|
||||
if valid.any():
|
||||
cumsum = np.cumsum(fft_mag)
|
||||
band_sums = cumsum[self._band_ends[valid] - 1] - np.where(
|
||||
self._band_starts[valid] > 0, cumsum[self._band_starts[valid] - 1], 0.0
|
||||
)
|
||||
buf[valid] = band_sums / self._band_widths[valid]
|
||||
spec_max = float(np.max(buf))
|
||||
if spec_max > 1e-6:
|
||||
buf *= (1.0 / spec_max)
|
||||
|
||||
Reference in New Issue
Block a user