周波数帯域ごとのエネルギー(Bass, Mid, Trebleなど)を個別に取得し、異なる図形に反映させるサンプル。
p5.js 2.0 p5.sound
Audio Reactive Energy FFT
View Source Code
let mic, fft;
let nyquist = 22050; // default nyquist
let isStarted = false;
// 各帯域のプロパティを管理するオブジェクト
let bands = {
bass: { value: 0, color: "#FF3366", size: 0 },
lowMid: { value: 0, color: "#FF9933", size: 0 },
highMid: { value: 0, color: "#33CC99", size: 0 },
treble: { value: 0, color: "#3399FF", size: 0 },
};
function setup() {
createCanvas(windowWidth, windowHeight);
mic = new p5.AudioIn();
fft = new p5.FFT(1024);
mic.connect(fft);
noStroke();
rectMode(CENTER);
}
function draw() {
// 残像効果のための半透明背景
background(20, 20, 20, 50);
if (!isStarted) {
fill(255);
textAlign(CENTER, CENTER);
textSize(24);
text("Click to Start Microphone", width / 2, height / 2);
return;
}
// FFT解析を実行(必須)
let spectrum = fft.analyze();
// v2ではgetEnergy()が廃止されているため、自作関数で帯域ごとの平均振幅(0.0〜1.0)を取得
bands.bass.value = getEnergy(spectrum, "bass"); // 低音
bands.lowMid.value = getEnergy(spectrum, "lowMid"); // 中低音
bands.highMid.value = getEnergy(spectrum, "highMid"); // 中高音
bands.treble.value = getEnergy(spectrum, "treble"); // 高音
// 画面を4分割して各帯域の図形を描画
let sectionWidth = width / 4;
let sizes = [
bands.bass.value,
bands.lowMid.value,
bands.highMid.value,
bands.treble.value,
];
let colors = [
bands.bass.color,
bands.lowMid.color,
bands.highMid.color,
bands.treble.color,
];
let labels = ["Bass", "Low Mid", "High Mid", "Treble"];
for (let i = 0; i < 4; i++) {
let x = sectionWidth * i + sectionWidth / 2;
let y = height / 2;
// 値を滑らかにする(急激な変化を和らげる。v2は0〜1.0の範囲)
let targetSize = map(sizes[i], 0, 1, 10, sectionWidth - 20);
// 描画
fill(colors[i]);
// 帯域によって形を変える
if (i === 0) {
// Bass: 四角
rect(x, y, targetSize, targetSize);
} else if (i === 1) {
// Low Mid: 角丸四角
rect(x, y, targetSize, targetSize, 20);
} else if (i === 2) {
// High Mid: 円
circle(x, y, targetSize);
} else {
// Treble: 三角形
let r = targetSize / 2;
triangle(x, y - r, x - r, y + r, x + r, y + r);
}
// ラベルと値のテキスト表示
fill(255);
textAlign(CENTER, CENTER);
textSize(18);
text(labels[i], x, height - 80);
text(Math.round(sizes[i]), x, height - 50);
}
}
function mousePressed() {
if (!isStarted) {
userStartAudio();
// AudioContextが開始された後に正確なナイキスト周波数を取得
const ctx = getAudioContext();
if (ctx && ctx.sampleRate) {
nyquist = ctx.sampleRate / 2;
}
mic.start();
isStarted = true;
}
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}
// p5.sound v2にはgetEnergy()がないため、独自関数で代用
function getEnergy(spectrum, preset) {
if (!spectrum || spectrum.length === 0) return 0;
const bins = spectrum.length;
// p5.soundの旧仕様に近い帯域幅設定(Hz)
let freq1, freq2;
switch (preset) {
case "bass":
freq1 = 20;
freq2 = 140;
break;
case "lowMid":
freq1 = 140;
freq2 = 400;
break;
case "mid":
freq1 = 400;
freq2 = 2600;
break;
case "highMid":
freq1 = 2600;
freq2 = 5200;
break;
case "treble":
freq1 = 5200;
freq2 = 14000;
break;
default:
freq1 = 0;
freq2 = nyquist;
}
// 周波数を配列のインデックスに変換
let i1 = Math.round((freq1 / nyquist) * bins);
let i2 = Math.round((freq2 / nyquist) * bins);
i1 = constrain(i1, 0, bins - 1);
i2 = constrain(i2, 0, bins - 1);
if (i1 === i2) return spectrum[i1] || 0;
// 指定範囲内の振幅の平均値を計算
let sum = 0;
let count = i2 - i1 + 1;
for (let i = i1; i <= i2; i++) {
sum += spectrum[i] || 0;
}
let result = sum / count;
if (isNaN(result)) return 0;
// p5.sound v2のanalyze()は非常に小さな「リニア振幅」を返すため、
// 人間の知覚に近いデシベル(dB)に変換し、0.0〜1.0スケールに正規化して返します。
let db = result > 0 ? 20 * Math.log10(result) : -100;
// J-POP等の激しい音圧に合わせて -70dB から -30dB を 0.0〜1.0 にマッピング
let normalized = map(db, -70, -30, 0, 1);
return constrain(normalized, 0, 1);
}