Claude Code Plugins

Community-maintained marketplace

Feedback

Audio analysis with Tone.js and Web Audio API including FFT, frequency data extraction, amplitude measurement, and waveform analysis. Use when extracting audio data for visualizations, beat detection, or any audio-reactive features.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name audio-analysis
description Audio analysis with Tone.js and Web Audio API including FFT, frequency data extraction, amplitude measurement, and waveform analysis. Use when extracting audio data for visualizations, beat detection, or any audio-reactive features.

Audio Analysis

FFT, frequency extraction, and audio data analysis.

Quick Start

import * as Tone from 'tone';

// Create analyzer
const analyser = new Tone.Analyser('fft', 256);
const player = new Tone.Player('/audio/music.mp3');

player.connect(analyser);
player.toDestination();

// Get frequency data
const frequencyData = analyser.getValue(); // Float32Array

Analyzer Types

FFT Analyzer

// FFT (Fast Fourier Transform) - frequency spectrum
const fftAnalyser = new Tone.Analyser({
  type: 'fft',
  size: 256,        // Must be power of 2: 32, 64, 128, 256, 512, 1024, 2048
  smoothing: 0.8    // 0-1, higher = smoother transitions
});

// Returns Float32Array of dB values (typically -100 to 0)
const fftData = fftAnalyser.getValue();

Waveform Analyzer

// Waveform - time domain data
const waveformAnalyser = new Tone.Analyser({
  type: 'waveform',
  size: 1024
});

// Returns Float32Array of amplitude values (-1 to 1)
const waveformData = waveformAnalyser.getValue();

Meter (Volume Level)

// Meter - overall volume level
const meter = new Tone.Meter({
  smoothing: 0.9,
  normalRange: false  // true for 0-1, false for dB
});

player.connect(meter);

// Get current level
const level = meter.getValue(); // dB or 0-1

FFT Size Impact

Size Frequency Resolution Time Resolution Use Case
32 Low High Beat detection
128 Medium Medium General visualization
256 Good Good Balanced (default)
1024 High Low Detailed spectrum
2048 Very High Very Low Audio analysis tools

Frequency Bands

Manual Band Extraction

const analyser = new Tone.Analyser('fft', 256);

function getFrequencyBands() {
  const data = analyser.getValue();
  const binCount = data.length;

  // Define frequency band ranges (approximate for 44.1kHz sample rate)
  // Each bin = (sampleRate / 2) / binCount Hz
  const bands = {
    sub: average(data, 0, Math.floor(binCount * 0.03)),      // ~20-60 Hz
    bass: average(data, Math.floor(binCount * 0.03), Math.floor(binCount * 0.08)),  // ~60-250 Hz
    lowMid: average(data, Math.floor(binCount * 0.08), Math.floor(binCount * 0.15)), // ~250-500 Hz
    mid: average(data, Math.floor(binCount * 0.15), Math.floor(binCount * 0.3)),     // ~500-2000 Hz
    highMid: average(data, Math.floor(binCount * 0.3), Math.floor(binCount * 0.5)),  // ~2000-4000 Hz
    high: average(data, Math.floor(binCount * 0.5), binCount)                         // ~4000+ Hz
  };

  return bands;
}

function average(data, start, end) {
  let sum = 0;
  for (let i = start; i < end; i++) {
    sum += data[i];
  }
  return sum / (end - start);
}

Normalized Band Values

function getNormalizedBands() {
  const bands = getFrequencyBands();

  // Convert dB to 0-1 range (assuming -100 to 0 dB range)
  const normalize = (db) => Math.max(0, Math.min(1, (db + 100) / 100));

  return {
    sub: normalize(bands.sub),
    bass: normalize(bands.bass),
    lowMid: normalize(bands.lowMid),
    mid: normalize(bands.mid),
    highMid: normalize(bands.highMid),
    high: normalize(bands.high)
  };
}

Beat Detection

Simple Peak Detection

class BeatDetector {
  constructor(threshold = 0.7, decay = 0.98) {
    this.threshold = threshold;
    this.decay = decay;
    this.peak = 0;
    this.lastBeat = 0;
    this.minInterval = 200; // Minimum ms between beats
  }

  detect(analyser) {
    const data = analyser.getValue();

    // Focus on bass frequencies for beat detection
    const bassEnergy = this.getBassEnergy(data);

    // Decay the peak
    this.peak *= this.decay;

    // Update peak if higher
    if (bassEnergy > this.peak) {
      this.peak = bassEnergy;
    }

    // Detect beat
    const now = Date.now();
    const threshold = this.peak * this.threshold;

    if (bassEnergy > threshold && now - this.lastBeat > this.minInterval) {
      this.lastBeat = now;
      return true;
    }

    return false;
  }

  getBassEnergy(data) {
    // Average of low frequency bins
    let sum = 0;
    const bassRange = Math.floor(data.length * 0.1);
    for (let i = 0; i < bassRange; i++) {
      // Convert dB to linear and sum
      sum += Math.pow(10, data[i] / 20);
    }
    return sum / bassRange;
  }
}

// Usage
const beatDetector = new BeatDetector();
const analyser = new Tone.Analyser('fft', 256);

function update() {
  if (beatDetector.detect(analyser)) {
    console.log('Beat!');
    // Trigger visual effect
  }
  requestAnimationFrame(update);
}

Energy History Beat Detection

class EnergyBeatDetector {
  constructor(historySize = 43, sensitivity = 1.3) {
    this.history = new Array(historySize).fill(0);
    this.sensitivity = sensitivity;
    this.historyIndex = 0;
  }

  detect(analyser) {
    const data = analyser.getValue();
    const currentEnergy = this.calculateEnergy(data);

    // Calculate average energy from history
    const avgEnergy = this.history.reduce((a, b) => a + b) / this.history.length;

    // Update history
    this.history[this.historyIndex] = currentEnergy;
    this.historyIndex = (this.historyIndex + 1) % this.history.length;

    // Beat if current energy exceeds average by sensitivity factor
    return currentEnergy > avgEnergy * this.sensitivity;
  }

  calculateEnergy(data) {
    let energy = 0;
    for (let i = 0; i < data.length; i++) {
      const amplitude = Math.pow(10, data[i] / 20);
      energy += amplitude * amplitude;
    }
    return energy;
  }
}

Amplitude Analysis

RMS (Root Mean Square)

function getRMS(analyser) {
  const waveform = analyser.getValue(); // Waveform analyzer

  let sum = 0;
  for (let i = 0; i < waveform.length; i++) {
    sum += waveform[i] * waveform[i];
  }

  return Math.sqrt(sum / waveform.length);
}

Peak Amplitude

function getPeakAmplitude(analyser) {
  const waveform = analyser.getValue();
  let peak = 0;

  for (let i = 0; i < waveform.length; i++) {
    const abs = Math.abs(waveform[i]);
    if (abs > peak) peak = abs;
  }

  return peak;
}

Smoothing Techniques

Exponential Smoothing

class SmoothValue {
  constructor(smoothing = 0.9) {
    this.value = 0;
    this.smoothing = smoothing;
  }

  update(newValue) {
    this.value = this.smoothing * this.value + (1 - this.smoothing) * newValue;
    return this.value;
  }
}

// Usage
const smoothBass = new SmoothValue(0.85);
const bassLevel = smoothBass.update(rawBassLevel);

Moving Average

class MovingAverage {
  constructor(size = 10) {
    this.size = size;
    this.values = [];
  }

  update(value) {
    this.values.push(value);
    if (this.values.length > this.size) {
      this.values.shift();
    }
    return this.values.reduce((a, b) => a + b) / this.values.length;
  }
}

Complete Analysis System

class AudioAnalysisSystem {
  constructor() {
    this.fftAnalyser = new Tone.Analyser('fft', 256);
    this.waveformAnalyser = new Tone.Analyser('waveform', 1024);
    this.meter = new Tone.Meter({ smoothing: 0.9 });

    this.smoothers = {
      bass: new SmoothValue(0.85),
      mid: new SmoothValue(0.9),
      high: new SmoothValue(0.9),
      volume: new SmoothValue(0.95)
    };

    this.beatDetector = new BeatDetector();
  }

  connect(source) {
    source.connect(this.fftAnalyser);
    source.connect(this.waveformAnalyser);
    source.connect(this.meter);
    source.toDestination();
  }

  getAnalysis() {
    const fft = this.fftAnalyser.getValue();
    const waveform = this.waveformAnalyser.getValue();
    const volume = this.meter.getValue();

    const bands = this.extractBands(fft);

    return {
      // Raw data
      fft,
      waveform,

      // Smoothed bands (0-1)
      bass: this.smoothers.bass.update(bands.bass),
      mid: this.smoothers.mid.update(bands.mid),
      high: this.smoothers.high.update(bands.high),

      // Volume
      volume: this.smoothers.volume.update(this.normalizeDb(volume)),
      volumeDb: volume,

      // Beat
      isBeat: this.beatDetector.detect(this.fftAnalyser),

      // Waveform metrics
      rms: this.getRMS(waveform),
      peak: this.getPeak(waveform)
    };
  }

  extractBands(fft) {
    const len = fft.length;
    return {
      bass: this.normalizeDb(this.avgRange(fft, 0, len * 0.1)),
      mid: this.normalizeDb(this.avgRange(fft, len * 0.1, len * 0.5)),
      high: this.normalizeDb(this.avgRange(fft, len * 0.5, len))
    };
  }

  avgRange(data, start, end) {
    let sum = 0;
    const s = Math.floor(start);
    const e = Math.floor(end);
    for (let i = s; i < e; i++) sum += data[i];
    return sum / (e - s);
  }

  normalizeDb(db) {
    return Math.max(0, Math.min(1, (db + 100) / 100));
  }

  getRMS(waveform) {
    let sum = 0;
    for (let i = 0; i < waveform.length; i++) {
      sum += waveform[i] * waveform[i];
    }
    return Math.sqrt(sum / waveform.length);
  }

  getPeak(waveform) {
    let peak = 0;
    for (let i = 0; i < waveform.length; i++) {
      const abs = Math.abs(waveform[i]);
      if (abs > peak) peak = abs;
    }
    return peak;
  }

  dispose() {
    this.fftAnalyser.dispose();
    this.waveformAnalyser.dispose();
    this.meter.dispose();
  }
}

Temporal Collapse Usage

class TemporalAudioAnalysis extends AudioAnalysisSystem {
  getCountdownData() {
    const analysis = this.getAnalysis();

    return {
      // For bloom intensity
      glowIntensity: analysis.bass * 0.5 + analysis.volume * 0.5,

      // For particle speed
      particleEnergy: analysis.mid,

      // For chromatic aberration
      distortion: analysis.high * 0.3,

      // For digit pulse
      pulse: analysis.isBeat ? 1 : 0,

      // For background intensity
      ambientLevel: analysis.rms
    };
  }
}

Performance Tips

// 1. Use appropriate FFT size
const analyser = new Tone.Analyser('fft', 128); // Smaller = faster

// 2. Don't analyze every frame if not needed
let frameCount = 0;
function update() {
  if (frameCount % 2 === 0) { // Every other frame
    const data = analyser.getValue();
  }
  frameCount++;
}

// 3. Reuse arrays
const dataArray = new Float32Array(256);
analyser.getValue(dataArray); // Pass in array to avoid allocation

// 4. Use smoothing to reduce visual jitter
const smoothedValue = smoother.update(rawValue);

Reference

  • See audio-playback for loading and playing audio
  • See audio-reactive for connecting analysis to visuals
  • See audio-router for audio domain routing