chore(ui,stage-ui,stage-pages): now support to display visualizer in both linear and log

This commit is contained in:
Neko Ayaka
2025-12-20 21:07:19 +08:00
parent eb81a94847
commit bd7d5d67b8
3 changed files with 32 additions and 11 deletions
@@ -12,7 +12,7 @@ import {
updateBeatSyncParameters,
} from '@proj-airi/stage-shared/beat-sync'
import { Alert, AudioSpectrumVisualizer } from '@proj-airi/stage-ui/components'
import { Button, FieldCheckbox, FieldRange } from '@proj-airi/ui'
import { Button, FieldCheckbox, FieldRange, SelectTab } from '@proj-airi/ui'
import { createTimeline } from 'animejs'
import { nanoid } from 'nanoid'
import { computed, onMounted, onUnmounted, ref, toRaw, watch } from 'vue'
@@ -22,6 +22,11 @@ const state = ref<BeatSyncDetectorState>()
const frequencies = ref<number[]>([])
const totalFreqHistory = ref<number[]>([])
const isUpdatingFrequencies = ref(false)
const spectrumScale = ref<'linear' | 'logarithm'>('logarithm')
const spectrumScaleOptions = [
{ label: 'Linear', value: 'linear' as const, icon: 'i-solar:chart-2-bold-duotone' },
{ label: 'Logarithm', value: 'logarithm' as const, icon: 'i-solar:chart-bold-duotone' },
]
const { t } = useI18n()
@@ -304,12 +309,21 @@ onUnmounted(() => {
</div>
</h2>
<div bg="neutral/10" h-64px max-w-400px w-full overflow-hidden rounded-2xl>
<AudioSpectrumVisualizer
v-if="isUpdatingFrequencies"
:frequencies="frequencies"
h-full w-full gap-0
bars-class="bg-primary-400/50 dark:bg-primary-500/50 rounded-none"
<div class="max-w-400px w-full flex flex-col gap-3">
<div bg="neutral/10" h-64px w-full overflow-hidden rounded-2xl>
<AudioSpectrumVisualizer
v-if="isUpdatingFrequencies"
:frequencies="frequencies"
:scale="spectrumScale"
h-full w-full gap-0
bars-class="bg-primary-400/50 dark:bg-primary-500/50 rounded-none"
/>
</div>
<SelectTab
v-model="spectrumScale"
size="sm"
:options="spectrumScaleOptions"
/>
</div>
@@ -1,8 +1,11 @@
<script setup lang="ts">
const props = defineProps<{
const props = withDefaults(defineProps<{
frequencies: number[]
barsClass?: string
}>()
scale?: 'linear' | 'logarithm'
}>(), {
scale: 'logarithm',
})
const AMPLIFICATION = 5
@@ -31,7 +34,10 @@ function toLogScale(value: number) {
function getBarHeight(frequency: number, index: number) {
const reductionFactor = getReductionFactor(index, props.frequencies.length)
const { minHeight, maxHeight } = getHeightBounds(props.frequencies.length)
const scaled = toLogScale(frequency * reductionFactor) * 100
const normalized = props.scale === 'linear'
? Math.min(1, Math.max(0, frequency * AMPLIFICATION * reductionFactor))
: toLogScale(frequency * reductionFactor)
const scaled = normalized * 100
return Math.min(maxHeight, Math.max(minHeight, scaled))
}
</script>
@@ -36,7 +36,7 @@ const rootStyle = computed(() => ({
'--select-tab-count': String(itemCount.value),
'--select-tab-active-index': String(Math.max(activeIndex.value, 0)),
'--select-tab-padding': props.size === 'sm' ? '0px' : '0px',
'--select-tab-gap': '0.5rem',
'--select-tab-gap': '0.25rem',
'--select-tab-indicator-opacity': activeIndex.value === -1 ? '0' : '1',
}))
</script>
@@ -49,6 +49,7 @@ const rootStyle = computed(() => ({
:class="[
'select-tab',
'relative', 'flex', 'w-full', 'items-stretch', 'rounded-xl',
'overflow-hidden',
'bg-white-400/6', 'dark:bg-neutral-900/70',
'transition-[border-color,box-shadow,opacity]', 'duration-200', 'ease-out', 'border-2',
'border-neutral-100', 'dark:border-neutral-800',