feat: add band_extract audio source type for frequency band filtering
Lint & Test / test (push) Failing after 29s
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:
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user