Adapt fullscreen beat response

This commit is contained in:
lucast committed 2026-10-05 22:05:08 +02:00
1 parent 27c289483b
commit d07221f600
3 files changed
+41 -12

No files matched your search

@@ -5,6 +5,7 @@ import {
toOkLab,
type AlbumPalette,
} from "./palette"
import { shapeBeatPulse } from "~/utils/beat"
const props = defineProps<{ src: string; active: boolean }>()
const ANIMATION_SPEED = 0.3
@@ -159,7 +160,7 @@ function draw(now: number) {
(value, channel) =>
value * (1 - blend) + accent[channel] * blend,
) as [number, number, number]
const beat = beatEnergy.value
const beat = shapeBeatPulse(beatEnergy.value)
lab[0] = Math.min(1, lab[0] + beat * 0.01)
lab[1] *= 1 + beat * 0.02
lab[2] *= 1 + beat * 0.02
@@ -171,7 +172,7 @@ function draw(now: number) {
image.data[offset + 3] = 255
}
}
element.style.transform = `scale(${1.08 + beatEnergy.value * 0.003})`
element.style.transform = `scale(${1.08 + shapeBeatPulse(beatEnergy.value) * 0.003})`
context.putImageData(image, 0, 0)
if (props.active) frame = requestAnimationFrame(draw)
}
+29 -10
View File
@@ -1,4 +1,5 @@
import { Howl, Howler } from "howler"
import { shapeBeatPulse } from "~/utils/beat"
export default defineNuxtPlugin((nuxtApp) => {
const mediaBaseUrl = useRuntimeConfig().public.mediaBaseUrl
@@ -145,12 +146,11 @@ export default defineNuxtPlugin((nuxtApp) => {
const samples = new Uint8Array(analysisNode.frequencyBinCount)
let previousBass: number | null = null
let riseAverage = 0.01
let riseVariance = 0.0004
let pulse = 0
let lastBeatLogAt = 0
let lastLevelLogAt = 0
const attackNoiseFloor = 0.05
const strongThumpThreshold = 0.05
const thumpCooldown = 800
const sample = () => {
if (!analysisNode) return
analysisNode.getByteFrequencyData(samples)
@@ -164,17 +164,37 @@ export default defineNuxtPlugin((nuxtApp) => {
for (let bin = firstBin; bin <= lastBin; bin++) bass += samples[bin]
bass /= Math.max(1, lastBin - firstBin + 1) * 255
if (previousBass === null) previousBass = bass
const attack = Math.max(0, bass - previousBass - attackNoiseFloor)
const rise = Math.max(0, bass - previousBass)
previousBass = previousBass * 0.985 + bass * 0.015
const riseDeviation = Math.sqrt(riseVariance)
const strongThumpThreshold = Math.min(
0.1,
Math.max(0.045, riseAverage + riseDeviation * 2),
)
const attack = Math.max(0, rise - strongThumpThreshold)
const thumpCooldown = Math.min(
500,
Math.max(300, 300 + riseDeviation * 4000),
)
const now = performance.now()
const isStrongThump =
attack >= strongThumpThreshold && now - lastBeatLogAt > thumpCooldown
rise >= strongThumpThreshold && now - lastBeatLogAt > thumpCooldown
const riseDelta = rise - riseAverage
riseAverage += riseDelta * 0.01
riseVariance += (riseDelta * riseDelta - riseVariance) * 0.01
if (isStrongThump) {
const thumpStrength = Math.min(
0.38,
0.1 + (attack - strongThumpThreshold) * 2.5,
0.1 + attack * 2.5,
)
pulse = Math.max(pulse, thumpStrength)
if (DEBUG_BEAT_ANALYSIS) {
console.log(
"[fullscreen beat]",
`detected=${thumpStrength.toFixed(2)}`,
`visual=${shapeBeatPulse(thumpStrength).toFixed(2)}`,
)
}
} else pulse *= 0.94
beatEnergy.value = Math.min(1, pulse)
if (DEBUG_BEAT_ANALYSIS && now - lastLevelLogAt > 1000) {
@@ -183,13 +203,12 @@ export default defineNuxtPlugin((nuxtApp) => {
"[beat level]",
`bass=${bass.toFixed(3)}`,
`attack=${attack.toFixed(3)}`,
`gate=${strongThumpThreshold.toFixed(3)}`,
`cooldown=${Math.round(thumpCooldown)}`,
`pulse=${beatEnergy.value.toFixed(2)}`,
)
}
if (isStrongThump) {
lastBeatLogAt = now
console.log("[fullscreen beat]", beatEnergy.value.toFixed(2))
}
if (isStrongThump) lastBeatLogAt = now
analysisFrame = requestAnimationFrame(sample)
}
analysisFrame = requestAnimationFrame(sample)
+9
View File
@@ -0,0 +1,9 @@
export function shapeBeatPulse(value: number) {
const strength = Math.max(0, Math.min(0.4, value))
if (strength <= 0.2) return strength * 0.4
if (strength <= 0.35) {
const progress = (strength - 0.2) / 0.15
return 0.08 + (1 - (1 - progress) ** 3) * 0.42
}
return 0.5 + ((strength - 0.35) / 0.05) * 0.05
}