C++、結構いろいろな機能があって、複雑ですね。
いっこつづ覚えていこう~
前回実装した、音声処理のオシレータで、C++の機能である「std::function」というものが出てきました。実際に、どのようなものかを調査しましたので、その備忘録となります。
こんな人の役に立つかも
・JUCEプログラミングでオシレータを実装したい人
・JUCEプログラミングのチュートリアルをより詳細に勉強したい人
概要
プラグインにオシレータを実装する際、オシレータクラスのコンストラクタに、initializeという関数でオシレータの波形を初期化する部分がありました。
public:
OscillatorProcessor()
{
//出力する音の周波数は440ヘルツを指定します。
oscillator.setFrequency(440.0f);
//出力する波形をサイン波に設定します。↓↓ココの部分
oscillator.initialise([](float x) { return std::sin(x); });
}
プログラムを分解
initializeの部分を取り出してみると、次のようになっています。
このinitializeに与える引数としては、std::functionを与えるということでした。
少し見にくいのですが、initializeに渡している引数は、一つで、その中に、「ラムダ式」というものが利用されています。
[](float x){
return std::sin(x);
}
ということは、float型のxを引数にとって、xの値のsinを返すという関数になるのですね。
疑問点
ここで、いきなりラムダ式に与えている「float x」とは何者?となりました^^;
また、音声のテーブルとして定義する場合、配列のように時系列でいくつか与えられているようなイメージでしたので、この場合、xが単に一つの値ではないような気もしています。
こんなイメージで、Xを与えると、sinの値が帰ってきて、その数値を複数保持しておくようなテーブルをイメージしています。
ということで、少しだけプログラムを追うことにしました。
initialisedの実装部分を探す
DSPモジュールなので、modules階層の「juce_dsp.h」を見てみることにしました。
うると、oscillator関連のhファイルを呼び出しているようです。
#include "widgets/juce_Oscillator.h"
次に、widgets階層の「juce_Oscillator.h」をみてみました。
initialiseの実装部分は次のようになっていました。
/** Initialises the oscillator with a waveform. */
void initialise (const std::function<NumericType (NumericType)>& function,
size_t lookupTableNumPoints = 0)
{
if (lookupTableNumPoints != 0)
{
auto* table = new LookupTableTransform<NumericType> (function,
-MathConstants<NumericType>::pi,
MathConstants<NumericType>::pi,
lookupTableNumPoints);
lookupTable.reset (table);
generator = [table] (NumericType x) { return (*table) (x); };
}
else
{
generator = function;
}
}
第二引数の「lookupTableNumPoints」は特に与えていませんので、elseの「generator = function」が実行されて、ラムダ式がそのままgeneratorに入ってきています。
次に、generatorという変数が何者なのかを調べます。
std::function<NumericType (NumericType)> generator;
juce_Oscillator.hのprivateな変数の中に、generatorがstd::functionのオブジェクトである定義がありました。ということで、generatorは、値を渡すとsinの値を返すようなオブジェクトであるということができます。
実際に、processでは、generatorに引数を与えることで、音声データに反映させるような記載も見られます。
//process関数の中での使われ方の一例
for (size_t i = 0; i < len; ++i)
dst[i] = src[i] + generator (buffer[i]);
まとめ
私は、C言語でデータテーブルとして値を保持していくというようなプログラミングをしてきましたので、波形データなどは、あらかじめデータテーブルのような(実際には2次元配列など)にあらかじめ定数として確保して、あとから計算で利用する、みたいなパターンが頭にありました。そのため、関数自体をオブジェクトに格納して、あとから利用する、というような高度な使い方を思い立つことができませんでしたので、今回のような調査を行いました。
ということで、initializeでは、ラムダ式で波形を定義している、ということになります。また、波形をいろいろと変化させるためには、ラムダ式で波形の計算式を記載すればよいことになります。