diff --git a/plugins/audio-visualizer-luna/src/Settings.tsx b/plugins/audio-visualizer-luna/src/Settings.tsx index d7e8297..1c27e9b 100644 --- a/plugins/audio-visualizer-luna/src/Settings.tsx +++ b/plugins/audio-visualizer-luna/src/Settings.tsx @@ -54,6 +54,7 @@ export const settings = await ReactiveStore.getPluginStorage( scrollingOscilloscope: false, groupedSlots: false, transparentContainers: false, + idleMode: 1, miniSlots: [] as string[], customColors: [] as string[], }, @@ -92,6 +93,7 @@ export const Settings = () => { const [transparentContainers, setTransparentContainers] = React.useState( settings.transparentContainers, ); + const [idleMode, setIdleMode] = React.useState(settings.idleMode); const [showColorPicker, setShowColorPicker] = React.useState(false); const [isColorAnimIn, setIsColorAnimIn] = React.useState(false); @@ -313,6 +315,21 @@ export const Settings = () => { }} /> + ) => { + const v = Number(e.target.value); + setIdleMode(v); + settings.idleMode = v; + }} + > + Enabled + Disabled & Hide + Disabled & Static + + {/* Color picker modal */} {shouldRenderColor && ( <> diff --git a/plugins/audio-visualizer-luna/src/index.ts b/plugins/audio-visualizer-luna/src/index.ts index 0a80177..a67282a 100644 --- a/plugins/audio-visualizer-luna/src/index.ts +++ b/plugins/audio-visualizer-luna/src/index.ts @@ -46,6 +46,7 @@ interface SlotGroup { const groups = new Map(); let navArrowsEl: HTMLElement | null = null; +let idleHidden = false; const getSlot = (key: SlotKey): VisualizerType => (settings as unknown as Record)[key] ?? "none"; @@ -134,8 +135,9 @@ const syncGroupHeights = (group: SlotGroup): void => { const updateGroupVisibility = (group: SlotGroup): void => { const activeCount = group.slots.filter(s => s.currentType !== "none").length; const allNone = activeCount === 0; - group.groupContainer.style.display = allNone ? "none" : "flex"; - if (!allNone) syncGroupHeights(group); + const hidden = allNone || idleHidden; + group.groupContainer.style.display = hidden ? "none" : "flex"; + if (!hidden) syncGroupHeights(group); group.groupContainer.classList.toggle( "av-grouped", @@ -143,7 +145,7 @@ const updateGroupVisibility = (group: SlotGroup): void => { ); if (group === groups.get("topNav-left") && navArrowsEl) { - navArrowsEl.style.marginRight = allNone ? "" : "0"; + navArrowsEl.style.marginRight = hidden ? "" : "0"; } }; @@ -398,6 +400,19 @@ const generateIdleData = (): AudioData => { }; }; +// Static idle data — flat, silent, no movement +const STATIC_IDLE_DATA: AudioData = { + byteFrequency: new Uint8Array(IDLE_SIZE), + byteTimeDomain: new Uint8Array(IDLE_SIZE).fill(128), + floatFrequency: new Float32Array(IDLE_SIZE).fill(-100), + floatTimeDomain: new Float32Array(IDLE_SIZE), + leftTimeDomain: new Float32Array(IDLE_SIZE), + rightTimeDomain: new Float32Array(IDLE_SIZE), + sampleRate: 44100, + fftSize: IDLE_SIZE * 2, + binCount: IDLE_SIZE, +}; + // Animation Loop let animationId: number | null = null; @@ -478,12 +493,24 @@ const animate = (): void => { scheduleRetry(); } - const renderData = hasSignal ? data : generateIdleData(); + // idleMode: 0 = animated, 1 = hide when idle, 2 = static when idle + const idleMode = settings.idleMode ?? 0; + const newIdleHidden = !hasSignal && idleMode === 1; + if (newIdleHidden !== idleHidden) { + idleHidden = newIdleHidden; + for (const group of groups.values()) updateGroupVisibility(group); + } - for (const group of groups.values()) { - for (const slot of group.slots) { - if (!slot.canvas || slot.currentType === "none" || !slot.visualizer) continue; - slot.visualizer.render(renderData, settings.barColor); + if (!idleHidden) { + let renderData: AudioData; + if (hasSignal) renderData = data as AudioData; + else if (idleMode === 2) renderData = STATIC_IDLE_DATA; + else renderData = generateIdleData(); + for (const group of groups.values()) { + for (const slot of group.slots) { + if (!slot.canvas || slot.currentType === "none" || !slot.visualizer) continue; + slot.visualizer.render(renderData, settings.barColor); + } } }