テープLEDでイルミネーション!


その3 PC 側のプログラム

コンセプトが”PC から操作できる”なので、PC で操作するためのソフトを書きます。

さあ、どうしましょうかねぇ・・・思いつくのは・・・

  • 時間で合わせて色を変える
  • CPU、GPU の使用率で色を変える
  • 温度で色を変える
  • メール受信などの通知で色を変える

・・・etc

まあ、いっぱいあると思いますが今回は、 C# で作成したテストアプリと、 C++ で作成した foobar2000 用のプラグイン をご紹介します。

どちらも Visual Studio 2015 で作成されております。 完全フリーの無料版「Visual Studio Community」があるので、 もっていない方はマイクロソフトさんからダウンロードしてください。 (Visual Studio 2015 の無料版は、個人使用に限りますが professional 版と同等の機能を使用できるらしいよ!)

(1) まずは、C# で軽くテストアプリを作るよ!

C# での作成例として、テスト用のアプリを作りました。 マイコンとの通信部分はクラスライブラリとして実装していますので、 ご自身のプログラムに組み込んで自由に使ってください。

テストアプリでできること。

  • LED の調光。
  • 設定値のマイコンへの保存。
  • 設定値をマイコンから読み込む。

クラスライブラリを使用してもらえれば、 C# で簡単に LED の制御ができますので、 ぜひ試してみてください。

ダウンロード

ソースは、”ここ”から DL してね。

バイナリは、”ここ”から DL してね。

Windows 10 64bit で動作確認しましたが、.NET Framework 4.0 さえ入っていれば、 Windows 7, 8, 8.1 でも動くと思われます。

(2) foobar2000 用の プラグイン

プラグインの作成例としては、ダメダメな部分が多く、 参考にならないかもしれませんが(むしろ参考にしてはダメw)、 動作自体は問題ないと思います。

  • まあ、どの辺がダメかというと、foobar 側で FFT を実行してくれているらしく、 本来ならば自前で FFT なぞする必要がないらしいのですが、 私が良くわからないため、無駄に FFT しちゃってるあたりがダメですw
  • そもそも、なんで DSPプラグインなんだ? と言う点もダメですw
  • 設定画面作る方法が良くわからなくて、INI ファイルでの設定に逃げた点もダメですねw

(だって、英語苦手ですし、ろくなサンプルがないんだもん・・・)

ただ、FFT やその他の処理、マイコンとの通信部分は別スレッドで実行していますので、 プラグイン自体が foobar の負荷になることは、ほとんどありません。 (foobar 側でやっていることは、音声データ(5000 サンプリング程度)のコピーのみです)

また、マイコンとの通信などで処理が遅延した場合は、 処理が終わるまでプラグイン内の処理をパスするので、 音飛びなどの再生に関する不具合は起きません。

もちろん、音声データへは”まったく手を加えません”のでご安心を・・・

ダウンロード

ソースは、”ここ”から DL してね。

バイナリは、”ここ”から DL してね。

Windows 10 64bit で動作確認しましたが、 Windows 7, 8, 8.1 でも動くと思われます。
<謝辞>

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

感謝です!!!

FFT のテストを行ってみました。

foobar 以外のプラグインでも FFT 部分は参考になるでしょうから、 FFT の部分の説明と、動作確認結果を説明します。

まず、テスト波形として

  • サンプリング 44.1[kHz] 16bit
  • 0[dB](0~0xffffの最大振幅)
  • 2.5[kHz]のサイン波

の 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 は、

pitch = 44100 / 256

となります。簡単でしょ? まあ、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;
}

(3) プラグインの使い方を説明

プラグインの使い方(INI ファイルの設定)を動作の流れを追って以下に示します。

まず、ダウンロードした「foo_LEDCtrl.dll」「foo_LEDCtrl.ini」を、 「C:\Program Files (x86)\foobar2000\components」へコピーします。

プラグインの動作を決めているのが「foo_LEDCtrl.ini」ですので、 設定項目順に説明していきます。

<補足>

Window7 以降の場合、編集するためには「管理者権限」が必要となりますので、 注意してください。

COM ポートの設定

使用する COMポート をここに指定してください。

<INIファイルの設定>

PORTNAME デバイスマネージャで確認できます。

この例だと「通信ポート(COM1)」の”COM1”を設定します。

FFT

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 高音ガンマ値

ま、応用例なので、皆様勝手にソースをいろいろいじって遊んでみてくださればと思います。

次回へすすむ