AudioUnitEffectExample読み解き #その2
前回は主にFilterKernel(AUKernelBase)を中心に、"どのように音響処理を書き込んでいくのか"を読み解いていきました。
ただ処理の内容だけ書いても、"それをどのように制御するのか"、"GUIにどのような情報を提供するのか"などのAudioUnitsにおける外面に関する部分をどんどん読み解いて来ましょう。
つまりは今回のAudioUnitEffectExampleのFilter(AUEffectBase)クラスやら定数などの宣言に着目していこうという回です。
開発環境 : Yosemite(10.10.3), Xcode 6.3
・Filter
まずは今回のメインであるFilterクラスがこちら。
class Filter : public AUEffectBase { public: Filter(AudioUnit component); virtual OSStatus Version() { return kFilterVersion; } virtual AUKernelBase* NewKernel() { return new FilterKernel(this); } virtual OSStatus GetPropertyInfo( AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, UInt32 &outDataSize, Boolean &outWritable ); virtual OSStatus GetProperty( AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void *outData ); virtual OSStatus GetParameterInfo( AudioUnitScope inScope, AudioUnitParameterID inParameterID, AudioUnitParameterInfo &outParameterInfo ); virtual OSStatus GetPresets(CFArrayRef *outData) const; virtual OSStatus NewFactoryPresetSet(const AUPreset &inNewFactoryPreset); /* CocoaGUIでしか使わない処理 virtual bool SupportsTail () { return true; } virtual Float64 GetTailTime() { return 0.001; } virtual Float64 GetLatency() { return 0.0; } */ protected: };
まず前回同様、CocoaGUIでしか使わない下三つの関数は消しておきます。
そしてFilterクラスの中身に入っていくのですがとりあえずAUEffectBaseというクラスを引き継いでますね。これはプロジェクト内にある"AUPublic > OtherBases"にあるので余裕のある人は見ればいいと思います。
余談として、自分はC/C++のポインタは右寄せ派です。このサンプルソースは右寄せだったり左寄せだったりバラバラなので、全部右寄せにしています。理由はTABとかで列合わせするときに綺麗に見えるという完全に個人的な意見です。こういうどうでもいところに神経質になったりするんです。
あとはBSD/オールマンのスタイルでいくかK&Rのスタイルとかね。自分は基本的には前者です。昔はソースコードを過密に書くことが好きだったのですが今ではそこまででもって感じです。
一番はその場その場で変えていくのがいいと思いますが、ソースコード内で流儀がバラバラ(?)なときは著しくストレスが溜まります。本当に、溜まります。
そして最後に、引数に*が使われていればポインタですが、&は参照渡しというんですね。どうやらクラスのインスタンスを引数にする際に使われることが多いとのことです。
・定数定義
AUDIOCOMPONENT_ENTRY(AUBaseProcessFactory, Filter) enum { kFilterParam_CutoffFrequency = 0, kFilterParam_Resonance = 1 }; // Parameter Name static CFStringRef kCutoffFreq_Name = CFSTR("cutoff frequency"); static CFStringRef kResonance_Name = CFSTR("resonance"); // Default Parameter & Limit Parameter const float kMinCutoffHz = 12.0; const float kDefaultCutoff = 1000.0; const float kMinResonance = -20.0; const float kMaxResonance = 20.0; const float kDefaultResonance = 0; // Factory presets static const int kPreset_One = 0; static const int kPreset_Two = 1; static const int kNumberPresets = 2; // Preset Name static AUPreset kPresets[kNumberPresets] = { { kPreset_One, CFSTR("Preset One") }, { kPreset_Two, CFSTR("Preset Two") } }; // Default Preset static const int kPresetDefault = kPreset_One; static const int kPresetDefaultIndex = 0;
Filterクラスの中身を見て行く前にそこで多用される定数などを確認していきましょう。といっても、大体がコメントに書いてある通りです。ここらへんを弄るとパラメータ名の表示とかデフォルトで設定されている値が帰れますよ、というくらいしか言うことがないです。
また最初のAUDIOCOMPONENT_ENTRY(AUBaseProcessFactory, Filter)というマクロに関してはイマイチ自分でも詳しく分かってません。これでComponent Managerとかにエントリーさせて、ソフトウェアとかで読み込めるようにしているのかも。あと引数のFilterはクラス名に依存しているっぽい。
例えばFilterクラスをTestクラスに書き換えた場合はここがTestとなる。またクラス名を書き換えた場合はAU Sourceにあるexpファイルの_FilterFactoryも_TestFactoryに変更する必要がある。もちろんだけどexpのファイル名は関係無いです。
・メソッド定義
ここからはFilterクラスで宣言されたメソッド群をどんどん定義していくという形です。
まずはコンストラクタから。それと、よくみたらデストラクタはないようですね。
Filter::Filter(AudioUnit component) : AUEffectBase(component)
{
SetParameter(kFilterParam_CutoffFrequency, kDefaultCutoff);
SetParameter(kFilterParam_Resonance, kDefaultResonance);
SetParamHasSampleRateDependency(true);
}
まずは引数にAudioUnitがありますが、これは一切ノータッチでデフォルトのパラメータをSetParameter()で設定している模様。あとはSetParamHasSampleRateDependencyがtrueになっているようです。これはそのままのとおりパラメータがサンプルレートに依存していることを指しています。
一応確認でkFilterParam_CutoffFrequency(Resonance)をみるとenumで設定された定数値の模様。所謂、パラメータのIDです。
OSStatus Filter::GetParameterInfo( AudioUnitScope inScope, AudioUnitParameterID inParameterID, AudioUnitParameterInfo &outParameterInfo ) { OSStatus result = noErr; outParameterInfo.flags = kAudioUnitParameterFlag_IsWritable + kAudioUnitParameterFlag_IsReadable; if (inScope == kAudioUnitScope_Global) { switch(inParameterID) { case kFilterParam_CutoffFrequency: AUBase::FillInParameterName (outParameterInfo, kCutoffFreq_Name, false); outParameterInfo.unit = kAudioUnitParameterUnit_Hertz; outParameterInfo.minValue = kMinCutoffHz; outParameterInfo.maxValue = GetSampleRate() * 0.5; outParameterInfo.defaultValue = kDefaultCutoff; outParameterInfo.flags += kAudioUnitParameterFlag_IsHighResolution; outParameterInfo.flags += kAudioUnitParameterFlag_DisplayLogarithmic; break; case kFilterParam_Resonance: AUBase::FillInParameterName (outParameterInfo, kResonance_Name, false); outParameterInfo.unit = kAudioUnitParameterUnit_Decibels; outParameterInfo.minValue = kMinResonance; outParameterInfo.maxValue = kMaxResonance; outParameterInfo.defaultValue = kDefaultResonance; outParameterInfo.flags += kAudioUnitParameterFlag_IsHighResolution; break; default: result = kAudioUnitErr_InvalidParameter; break; } } else { result = kAudioUnitErr_InvalidParameter; } return result; }
次のFilter::GetParameterInfo( … )は順に読み解いていった方がよさそう。
まずは呼び出されたらとりあえず返り値のOSStatusをnoErrにしておく。つまり問題なく呼び出されたということを指している。
outParameterInfo.flagsに関しては正直ちんぷんかんぷん。どうやら読み書き可能のフラグを立てているようだけどどういう仕組みかは分からんです。
その次はinScopeの中身がGlobalなScopeなのかを尋ねてから、GlobalならinParameterIDによってswitchでoutParameterInfoに書き込む内容を変えているようだ。もし存在しないIDが来たなら先ほどのOSStatusをkAudioUnitErr_InvalidParameterにして返すという形だ。もしGlobalなScopeでなかった場合でも同じエラーを返す処理をelseで書かれている。
そして前回に処理のほとんどを消したGetPropertyInfo( … ), GetProperty( … )については割愛。
最後はPresetあたりの処理を読み解いていきます。
OSStatus Filter::GetPresets (CFArrayRef *outData) const { if(outData == NULL) return noErr; CFMutableArrayRef theArray = CFArrayCreateMutable (NULL, kNumberPresets, NULL); for (int i = 0; i < kNumberPresets; ++i) { CFArrayAppendValue (theArray, &kPresets[i]); } *outData = (CFArrayRef)theArray; return noErr; } OSStatus Filter::NewFactoryPresetSet (const AUPreset &inNewFactoryPreset) { SInt32 chosenPreset = inNewFactoryPreset.presetNumber; for(int i = 0; i < kNumberPresets; ++i) { if(chosenPreset == kPresets[i].presetNumber) { switch(chosenPreset) { case kPreset_One: SetParameter(kFilterParam_CutoffFrequency, 200.0 ); SetParameter(kFilterParam_Resonance, -5.0 ); break; case kPreset_Two: SetParameter(kFilterParam_CutoffFrequency, 1000.0 ); SetParameter(kFilterParam_Resonance, 10.0 ); break; } SetAFactoryPresetAsCurrent (kPresets[i]); return noErr; } } return kAudioUnitErr_InvalidPropertyValue; }
まずGetPresets( … )はconstを宣言しているので、この関数内ではメンバ変数を書き換える処理はできません。
そして最初に引数で受け取ったoutDataがNullであれば何もせずにnoErrを返して処理を終了します。そうでなければCFMutableArray(CoreFoundationにおける可変長配列)をkNumberPresets(プリセットの数)だけ用意して、for文でkPresetsの中身を追加していき、最終的にoutDataにCFArrayRef(この場合は不変長配列なのかな?)にして代入している。
次に、NewFactoryPresetSet( … )では最初に引数として渡されたAUPreset(constなので変更不可)のメンバ変数にあるpresetNumberで選択されたプリセットを識別してるよう。そのプリセットのナンバーからあらかじめ用意しておいたパラメータをSetParameter( … )で設定しているよう。もしプリセットナンバーが範囲外ならkAudioUnitErr_InvalidPropertyValueというOSStatusを返している模様。
だいたいこんな感じで全部ある程度のところまで読み解くことができた。当面の目標は達成できたので、次は何かしらエフェクターを作ってみようと密かに画策中です。