feat: add band_extract audio source type for frequency band filtering
Some checks failed
Lint & Test / test (push) Failing after 29s
Some checks failed
Lint & Test / test (push) Failing after 29s
New audio source type that filters a parent source to a specific frequency band (bass 20-250Hz, mid 250-4kHz, treble 4k-20kHz, or custom range). Supports chaining with frequency range intersection and cycle detection. Band filtering applied in both CSS audio streams and test WebSocket.
This commit is contained in:
@@ -148,7 +148,7 @@ import {
|
||||
showAudioSourceModal, closeAudioSourceModal, saveAudioSource,
|
||||
editAudioSource, cloneAudioSource, deleteAudioSource,
|
||||
testAudioSource, closeTestAudioSourceModal,
|
||||
refreshAudioDevices,
|
||||
refreshAudioDevices, onBandPresetChange,
|
||||
} from './features/audio-sources.ts';
|
||||
|
||||
// Layer 5: value sources
|
||||
@@ -474,6 +474,7 @@ Object.assign(window, {
|
||||
testAudioSource,
|
||||
closeTestAudioSourceModal,
|
||||
refreshAudioDevices,
|
||||
onBandPresetChange,
|
||||
|
||||
// value sources
|
||||
showValueSourceModal,
|
||||
|
||||
@@ -34,7 +34,7 @@ const _valueSourceTypeIcons = {
|
||||
adaptive_time: _svg(P.clock), adaptive_scene: _svg(P.cloudSun),
|
||||
daylight: _svg(P.sun),
|
||||
};
|
||||
const _audioSourceTypeIcons = { mono: _svg(P.mic), multichannel: _svg(P.volume2) };
|
||||
const _audioSourceTypeIcons = { mono: _svg(P.mic), multichannel: _svg(P.volume2), band_extract: _svg(P.activity) };
|
||||
const _deviceTypeIcons = {
|
||||
wled: _svg(P.wifi), adalight: _svg(P.usb), ambiled: _svg(P.usb),
|
||||
mqtt: _svg(P.send), ws: _svg(P.globe), openrgb: _svg(P.palette),
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/**
|
||||
* Audio Sources — CRUD for multichannel and mono audio sources.
|
||||
* Audio Sources — CRUD for multichannel, mono, and band extract audio sources.
|
||||
*
|
||||
* Audio sources are managed entities that encapsulate audio device
|
||||
* configuration. Multichannel sources represent physical audio devices;
|
||||
* mono sources extract a single channel from a multichannel source.
|
||||
* CSS audio type references a mono source by ID.
|
||||
* mono sources extract a single channel from a multichannel source;
|
||||
* band extract sources filter a parent source to a frequency band.
|
||||
* CSS audio type references an audio source by ID.
|
||||
*
|
||||
* Card rendering is handled by streams.js (Audio tab).
|
||||
* This module manages the editor modal and API operations.
|
||||
@@ -38,6 +39,10 @@ class AudioSourceModal extends Modal {
|
||||
audioTemplate: (document.getElementById('audio-source-audio-template') as HTMLSelectElement).value,
|
||||
parent: (document.getElementById('audio-source-parent') as HTMLSelectElement).value,
|
||||
channel: (document.getElementById('audio-source-channel') as HTMLSelectElement).value,
|
||||
bandParent: (document.getElementById('audio-source-band-parent') as HTMLSelectElement).value,
|
||||
band: (document.getElementById('audio-source-band') as HTMLSelectElement).value,
|
||||
freqLow: (document.getElementById('audio-source-freq-low') as HTMLInputElement).value,
|
||||
freqHigh: (document.getElementById('audio-source-freq-high') as HTMLInputElement).value,
|
||||
tags: JSON.stringify(_audioSourceTagsInput ? _audioSourceTagsInput.getValue() : []),
|
||||
};
|
||||
}
|
||||
@@ -49,21 +54,27 @@ const audioSourceModal = new AudioSourceModal();
|
||||
let _asTemplateEntitySelect: EntitySelect | null = null;
|
||||
let _asDeviceEntitySelect: EntitySelect | null = null;
|
||||
let _asParentEntitySelect: EntitySelect | null = null;
|
||||
let _asBandParentEntitySelect: EntitySelect | null = null;
|
||||
|
||||
// ── Modal ─────────────────────────────────────────────────────
|
||||
|
||||
const _titleKeys: Record<string, Record<string, string>> = {
|
||||
multichannel: { add: 'audio_source.add.multichannel', edit: 'audio_source.edit.multichannel' },
|
||||
mono: { add: 'audio_source.add.mono', edit: 'audio_source.edit.mono' },
|
||||
band_extract: { add: 'audio_source.add.band_extract', edit: 'audio_source.edit.band_extract' },
|
||||
};
|
||||
|
||||
export async function showAudioSourceModal(sourceType: any, editData?: any) {
|
||||
const isEdit = !!editData;
|
||||
const titleKey = isEdit
|
||||
? (editData.source_type === 'mono' ? 'audio_source.edit.mono' : 'audio_source.edit.multichannel')
|
||||
: (sourceType === 'mono' ? 'audio_source.add.mono' : 'audio_source.add.multichannel');
|
||||
const st = isEdit ? editData.source_type : sourceType;
|
||||
const titleKey = _titleKeys[st]?.[isEdit ? 'edit' : 'add'] || _titleKeys.multichannel.add;
|
||||
|
||||
document.getElementById('audio-source-modal-title')!.innerHTML = `${ICON_MUSIC} ${t(titleKey)}`;
|
||||
(document.getElementById('audio-source-id') as HTMLInputElement).value = isEdit ? editData.id : '';
|
||||
(document.getElementById('audio-source-error') as HTMLElement).style.display = 'none';
|
||||
|
||||
const typeSelect = document.getElementById('audio-source-type') as HTMLSelectElement;
|
||||
typeSelect.value = isEdit ? editData.source_type : sourceType;
|
||||
typeSelect.value = st;
|
||||
typeSelect.disabled = isEdit; // can't change type after creation
|
||||
|
||||
onAudioSourceTypeChange();
|
||||
@@ -77,9 +88,15 @@ export async function showAudioSourceModal(sourceType: any, editData?: any) {
|
||||
(document.getElementById('audio-source-audio-template') as HTMLSelectElement).onchange = _filterDevicesBySelectedTemplate;
|
||||
await _loadAudioDevices();
|
||||
_selectAudioDevice(editData.device_index, editData.is_loopback);
|
||||
} else {
|
||||
} else if (editData.source_type === 'mono') {
|
||||
_loadMultichannelSources(editData.audio_source_id);
|
||||
(document.getElementById('audio-source-channel') as HTMLSelectElement).value = editData.channel || 'mono';
|
||||
} else if (editData.source_type === 'band_extract') {
|
||||
_loadBandParentSources(editData.audio_source_id);
|
||||
(document.getElementById('audio-source-band') as HTMLSelectElement).value = editData.band || 'bass';
|
||||
(document.getElementById('audio-source-freq-low') as HTMLInputElement).value = String(editData.freq_low ?? 20);
|
||||
(document.getElementById('audio-source-freq-high') as HTMLInputElement).value = String(editData.freq_high ?? 20000);
|
||||
onBandPresetChange();
|
||||
}
|
||||
} else {
|
||||
(document.getElementById('audio-source-name') as HTMLInputElement).value = '';
|
||||
@@ -89,8 +106,14 @@ export async function showAudioSourceModal(sourceType: any, editData?: any) {
|
||||
_loadAudioTemplates();
|
||||
(document.getElementById('audio-source-audio-template') as HTMLSelectElement).onchange = _filterDevicesBySelectedTemplate;
|
||||
await _loadAudioDevices();
|
||||
} else {
|
||||
} else if (sourceType === 'mono') {
|
||||
_loadMultichannelSources();
|
||||
} else if (sourceType === 'band_extract') {
|
||||
_loadBandParentSources();
|
||||
(document.getElementById('audio-source-band') as HTMLSelectElement).value = 'bass';
|
||||
(document.getElementById('audio-source-freq-low') as HTMLInputElement).value = '20';
|
||||
(document.getElementById('audio-source-freq-high') as HTMLInputElement).value = '20000';
|
||||
onBandPresetChange();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +134,12 @@ export function onAudioSourceTypeChange() {
|
||||
const type = (document.getElementById('audio-source-type') as HTMLSelectElement).value;
|
||||
(document.getElementById('audio-source-multichannel-section') as HTMLElement).style.display = type === 'multichannel' ? '' : 'none';
|
||||
(document.getElementById('audio-source-mono-section') as HTMLElement).style.display = type === 'mono' ? '' : 'none';
|
||||
(document.getElementById('audio-source-band-extract-section') as HTMLElement).style.display = type === 'band_extract' ? '' : 'none';
|
||||
}
|
||||
|
||||
export function onBandPresetChange() {
|
||||
const band = (document.getElementById('audio-source-band') as HTMLSelectElement).value;
|
||||
(document.getElementById('audio-source-custom-freq') as HTMLElement).style.display = band === 'custom' ? '' : 'none';
|
||||
}
|
||||
|
||||
// ── Save ──────────────────────────────────────────────────────
|
||||
@@ -136,9 +165,16 @@ export async function saveAudioSource() {
|
||||
payload.device_index = parseInt(devIdx) || -1;
|
||||
payload.is_loopback = devLoop !== '0';
|
||||
payload.audio_template_id = (document.getElementById('audio-source-audio-template') as HTMLSelectElement).value || null;
|
||||
} else {
|
||||
} else if (sourceType === 'mono') {
|
||||
payload.audio_source_id = (document.getElementById('audio-source-parent') as HTMLSelectElement).value;
|
||||
payload.channel = (document.getElementById('audio-source-channel') as HTMLSelectElement).value;
|
||||
} else if (sourceType === 'band_extract') {
|
||||
payload.audio_source_id = (document.getElementById('audio-source-band-parent') as HTMLSelectElement).value;
|
||||
payload.band = (document.getElementById('audio-source-band') as HTMLSelectElement).value;
|
||||
if (payload.band === 'custom') {
|
||||
payload.freq_low = parseFloat((document.getElementById('audio-source-freq-low') as HTMLInputElement).value) || 20;
|
||||
payload.freq_high = parseFloat((document.getElementById('audio-source-freq-high') as HTMLInputElement).value) || 20000;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -321,6 +357,30 @@ function _loadMultichannelSources(selectedId?: any) {
|
||||
}
|
||||
}
|
||||
|
||||
function _loadBandParentSources(selectedId?: any) {
|
||||
const select = document.getElementById('audio-source-band-parent') as HTMLSelectElement | null;
|
||||
if (!select) return;
|
||||
// Band extract can reference any audio source type
|
||||
const sources = _cachedAudioSources;
|
||||
select.innerHTML = sources.map(s =>
|
||||
`<option value="${s.id}"${s.id === selectedId ? ' selected' : ''}>${escapeHtml(s.name)}</option>`
|
||||
).join('');
|
||||
|
||||
if (_asBandParentEntitySelect) _asBandParentEntitySelect.destroy();
|
||||
if (sources.length > 0) {
|
||||
_asBandParentEntitySelect = new EntitySelect({
|
||||
target: select,
|
||||
getItems: () => sources.map((s: any) => ({
|
||||
value: s.id,
|
||||
label: s.name,
|
||||
icon: getAudioSourceIcon(s.source_type),
|
||||
desc: s.source_type,
|
||||
})),
|
||||
placeholder: t('palette.search'),
|
||||
} as any);
|
||||
}
|
||||
}
|
||||
|
||||
function _loadAudioTemplates(selectedId?: any) {
|
||||
const select = document.getElementById('audio-source-audio-template') as HTMLSelectElement | null;
|
||||
if (!select) return;
|
||||
@@ -469,7 +529,7 @@ export function initAudioSourceDelegation(container: HTMLElement): void {
|
||||
const handler = _audioSourceActions[action];
|
||||
if (handler) {
|
||||
// Verify we're inside an audio source section
|
||||
const section = btn.closest<HTMLElement>('[data-card-section="audio-multi"], [data-card-section="audio-mono"]');
|
||||
const section = btn.closest<HTMLElement>('[data-card-section="audio-multi"], [data-card-section="audio-mono"], [data-card-section="audio-band-extract"]');
|
||||
if (!section) return;
|
||||
const card = btn.closest<HTMLElement>('[data-id]');
|
||||
const id = card?.getAttribute('data-id');
|
||||
|
||||
@@ -875,7 +875,7 @@ async function _loadAudioSources() {
|
||||
try {
|
||||
const sources: any[] = await audioSourcesCache.fetch();
|
||||
select.innerHTML = sources.map(s => {
|
||||
const badge = s.source_type === 'multichannel' ? ' [multichannel]' : ' [mono]';
|
||||
const badge = s.source_type === 'multichannel' ? ' [multichannel]' : s.source_type === 'band_extract' ? ' [band]' : ' [mono]';
|
||||
return `<option value="${s.id}">${escapeHtml(s.name)}${badge}</option>`;
|
||||
}).join('');
|
||||
if (sources.length === 0) {
|
||||
|
||||
@@ -57,7 +57,7 @@ import {
|
||||
getEngineIcon, getAudioEngineIcon, getPictureSourceIcon, getAudioSourceIcon, getColorStripIcon,
|
||||
ICON_TEMPLATE, ICON_CLONE, ICON_EDIT, ICON_TEST, ICON_LINK_SOURCE,
|
||||
ICON_FPS, ICON_WEB, ICON_VALUE_SOURCE, ICON_CLOCK, ICON_AUDIO_LOOPBACK, ICON_AUDIO_INPUT,
|
||||
ICON_AUDIO_TEMPLATE, ICON_MONITOR, ICON_WRENCH, ICON_RADIO,
|
||||
ICON_AUDIO_TEMPLATE, ICON_MONITOR, ICON_WRENCH, ICON_RADIO, ICON_ACTIVITY,
|
||||
ICON_CAPTURE_TEMPLATE, ICON_PP_TEMPLATE, ICON_CSPT, ICON_HELP, ICON_TRASH, ICON_PALETTE,
|
||||
} from '../core/icons.ts';
|
||||
import * as P from '../core/icon-paths.ts';
|
||||
@@ -106,6 +106,7 @@ const csProcStreams = new CardSection('proc-streams', { titleKey: 'streams.secti
|
||||
const csProcTemplates = new CardSection('proc-templates', { titleKey: 'postprocessing.title', gridClass: 'templates-grid', addCardOnclick: "showAddPPTemplateModal()", keyAttr: 'data-pp-template-id', emptyKey: 'section.empty.pp_templates', bulkActions: _ppTemplateDeleteAction });
|
||||
const csAudioMulti = new CardSection('audio-multi', { titleKey: 'audio_source.group.multichannel', gridClass: 'templates-grid', addCardOnclick: "showAudioSourceModal('multichannel')", keyAttr: 'data-id', emptyKey: 'section.empty.audio_sources', bulkActions: _audioSourceDeleteAction });
|
||||
const csAudioMono = new CardSection('audio-mono', { titleKey: 'audio_source.group.mono', gridClass: 'templates-grid', addCardOnclick: "showAudioSourceModal('mono')", keyAttr: 'data-id', emptyKey: 'section.empty.audio_sources', bulkActions: _audioSourceDeleteAction });
|
||||
const csAudioBandExtract = new CardSection('audio-band-extract', { titleKey: 'audio_source.group.band_extract', gridClass: 'templates-grid', addCardOnclick: "showAudioSourceModal('band_extract')", keyAttr: 'data-id', emptyKey: 'section.empty.audio_sources', bulkActions: _audioSourceDeleteAction });
|
||||
const csStaticStreams = new CardSection('static-streams', { titleKey: 'streams.group.static_image', gridClass: 'templates-grid', addCardOnclick: "showAddStreamModal('static_image')", keyAttr: 'data-stream-id', emptyKey: 'section.empty.picture_sources', bulkActions: _streamDeleteAction });
|
||||
const csVideoStreams = new CardSection('video-streams', { titleKey: 'streams.group.video', gridClass: 'templates-grid', addCardOnclick: "showAddStreamModal('video')", keyAttr: 'data-stream-id', emptyKey: 'section.empty.picture_sources', bulkActions: _streamDeleteAction });
|
||||
const csAudioTemplates = new CardSection('audio-templates', { titleKey: 'audio_template.title', gridClass: 'templates-grid', addCardOnclick: "showAddAudioTemplateModal()", keyAttr: 'data-audio-template-id', emptyKey: 'section.empty.audio_templates', bulkActions: _audioTemplateDeleteAction });
|
||||
@@ -275,7 +276,7 @@ const _streamSectionMap = {
|
||||
proc_templates: [csProcTemplates],
|
||||
css_processing: [csCSPTemplates],
|
||||
color_strip: [csColorStrips],
|
||||
audio: [csAudioMulti, csAudioMono],
|
||||
audio: [csAudioMulti, csAudioMono, csAudioBandExtract],
|
||||
audio_templates: [csAudioTemplates],
|
||||
value: [csValueSources],
|
||||
sync: [csSyncClocks],
|
||||
@@ -462,6 +463,7 @@ function renderPictureSourcesList(streams: any) {
|
||||
|
||||
const multichannelSources = _cachedAudioSources.filter(s => s.source_type === 'multichannel');
|
||||
const monoSources = _cachedAudioSources.filter(s => s.source_type === 'mono');
|
||||
const bandExtractSources = _cachedAudioSources.filter(s => s.source_type === 'band_extract');
|
||||
|
||||
// CSPT templates
|
||||
const csptTemplates = csptCache.data;
|
||||
@@ -545,12 +547,19 @@ function renderPictureSourcesList(streams: any) {
|
||||
}
|
||||
];
|
||||
|
||||
const _bandLabels: Record<string, string> = { bass: 'Bass', mid: 'Mid', treble: 'Treble', custom: 'Custom' };
|
||||
|
||||
const _getSectionForSource = (sourceType: string): string => {
|
||||
if (sourceType === 'multichannel') return 'audio-multi';
|
||||
if (sourceType === 'mono') return 'audio-mono';
|
||||
return 'audio-band-extract';
|
||||
};
|
||||
|
||||
const renderAudioSourceCard = (src: any) => {
|
||||
const isMono = src.source_type === 'mono';
|
||||
const icon = getAudioSourceIcon(src.source_type);
|
||||
|
||||
let propsHtml = '';
|
||||
if (isMono) {
|
||||
if (src.source_type === 'mono') {
|
||||
const parent = _cachedAudioSources.find(s => s.id === src.audio_source_id);
|
||||
const parentName = parent ? parent.name : src.audio_source_id;
|
||||
const chLabel = src.channel === 'left' ? 'L' : src.channel === 'right' ? 'R' : 'M';
|
||||
@@ -561,6 +570,20 @@ function renderPictureSourcesList(streams: any) {
|
||||
${parentBadge}
|
||||
<span class="stream-card-prop" title="${escapeHtml(t('audio_source.channel'))}">${ICON_RADIO} ${chLabel}</span>
|
||||
`;
|
||||
} else if (src.source_type === 'band_extract') {
|
||||
const parent = _cachedAudioSources.find(s => s.id === src.audio_source_id);
|
||||
const parentName = parent ? parent.name : src.audio_source_id;
|
||||
const parentSection = parent ? _getSectionForSource(parent.source_type) : 'audio-multi';
|
||||
const parentBadge = parent
|
||||
? `<span class="stream-card-prop stream-card-link" title="${escapeHtml(t('audio_source.band_parent'))}" onclick="event.stopPropagation(); navigateToCard('streams','audio','${parentSection}','data-id','${src.audio_source_id}')">${getAudioSourceIcon(parent.source_type)} ${escapeHtml(parentName)}</span>`
|
||||
: `<span class="stream-card-prop" title="${escapeHtml(t('audio_source.band_parent'))}">${ICON_ACTIVITY} ${escapeHtml(parentName)}</span>`;
|
||||
const bandLabel = _bandLabels[src.band] || src.band;
|
||||
const freqRange = `${Math.round(src.freq_low)}–${Math.round(src.freq_high)} Hz`;
|
||||
propsHtml = `
|
||||
${parentBadge}
|
||||
<span class="stream-card-prop" title="${escapeHtml(t('audio_source.band'))}">${ICON_ACTIVITY} ${bandLabel}</span>
|
||||
<span class="stream-card-prop" title="${escapeHtml(t('audio_source.freq_range'))}">${freqRange}</span>
|
||||
`;
|
||||
} else {
|
||||
const devIdx = src.device_index ?? -1;
|
||||
const loopback = src.is_loopback !== false;
|
||||
@@ -664,6 +687,7 @@ function renderPictureSourcesList(streams: any) {
|
||||
const procTemplateItems = csProcTemplates.applySortOrder(_cachedPPTemplates.map(t => ({ key: t.id, html: renderPPTemplateCard(t) })));
|
||||
const multiItems = csAudioMulti.applySortOrder(multichannelSources.map(s => ({ key: s.id, html: renderAudioSourceCard(s) })));
|
||||
const monoItems = csAudioMono.applySortOrder(monoSources.map(s => ({ key: s.id, html: renderAudioSourceCard(s) })));
|
||||
const bandExtractItems = csAudioBandExtract.applySortOrder(bandExtractSources.map(s => ({ key: s.id, html: renderAudioSourceCard(s) })));
|
||||
const audioTemplateItems = csAudioTemplates.applySortOrder(_cachedAudioTemplates.map(t => ({ key: t.id, html: renderAudioTemplateCard(t) })));
|
||||
const staticItems = csStaticStreams.applySortOrder(staticImageStreams.map(s => ({ key: s.id, html: renderStreamCard(s) })));
|
||||
const videoItems = csVideoStreams.applySortOrder(videoStreams.map(s => ({ key: s.id, html: renderStreamCard(s) })));
|
||||
@@ -701,6 +725,7 @@ function renderPictureSourcesList(streams: any) {
|
||||
csGradients.reconcile(gradientItems);
|
||||
csAudioMulti.reconcile(multiItems);
|
||||
csAudioMono.reconcile(monoItems);
|
||||
csAudioBandExtract.reconcile(bandExtractItems);
|
||||
csAudioTemplates.reconcile(audioTemplateItems);
|
||||
csStaticStreams.reconcile(staticItems);
|
||||
csVideoStreams.reconcile(videoItems);
|
||||
@@ -718,7 +743,7 @@ function renderPictureSourcesList(streams: any) {
|
||||
else if (tab.key === 'css_processing') panelContent = csCSPTemplates.render(csptItems);
|
||||
else if (tab.key === 'color_strip') panelContent = csColorStrips.render(colorStripItems);
|
||||
else if (tab.key === 'gradients') panelContent = csGradients.render(gradientItems);
|
||||
else if (tab.key === 'audio') panelContent = csAudioMulti.render(multiItems) + csAudioMono.render(monoItems);
|
||||
else if (tab.key === 'audio') panelContent = csAudioMulti.render(multiItems) + csAudioMono.render(monoItems) + csAudioBandExtract.render(bandExtractItems);
|
||||
else if (tab.key === 'audio_templates') panelContent = csAudioTemplates.render(audioTemplateItems);
|
||||
else if (tab.key === 'value') panelContent = csValueSources.render(valueItems);
|
||||
else if (tab.key === 'sync') panelContent = csSyncClocks.render(syncClockItems);
|
||||
@@ -729,7 +754,7 @@ function renderPictureSourcesList(streams: any) {
|
||||
}).join('');
|
||||
|
||||
container.innerHTML = panels;
|
||||
CardSection.bindAll([csRawStreams, csRawTemplates, csProcStreams, csProcTemplates, csCSPTemplates, csColorStrips, csGradients, csAudioMulti, csAudioMono, csAudioTemplates, csStaticStreams, csVideoStreams, csValueSources, csSyncClocks, csWeatherSources]);
|
||||
CardSection.bindAll([csRawStreams, csRawTemplates, csProcStreams, csProcTemplates, csCSPTemplates, csColorStrips, csGradients, csAudioMulti, csAudioMono, csAudioBandExtract, csAudioTemplates, csStaticStreams, csVideoStreams, csValueSources, csSyncClocks, csWeatherSources]);
|
||||
|
||||
// Event delegation for card actions (replaces inline onclick handlers)
|
||||
initSyncClockDelegation(container);
|
||||
@@ -747,7 +772,7 @@ function renderPictureSourcesList(streams: any) {
|
||||
'css-proc-templates': 'css_processing',
|
||||
'color-strips': 'color_strip',
|
||||
'gradients': 'gradients',
|
||||
'audio-multi': 'audio', 'audio-mono': 'audio',
|
||||
'audio-multi': 'audio', 'audio-mono': 'audio', 'audio-band-extract': 'audio',
|
||||
'audio-templates': 'audio_templates',
|
||||
'value-sources': 'value',
|
||||
'sync-clocks': 'sync',
|
||||
|
||||
@@ -835,7 +835,7 @@ function _populateAudioSourceDropdown(selectedId: any) {
|
||||
const select = document.getElementById('value-source-audio-source') as HTMLSelectElement;
|
||||
if (!select) return;
|
||||
select.innerHTML = _cachedAudioSources.map((s: any) => {
|
||||
const badge = s.source_type === 'multichannel' ? ' [multichannel]' : ' [mono]';
|
||||
const badge = s.source_type === 'multichannel' ? ' [multichannel]' : s.source_type === 'band_extract' ? ' [band]' : ' [mono]';
|
||||
return `<option value="${s.id}"${s.id === selectedId ? ' selected' : ''}>${escapeHtml(s.name)}${badge}</option>`;
|
||||
}).join('');
|
||||
|
||||
|
||||
@@ -300,7 +300,7 @@ export interface ValueSource {
|
||||
export interface AudioSource {
|
||||
id: string;
|
||||
name: string;
|
||||
source_type: 'multichannel' | 'mono';
|
||||
source_type: 'multichannel' | 'mono' | 'band_extract';
|
||||
description?: string;
|
||||
tags: string[];
|
||||
created_at: string;
|
||||
@@ -314,6 +314,11 @@ export interface AudioSource {
|
||||
// Mono
|
||||
audio_source_id?: string;
|
||||
channel?: string;
|
||||
|
||||
// Band Extract
|
||||
band?: string;
|
||||
freq_low?: number;
|
||||
freq_high?: number;
|
||||
}
|
||||
|
||||
// ── Picture Source ─────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user