JUCEのGUIを勉強しようと
2017アドベントカレンダーから
を参考にして作った。
これをちゃんと、動作するまでの紹介になります。
追記 2018-01-19:後にAudioProcessorParameterなるものがでてきたので、この情報はちょっと古いようです。
Audio-Pluginを作る
違う点として、
まず、AUやVST Pluginとして作るところからはじめました。
New Projectで Audio Plug-inを選択して始めます。
そして、
ProjucerのGUIエディタを紹介してみる
と同じように作っていきます。
まず大前提
VSTのGUI側は オーディオのコアの処理と別で
画面が消えると破棄される事を考慮すると良い。
コアの処理からUIを操作するのではなく UIの処理がコアの値をポーリングするような実装
つまり、コアに値を持つ。
コアにUIの参照は持たない。UIからコアの参照を持つのじゃ。
値を保持する
同じものをDAWのLogicXで右上の表示から、「エディタ」を選ぶと、
ホストアプリの標準のUIになる。
ここでは、スライダーの値をs1という内部値でもつことにしました。
内部値を持つ
これを行うには、以下の作業が必要です。
内部パラメータを持つため、
enum Parameters {
s1 = 0,
totalNumParam
};
ここでは、enum名がs1で持ってます。
VSTは値をIndexで管理するので、このenumはIndexをわかりやすくする用。
enum名でなんだかわかるようにしておく。
int getNumParameters() override;
float getParameter (int parameterIndex) override;
const String getParameterName (int parameterIndex) override;
const String getParameterText (int parameterIndex) override;
void setParameter (int parameterIndex, float newValue) override;
float UserParams[totalNumParam];
これ、UserParamsにfloatで値を保持しておき、
override系は、ホストアプリが値を得るための関数群になります。
Indexアクセスですね。
float UserParams[];
は、先ほどのenumの数分配列を用意しています。
enumの最後の項目が、要素数になるといったテクニッック。
値をポーリングする
VSTが持つ内部値をUIに定期的に反映させるために
Timerコールバックを使います。
ホストアプリからスライダー変更を通知させるために
タイマーを動かして、GUIのスライダーを更新させるために使っています。
UI系の初期化時に、processor(内部処理のコアがかかれている)を渡して、表示に備えます。
NewComponent側(スライダー作っているもの)にprocessorのポインタを渡す。
これで、コア処置と UIの連携ができます。
setProcessor関数でコアの参照(ポインタ)をUI処置側に持たせて、
スライダーを変更した時のイベント(コールバック)内で、
processorの内部値を変更しています。
NewComonent.cppの中身
スライダーを動かしたら内部値を変更します。
スライダーのイベントで、値を変えているここ重要で
setParameterNotifyingHost
で、内部値の更新とともに、ホストアプリへも通知します。
これで、Logicでレコードしたりすれば、オートメーションに書かれるはずです。
if (sliderThatWasMoved == slider)
{
//[UserSliderCode_slider] -- add your slider handling code here..
if(processor != nullptr)
{
processor->beginParameterChangeGesture(0);
processor->setParameterNotifyingHost(0,slider->getValue());
processor->endParameterChangeGesture(0);
}
//[/UserSliderCode_slider]
}
enum と Index
上のソースだとIndexに0 を直接書いているけど、
GuitestAudioProcessor::Parameters::s1 はenumだけどintにキャストされるとIndex値になるので、
processor->setParameterNotifyingHost(GuitestAudioProcessor::Parameters::s1,slider->getValue());
とすると、enum値とIndexと一致してあとで見た時わかりやすい。
ParameterChangeGesture
この前後を
beginParameterChangeGestureとendParameterChangeGestureでくくられているのは何か?
これくくらないとどうなるか?
例えば、LogicでLatchモード(再生中もコントロールいじった時だけ上書きするモード)で動かした時
1.オートメーションに書かれた値で、コントロールの表示が連動し、UIが変わります。
これは先ほどのTimerで、
void GuitestAudioProcessorEditor::timerCallback()
{
adventCalnedar.updateParams(processor.UserParams[0]);
}
PluginEditorから30msec間隔で更新されつづけています。
void AdventCalendar::updateParams(float val)
{
slider->setValue(val,dontSendNotification);
}
ここで、スライダーに値がセットされています。
2.さらにユーザーが、スライダーを動かそうとする。
どちらの値が優先されるか?
が不明です。
(Unityでいうところの、「エディター実行中に編集しても編集結果が反映されない」と同じ状況。動作結果を表示したいUIと編集したいUIが同じために起きる混乱)
ここで、beginParameterChangeGesture でくくることで、
「今、このパラメータはスライダーで変更しようとユーザーがジェスチャーしている」
とホストアプリに伝えることで、 ホストアプリからのスライダー更新が抑制されるのです。
このホストからの抑制処理が来ないと、Latchモードとかで、ホストの処理が優先されたりして、
スライダーをいじっても変更できない恐れがあります。
追記
内部値の表示や範囲について
getParameter
VSTやAudioUnitが持っている内部値は、一定のインターフェースで保持される。
float型で、0.0~1.0に正規化された値。(じゃなくてもfloatなら良さそうだけど後述)
getParameterText
これを表示時に倍率かけたり、文字列に置き換えたりしたものがホストアプリに表示される仕組み。
Liveとかだと0.0~100とか1.0超えてもOKと聞いたけど、いろいろなDAWに対応しようと思うと低い仕様に合わせる方が無難か
落とし穴が
Logicのオートメーションの値が0.0~1.0表記
値の範囲を0.0~1.0にしている限りは問題ないのですが、
例えば0~127にしたい とした場合にAudioUnit(Logic固有?)の場合で混乱があります。
オートメーションや、コントロール表示の時は、相変わらず内部値をそのまま表示している。
エディタ表示の時だけ変換されるといった感じ。
AbletonLiveでもオートメーション値は0.0~1.0表記みたい。
これは、どう解決するものなのか・・・
まぁ、内部値は何か方法があるような。
とりあえずは動いているからよしとするか。
追記(2018-01-19)
AudioProcessor::getParameterって非推奨なのか!
AudioProcessorParameter使うと良いらしい。
使い方はここらあたりにある
https://juce.com/doc/tutorial_audio_parameter
setParameterNotifyingHostも非推奨?!
AudioProcessorParameterを使うと・・・
たとえば
void GUIComponent::sliderValueChanged (Slider* sliderThatWasMoved)
{
//[UsersliderValueChanged_Pre]
//[/UsersliderValueChanged_Pre]
if (sliderThatWasMoved == sliderAR)
{
//[UserSliderCode_sliderAR] -- add your slider handling code here..
processor->paramOsc1AR->beginChangeGesture();
*(processor->paramOsc1AR) = sliderAR->getValue();
processor->paramOsc1AR->endChangeGesture();
//[/UserSliderCode_sliderAR]
}
といった感じですみそう。