今回はそのUIから実際にパラメータを変更できるようにしてみました。
UIが絡むケースとして、主に
1: UIからAudioUnitのパラメータを変更するこの2つがあると思います。2の外部というのは例えばプログラム側でAudioUnitのパラメータを直接変更した場合やプリセットを変更した時が挙げられます。
2: 外部から変更されたAudioUnitのパラメータをUIに反映する
1: UIからAudioUnitのパラメータを変更する
この場合は典型的なCocoaアプリと同じように、ビューコントローラにアクションを
送ります。
- (IBAction)changeFrequencyAction:(id)sender {self.freq.value = [sender floatValue];}- (IBAction)changeDepthAction:(id)sender {self.depth.value = [sender floatValue];}
ちなみにsenderはNSSliderです。
freqとdepthプロパティはAudioUnitのparameterTreeにセットしたAUParameterで
というふうに値をセットしています。- (void)connectViewWithAU {AUParameterTree *paramTree = self.unit.parameterTree;if (paramTree) {self.depth = [paramTree valueForKey: VibratoParameterIdentifierDepth];self.freq = [paramTree valueForKey: VibratoParameterIdentifierFrequency];
VibratoParameterIdentifierDepthとVibratoParameterIdentifierFrequencyは以前の記事でAudioUnitにパラメータを追加した時に定義したidentifierです。
このidentifierをAUParameterTreeにvalueForKey:のkeyとして指定すると
対応するAUParameterが得られるようです。
さて、AUParameterのvalueが変化すると、以前の記事で実装した
で書いたblockが実行され、AudioUnit内部のパラメータ(共にfloat)が変更される仕組みになっています。self.parameterTree.implementorValueObserver = ^(AUParameter *param, AUValue value) {switch (param.address) {case VibratoParameterAddressFrequency:unit.freq = value;break;case VibratoParameterAddressDepth:unit.depth = value;default:break;}};
2: 外部から変更されたAudioUnitのパラメータをUIに反映する
こちらのケースに関しては上手く説明できないので該当部分を全部載せます。orz
まず、下部にあるaddObserver:forKeyPath:options:context:はKey-Value Observingの手法で- (void)connectViewWithAU {AUParameterTree *paramTree = self.unit.parameterTree;if (paramTree) {self.depth = [paramTree valueForKey: VibratoParameterIdentifierDepth];self.freq = [paramTree valueForKey: VibratoParameterIdentifierFrequency];// prevent retain cycle in parameter observer__weak AudioUnitViewController *weakSelf = self;__weak AUParameter *weakFreq = self.freq;__weak AUParameter *weakDepth = self.depth;self.token = [paramTree tokenByAddingParameterObserver:^(AUParameterAddress address, AUValue value) {_PF;__strong AudioUnitViewController *strongSelf = weakSelf;__strong AUParameter *strongFreq = weakFreq;__strong AUParameter *strongDepth = weakDepth;dispatch_async(dispatch_get_main_queue(), ^{if (address == strongFreq.address){strongSelf->_freqSlider.floatValue = value;} else if (address == strongDepth.address) {strongSelf->_depthSlider.floatValue = value;}});}];self.freqSlider.floatValue = self.freq.value;self.depthSlider.floatValue = self.depth.value;[self.unit addObserver:self forKeyPath:@"allParameterValues"options:NSKeyValueObservingOptionNewcontext:self.token];} else {NSLog(@"paramTree is NULL!\n");}}
allParameterValuesキーの値が変更された時に
が実行されるのでblockの中でfreqSliderとdepthSliderの値が変更されます。- (void)observeValueForKeyPath:(NSString *)keyPathofObject:(id)objectchange:(NSDictionary<NSString *, id> *)changecontext:(void *)context{dispatch_async(dispatch_get_main_queue(), ^{self.freqSlider.floatValue = self.freq.value;self.depthSlider.floatValue = self.depth.value;});}
allParameterValuesに関してはAUAudioUnit.hで
と書かれており、プリセットが選択された時に変更されるkeyのようですね。AUAudioUnit has an additional pseudo-property, "allParameterValues", on which KVOnotifications are issued in response to certain events where potentially all parametervalues are invalidated (e.g. selection of a preset or setting fullState).
個別にパラメータが変更された場合、tokenByAddingParameterObserver:で書いたblockが実行されるようです。
引数としてAUParameterAddressが渡ってくるので、この値でどのパラメータが変更になったかを判断して、スライダーの値を変更してやります。
なお、この実装はFilterDemoViewControllerを参考にしましたが、
__weakや__strongを使って面倒なことをやっている理由は理解できてません。orz
インスタンス変数を直接使ったり、"->"を使って意地でもプロパティを避けようとしている感じはあるのですが、自分がARCやblock, GCDに慣れてないこともあり、
いまいち理由が分かっていません。
ちなみに、スライダーから値を変更した時にも巡り巡ってtokenByAddingParameterObserver:で書いたblockが実行されるようです。
これでUI <-> AUParameter -> AudioUnitという流れのパラメータの変更は追随できるようになったと思います。
(AUParameter <- AudioUnitという流れに関しては、parameterTree.implementorValueProviderにblockを指定したのである程度できているとは思いますが、即時反映されるわけではないんじゃないかと思います。この辺は全くの未確認です)
というわけで、無事Hosting AUで音を鳴らしながらパラメータをUIから変更できるようになりました!