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

StandardDocumentation
W3C DTCGdesigntokens.org
Web Audio APIdeveloper.mozilla.org/Web/API/Web_Audio_API
AnalyserNodedeveloper.mozilla.org/Web/API/AnalyserNode

Quick Reference

Load from: tokens/audio.tokens.json

Frequency Bands

TokenRangeUse
frequency.bass60-250HzKick drums
frequency.mid500-2000HzVocals
frequency.high4000-8000HzCymbals

Sensitivity Levels

TokenValueUse
sensitivity.subtle0.5Gentle response
sensitivity.normal1Standard
sensitivity.high2Dramatic

Beat Detection

TokenValueUse
beat.threshold.low0.3Detect subtle beats
beat.threshold.normal0.5Standard detection
beat.decay.fast0.9Quick 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 MotionReduced Alternative
Beat-synced animationStatic or gentle glow
Rapid frequency responseSlow, averaged response
Position displacementOpacity 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

دانلود ZIP