diff --git a/CLAUDE.md b/CLAUDE.md index 948809d..5a94b42 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -156,6 +156,7 @@ When creating or modifying entity cards (devices, targets, CSS sources, streams, - Clone (📋) and Edit (✏️) icon buttons in `.template-card-actions` - Delete (✕) button as `.card-remove-btn` - Property badges in `.stream-card-props` with emoji icons +- **Crosslinks**: When a card references another entity (audio source, picture source, capture template, PP template, etc.), make the property badge a clickable link using the `stream-card-link` CSS class and an `onclick` handler calling `navigateToCard(tab, subTab, sectionKey, cardAttr, cardValue)`. Only add the link when the referenced entity is found (to avoid broken navigation). Example: `🎵 Name` ### Modal footer buttons diff --git a/server/src/wled_controller/core/processing/value_stream.py b/server/src/wled_controller/core/processing/value_stream.py index 63d4086..998166e 100644 --- a/server/src/wled_controller/core/processing/value_stream.py +++ b/server/src/wled_controller/core/processing/value_stream.py @@ -449,7 +449,7 @@ class SceneValueStream(ValueStream): self._max = max_value self._live_stream_manager = live_stream_manager self._live_stream = None - self._prev_value = 0.5 # neutral start + self._prev_value = None # None = no frame seen yet; skip smoothing on first frame def start(self) -> None: if self._live_stream_manager and self._picture_source_id: @@ -471,15 +471,15 @@ class SceneValueStream(ValueStream): except Exception as e: logger.warning(f"SceneValueStream failed to release live stream: {e}") self._live_stream = None - self._prev_value = 0.5 + self._prev_value = None def get_value(self) -> float: if self._live_stream is None: - return self._prev_value + return self._prev_value if self._prev_value is not None else 0.0 frame = self._live_stream.get_latest_frame() if frame is None: - return self._prev_value + return self._prev_value if self._prev_value is not None else 0.0 # Fast luminance: subsample to ~64x64 via numpy stride (zero-copy view) img = frame.image @@ -500,8 +500,11 @@ class SceneValueStream(ValueStream): if self._behavior == "complement": raw = 1.0 - raw - # Temporal smoothing (EMA) - smoothed = self._smoothing * self._prev_value + (1.0 - self._smoothing) * raw + # Temporal smoothing (EMA) — skip on first frame (no history to blend with) + if self._prev_value is None: + smoothed = raw + else: + smoothed = self._smoothing * self._prev_value + (1.0 - self._smoothing) * raw self._prev_value = smoothed # Map to output range diff --git a/server/src/wled_controller/static/js/features/value-sources.js b/server/src/wled_controller/static/js/features/value-sources.js index c3d209b..674d4ec 100644 --- a/server/src/wled_controller/static/js/features/value-sources.js +++ b/server/src/wled_controller/static/js/features/value-sources.js @@ -482,9 +482,13 @@ export function createValueSourceCard(src) { } else if (src.source_type === 'audio') { const audioSrc = _cachedAudioSources.find(a => a.id === src.audio_source_id); const audioName = audioSrc ? audioSrc.name : (src.audio_source_id || '-'); + const audioSection = audioSrc ? (audioSrc.source_type === 'mono' ? 'audio-mono' : 'audio-multi') : 'audio-multi'; const modeLabel = src.mode || 'rms'; + const audioBadge = audioSrc + ? `🎵 ${escapeHtml(audioName)}` + : `🎵 ${escapeHtml(audioName)}`; propsHtml = ` - 🎵 ${escapeHtml(audioName)} + ${audioBadge} 📈 ${modeLabel.toUpperCase()} ↕️ ${src.min_value ?? 0}–${src.max_value ?? 1} `; @@ -497,8 +501,16 @@ export function createValueSourceCard(src) { } else if (src.source_type === 'adaptive_scene') { const ps = _cachedStreams.find(s => s.id === src.picture_source_id); const psName = ps ? ps.name : (src.picture_source_id || '-'); + let psSubTab = 'raw', psSection = 'raw-streams'; + if (ps) { + if (ps.stream_type === 'static_image') { psSubTab = 'static_image'; psSection = 'static-streams'; } + else if (ps.stream_type === 'processed') { psSubTab = 'processed'; psSection = 'proc-streams'; } + } + const psBadge = ps + ? `🖥️ ${escapeHtml(psName)}` + : `🖥️ ${escapeHtml(psName)}`; propsHtml = ` - 🖥️ ${escapeHtml(psName)} + ${psBadge} 🔄 ${src.scene_behavior || 'complement'} `; }