その3 PC 側のプログラム
コンセプトが”PC から操作できる”なので、PC で操作するためのソフトを書きます。
さあ、どうしましょうかねぇ・・・思いつくのは・・・
・・・etc
まあ、いっぱいあると思いますが今回は、 C# で作成したテストアプリと、 C++ で作成した foobar2000 用のプラグイン をご紹介します。
どちらも Visual Studio 2015 で作成されております。 完全フリーの無料版「Visual Studio Community」があるので、 もっていない方はマイクロソフトさんからダウンロードしてください。 (Visual Studio 2015 の無料版は、個人使用に限りますが professional 版と同等の機能を使用できるらしいよ!)
C# での作成例として、テスト用のアプリを作りました。 マイコンとの通信部分はクラスライブラリとして実装していますので、 ご自身のプログラムに組み込んで自由に使ってください。
テストアプリでできること。
クラスライブラリを使用してもらえれば、 C# で簡単に LED の制御ができますので、 ぜひ試してみてください。
ソースは、”ここ”から DL してね。
バイナリは、”ここ”から DL してね。
プラグインの作成例としては、ダメダメな部分が多く、 参考にならないかもしれませんが(むしろ参考にしてはダメw)、 動作自体は問題ないと思います。
(だって、英語苦手ですし、ろくなサンプルがないんだもん・・・)
ただ、FFT やその他の処理、マイコンとの通信部分は別スレッドで実行していますので、 プラグイン自体が foobar の負荷になることは、ほとんどありません。 (foobar 側でやっていることは、音声データ(5000 サンプリング程度)のコピーのみです)
また、マイコンとの通信などで処理が遅延した場合は、 処理が終わるまでプラグイン内の処理をパスするので、 音飛びなどの再生に関する不具合は起きません。
もちろん、音声データへは”まったく手を加えません”のでご安心を・・・
ソースは、”ここ”から DL してね。
バイナリは、”ここ”から DL してね。
FFT は以下のソースを使用させて頂きました。
Ooura's Mathematical Software Packages 様
http://www.kurims.kyoto-u.ac.jp/~ooura/index-j.htmlまた、作成にあたり、以下のホームページを参考にさせて頂きました。
「みる きく 考える 進む」 様
http://geisterchor.blogspot.jp/2011/04/fft_16.html感謝です!!!
foobar 以外のプラグインでも FFT 部分は参考になるでしょうから、 FFT の部分の説明と、動作確認結果を説明します。
まず、テスト波形として
の wav を用意しました。
これを foobar で再生すると下図のようなデータが得られました。 どうやら 0~0xffff が ±1 でスケーリングされているようです。
で、これを fftしたものが下図となります。 Y 軸は波高値となるようにスケーリングしていますが、 結果はピッタリと 2.5[kHz] のスペクトルの波高値が 1 になりましたので、 問題ないようです。
ちなみに対数を取ると下図のようになります。 ( (log10(波高値) + 3) / 3 で適当にスケーリングしましたw)
FFT の使い方は、謝辞で述べた「みる きく 考える 進む」様を参考にしてください。 ただし、本サンプルは C++ で書かれていますので、 malloc などの C の関数は使用しないようにアレンジしています。
さて、多分皆さんが興味があるのは、 foobar でデータがどのように格納されているかだと思います。
下に示したソース「fft_audio_chunk.cpp」の75行目がそれなんですが、 ステレオのデータが L,R のペア順で前述の通り「-1 ~ 1」で 正規化された状態で並んでいます。 ですから、特に難しく考えることなくサンプル例のように取り出せばOKです。 ちなみに、ここでは L,R の平均を取っています。
FFT に使用するデータ数は「2のべき乗」つまり 2,4,8,16・・・・でなくてはなりません。 これやらないとハングするんで注意です。 プログラム例では50行目で丸めを行っています。
// //////////////////////////////////////////////////// // データをセットする // // *chunk : チャンクデータ // //////////////////////////////////////////////////// bool fft_audio_chunk::set_data(const audio_chunk *chunk) { // サンプリングレートを保存 bool change_rate = false; double rate = (double)chunk->get_sample_rate(); if (smp_rate_ != rate) { change_rate = true; smp_rate_ = rate; } // バッファの準備 size_t smp_count = chunk->get_sample_count(); if (smp_count < 2) return false; // 2のべきではない場合は丸める if ((smp_count & (smp_count - 1)) != 0) { unsigned int i = 0; while (smp_count > pow(2, i)) i++; smp_count = pow(2, i - 1); } // データ数が違うか、サンプリングレートが変更されている場合はバッファの再確保 if (fft_len_ != smp_count || change_rate) { fft_len_ = smp_count; fft_data_n_ = fft_len_ * 2; fft_a.reset(new double[fft_data_n_]); fft_ip.reset(new int[2 + (int)sqrt(fft_data_n_)]); fft_w.reset(new double[fft_len_]); fft_p.reset(new double[fft_len_ / 2]); fft_ip[0] = 0; } // データを用意する const float* data = chunk->get_data(); for (size_t i = 0; i < fft_len_; i++) { fft_a[i * 2] = (data[i * 2] + data[i * 2 + 1]) / 2; fft_a[i * 2 + 1] = 0; } return true; }
次に FFT ですが、使い方(というか、FFT 関数に渡すバッファのサイズなんですが)は本家を参考にしてください。 今回の FFT に補足説明するとしますと、波高値を取り出すために 複素数の絶対値を取って、それをサンプル数で割って2を掛けています。
この辺は、プログラムというかフーリエ変換の説明になりますので、 その手のページで勉強してください。
あと、110~111行目で対数を取ってますが、正直言うとこれは適当ですw
見た目それっぽい以外に、なんの根拠もありません。
119行目以降は、指定された周波数帯域の平均をとっている処理となっています。
ちなみに、データ1つあたりの周波数は、「サンプリング周波数 / FFT した点数」 で計算できます。例えば、サンプリング周波数 44.1[kHz]、FFT 点数 256 点であれば、 データ 1 つあたりの周波数 pitch は、
となります。簡単でしょ? まあ、1個目は 0[Hz] ~ 256 個目は 44.1[kHz] となるのですから、 当然と言えば当然ですね。
で、FFT 後のデータは半分の 128 で折り返したようになっています。 (ちょうど半分を境に、鏡写しのように同じデータが並んでいる)
というわけで、496 行目の for ループでは、 fft_len_ / 2 というように、 半分しか計算していないんですね。
// //////////////////////////////////////////////////// // fftを実行する // //////////////////////////////////////////////////// bool fft_audio_chunk::execute() { // FFT cdft(fft_data_n_, -1, fft_a.get(), fft_ip.get(), fft_w.get()); // 波高値を計算して log を取る double c = 2.0 / (double)fft_len_; for (size_t i = 0; i < fft_len_ / 2; i++) { fft_p[i] = sqrt(fft_a[i * 2] * fft_a[i * 2] + fft_a[i * 2 + 1] * fft_a[i * 2 + 1]) * c; fft_p[i] = (log10(fft_p[i]) + 3.0) / 3.0; fft_p[i] = fft_p[i] < 0 ? 0 : fft_p[i]; } mean_.low = mean_.mid = mean_.hi = 0; // 1 データあたりの周波数を計算 double pitch = (double)fft_len_ / smp_rate_; // 低音の平均を計算 int bi = 0; int ei = low_freq_ * pitch; if (ei > fft_len_ / 2 || bi == ei) return false; for (size_t i = bi; i < ei; i++) mean_.low += fft_p[i]; mean_.low /= (double)(ei - bi); // 中音の平均を計算 bi = ei; ei = mid_freq_ * pitch; if (ei > fft_len_ / 2 || ei > fft_len_ / 2 || bi == ei) return false; for (size_t i = bi; i < ei; i++) mean_.mid += fft_p[i]; mean_.mid /= (double)(ei - bi); // 高音の平均を計算 bi = ei; ei = hi_freq_ * pitch; if (ei > fft_len_ / 2 || ei > fft_len_ / 2 || bi == ei) return false; for (size_t i = bi; i < ei; i++) mean_.hi += fft_p[i]; mean_.hi /= (double)(ei - bi); return true; }
プラグインの使い方(INI ファイルの設定)を動作の流れを追って以下に示します。
まず、ダウンロードした「foo_LEDCtrl.dll」「foo_LEDCtrl.ini」を、 「C:\Program Files (x86)\foobar2000\components」へコピーします。
プラグインの動作を決めているのが「foo_LEDCtrl.ini」ですので、 設定項目順に説明していきます。
Window7 以降の場合、編集するためには「管理者権限」が必要となりますので、 注意してください。
使用する COMポート をここに指定してください。
<INIファイルの設定>
PORTNAME | デバイスマネージャで確認できます。 |
この例だと「通信ポート(COM1)」の”COM1”を設定します。
FFT 結果から指定された周波数帯域内の平均値を計算し、 低音、中音、高音の値を算出します。
<INIファイルの設定>
LOW_FREQ | 低音 0 ~ LOW_FREQ[Hz] |
MID_FREQ | 中音 LOW_FREQ ~ MID_FREQ[Hz] |
HI_FREQ | 高音 HI_FREQ : MID_FREQ ~ HI_FREQ[Hz] |
なんちゃってですw 現在よりも過去のデータの平均値(移動平均サイズは任意)と、 現在の差分を今回の値に加えます。 結果、変化に対してより大きく変動することになり、光り方にメリハリが付きます。
<INIファイルの設定>
ENABLE_ENHANCER | 1 : 有効/ 0 : 無効 |
ENHANCER_MEAN_COUNT | 移動平均点数 |
LOW_ENHANCE | 低音差分への倍率 |
MID_ENHANCE | 中音差分への倍率 |
HI_ENHANCE | 高音差分への倍率 |
指定倍率を掛算してレベル補正します。 LED を全灯するとわかるのですが、若干青がかっています。 これは、LED の色によって、同じ電圧でも同じ電流が流れるわけではないのと、 そもそも、LED の変換効率も違うはずですし、 人間の目の特性(比視感度特性)などによっても違うからなんです。 なんで、「青が強いな・・・」と思ったら青の補正値(HI_RATIO)を 0.8 から 0.7 へ下げるとかします。
<INIファイルの設定>
LOW_RATIO | 低音への倍率 |
MID_RATIO | 中音への倍率 |
HI_RATIO | 高音への倍率 |
RGB 値は FFT 結果の対数を取ったものなので、 ある程度人間の感覚に近いものとなっているはずですが、 聴覚と視覚との相関が揃っているかもわかんないですし、 目の特性、個人差などもあるでしょうから、 ガンマ値を変更することで、 明るさの変化の具合を調整します。 概念は下図を参照してください。
要するに ガンマが 1 より小さければ、値が小さいときより大きいときの方が大きく変化する。 1 より大きければその逆。1 であればリニアだと言うことです。
<INIファイルの設定>
LOW_GANMA | 低音ガンマ値 |
MID_GANMA | 中音ガンマ値 |
HI_GANMA | 高音ガンマ値 |
ま、応用例なので、皆様勝手にソースをいろいろいじって遊んでみてくださればと思います。