音声スペクトルの「重心」(音の明るさ・高さの平均値)を算出し、背景色などに反映させるサンプル。
p5.js 2.0 p5.sound
Audio Reactive Spectral Centroid FFT
View Source Code
let mic, fft;
let nyquist = 22050; // Cache nyquist globally to avoid AudioContext lockups
let isStarted = false;
// 重心の過去の値を保存して滑らかにするための配列
let centroidHistory = [];
const historyLen = 50;
function setup() {
createCanvas(windowWidth, windowHeight);
mic = new p5.AudioIn();
fft = new p5.FFT(1024);
mic.connect(fft);
colorMode(HSB, 360, 100, 100);
noStroke();
}
function draw() {
if (!isStarted) {
background(20);
fill(255);
textAlign(CENTER, CENTER);
textSize(24);
text("Click to Start Microphone", width / 2, height / 2);
return;
}
// スペクトル解析を実行(必須)
let spectrum = fft.analyze();
// v2ではgetCentroid()が廃止されているため、自作関数でスペクトルの重心(Hz)を取得
let centroid = getCentroid(spectrum);
// 履歴に追加
if (centroid > 0) {
centroidHistory.push(centroid);
if (centroidHistory.length > historyLen) {
centroidHistory.shift();
}
}
// 過去の重心の平均値を計算して滑らかにする
let avgCentroid = 0;
if (centroidHistory.length > 0) {
let sum = centroidHistory.reduce((a, b) => a + b, 0);
avgCentroid = sum / centroidHistory.length;
}
// 人間の声を中心とした音楽的によく使われる帯域(約100Hz〜8000Hz)を
// 基準に、色相(Hue: 0〜360)にマッピングする
let mappedHue = map(
log(max(avgCentroid, 1)),
log(100),
log(8000),
0,
360,
true,
);
// 背景色を重心から計算した色にする
background(mappedHue, 80, 20); // 背景は暗めに
// 中央の円を描画(P5 v2では Amplitude は別途必要だけど簡易的にRMSを計算する手もある)
// ここでは正規化されたFFTのエネルギーをボリュームとして使う
let vol = sumEnergy(spectrum);
let size = map(vol, 0, 1, 50, height * 0.8);
fill(mappedHue, 80, 100); // 円は明るく
circle(width / 2, height / 2, size);
// データのテキスト表示
fill(0, 0, 100);
textAlign(LEFT, TOP);
textSize(24);
text("Spectral Centroid", 20, 20);
textSize(16);
text(`Current Centroid: ${round(centroid)} Hz`, 20, 60);
text(`Smoothed Centroid: ${round(avgCentroid)} Hz`, 20, 85);
text(`Mapped Hue: ${round(mappedHue)}°`, 20, 110);
}
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にはgetCentroid()がないため、独自関数で代用
function getCentroid(spectrum) {
const bins = spectrum.length;
let num = 0;
let den = 0;
for (let i = 0; i < bins; i++) {
let freq = (i / bins) * nyquist;
let amp = spectrum[i];
num += freq * amp;
den += amp;
}
if (den === 0) return 0;
return num / den;
}
// スペクトル全体のエネルギー合計(簡易ボリューム用)をdBスケールに正規化して返す
function sumEnergy(spectrum) {
let sum = 0;
for (let i = 0; i < spectrum.length; i++) {
sum += spectrum[i] || 0;
}
let avg = sum / spectrum.length;
if (isNaN(avg) || avg <= 0) return 0;
// p5.sound v2のリニア振幅をデシベル(dB)に変換し、0.0〜1.0スケールに正規化
let db = 20 * Math.log10(avg);
let normalized = map(db, -70, -30, 0, 1);
return constrain(normalized, 0, 1);
}