Attention! Translated article might be found on my English blog.

2016年8月2日火曜日

自作v3 AudioUnitにパラメータを追加する

AudioUnitV3Example: A Basic AudioUnit Extension and Host ImplementationのFilterDemoを参考にビブラートユニットを作成中です。

今回はパラメータを追加してみました。
AudioUnit App Extensionのテンプレートでは
// Create parameter objects.
AUParameter *param1 = [AUParameterTree createParameterWithIdentifier:@"param1" name:@"Parameter 1" address:myParam1 min:0 max:100 unit:kAudioUnitParameterUnit_Percent unitName:nil flags:0 valueStrings:nil dependentParameters:nil];
としてParameter 1というパラメータを追加しているようです。
addressに指定しているmyParam1というのは
// Define parameter addresses. (These needn't be static).
#define myParam1 0
と定義されており、なにやら意味不明ですが、FilterDemoでは
enum {
FilterParamCutoff = 0,
FilterParamResonance = 1
};
とenumを指定しており、連番であれば良いのではないでしょうか。

ひとまず今回は
typedef NS_ENUM(AUParameterAddress, VibratoParameterAddress) {
    VibratoParameterAddressFrequency = 0,
    VibratoParameterAddressDepth = 1
};
と、それっぽくしてみました。
で、実際にパラメータを以下のように追加しました。
AUParameter *freqParameter = [AUParameterTree createParameterWithIdentifier:VibratoParameterIdentifierFrequency
                                                                           name:VibratoParameterNameFrequency
                                                                        address:VibratoParameterAddressFrequency
                                                                            min:0
                                                                            max:1
                                                                           unit:kAudioUnitParameterUnit_Hertz
                                                                       unitName:nil
                                                                          flags:0
                                                                   valueStrings:nil
                                                            dependentParameters:nil];
freqParameter.value = .0025;
self.freq = freqParameter.value;
    
AUParameter *depthParameter = [AUParameterTree createParameterWithIdentifier:VibratoParameterIdentifierDepth
                                                                            name:VibratoParameterNameDepth
                                                                         address:VibratoParameterAddressDepth
                                                                             min:0
                                                                             max:250
                                                                            unit:kAudioUnitParameterUnit_Seconds
                                                                        unitName:nil
                                                                           flags:0
                                                                    valueStrings:nil
                                                             dependentParameters:nil];
depthParameter.value = 250;
self.depth = depthParameter.value;
    
self.parameterTree = [AUParameterTree createTreeWithChildren:@[freqParameter, depthParameter]];
なお、
NSString *VibratoParameterIdentifierFrequency = @"VibratoParameterIdentifierFrequency";
NSString *VibratoParameterIdentifierDepth = @"VibratoParameterIdentifierDepth";

NSString *VibratoParameterNameFrequency = @"Frequency";
NSString *VibratoParameterNameDepth = @"Depth";
と定義しました。

で、これらのパラメータを外部から変更をどう感知するかですが、FilterDemoによると、
// implementorValueObserver is called when a parameter changes value.
_parameterTree.implementorValueObserver = ^(AUParameter *param, AUValue value) {
        filterKernel->setParameter(param.address, value);
};
// implementorValueProvider is called when the value needs to be refreshed.
_parameterTree.implementorValueProvider = ^(AUParameter *param) {
return filterKernel->getParameter(param.address);
};
となっており、パラメータの設定や取得の際にはblockが実行されるようです。
ひとまず今回は

MyAudioUnit *unit = self;
self.parameterTree.implementorValueObserver = ^(AUParameter *param, AUValue value) {
    switch (param.address) {
        case VibratoParameterAddressFrequency:
            unit.freq = value;
            break;
        case VibratoParameterAddressDepth:
            unit.depth = value;
        default:
            break;
    }
};
    
self.parameterTree.implementorValueProvider = ^(AUParameter *param) {
    switch (param.address) {
        case VibratoParameterAddressFrequency:
            return unit.freq;
        case VibratoParameterAddressDepth:
            return unit.depth;
        default:
            break;
    }
    return 0.0f;
};
としてみました。

あと、パラメータを文字列として表現するためのblockが必要なようですね。
FilterDemoでは
// A function to provide string representations of parameter values.
_parameterTree.implementorStringFromValueCallback = ^(AUParameter *param, const AUValue *__nullable valuePtr) {
AUValue value = valuePtr == nil ? param.value : *valuePtr;
switch (param.address) {
case FilterParamCutoff:
return [NSString stringWithFormat:@"%.f", value];
case FilterParamResonance:
return [NSString stringWithFormat:@"%.2f", value];
default:
return @"?";
}
};
となっています。
これに倣って
self.parameterTree.implementorStringFromValueCallback = ^(AUParameter *param, const AUValue *__nullable valuePtr) {
        AUValue value = valuePtr == nil ? param.value : *valuePtr;
        
        switch (param.address) {
            case VibratoParameterAddressFrequency:
                return [NSString stringWithFormat:@"%.f", value];
            case VibratoParameterAddressDepth:
                return [NSString stringWithFormat:@"%.0f", value];
            default:
                return @"?";
        }
    };
としました。

ひとまずこれでパラメータの設定は完了したと思います。

実際に外部からパラメータを変えてみようと思ったのですが……
なんと、AUV3HostでもHosting AUでもパラメータ設定用のUIが表示できない… orz

v3のAUだと自前のUIが必要かもしれません。
なので、パラメータの確認の前にUI作りを行いたいと思います…

今回はここまで!