P5.jsでAudio Visualizerを作ってみた!【ビート検知】
こんにちは。
今回はP5.jsをつかってサウンドビジュアライザーを作成してみました。
P5.jsは、プログラミングで絵を描くためによく使われているProcessingという言語のJavaScript版ライブラリになります。
以前からビジュアライザーを作りに興味はあったのですが、今回はコチラの記事
Adobe製品を使わない"デザイナー"?「ビジュアルコーダー」が考える、自己満足で終わらないWebデザインとは
に触発されまして、Web上にビジュアライザーを作れたら面白いと思いやってみました!!
P5.jsはオーディオを扱うにも便利ですが、ビートの検知で苦労したところがありました。
かなり荒い調整なので参考になるかどうかはわかりませんが、簡単に記事に残しておこうと思います。
作ったもの
Web版
イメージ画像
https://izm51.github.io/visual-coding/visualizer1-pastel/
※ブラウザの設定でマイクを許可し、画面をクリックすると開始します。
YouTube Demo
デモ動画
背景はパステルカラーがくるくると回っていて、スネア(の音域)に合わせて色が変わります。
そしてキックに合わせて中央の丸が大きくなり、そこから円や四角が近づいてくるようになっています。
下辺にはスペクトラムも設置しました。
始めは何となくで決めた色でしたが、パステルカラーとホワイトのおかげでKawaii感じになりました。
Beat Detection の実装
P5.jsには描画だけでなく、Audioに関する便利な関数もたくさんありました。
おかげでマイクの取得やfftなどは非常に簡単でした!
しかしビートの検知には若干苦労しました。
P5.js自体にもPeakDetectという関数があったのですが、ちょっと反応がとがりすぎという印象でした。
(触り始めのときの感想なので、チューニング次第では普通に使えるかも?)
そこで今回は、下記記事を参考に簡単なBeatDetectorも実装してみました。
https://www.airtightinteractive.com/2013/10/making-audio-reactive-visuals/
※テンポを計算するものではなく、ある周波数領域の音量が大きくなったのを検知するものです。
class BeatDetect {
constructor(mode = 'kick', freq2) {
if (!isNaN(freq2) && !isNaN(mode)) {
this.freq1 = mode;
this.freq2 = freq2;
} else {
if (mode == 'snare') {
this.freq1 = 2000;
this.freq2 = 6000;
} else if (mode == 'male') {
this.freq1 = 200;
this.freq2 = 2000;
} else {
// mode == "kick"
this.freq1 = 20;
this.freq2 = 80;
}
}
this.time = 0;
this.threshold = 0;
this.minThreshold = 0;
this.decayRate = 0.01;
this.minThresholdRate = 0.8;
this.holdTime = 45;
this.marginThresholdTime = 10;
this.marginThreshold = 0.06;
}
update(fft) {
const e = fft.getEnergy(this.freq1, this.freq2);
const level = e / 255.0 || 0.0;
let isBeat = false;
if (level > this.threshold && level > this.minThreshold) {
this.threshold = level * 1.05;
this.minThreshold = max(this.minThreshold, level * this.minThresholdRate);
if (this.time > this.marginThresholdTime) {
isBeat = true;
}
this.time = 0;
} else {
if (this.time == this.marginThresholdTime) {
this.threshold -= this.marginThreshold;
}
this.time += 1;
if (this.time > this.holdTime) {
this.threshold -= this.decayRate;
}
}
return { threshold: this.threshold, level: level, isBeat: isBeat };
}
}
全体のコードはGitHubにあります。
課題としては、
- 反応の良い周波数帯を探る
- 一つの音に何回も反応させたくない(短時間で反応しすぎるのを防ぐ)
- 逆にアクセントの強弱で弱くなってるキック等にはちゃんと反応させたい
という感じでした。
キックとスネアを検知する
まずは曲のリズムに良い感じに反応する周波数帯を探しました。
自分がよく聞くのはMathRock(バンド系)とFutureBounce(クラブ系)、あとはアイドル系です。
なので、今回はその3ジャンルで探りました。
ビートを取るためであれば低音域が最適かと思っていましたが、キックにメロディがかぶりまくることもあれば、低音域で全く音が鳴っていないこともあり、低音だけでは絵の変化に面白みがないパターンがありました。
いろいろ試した結果、バスドラの音域(20Hz~80Hz)とスネアやクラップらへんの音域(2000Hz~6000Hz)が曲への反応がよさそうだったので、この2領域を使うことにしてみました。
ビートが連続で検知されまくるのを防ぐ
次に、一回のバスドラで何回もビートが検知されたり、曲が静かになって音量に変化がない時に連続で反応してしまうことがあり、そこに対処しました。
始めはthreshold、decayRate、holdTimeの変数だけでちょうどいい調整を探していたのですが、曲によってどうにも反応が変わってしまい、いろいろと手探りで変数を増やしました。
具体的には下記の部分になります。
if (level > this.threshold && level > this.minThreshold) {
this.threshold = level * 1.05;
this.minThreshold = max(this.minThreshold, level * this.minThresholdRate);
if (this.time > this.marginThresholdTime) {
isBeat = true;
}
if (this.time > this.marginThresholdTime)
で連続の検知を防止し、
this.minThreshold = max(this.minThreshold, level * this.minThresholdRate);
で曲中の静かな部分ではビートを検知しすぎないようにしました。
強弱のあるキックにもうまく反応させる
今回のビート検知の仕組みでは少し前に鳴った音量がビート検知の閾値になるため、例えばバンド系の曲でバスドラがダブルで踏まれているときに、二つ目が少し弱かったりすると反応が悪くなってしまいました。
そこで、無理やり感はありますがビート検知から少し時間が経過したら、holdTimeより少し早いタイミングでthresholdを少しだけガクッと下げるようにしました。
if (this.time == this.marginThresholdTime) {
this.threshold -= this.marginThreshold;
} // this.marginThreshold = 0.06
なにかもっとちゃんとしたチューニングはありそうですが、今回はいい感じの反応になったので一旦これで満足しています。
まとめ
という訳で、今回は初めてのビジュアライザーを作成してみました。
思い返せば中学生くらいの頃から音に合わせて動くプロジェクションマッピングとか、高校時代もインスタレーションに興味がありましたがなんだか手が出せずにいました。
少しずつWebやオーディオの知識をためてきたおかげで、気づいたら昔憧れていた領域に手を出せるようになっていました。
Processingなら気軽にビジュアルコーディングができます!
今後もまた面白いものを作っていきたいと思います。
ではまた。