Add audio capture timing metrics to target pipeline
Instrument AudioCaptureStream with read/FFT timing and AudioColorStripStream with render timing. Display audio-specific timing segments (read/fft/render/send) in the target card breakdown bar when an audio source is active. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -387,13 +387,14 @@ export async function loadTargetsTab() {
|
||||
_loadTargetsLock = true;
|
||||
|
||||
try {
|
||||
// Fetch devices, targets, CSS sources, picture sources, and pattern templates in parallel
|
||||
const [devicesResp, targetsResp, cssResp, psResp, patResp] = await Promise.all([
|
||||
// Fetch devices, targets, CSS sources, picture sources, pattern templates, and value sources in parallel
|
||||
const [devicesResp, targetsResp, cssResp, psResp, patResp, vsResp] = await Promise.all([
|
||||
fetchWithAuth('/devices'),
|
||||
fetchWithAuth('/picture-targets'),
|
||||
fetchWithAuth('/color-strip-sources').catch(() => null),
|
||||
fetchWithAuth('/picture-sources').catch(() => null),
|
||||
fetchWithAuth('/pattern-templates').catch(() => null),
|
||||
fetchWithAuth('/value-sources').catch(() => null),
|
||||
]);
|
||||
|
||||
const devicesData = await devicesResp.json();
|
||||
@@ -422,6 +423,12 @@ export async function loadTargetsTab() {
|
||||
patternTemplates.forEach(pt => { patternTemplateMap[pt.id] = pt; });
|
||||
}
|
||||
|
||||
let valueSourceMap = {};
|
||||
if (vsResp && vsResp.ok) {
|
||||
const vsData = await vsResp.json();
|
||||
(vsData.sources || []).forEach(s => { valueSourceMap[s.id] = s; });
|
||||
}
|
||||
|
||||
// Fetch all device states, target states, and target metrics in batch
|
||||
const [batchDevStatesResp, batchTgtStatesResp, batchTgtMetricsResp] = await Promise.all([
|
||||
fetchWithAuth('/devices/batch/states'),
|
||||
@@ -478,8 +485,8 @@ export async function loadTargetsTab() {
|
||||
// Build items arrays for each section
|
||||
const deviceItems = ledDevices.map(d => ({ key: d.id, html: createDeviceCard(d) }));
|
||||
const cssItems = Object.values(colorStripSourceMap).map(s => ({ key: s.id, html: createColorStripCard(s, pictureSourceMap) }));
|
||||
const ledTargetItems = ledTargets.map(t => ({ key: t.id, html: createTargetCard(t, deviceMap, colorStripSourceMap) }));
|
||||
const kcTargetItems = kcTargets.map(t => ({ key: t.id, html: createKCTargetCard(t, pictureSourceMap, patternTemplateMap) }));
|
||||
const ledTargetItems = ledTargets.map(t => ({ key: t.id, html: createTargetCard(t, deviceMap, colorStripSourceMap, valueSourceMap) }));
|
||||
const kcTargetItems = kcTargets.map(t => ({ key: t.id, html: createKCTargetCard(t, pictureSourceMap, patternTemplateMap, valueSourceMap) }));
|
||||
const patternItems = patternTemplates.map(pt => ({ key: pt.id, html: createPatternTemplateCard(pt) }));
|
||||
|
||||
// Track which target cards were replaced/added (need chart re-init)
|
||||
@@ -630,7 +637,7 @@ function _cssSourceName(cssId, colorStripSourceMap) {
|
||||
return css ? escapeHtml(css.name) : escapeHtml(cssId);
|
||||
}
|
||||
|
||||
export function createTargetCard(target, deviceMap, colorStripSourceMap) {
|
||||
export function createTargetCard(target, deviceMap, colorStripSourceMap, valueSourceMap) {
|
||||
const state = target.state || {};
|
||||
const metrics = target.metrics || {};
|
||||
|
||||
@@ -642,6 +649,9 @@ export function createTargetCard(target, deviceMap, colorStripSourceMap) {
|
||||
const cssId = target.color_strip_source_id || '';
|
||||
const cssSummary = _cssSourceName(cssId, colorStripSourceMap);
|
||||
|
||||
const bvsId = target.brightness_value_source_id || '';
|
||||
const bvs = bvsId && valueSourceMap ? valueSourceMap[bvsId] : null;
|
||||
|
||||
// Determine if overlay is available (picture-based CSS)
|
||||
const css = cssId ? colorStripSourceMap[cssId] : null;
|
||||
const overlayAvailable = !css || css.source_type === 'picture';
|
||||
@@ -667,8 +677,9 @@ export function createTargetCard(target, deviceMap, colorStripSourceMap) {
|
||||
</div>
|
||||
<div class="stream-card-props">
|
||||
<span class="stream-card-prop" title="${t('targets.device')}">💡 ${escapeHtml(deviceName)}</span>
|
||||
<span class="stream-card-prop" title="${t('targets.fps')}">⚡ ${target.fps || 30} fps</span>
|
||||
<span class="stream-card-prop" title="${t('targets.fps')}">⚡ ${target.fps || 30}</span>
|
||||
<span class="stream-card-prop stream-card-prop-full" title="${t('targets.color_strip_source')}">🎞️ ${cssSummary}</span>
|
||||
${bvs ? `<span class="stream-card-prop stream-card-prop-full" title="${t('targets.brightness_vs')}">🔆 ${escapeHtml(bvs.name)}</span>` : ''}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
${isProcessing ? `
|
||||
@@ -688,15 +699,27 @@ export function createTargetCard(target, deviceMap, colorStripSourceMap) {
|
||||
<div class="timing-total"><strong>${state.timing_total_ms}ms</strong></div>
|
||||
</div>
|
||||
<div class="timing-bar">
|
||||
${state.timing_audio_read_ms != null ? `
|
||||
<span class="timing-seg timing-audio-read" style="flex:${state.timing_audio_read_ms}" title="read ${state.timing_audio_read_ms}ms"></span>
|
||||
<span class="timing-seg timing-audio-fft" style="flex:${state.timing_audio_fft_ms}" title="fft ${state.timing_audio_fft_ms}ms"></span>
|
||||
<span class="timing-seg timing-audio-render" style="flex:${state.timing_audio_render_ms || 0.1}" title="render ${state.timing_audio_render_ms}ms"></span>
|
||||
` : `
|
||||
${state.timing_extract_ms != null ? `<span class="timing-seg timing-extract" style="flex:${state.timing_extract_ms}" title="extract ${state.timing_extract_ms}ms"></span>` : ''}
|
||||
${state.timing_map_leds_ms != null ? `<span class="timing-seg timing-map" style="flex:${state.timing_map_leds_ms}" title="map ${state.timing_map_leds_ms}ms"></span>` : ''}
|
||||
${state.timing_smooth_ms != null ? `<span class="timing-seg timing-smooth" style="flex:${state.timing_smooth_ms || 0.1}" title="smooth ${state.timing_smooth_ms}ms"></span>` : ''}
|
||||
`}
|
||||
<span class="timing-seg timing-send" style="flex:${state.timing_send_ms}" title="send ${state.timing_send_ms}ms"></span>
|
||||
</div>
|
||||
<div class="timing-legend">
|
||||
${state.timing_audio_read_ms != null ? `
|
||||
<span class="timing-legend-item"><span class="timing-dot timing-audio-read"></span>read ${state.timing_audio_read_ms}ms</span>
|
||||
<span class="timing-legend-item"><span class="timing-dot timing-audio-fft"></span>fft ${state.timing_audio_fft_ms}ms</span>
|
||||
<span class="timing-legend-item"><span class="timing-dot timing-audio-render"></span>render ${state.timing_audio_render_ms}ms</span>
|
||||
` : `
|
||||
${state.timing_extract_ms != null ? `<span class="timing-legend-item"><span class="timing-dot timing-extract"></span>extract ${state.timing_extract_ms}ms</span>` : ''}
|
||||
${state.timing_map_leds_ms != null ? `<span class="timing-legend-item"><span class="timing-dot timing-map"></span>map ${state.timing_map_leds_ms}ms</span>` : ''}
|
||||
${state.timing_smooth_ms != null ? `<span class="timing-legend-item"><span class="timing-dot timing-smooth"></span>smooth ${state.timing_smooth_ms}ms</span>` : ''}
|
||||
`}
|
||||
<span class="timing-legend-item"><span class="timing-dot timing-send"></span>send ${state.timing_send_ms}ms</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user