audio-reactive
ساخت انیمیشنها و ویژوالایزرهای واکنشگرا بر اساس ریتم و فرکانس صدا
این Skill با استفاده از Web Audio API و AnalyserNode، دادههای صوتی را به حرکت، مقیاس، موقعیت، شفافیت و ویژوالایزرهای فرکانسی تبدیل میکند. برای beat sync، نمایش spectrum، waveform و افکتهای صوتی در UI یا ویدیو الگوهای آماده ارائه میدهد.
توکنهای استاندارد در tokens/audio.tokens.json تنظیم حساسیت، باندهای bass، mid و high، smoothing، آستانهی تشخیص ضرب و دامنهی پاسخ را ساده میکنند. همچنین برای prefers-reduced-motion راهکارهایی مانند کاهش سرعت واکنش یا جایگزینی حرکت با تغییر opacity دارد.
چه چیزی میگیرید
- الگوی راهاندازی Audio Analyzer با Web Audio API و AnalyserNode
- تشخیص beat با threshold، decay و debounce قابل تنظیم
- ساخت frequency bars، waveform و spectrum visualizer روی Canvas
- نگاشت bass، mid و high به scale، position، rotation و opacity
- تنظیمات آماده برای حساسیت، smoothing و حالت reduced motion
برای چه کاری خوب است
- ساخت music visualizer برای وبسایت، اپلیکیشن یا صفحهی پخش موسیقی
- ایجاد UI یا motion graphics که با ضرب و فرکانس صدا واکنش نشان میدهد
- توسعهی تجربههای تعاملی با Canvas، GSAP، Framer Motion یا JavaScript
- ساخت انیمیشنهای ریتمیک برای ویدیو، لندینگپیج و پروژههای خلاقانه
برای چه کاری نیست
- انیمیشنهای کاملاً مستقل از صدا یا پروژههایی که به تحلیل real-time نیاز ندارند
- پردازش حرفهای، میکس یا مسترینگ فایلهای صوتی
- محیطهایی که دسترسی مرورگر به microphone یا AudioContext در آنها ممکن نیست
دربارهٔ این اسکیل
Audio-Reactive
Animations driven by audio analysis and beat detection.
Standards Reference
| Standard | Documentation |
|---|---|
| W3C DTCG | designtokens.org |
| Web Audio API | developer.mozilla.org/Web/API/Web_Audio_API |
| AnalyserNode | developer.mozilla.org/Web/API/AnalyserNode |
Quick Reference
Load from: tokens/audio.tokens.json
Frequency Bands
| Token | Range | Use |
|---|---|---|
frequency.bass | 60-250Hz | Kick drums |
frequency.mid | 500-2000Hz | Vocals |
frequency.high | 4000-8000Hz | Cymbals |
Sensitivity Levels
| Token | Value | Use |
|---|---|---|
sensitivity.subtle | 0.5 | Gentle response |
sensitivity.normal | 1 | Standard |
sensitivity.high | 2 | Dramatic |
Beat Detection
| Token | Value | Use |
|---|---|---|
beat.threshold.low | 0.3 | Detect subtle beats |
beat.threshold.normal | 0.5 | Standard detection |
beat.decay.fast | 0.9 | Quick response |
Selection Guide
What audio effect?
│
├── BEAT SYNC → Pulse on rhythm
│ ├── Kick drum → frequency.bass
│ ├── Snare → frequency.mid
│ └── Hi-hat → frequency.high
│
├── FREQUENCY VIZ → Spectrum display
│ ├── Simple bars → bar-count.low (16)
│ ├── Detailed → bar-count.medium (32)
│ └── High-res → bar-count.high (64)
│
├── RESPONSIVENESS
│ ├── Subtle → sensitivity.subtle + smoothing.heavy
│ ├── Normal → sensitivity.normal + smoothing.medium
│ └── Reactive → sensitivity.high + smoothing.light
│
└── ANIMATION TYPE
├── Scale → response.scale-min to scale-max
├── Opacity → response.opacity-min to opacity-max
└── Position → response.position-range
Patterns
Audio Analyzer Setup
JavaScript:
class AudioReactiveEngine {
constructor(audioElement) {
this.audioContext = new AudioContext();
this.analyser = this.audioContext.createAnalyser();
// Configure analyser
this.analyser.fftSize = 256;
this.analyser.smoothingTimeConstant = 0.75; // {motion.audio.smoothing.medium}
// Connect audio source
const source = this.audioContext.createMediaElementSource(audioElement);
source.connect(this.analyser);
this.analyser.connect(this.audioContext.destination);
// Data arrays
this.frequencyData = new Uint8Array(this.analyser.frequencyBinCount);
this.timeData = new Uint8Array(this.analyser.frequencyBinCount);
}
getFrequencyData() {
this.analyser.getByteFrequencyData(this.frequencyData);
return this.frequencyData;
}
getAverageFrequency(startHz, endHz) {
const nyquist = this.audioContext.sampleRate / 2;
const startIndex = Math.floor((startHz / nyquist) * this.frequencyData.length);
const endIndex = Math.floor((endHz / nyquist) * this.frequencyData.length);
let sum = 0;
for (let i = startIndex; i <= endIndex; i++) {
sum += this.frequencyData[i];
}
return sum / (endIndex - startIndex + 1) / 255;
}
getBass() {
return this.getAverageFrequency(60, 250); // {motion.audio.frequency.bass}
}
getMids() {
return this.getAverageFrequency(500, 2000); // {motion.audio.frequency.mid}
}
getHighs() {
return this.getAverageFrequency(4000, 8000); // {motion.audio.frequency.high}
}
}
Beat Detection
JavaScript:
class BeatDetector {
constructor(analyser) {
this.analyser = analyser;
this.threshold = 0.5; // {motion.audio.beat.threshold.normal}
this.decay = 0.95; // {motion.audio.beat.decay.medium}
this.lastBeat = 0;
this.energy = 0;
this.onBeat = null;
}
update() {
const data = this.analyser.getFrequencyData();
// Calculate current energy (bass-heavy)
let currentEnergy = 0;
const bassEnd = Math.floor(data.length * 0.1); // Focus on bass
for (let i = 0; i < bassEnd; i++) {
currentEnergy += data[i] / 255;
}
currentEnergy /= bassEnd;
// Beat detection
if (currentEnergy > this.energy * (1 + this.threshold)) {
const now = performance.now();
if (now - this.lastBeat > 100) { // Debounce
this.lastBeat = now;
this.onBeat?.(currentEnergy);
}
}
// Decay
this.energy = Math.max(currentEnergy, this.energy * this.decay);
requestAnimationFrame(() => this.update());
}
}
Scale on Beat
Framer Motion:
const BeatScale = ({ audioEngine }) => {
const [scale, setScale] = useState(1);
useEffect(() => {
const beatDetector = new BeatDetector(audioEngine.analyser);
beatDetector.onBeat = (energy) => {
// Scale from 1 to 1.5 based on energy
const newScale = 1 + (energy * 0.5); // {motion.audio.response.scale-max}
setScale(newScale);
// Decay back
setTimeout(() => setScale(1), 100);
};
beatDetector.update();
}, []);
return (
<motion.div
animate={{ scale }}
transition={{ type: 'spring', stiffness: 500, damping: 15 }}
/>
);
};
Frequency Bars Visualizer
Canvas:
class FrequencyBars {
constructor(canvas, audioEngine) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.audio = audioEngine;
this.barCount = 32; // {motion.audio.visualization.bar-count.medium}
this.sensitivity = 1; // {motion.audio.sensitivity.normal}
}
render() {
const { width, height } = this.canvas;
const data = this.audio.getFrequencyData();
this.ctx.clearRect(0, 0, width, height);
const barWidth = width / this.barCount;
const step = Math.floor(data.length / this.barCount);
for (let i = 0; i < this.barCount; i++) {
// Average samples for this bar
let sum = 0;
for (let j = 0; j < step; j++) {
sum += data[i * step + j];
}
const value = (sum / step / 255) * this.sensitivity;
const barHeight = value * height;
const x = i * barWidth;
const y = height - barHeight;
// Color based on frequency
const hue = (i / this.barCount) * 120; // Green to red
this.ctx.fillStyle = `hsl(${hue}, 80%, 50%)`;
this.ctx.fillRect(x, y, barWidth - 2, barHeight);
}
requestAnimationFrame(() => this.render());
}
}
Audio-Reactive Position
JavaScript + GSAP:
class AudioReactiveElement {
constructor(element, audioEngine) {
this.element = element;
this.audio = audioEngine;
this.positionRange = 50; // {motion.audio.response.position-range}
this.rotationRange = 30; // {motion.audio.response.rotation-range}
this.animate();
}
animate() {
const bass = this.audio.getBass();
const mids = this.audio.getMids();
const highs = this.audio.getHighs();
// Map audio to transforms
const x = (bass - 0.5) * this.positionRange * 2;
const y = (mids - 0.5) * this.positionRange;
const rotation = (highs - 0.5) * this.rotationRange * 2;
gsap.to(this.element, {
x,
y,
rotation,
duration: 0.1,
ease: 'power1.out'
});
requestAnimationFrame(() => this.animate());
}
}
Waveform Visualization
Canvas:
class Waveform {
constructor(canvas, audioEngine) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.audio = audioEngine;
}
render() {
const { width, height } = this.canvas;
const data = this.audio.timeData;
this.ctx.clearRect(0, 0, width, height);
this.ctx.beginPath();
this.ctx.moveTo(0, height / 2);
const sliceWidth = width / data.length;
for (let i = 0; i < data.length; i++) {
const value = data[i] / 255;
const y = value * height;
const x = i * sliceWidth;
if (i === 0) {
this.ctx.moveTo(x, y);
} else {
this.ctx.lineTo(x, y);
}
}
this.ctx.strokeStyle = '#00ff88';
this.ctx.lineWidth = 2;
this.ctx.stroke();
requestAnimationFrame(() => this.render());
}
}
Reduced Motion
For prefers-reduced-motion: reduce:
| Full Motion | Reduced Alternative |
|---|---|
| Beat-synced animation | Static or gentle glow |
| Rapid frequency response | Slow, averaged response |
| Position displacement | Opacity changes only |
Implementation:
const prefersReducedMotion = window.matchMedia(
'(prefers-reduced-motion: reduce)'
).matches;
if (prefersReducedMotion) {
// Use heavy smoothing and subtle sensitivity
analyser.smoothingTimeConstant = 0.9; // {motion.audio.smoothing.heavy}
sensitivity = 0.5; // {motion.audio.sensitivity.subtle}
// Use only opacity, not position
useOpacityOnly = true;
}
Cross-References
- See
looping/for rhythm-based loop timing - See
particles/for audio-reactive particle effects - See
scale/for scaling animations - See
color/for audio-reactive color changes - See
sequencing/for beat-timed sequences
References
- No additional references needed for this skill