audio-reactive
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAudio Reactive Visuals
音频响应式视觉效果
Connecting audio analysis to visual parameters.
将音频分析与视觉参数关联。
Quick Start
快速开始
javascript
import * as Tone from 'tone';
const analyser = new Tone.Analyser('fft', 256);
const player = new Tone.Player('/audio/music.mp3');
player.connect(analyser);
player.toDestination();
function animate() {
const fft = analyser.getValue();
const bass = (fft[2] + 100) / 100; // Normalize to 0-1
// Apply to visual
element.style.transform = `scale(${1 + bass * 0.2})`;
requestAnimationFrame(animate);
}javascript
import * as Tone from 'tone';
const analyser = new Tone.Analyser('fft', 256);
const player = new Tone.Player('/audio/music.mp3');
player.connect(analyser);
player.toDestination();
function animate() {
const fft = analyser.getValue();
const bass = (fft[2] + 100) / 100; // Normalize to 0-1
// Apply to visual
element.style.transform = `scale(${1 + bass * 0.2})`;
requestAnimationFrame(animate);
}Core Patterns
核心模式
Audio-to-Visual Mapping
音频-视觉映射
javascript
class AudioReactiveMapper {
constructor(analyser) {
this.analyser = analyser;
this.smoothers = {
bass: new Smoother(0.85),
mid: new Smoother(0.9),
high: new Smoother(0.92)
};
}
// Get values optimized for different visual properties
getValues() {
const fft = this.analyser.getValue();
const len = fft.length;
// Extract and normalize bands
const rawBass = this.avgRange(fft, 0, len * 0.1);
const rawMid = this.avgRange(fft, len * 0.1, len * 0.5);
const rawHigh = this.avgRange(fft, len * 0.5, len);
return {
bass: this.smoothers.bass.update(this.normalize(rawBass)),
mid: this.smoothers.mid.update(this.normalize(rawMid)),
high: this.smoothers.high.update(this.normalize(rawHigh))
};
}
avgRange(data, start, end) {
let sum = 0;
for (let i = Math.floor(start); i < Math.floor(end); i++) {
sum += data[i];
}
return sum / (end - start);
}
normalize(db) {
return Math.max(0, Math.min(1, (db + 100) / 100));
}
}
class Smoother {
constructor(factor = 0.9) {
this.factor = factor;
this.value = 0;
}
update(input) {
this.value = this.factor * this.value + (1 - this.factor) * input;
return this.value;
}
}javascript
class AudioReactiveMapper {
constructor(analyser) {
this.analyser = analyser;
this.smoothers = {
bass: new Smoother(0.85),
mid: new Smoother(0.9),
high: new Smoother(0.92)
};
}
// Get values optimized for different visual properties
getValues() {
const fft = this.analyser.getValue();
const len = fft.length;
// Extract and normalize bands
const rawBass = this.avgRange(fft, 0, len * 0.1);
const rawMid = this.avgRange(fft, len * 0.1, len * 0.5);
const rawHigh = this.avgRange(fft, len * 0.5, len);
return {
bass: this.smoothers.bass.update(this.normalize(rawBass)),
mid: this.smoothers.mid.update(this.normalize(rawMid)),
high: this.smoothers.high.update(this.normalize(rawHigh))
};
}
avgRange(data, start, end) {
let sum = 0;
for (let i = Math.floor(start); i < Math.floor(end); i++) {
sum += data[i];
}
return sum / (end - start);
}
normalize(db) {
return Math.max(0, Math.min(1, (db + 100) / 100));
}
}
class Smoother {
constructor(factor = 0.9) {
this.factor = factor;
this.value = 0;
}
update(input) {
this.value = this.factor * this.value + (1 - this.factor) * input;
return this.value;
}
}Visual Property Mappings
视觉属性映射
Scale/Size
缩放/尺寸
javascript
// Pulse on bass
function updateScale(element, bass) {
const scale = 1 + bass * 0.3; // 1.0 to 1.3
element.style.transform = `scale(${scale})`;
}
// Three.js mesh
function updateMeshScale(mesh, bass) {
const scale = 1 + bass * 0.5;
mesh.scale.setScalar(scale);
}javascript
// Pulse on bass
function updateScale(element, bass) {
const scale = 1 + bass * 0.3; // 1.0 to 1.3
element.style.transform = `scale(${scale})`;
}
// Three.js mesh
function updateMeshScale(mesh, bass) {
const scale = 1 + bass * 0.5;
mesh.scale.setScalar(scale);
}Position/Movement
位置/移动
javascript
// Vibration based on high frequencies
function updatePosition(element, high) {
const shake = high * 5; // Pixels
const x = (Math.random() - 0.5) * shake;
const y = (Math.random() - 0.5) * shake;
element.style.transform = `translate(${x}px, ${y}px)`;
}
// Smooth wave motion
function updateWavePosition(mesh, mid, time) {
mesh.position.y = Math.sin(time * 2) * mid * 2;
}javascript
// Vibration based on high frequencies
function updatePosition(element, high) {
const shake = high * 5; // Pixels
const x = (Math.random() - 0.5) * shake;
const y = (Math.random() - 0.5) * shake;
element.style.transform = `translate(${x}px, ${y}px)`;
}
// Smooth wave motion
function updateWavePosition(mesh, mid, time) {
mesh.position.y = Math.sin(time * 2) * mid * 2;
}Rotation
旋转
javascript
// Continuous rotation speed based on energy
class SpinController {
constructor() {
this.rotation = 0;
}
update(energy) {
const speed = 0.01 + energy * 0.05;
this.rotation += speed;
return this.rotation;
}
}javascript
// Continuous rotation speed based on energy
class SpinController {
constructor() {
this.rotation = 0;
}
update(energy) {
const speed = 0.01 + energy * 0.05;
this.rotation += speed;
return this.rotation;
}
}Color/Brightness
颜色/亮度
javascript
// Brightness based on volume
function updateBrightness(element, volume) {
const brightness = 0.5 + volume * 0.5; // 50% to 100%
element.style.filter = `brightness(${brightness})`;
}
// Color shift based on frequency balance
function getReactiveColor(bass, mid, high) {
// More bass = more red/magenta, more high = more cyan
const r = Math.floor(bass * 255);
const g = Math.floor(mid * 100);
const b = Math.floor(high * 255);
return `rgb(${r}, ${g}, ${b})`;
}
// Three.js emissive intensity
function updateGlow(material, bass) {
material.emissiveIntensity = 1 + bass * 3;
}javascript
// Brightness based on volume
function updateBrightness(element, volume) {
const brightness = 0.5 + volume * 0.5; // 50% to 100%
element.style.filter = `brightness(${brightness})`;
}
// Color shift based on frequency balance
function getReactiveColor(bass, mid, high) {
// More bass = more red/magenta, more high = more cyan
const r = Math.floor(bass * 255);
const g = Math.floor(mid * 100);
const b = Math.floor(high * 255);
return `rgb(${r}, ${g}, ${b})`;
}
// Three.js emissive intensity
function updateGlow(material, bass) {
material.emissiveIntensity = 1 + bass * 3;
}Opacity/Visibility
透明度/可见性
javascript
// Fade based on volume
function updateOpacity(element, volume) {
element.style.opacity = 0.3 + volume * 0.7;
}javascript
// Fade based on volume
function updateOpacity(element, volume) {
element.style.opacity = 0.3 + volume * 0.7;
}Beat Response
节拍响应
Simple Beat Flash
简单节拍闪烁
javascript
class BeatFlash {
constructor(decayRate = 0.95) {
this.intensity = 0;
this.decayRate = decayRate;
}
trigger() {
this.intensity = 1;
}
update() {
this.intensity *= this.decayRate;
return this.intensity;
}
}
// Usage
const flash = new BeatFlash();
function animate() {
if (beatDetector.detect(analyser)) {
flash.trigger();
}
const intensity = flash.update();
element.style.backgroundColor = `rgba(0, 245, 255, ${intensity})`;
requestAnimationFrame(animate);
}javascript
class BeatFlash {
constructor(decayRate = 0.95) {
this.intensity = 0;
this.decayRate = decayRate;
}
trigger() {
this.intensity = 1;
}
update() {
this.intensity *= this.decayRate;
return this.intensity;
}
}
// Usage
const flash = new BeatFlash();
function animate() {
if (beatDetector.detect(analyser)) {
flash.trigger();
}
const intensity = flash.update();
element.style.backgroundColor = `rgba(0, 245, 255, ${intensity})`;
requestAnimationFrame(animate);
}Beat Scale Pop
节拍缩放弹出
javascript
class BeatPop {
constructor(popScale = 1.3, decayRate = 0.9) {
this.scale = 1;
this.targetScale = 1;
this.popScale = popScale;
this.decayRate = decayRate;
}
trigger() {
this.targetScale = this.popScale;
}
update() {
// Lerp toward target, then decay target back to 1
this.scale += (this.targetScale - this.scale) * 0.3;
this.targetScale = 1 + (this.targetScale - 1) * this.decayRate;
return this.scale;
}
}javascript
class BeatPop {
constructor(popScale = 1.3, decayRate = 0.9) {
this.scale = 1;
this.targetScale = 1;
this.popScale = popScale;
this.decayRate = decayRate;
}
trigger() {
this.targetScale = this.popScale;
}
update() {
// Lerp toward target, then decay target back to 1
this.scale += (this.targetScale - this.scale) * 0.3;
this.targetScale = 1 + (this.targetScale - 1) * this.decayRate;
return this.scale;
}
}Beat Spawn
节拍生成元素
javascript
class BeatSpawner {
constructor(onSpawn) {
this.onSpawn = onSpawn;
this.cooldown = 0;
this.minInterval = 200; // ms
}
check(isBeat) {
if (isBeat && this.cooldown <= 0) {
this.onSpawn();
this.cooldown = this.minInterval;
}
this.cooldown -= 16; // Assuming 60fps
}
}
// Usage: spawn particles on beat
const spawner = new BeatSpawner(() => {
particleSystem.emit(10);
});javascript
class BeatSpawner {
constructor(onSpawn) {
this.onSpawn = onSpawn;
this.cooldown = 0;
this.minInterval = 200; // ms
}
check(isBeat) {
if (isBeat && this.cooldown <= 0) {
this.onSpawn();
this.cooldown = this.minInterval;
}
this.cooldown -= 16; // Assuming 60fps
}
}
// Usage: spawn particles on beat
const spawner = new BeatSpawner(() => {
particleSystem.emit(10);
});React Three Fiber Integration
React Three Fiber 集成
Audio Reactive Component
音频响应式组件
tsx
import { useRef } from 'react';
import { useFrame } from '@react-three/fiber';
function AudioReactiveSphere({ audioData }) {
const meshRef = useRef();
const materialRef = useRef();
useFrame(() => {
if (!audioData || !meshRef.current) return;
const { bass, mid, high, isBeat } = audioData;
// Scale on bass
const scale = 1 + bass * 0.5;
meshRef.current.scale.setScalar(scale);
// Rotation speed on mid
meshRef.current.rotation.y += 0.01 + mid * 0.03;
// Emissive on high
if (materialRef.current) {
materialRef.current.emissiveIntensity = 1 + high * 2;
}
});
return (
<mesh ref={meshRef}>
<sphereGeometry args={[1, 32, 32]} />
<meshStandardMaterial
ref={materialRef}
color="#111"
emissive="#00F5FF"
emissiveIntensity={1}
/>
</mesh>
);
}tsx
import { useRef } from 'react';
import { useFrame } from '@react-three/fiber';
function AudioReactiveSphere({ audioData }) {
const meshRef = useRef();
const materialRef = useRef();
useFrame(() => {
if (!audioData || !meshRef.current) return;
const { bass, mid, high, isBeat } = audioData;
// Scale on bass
const scale = 1 + bass * 0.5;
meshRef.current.scale.setScalar(scale);
// Rotation speed on mid
meshRef.current.rotation.y += 0.01 + mid * 0.03;
// Emissive on high
if (materialRef.current) {
materialRef.current.emissiveIntensity = 1 + high * 2;
}
});
return (
<mesh ref={meshRef}>
<sphereGeometry args={[1, 32, 32]} />
<meshStandardMaterial
ref={materialRef}
color="#111"
emissive="#00F5FF"
emissiveIntensity={1}
/>
</mesh>
);
}Audio Context Hook
音频上下文 Hook
tsx
function useAudioAnalysis(playerRef) {
const [data, setData] = useState(null);
const analyserRef = useRef(null);
const mapperRef = useRef(null);
useEffect(() => {
analyserRef.current = new Tone.Analyser('fft', 256);
mapperRef.current = new AudioReactiveMapper(analyserRef.current);
if (playerRef.current) {
playerRef.current.connect(analyserRef.current);
}
return () => analyserRef.current?.dispose();
}, []);
useFrame(() => {
if (mapperRef.current) {
setData(mapperRef.current.getValues());
}
});
return data;
}tsx
function useAudioAnalysis(playerRef) {
const [data, setData] = useState(null);
const analyserRef = useRef(null);
const mapperRef = useRef(null);
useEffect(() => {
analyserRef.current = new Tone.Analyser('fft', 256);
mapperRef.current = new AudioReactiveMapper(analyserRef.current);
if (playerRef.current) {
playerRef.current.connect(analyserRef.current);
}
return () => analyserRef.current?.dispose();
}, []);
useFrame(() => {
if (mapperRef.current) {
setData(mapperRef.current.getValues());
}
});
return data;
}Post-Processing Integration
后期处理集成
Audio-Reactive Bloom
音频响应式 Bloom 效果
tsx
function AudioReactiveBloom({ audioData }) {
const bloomRef = useRef();
useFrame(() => {
if (bloomRef.current && audioData) {
// Bass drives bloom intensity
bloomRef.current.intensity = 1 + audioData.bass * 2;
// High frequencies lower threshold (more bloom)
bloomRef.current.luminanceThreshold = 0.3 - audioData.high * 0.2;
}
});
return (
<EffectComposer>
<Bloom ref={bloomRef} luminanceThreshold={0.2} intensity={1} />
</EffectComposer>
);
}tsx
function AudioReactiveBloom({ audioData }) {
const bloomRef = useRef();
useFrame(() => {
if (bloomRef.current && audioData) {
// Bass drives bloom intensity
bloomRef.current.intensity = 1 + audioData.bass * 2;
// High frequencies lower threshold (more bloom)
bloomRef.current.luminanceThreshold = 0.3 - audioData.high * 0.2;
}
});
return (
<EffectComposer>
<Bloom ref={bloomRef} luminanceThreshold={0.2} intensity={1} />
</EffectComposer>
);
}Audio-Reactive Chromatic Aberration
音频响应式色差效果
tsx
function AudioReactiveChroma({ audioData }) {
const chromaRef = useRef();
useFrame(() => {
if (chromaRef.current && audioData) {
// High frequencies drive aberration
const offset = 0.001 + audioData.high * 0.005;
chromaRef.current.offset.set(offset, offset * 0.5);
}
});
return <ChromaticAberration ref={chromaRef} offset={[0.002, 0.001]} />;
}tsx
function AudioReactiveChroma({ audioData }) {
const chromaRef = useRef();
useFrame(() => {
if (chromaRef.current && audioData) {
// High frequencies drive aberration
const offset = 0.001 + audioData.high * 0.005;
chromaRef.current.offset.set(offset, offset * 0.5);
}
});
return <ChromaticAberration ref={chromaRef} offset={[0.002, 0.001]} />;
}GSAP Integration
GSAP 集成
Audio-Driven Timeline
音频驱动时间线
javascript
class AudioTimeline {
constructor() {
this.timeline = gsap.timeline({ paused: true });
this.progress = 0;
}
build(duration) {
this.timeline
.to('.element', { scale: 1.5 }, 0)
.to('.element', { rotation: 360 }, duration * 0.5)
.to('.element', { scale: 1 }, duration);
}
updateWithAudio(bass) {
// Map bass to timeline progress
const targetProgress = this.progress + bass * 0.02;
this.progress = Math.min(1, targetProgress);
this.timeline.progress(this.progress);
}
}javascript
class AudioTimeline {
constructor() {
this.timeline = gsap.timeline({ paused: true });
this.progress = 0;
}
build(duration) {
this.timeline
.to('.element', { scale: 1.5 }, 0)
.to('.element', { rotation: 360 }, duration * 0.5)
.to('.element', { scale: 1 }, duration);
}
updateWithAudio(bass) {
// Map bass to timeline progress
const targetProgress = this.progress + bass * 0.02;
this.progress = Math.min(1, targetProgress);
this.timeline.progress(this.progress);
}
}Beat-Triggered GSAP
节拍触发的 GSAP 动画
javascript
function createBeatAnimation(element) {
return gsap.timeline({ paused: true })
.to(element, {
scale: 1.2,
duration: 0.1,
ease: 'power2.out'
})
.to(element, {
scale: 1,
duration: 0.3,
ease: 'power2.out'
});
}
// On beat
if (isBeat) {
beatAnimation.restart();
}javascript
function createBeatAnimation(element) {
return gsap.timeline({ paused: true })
.to(element, {
scale: 1.2,
duration: 0.1,
ease: 'power2.out'
})
.to(element, {
scale: 1,
duration: 0.3,
ease: 'power2.out'
});
}
// On beat
if (isBeat) {
beatAnimation.restart();
}Complete System Example
完整系统示例
javascript
class AudioVisualSystem {
constructor(player, visualElements) {
this.player = player;
this.elements = visualElements;
// Analysis
this.analyser = new Tone.Analyser('fft', 256);
this.mapper = new AudioReactiveMapper(this.analyser);
this.beatDetector = new BeatDetector();
// Reactive controllers
this.beatFlash = new BeatFlash();
this.beatPop = new BeatPop();
// Connect
this.player.connect(this.analyser);
this.player.toDestination();
}
update() {
const values = this.mapper.getValues();
const isBeat = this.beatDetector.detect(this.analyser);
if (isBeat) {
this.beatFlash.trigger();
this.beatPop.trigger();
}
const flashIntensity = this.beatFlash.update();
const popScale = this.beatPop.update();
// Apply to visuals
this.elements.background.style.opacity = 0.5 + values.bass * 0.5;
this.elements.circle.style.transform = `scale(${popScale})`;
this.elements.glow.style.boxShadow =
`0 0 ${flashIntensity * 50}px rgba(0, 245, 255, ${flashIntensity})`;
}
start() {
const animate = () => {
this.update();
requestAnimationFrame(animate);
};
animate();
}
}javascript
class AudioVisualSystem {
constructor(player, visualElements) {
this.player = player;
this.elements = visualElements;
// Analysis
this.analyser = new Tone.Analyser('fft', 256);
this.mapper = new AudioReactiveMapper(this.analyser);
this.beatDetector = new BeatDetector();
// Reactive controllers
this.beatFlash = new BeatFlash();
this.beatPop = new BeatPop();
// Connect
this.player.connect(this.analyser);
this.player.toDestination();
}
update() {
const values = this.mapper.getValues();
const isBeat = this.beatDetector.detect(this.analyser);
if (isBeat) {
this.beatFlash.trigger();
this.beatPop.trigger();
}
const flashIntensity = this.beatFlash.update();
const popScale = this.beatPop.update();
// Apply to visuals
this.elements.background.style.opacity = 0.5 + values.bass * 0.5;
this.elements.circle.style.transform = `scale(${popScale})`;
this.elements.glow.style.boxShadow =
`0 0 ${flashIntensity * 50}px rgba(0, 245, 255, ${flashIntensity})`;
}
start() {
const animate = () => {
this.update();
requestAnimationFrame(animate);
};
animate();
}
}Temporal Collapse Integration
时间折叠集成
javascript
const TEMPORAL_MAPPINGS = {
// Digit glow intensity
digitGlow: (bass, mid) => 1 + bass * 2 + mid,
// Particle emission rate
particleRate: (bass, isBeat) => isBeat ? 50 : bass * 20,
// Background pulse
backgroundBrightness: (bass) => 1 + bass * 0.3,
// Time dilation visual (warp effect)
warpIntensity: (high) => high * 0.5,
// Vignette darkness (close in on beat)
vignetteDarkness: (isBeat, current) => isBeat ? 0.7 : current * 0.95,
// Chromatic aberration (glitch on high)
chromaticOffset: (high) => 0.001 + high * 0.004
};
function applyTemporalMappings(audioData, visuals) {
const { bass, mid, high, isBeat } = audioData;
visuals.digitMaterial.emissiveIntensity =
TEMPORAL_MAPPINGS.digitGlow(bass, mid);
visuals.particles.emissionRate =
TEMPORAL_MAPPINGS.particleRate(bass, isBeat);
visuals.background.brightness =
TEMPORAL_MAPPINGS.backgroundBrightness(bass);
visuals.bloom.intensity =
1.5 + bass * 1.5;
visuals.chromatic.offset =
TEMPORAL_MAPPINGS.chromaticOffset(high);
}javascript
const TEMPORAL_MAPPINGS = {
// Digit glow intensity
digitGlow: (bass, mid) => 1 + bass * 2 + mid,
// Particle emission rate
particleRate: (bass, isBeat) => isBeat ? 50 : bass * 20,
// Background pulse
backgroundBrightness: (bass) => 1 + bass * 0.3,
// Time dilation visual (warp effect)
warpIntensity: (high) => high * 0.5,
// Vignette darkness (close in on beat)
vignetteDarkness: (isBeat, current) => isBeat ? 0.7 : current * 0.95,
// Chromatic aberration (glitch on high)
chromaticOffset: (high) => 0.001 + high * 0.004
};
function applyTemporalMappings(audioData, visuals) {
const { bass, mid, high, isBeat } = audioData;
visuals.digitMaterial.emissiveIntensity =
TEMPORAL_MAPPINGS.digitGlow(bass, mid);
visuals.particles.emissionRate =
TEMPORAL_MAPPINGS.particleRate(bass, isBeat);
visuals.background.brightness =
TEMPORAL_MAPPINGS.backgroundBrightness(bass);
visuals.bloom.intensity =
1.5 + bass * 1.5;
visuals.chromatic.offset =
TEMPORAL_MAPPINGS.chromaticOffset(high);
}Performance Tips
性能优化技巧
javascript
// 1. Update at lower frequency if possible
let frameCount = 0;
function animate() {
if (frameCount % 2 === 0) {
updateAudioData();
}
applyVisuals();
frameCount++;
}
// 2. Use smoothing to hide skipped frames
const smoother = new Smoother(0.95); // Higher = more smoothing
// 3. Batch DOM updates
function applyAllStyles(elements, values) {
// Single reflow
requestAnimationFrame(() => {
elements.forEach((el, i) => {
el.style.transform = `scale(${values[i]})`;
});
});
}
// 4. Use CSS variables for multiple elements
document.documentElement.style.setProperty('--audio-bass', bass);
// CSS: transform: scale(calc(1 + var(--audio-bass) * 0.2));javascript
// 1. Update at lower frequency if possible
let frameCount = 0;
function animate() {
if (frameCount % 2 === 0) {
updateAudioData();
}
applyVisuals();
frameCount++;
}
// 2. Use smoothing to hide skipped frames
const smoother = new Smoother(0.95); // Higher = more smoothing
// 3. Batch DOM updates
function applyAllStyles(elements, values) {
// Single reflow
requestAnimationFrame(() => {
elements.forEach((el, i) => {
el.style.transform = `scale(${values[i]})`;
});
});
}
// 4. Use CSS variables for multiple elements
document.documentElement.style.setProperty('--audio-bass', bass);
// CSS: transform: scale(calc(1 + var(--audio-bass) * 0.2));Reference
参考
- See for audio loading and transport
audio-playback - See for FFT and beat detection
audio-analysis - See for audio domain routing
audio-router
- 请查看 了解音频加载和传输相关内容
audio-playback - 请查看 了解 FFT 和节拍检测相关内容
audio-analysis - 请查看 了解音频域路由相关内容
audio-router