本記事は JUCE Advent Calendar 2017 の12月20日向けに投稿した記事です。
前書き
みんなすごい記事書いててすごいです。ぼくはDSPもC++もあまり詳しくないので、憧れちゃいます。
逆に言えば、音楽関連のトピックが多くて(じぶんもそうなんですけど)、音楽ができない人にはJUCE自体やっぱりとっつきにくいのかな、と感じる部分もあります。
いずれにせよ、激アツライブラリであることは間違いなく、もっといろいろな人にこの激アツを伝えたいため、今回このような形でカレンダーに参加させていただいた次第です。
主催のCOx2さん、ありがとうございます。
自分自身JUCEを知ったきっかけはCOx2さんの同人誌からでした。
もともとDTMをずっとやっていて、シンセいじりが大好きだったため、いつかおもしろVSTを作ってみたいなあと思っていたのです。
が、C++の経験が全く無かったため、どうやってはじめたものか、とずっと悩んでいました。
研究室の先輩がコミケでこの本を買ってきたのを見せてくれて「これしかない」と思い、C++がちっともわからないまま飛び込んでみました。
このようなフレームワークがあると、慣れない言語でもウオオオってなれるので良いですね。
前述したように、C++の知識がまったくないため、ちょくちょく怪しい部分がありますが、指摘等いただければありがたいです。。。
概要
さて、今回はDJデッキとして機能するVSTエフェクト「CatDeck」をJUCEを用いて実装しました。
これまでDJはTraktorを使って行っていたのですが、様々なエフェクターVSTと連携させて使うことを考えると、主にエフェクターと曲とのテンポ合わせの面で不便が生じるので、いっそVSTとしてDJデッキを実装してしまおうとしたわけです。
ミキサーはFL Studioに付属するPatcherというプラグインを使って別途組んでます。
▲ CatDeckのスクリーンショット。UIデザインが雑。
CatDeckの主な機能は以下の通りです:
- 📥 ドラッグ&ドロップで音楽ファイルをロード
- 🌊 Waveformが大きいのと小さいのと2種類表示される
- 🔁 BPMを入力するとVSTホストのBPMと自動的にテンポが合う
- 🔈 BPM編集中、Waveformを4つ並列に表示してビートを可視化
- 👉 CDJみたいに「Nudge」できる
- 🎱 CUEポイントを5つまで打てる
- ⏩ 「1小節もどるボタン」「1小節すすむボタン」
- 💾 CUEポイントとBPMの保存
本記事ではこれらのフィーチャーのうち、実装上チャレンジとなった箇所を取り上げて説明するやつにしたいと思います。
こういうことがJUCEを使ってこうやってできるんだ〜という感覚を共有できればいいと思います。自分用の備忘録も兼ねて。
ドラッグ&ドロップ・ファイルのロード
D&Dされたファイルを読むのは超〜〜〜〜カンタンです。すごい。
まず、 AudioProcessorEditor
(プラグインのUIにかかわるクラス)を継承しているクラス <プラグイン名>AudioProcessorEditor
に、追加で FileDragAndDropTarget
というクラスを継承させます。
すると、 filesDropped( &arrayOfPaths, iX, iY )
というリスナメソッドがファイルをドロップした時に呼び出されます。
ドロップした座標を気にしないのであれば、もう arrayOfPaths[0]
のファイルを読みに行くだけです。これ見ただけで使い方分かっちゃいますよね。
さらに、JUCEはオーディオデータのロードもめちゃくちゃカンタンです。
const File file( arrayOfPaths[0] );
ScopedPointer<AudioFormatReader> reader( formatManager.createReaderFor( file ) );
と書くだけで、勝手にファイルフォーマットまで推測してオーディオデータの使用に必要な情報を全部準備して AudioFormatReader
クラスの reader
のメンバとして取れるようにしてくれます。
取れる情報は、波形の実データの他、チャンネル数・サンプル数・サンプルレートなどです。
再生アルゴリズム
DJデッキにおけるオーディオ再生アルゴリズムは、結構いろいろ気を使うことがあって大変です。
再生速度は等倍であれば、波形のサンプルをそのまま出力バッファに投げていけばいいだけなのですが、等倍以外の場合は補間が必要になります。
この補間をサボったり、線形補間で済ませたりすると、想像以上に聴覚上問題が発生します。想像以上に。
幸運にも自分には波形補間の知識がちょっとだけあったので、前後5点を取るsinc補間で解決しました。
sinc補間とは、x(整数ではない)サンプル目の波形を導出する時に、xの周辺にあるサンプルをsinc関数で掛け算したものの総和を使う手法です。
sinc関数は sin( πdx ) / πdx
で求まります(πは円周率、dxは導出したいサンプルの位置と取り出したサンプルの位置の差・単位はサンプル)。
// super sinc interpolation guy
float AudioPlayer::getSample( const float* buffer, double index ) {
double sum = 0.0;
for ( int i = -5; i <= 5; i ++ ) {
int ii = int( floor( index + 0.5 ) ) + i;
if ( ii < 0 || getBufferSamples() < ii ) { continue; }
double d = ii - index;
sum += buffer[ ii ] * ( d == 0.0 ? 1.0 : sin( d * PI ) / d / PI );
}
return sum;
}
結構速度・品質的にはギリギリな感じだったのですが、どこかで最適化できるものでしょうか……
sin関数のコストを下げるとかはできるのかも?誰か良いこと知ってたら教えてください。
VSTホストからの情報
AudioProcessor
(プラグインの動作に関わるクラス)には、 getPlayHead()->getCurrentPosition( &CurrentPositionInfo )
というメソッドがあります。
これを使うことで、VSTホストの再生位置 timeInSeconds
/ ppqPosition
・BPM bpm
・再生中かを表すブール isPlaying
といった涎垂ものの情報がたくさん入っている AudioPlayHead::CurrentPositionInfo
が取れちゃいます。
ちなみに、MP3タグからBPM取り出すやつはサボってます(
Waveformの表示
CatDeckは、画面上部と画面中央部に2つWaveformがあります。
JUCEには AudioThumbnail
という、オーディオファイルのWaveformを表示するためだけに存在する超便利なクラスが用意されています。
が、どうしても半分のWaveformが描きたかったため、結局自分でWaveformを描画するためのクラスを作ってしまいました。
半分のWaveformが描画できると、同時に上下がパンポットに対応するようなWaveformを描画することができます。
▲ パンがWaveformから見えるようになる
オーディオデータをピクセルバイピクセルで表示していい感じになるように計算してfloat型の配列に入れた後、 g.drawLine()
で線をたくさん描いてます。
計算は、1ピクセルに対応する範囲のサンプルから、最も大きかったアンプリチュードを表示しているだけです。これだけでそこそこ見やすい波形が描けます。
ただ、全部のサンプルを見て比較計算をするような実装になっていて、リアルタイムではとてもできないので、なんかTraktorみたいに少しずつ波形が表示されるようになっちゃいました。これはこれでかっこいい。
▲ ちなみに、BPMの部分を上下にドラッグすることで曲のBPMを調整することができるのですが、その際に1小節先・2小節先・3小節先に相当するWaveformを同時に表示することで、BPMを合わせやすくしています。
メタデータの保存
BPMやCUEの位置といったメタデータは、MP3のタグデータとして付与してもいいのですが、タグ情報を扱うのは実装・管理面両方でちょっと面倒なため、今回はAppDataとして保存しています。
JUCEでAppDataを扱うのは簡単で、 PropertiesFile
というクラスを使えばいい感じにやってくれます。
PropertiesFile
はXMLの扱いに大変長けていて、保存・読み込みの両方においてXMLフォーマットをサポートするメソッドを持っています。
CatDeckでは、XMLデータとしてBPMとCUEの位置をAppDataに保存しています。
なお、 SHA256
(これもJUCEにある(すごい))を使って波形データの頭5秒から16桁のハッシュを取り出してオーディオファイルの同定に使っています。
▲ C:\Users\<USERNAME>\AppData\Roaming\JUCE\CatDeck.props
(Winの場合)にこんな感じのデータが書き込まれている。
JUCE、XMLの扱いはめちゃくちゃ強いのですが、自分はどっちかというとJSONが好きなので、もっとJSONが使いやすくならないかなーと思ってたりします。
おわり
ざっと、実装上ポイントとなった箇所はこんな感じです。
JUCEがいろいろな機能を持っていて、音楽関連のアプリケーション/VSTを簡単に作れそう、という感覚が共有できればよかったと思います。
今まで触ったC++のフレームワークの中では、oFを差し置いて一番触っていて楽しかったです。
「C++怖い……」の人もぜひ一度触ってみてください。
今後も時間を見つけていろいろ作ってみたいです。
おまけ: Protoplugについて
ちなみにDJエフェクターについて、前まではdblue GlitchやGrossBeat等を組み合わせたり、JUCEやReaktorで簡単なエフェクターを作ったりといろいろやってみたのですが、最近はProtoplugというJUCEでできたVSTを使っています。
これ実は、Luaを書いてシンセやエフェクターが作れる環境になっていて、DAWからVSTを開くとバンとテキストエディタが表示されるという激ヤバプラグインです。
JUCEなどで開発するようなVSTプラグインのプロトタイピング環境として生まれたようですが、C++から直接JUCEを叩きに行くよりはるかに気軽なコード量でいろいろ実現できるため、DAW上で痒いところに手が届かない時にめちゃくちゃ重宝します。
また、Lua言語で簡単に書けるため、「C++はやっぱりちょっと……」という人にもオススメしてあげてください。