feat(processed-audio-sources): phase 4 - runtime filter integration
Add AudioFilterPipeline for chained filter execution on AudioAnalysis. Wire filter pipelines into AudioColorStripStream, AudioValueStream, and WebSocket test endpoint. Add hot-update support via ProcessorManager.refresh_audio_filter_pipelines(). Thread AudioProcessingTemplateStore through dependency injection hierarchy.
This commit is contained in:
@@ -9,6 +9,7 @@ from starlette.websockets import WebSocket, WebSocketDisconnect
|
||||
from wled_controller.api.auth import AuthRequired
|
||||
from wled_controller.api.dependencies import (
|
||||
fire_entity_event,
|
||||
get_audio_processing_template_store,
|
||||
get_audio_source_store,
|
||||
get_audio_template_store,
|
||||
get_color_strip_store,
|
||||
@@ -210,11 +211,12 @@ async def test_audio_source_ws(
|
||||
ManagedAudioStream (ref-counted — shares with running targets), and streams
|
||||
AudioAnalysis snapshots as JSON at ~20 Hz.
|
||||
|
||||
NOTE: Audio processing filters from the template chain are NOT applied in
|
||||
this WebSocket yet — that will be wired in Phase 4 when the stream runtime
|
||||
integrates filter instances.
|
||||
Audio processing filters from the template chain are applied to the
|
||||
analysis before sending, so the WebSocket output matches what running
|
||||
streams see.
|
||||
"""
|
||||
from wled_controller.api.auth import verify_ws_token
|
||||
from wled_controller.core.audio.filters.pipeline import build_pipeline_from_template_ids
|
||||
|
||||
if not verify_ws_token(token):
|
||||
await websocket.close(code=4001, reason="Unauthorized")
|
||||
@@ -223,6 +225,7 @@ async def test_audio_source_ws(
|
||||
# Resolve source → device info + processing template chain
|
||||
store = get_audio_source_store()
|
||||
template_store = get_audio_template_store()
|
||||
apt_store = get_audio_processing_template_store()
|
||||
manager = get_processor_manager()
|
||||
|
||||
try:
|
||||
@@ -247,6 +250,15 @@ async def test_audio_source_ws(
|
||||
logger.debug("Audio template not found, falling back to best available engine: %s", e)
|
||||
pass # Fall back to best available engine
|
||||
|
||||
# Build filter pipeline from processing template chain
|
||||
pipeline = None
|
||||
if resolved.audio_processing_template_ids and apt_store:
|
||||
pipeline = build_pipeline_from_template_ids(
|
||||
resolved.audio_processing_template_ids, apt_store
|
||||
)
|
||||
if pipeline.empty:
|
||||
pipeline = None
|
||||
|
||||
# Acquire shared audio stream
|
||||
audio_mgr = manager.audio_capture_manager
|
||||
try:
|
||||
@@ -265,7 +277,10 @@ async def test_audio_source_ws(
|
||||
if analysis is not None and analysis.timestamp != last_ts:
|
||||
last_ts = analysis.timestamp
|
||||
|
||||
# Send raw analysis — filter processing will be added in Phase 4
|
||||
# Apply filter pipeline (channel extract, band extract, gain, etc.)
|
||||
if pipeline is not None:
|
||||
analysis = pipeline.process(analysis)
|
||||
|
||||
await websocket.send_json(
|
||||
{
|
||||
"spectrum": analysis.spectrum.tolist(),
|
||||
@@ -283,5 +298,7 @@ async def test_audio_source_ws(
|
||||
except Exception as e:
|
||||
logger.error(f"Audio test WebSocket error for {source_id}: {e}")
|
||||
finally:
|
||||
if pipeline is not None:
|
||||
pipeline.close()
|
||||
audio_mgr.release(device_index, is_loopback, engine_type)
|
||||
logger.info(f"Audio test WebSocket disconnected for source {source_id}")
|
||||
|
||||
Reference in New Issue
Block a user