audio-analysis について
Tone.js のアナライザーを使った JavaScript 向け音声分析ガイドです。周波数、波形、音量の取得に加え、帯域抽出、ビート検出、振幅計算、平滑化、性能上の留意点を扱います。
主な機能
- 01FFT による周波数スペクトル分析
- 02波形と音量の測定
- 03周波数帯域の抽出
- 04ビート検出の手法
- 05RMS とピーク振幅の計算
- 06値の平滑化手法
活用シーン
- 01音声スペクトルの可視化
- 02エフェクト同期のためのビート検出
- 03波形の振幅と音量の測定
- 04ビジュアル向け音声値の平滑化
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ bbeierle12/ skill-mcp-claude --skill audio-analysisインストール後、Claude Code で/audio-analysisを入力して読み込まれたか確認します。
同梱ファイル_meta.json
詳細
オーディオ分析
FFT、周波数抽出、オーディオデータ分析。
クイックスタート
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
アナライザーの種類
FFT アナライザー
// 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 - 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 - 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 サイズの影響
| サイズ | 周波数分解能 | 時間分解能 | 用途 |
|---|---|---|---|
| 32 | 低 | 高 | ビート検出 |
| 128 | 中 | 中 | 一般的なビジュアライゼーション |
| 256 | 良好 | 良好 | バランス型(デフォルト) |
| 1024 | 高 | 低 | 詳細なスペクトル |
| 2048 | 非常に高い | 非常に低い | オーディオ分析ツール |
周波数帯域
帯域の手動抽出
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);
}
正規化された帯域値
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)
};
}
ビート検出
シンプルなピーク検出
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);
}
エネルギー履歴によるビート検出
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;
}
}
振幅分析
RMS(実効値)
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);
}
ピーク振幅
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;
}
スムージング手法
指数平滑化
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);
移動平均
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;
}
}
完全な分析システム
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();
}
}
時間的な折りたたみの使用方法
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
};
}
}
パフォーマンスのヒント
// 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);
参考情報
- オーディオの読み込みと再生については、
audio-playbackを参照してください - 分析結果をビジュアルに接続する方法については、
audio-reactiveを参照してください - オーディオドメインのルーティングについては、
audio-routerを参照してください
コメント
Markdown 対応