mcpField
A
audio-reactive

audio-reactive

github.com/bbeierle12创意与媒体9102 次安装今天更新
代码仓库

关于 audio-reactive

一组将音频分析数据映射到视觉属性的代码模式,面向制作音频响应效果的开发者。内容涵盖节拍响应,并示例集成 Tone、React Three Fiber、后期处理和 GSAP。

主要功能

  1. 01将频段和音量映射到视觉属性
  2. 02实现节拍触发的动画效果
  3. 03提供 React Three Fiber 集成示例
  4. 04展示后期处理与 GSAP 用法
  5. 05包含音频视觉系统及性能提示

适用场景

  1. 01让图形尺寸、位置或颜色随音频变化
  2. 02制作节拍触发的闪烁或缩放效果
  3. 03在 React Three Fiber 场景中驱动视觉效果

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/bbeierle12/skill-mcp-claude --skill audio-reactive

安装后在 Claude Code 中输入/audio-reactive确认已加载。

附带文件_meta.json

详细介绍

音频驱动的视觉效果

将音频分析与视觉参数相连接。

快速开始

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);
}

核心模式

音频到视觉的映射
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;
  }
}

视觉属性映射

缩放/大小
// 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);
}
位置/移动
// 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;
}
旋转
// 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;
  }
}
颜色/亮度
// 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;
}
不透明度/可见性
// Fade based on volume
function updateOpacity(element, volume) {
  element.style.opacity = 0.3 + volume * 0.7;
}

节拍响应

简单节拍闪烁
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);
}
节拍缩放弹跳
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;
  }
}
节拍生成
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 集成

音频驱动组件
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>
  );
}
音频上下文 Hook
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;
}

后期处理集成

音频驱动的 Bloom
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>
  );
}
音频驱动的色差
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 集成

音频驱动的时间轴
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);
  }
}
节拍触发的 GSAP
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();
}

完整系统示例

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 集成

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);
}

性能提示

// 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));

参考

  • 音频加载和播放控制请参阅 audio-playback
  • FFT 和节拍检测请参阅 audio-analysis
  • 音频域路由请参阅 audio-router

评论

支持 Markdown