Add audio channel selection (mono/left/right), show device LED count in target editor

Audio capture now produces per-channel FFT spectrum and RMS alongside
the existing mono mix. Each audio color strip source can select which
channel to visualize via a new "Channel" dropdown. This enables stereo
setups with separate left/right segments on the same LED strip.

Also shows the device LED count under the device selector in the target
editor for quick reference.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-23 15:05:15 +03:00
parent 9d593379b8
commit f15ff8fea0
13 changed files with 129 additions and 31 deletions

View File

@@ -438,6 +438,7 @@ function _loadAudioState(css) {
document.getElementById('css-editor-audio-smoothing').value = smoothing;
document.getElementById('css-editor-audio-smoothing-val').textContent = parseFloat(smoothing).toFixed(2);
document.getElementById('css-editor-audio-channel').value = css.audio_channel || 'mono';
document.getElementById('css-editor-audio-palette').value = css.palette || 'rainbow';
document.getElementById('css-editor-audio-color').value = rgbArrayToHex(css.color || [0, 255, 0]);
document.getElementById('css-editor-audio-color-peak').value = rgbArrayToHex(css.color_peak || [255, 0, 0]);
@@ -461,6 +462,7 @@ function _resetAudioState() {
document.getElementById('css-editor-audio-sensitivity-val').textContent = '1.0';
document.getElementById('css-editor-audio-smoothing').value = 0.3;
document.getElementById('css-editor-audio-smoothing-val').textContent = '0.30';
document.getElementById('css-editor-audio-channel').value = 'mono';
document.getElementById('css-editor-audio-palette').value = 'rainbow';
document.getElementById('css-editor-audio-color').value = '#00ff00';
document.getElementById('css-editor-audio-color-peak').value = '#ff0000';
@@ -544,9 +546,12 @@ export function createColorStripCard(source, pictureSourceMap) {
} else if (isAudio) {
const vizLabel = t('color_strip.audio.viz.' + (source.visualization_mode || 'spectrum')) || source.visualization_mode || 'spectrum';
const sensitivityVal = (source.sensitivity || 1.0).toFixed(1);
const ch = source.audio_channel || 'mono';
const chBadge = ch !== 'mono' ? `<span class="stream-card-prop" title="${t('color_strip.audio.channel')}">${ch === 'left' ? 'L' : 'R'}</span>` : '';
propsHtml = `
<span class="stream-card-prop">🎵 ${escapeHtml(vizLabel)}</span>
<span class="stream-card-prop" title="${t('color_strip.audio.sensitivity')}">📶 ${sensitivityVal}</span>
${chBadge}
${source.mirror ? `<span class="stream-card-prop">🪞</span>` : ''}
`;
} else {
@@ -808,6 +813,7 @@ export async function saveCSSEditor() {
visualization_mode: document.getElementById('css-editor-audio-viz').value,
audio_device_index: parseInt(devIdx) || -1,
audio_loopback: devLoop !== '0',
audio_channel: document.getElementById('css-editor-audio-channel').value,
sensitivity: parseFloat(document.getElementById('css-editor-audio-sensitivity').value),
smoothing: parseFloat(document.getElementById('css-editor-audio-smoothing').value),
palette: document.getElementById('css-editor-audio-palette').value,

View File

@@ -140,6 +140,18 @@ function _updateFpsRecommendation() {
}
}
function _updateDeviceInfo() {
const deviceSelect = document.getElementById('target-editor-device');
const el = document.getElementById('target-editor-device-info');
const device = _targetEditorDevices.find(d => d.id === deviceSelect.value);
if (device && device.led_count) {
el.textContent = `${device.led_count} LEDs`;
el.style.display = '';
} else {
el.style.display = 'none';
}
}
function _updateKeepaliveVisibility() {
const deviceSelect = document.getElementById('target-editor-device');
const keepaliveGroup = document.getElementById('target-editor-keepalive-group');
@@ -267,10 +279,11 @@ export async function showTargetEditor(targetId = null) {
_targetNameManuallyEdited = !!targetId;
document.getElementById('target-editor-name').oninput = () => { _targetNameManuallyEdited = true; };
window._targetAutoName = _autoGenerateTargetName;
deviceSelect.onchange = () => { _updateKeepaliveVisibility(); _updateFpsRecommendation(); _autoGenerateTargetName(); };
deviceSelect.onchange = () => { _updateDeviceInfo(); _updateKeepaliveVisibility(); _updateFpsRecommendation(); _autoGenerateTargetName(); };
if (!targetId) _autoGenerateTargetName();
// Show/hide standby interval based on selected device capabilities
_updateDeviceInfo();
_updateKeepaliveVisibility();
_updateFpsRecommendation();