マイク入力の代わりに、ユーザーが選択・アップロードした音声ファイル(mp3等)を再生し、その音声を解析してビジュアライズするサンプル。
p5.js 2.0 p5.sound
Audio File Input Action Music Player
View Source Code
let soundFile;
let isFileLoaded = false;
let fft;
let amplitude;
// UI要素
let fileInput;
let playBtn;
let stopBtn;
let statusMsg;
function setup() {
createCanvas(windowWidth, windowHeight);
// FFTとAmplitudeの初期化(この時点では入力ソースがない)
fft = new p5.FFT(1024); // p5.sound v2は1024を明示的に渡さないと32binsに縮小されてしまう
amplitude = new p5.Amplitude();
// HTML側で用意したUI要素を取得
fileInput = select("#fileInput");
playBtn = select("#playBtn");
stopBtn = select("#stopBtn");
statusMsg = select("#statusMsg");
// ファイルが選択されたときのイベント
fileInput.changed(handleFile);
// ボタンのイベント
playBtn.mousePressed(togglePlay);
stopBtn.mousePressed(stopAudio);
colorMode(HSB, 360, 100, 100);
noStroke();
}
function draw() {
background(20);
fill(255);
textAlign(LEFT, TOP);
textSize(24);
text("File Audio Visualization", 20, 20);
// 音声ファイルがロードされていなければ描画をスキップ
if (!soundFile || !isFileLoaded) {
return;
}
// 現在の音量を取得 (0〜1)
let vol = amplitude.getLevel();
// 周波数スペクトルを取得
let spectrum = fft.analyze();
// ====== ビジュアライズの描画 ======
// 1. 背景の円(音量に反応)
let bgSize = map(vol, 0, 1, height * 0.2, height);
fill(200, 30, 30, 0.3); // 半透明の青紫系
circle(width / 2, height / 2, bgSize);
// 2. 円形のスペクトル
push();
translate(width / 2, height / 2);
let radius = 150;
// スペクトルの低音〜中音域(0〜100くらい)を中心に使う
let specLength = min(spectrum.length, 120);
for (let i = 0; i < specLength; i++) {
let angle = map(i, 0, specLength, 0, TWO_PI);
let amp = spectrum[i];
// === 修正部分 ===
// p5.sound v2のanalyze()は非常に小さな「リニア振幅 (0.01など)」を返すようになりました。
// そのため、これを人間が知覚するデシベル(dB)に変換して以前の0.0〜1.0スケールに直します。
let db = amp > 0 ? 20 * Math.log10(amp) : -100; // -100dB が無音
let normalizedAmp = map(db, -70, -30, 0, 1); // J-POP等の音圧に合わせて -70dB〜-30dB を 0.0〜1.0 にマッピング
normalizedAmp = constrain(normalizedAmp, 0, 1);
// スペクトルの強さに応じて線の長さを変える
let r = map(normalizedAmp, 0, 1, radius, radius + 200);
// 円の輪郭上の座標
let x1 = radius * cos(angle);
let y1 = radius * sin(angle);
// 外側に伸びる線の座標
let x2 = r * cos(angle);
let y2 = r * sin(angle);
strokeWeight(3);
stroke(map(i, 0, specLength, 0, 360), 80, 100);
line(x1, y1, x2, y2);
}
pop();
// 再生時間の表示
if (soundFile.isPlaying()) {
let duration = 0;
if (typeof soundFile.duration === "function") {
duration = soundFile.duration() || 0;
}
noStroke();
fill(255);
textSize(16);
textAlign(RIGHT, BOTTOM);
text(`Duration: ${duration.toFixed(1)} sec`, width - 20, height - 20);
}
}
// ファイルが選択されたときの処理
function handleFile(event) {
// 新しいファイルを選択した瞬間にフラグを下ろす
isFileLoaded = false;
// DOMイベントからネイティブのFileオブジェクトを安全に取得
let nativeFile = event.target ? event.target.files[0] : null;
if (!nativeFile) return;
if (nativeFile.type.startsWith("audio")) {
statusMsg.html("Loading audio...");
playBtn.attribute("disabled", "");
stopBtn.attribute("disabled", "");
// 既存の音声があれば停止
if (soundFile && soundFile.isPlaying()) {
soundFile.stop();
}
// Blob URLを作成して確実にファイルとして読み込ませる
let fileURL = URL.createObjectURL(nativeFile);
// 音声ファイルの読み込み(v2はPromiseを返す)
let loadPromise = loadSound(fileURL);
// ロード成功時の処理
loadPromise
.then((sf) => {
soundFile = sf;
// 解析ノードに接続する(v2で必須)
soundFile.connect(fft);
soundFile.connect(amplitude);
statusMsg.html("Ready: " + nativeFile.name);
playBtn.removeAttribute("disabled");
stopBtn.removeAttribute("disabled");
isFileLoaded = true;
})
.catch((err) => {
statusMsg.html("Error loading file!");
console.error(err);
});
} else {
statusMsg.html("Not an audio file. Please select mp3/wav.");
}
}
// 再生・一時停止の切り替え
function togglePlay() {
if (soundFile && isFileLoaded) {
if (soundFile.isPlaying()) {
soundFile.pause();
playBtn.html("Play");
statusMsg.html("Paused");
} else {
if (getAudioContext().state !== "running") {
getAudioContext().resume();
}
// v2ではstop()を呼んだ後はplay()だけで確実に頭出し再生されます
soundFile.play();
playBtn.html("Pause");
statusMsg.html("Playing");
}
}
}
// 停止処理
function stopAudio() {
if (soundFile && isFileLoaded) {
// p5.sound v2 特有のバグ回避:
// pause 中に stop() を呼ぶと Tone.js が内部状態のクリーンアップをスキップしてしまい、
// 次回の play() で続きから再生されてしまうため、一瞬だけ play() を挟んでから stop() を強制する
if (soundFile.isPaused() || soundFile.paused) {
soundFile.play();
}
// p5.sound v2 では stop() を呼ぶことで再生位置を 0 にリセットできます
// pause() とは異なり、次回の play() は必ず最初から始まります
soundFile.stop();
playBtn.html("Play");
statusMsg.html("Stopped");
}
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}