[追記 2019/1/7]
AUGraphはiOS 12まででDeprecatedとなりました。本記事はそれをふまえてご参照ください。
前回の投稿で、リアルタイム・オーディオ処理について書きましたが、その続きになります。
今回はリアルタイム・タイムストレッチ(音程をそのままで、スピードを変更する処理)の実現方法について説明していきます。
今回の内容も、以下のブログにて以前書いた内容を整理したものになります。
http://www.loopsessions.com/blog/?p=39
http://www.loopsessions.com/blog/?p=44
前回の投稿もとに、修正箇所と追加内容について説明していきます。
今回のオーディオ関連のクラスの定義は以下のようになります。
@interface TimeStretchAudioIO ()
{
AudioStreamBasicDescription _outputFormat;
AUGraph _graph;
AudioUnit _remoteIOUnit;
AudioUnit _converterUnit;
AudioUnit _aUiPodTimeUnit; // add
AudioUnitParameterID _paramId; // add
AudioUnitParameterInfo *_paramInfo; // add
}
@property (readonly) ExtAudioFileRef extAudioFile;
@property (nonatomic, assign) UInt32 numberOfChannels;
@property (nonatomic, assign) SInt64 totalFrames;
@property (nonatomic, assign) SInt64 currentFrame;
@end
AUGraphの準備
まずは以下の関数の修正を行います。
リアルタイム・タイムストレッチを実現するには、AUiPodTimeOther Unit (kAudioUnitSubType_AUiPodTimeOther
)を使用します。
接続は以下の通りです。
Callback -> AUiPodTimeOther -> AUConverter -> Remote IO
- (OSStatus)initAUGraph
{
OSStatus ret = noErr;
// AUGraphの準備
NewAUGraph(&_graph);
AUGraphOpen(_graph);
// AUNodeの作成
AudioComponentDescription cd;
cd.componentType = kAudioUnitType_FormatConverter;
cd.componentSubType = kAudioUnitSubType_AUConverter;
cd.componentManufacturer = kAudioUnitManufacturer_Apple;
cd.componentFlags = 0;
cd.componentFlagsMask = 0;
AUNode converterNode;
AUGraphAddNode(_graph, &cd, &converterNode);
AUGraphNodeInfo(_graph, converterNode, NULL, &_converterUnit);
cd.componentType = kAudioUnitType_FormatConverter;
cd.componentSubType = kAudioUnitSubType_AUiPodTimeOther;
cd.componentManufacturer = kAudioUnitManufacturer_Apple;
cd.componentFlags = 0;
cd.componentFlagsMask = 0;
AUNode aUiPodTimeNode;
AUGraphAddNode(_graph, &cd, &aUiPodTimeNode);
AUGraphNodeInfo(_graph, aUiPodTimeNode, NULL, &_aUiPodTimeUnit);
cd.componentType = kAudioUnitType_Output;
cd.componentSubType = kAudioUnitSubType_RemoteIO;
cd.componentManufacturer = kAudioUnitManufacturer_Apple;
cd.componentFlags = 0;
cd.componentFlagsMask = 0;
AUNode remoteIONode;
AUGraphAddNode(_graph, &cd, &remoteIONode);
AUGraphNodeInfo(_graph, remoteIONode, NULL, &_remoteIOUnit);
// Callbackの作成
AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = renderCallback;
callbackStruct.inputProcRefCon = self;
AUGraphSetNodeInputCallback(_graph,
converterNode,
0, // bus number
&callbackStruct);
// 各NodeをつなぐためのASBDの設定
UInt32 size = sizeof(AudioStreamBasicDescription);
// converter I
ret = AudioUnitSetProperty(_converterUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, 0,
&_outputFormat, size);
// remoteIO I
ret = AudioUnitSetProperty(_remoteIOUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, 0,
&_outputFormat, size);
AudioStreamBasicDescription outputFormatTmp;
// [GET] AUiPodTimeOther I
ret = AudioUnitGetProperty(_aUiPodTimeUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, 0,
&outputFormatTmp, &size);
// converter O
ret = AudioUnitSetProperty(_converterUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output, 0,
&outputFormatTmp, size);
// Nodeの接続
// AUConverter -> AUiPodTimeOther
ret = AUGraphConnectNodeInput(_graph, converterNode, 0, aUiPodTimeNode, 0);
// AUiPodTimeOther -> Remote IO
ret = AUGraphConnectNodeInput(_graph, aUiPodTimeNode, 0, remoteIONode, 0);
// コンソールに現在のAUGraph内の状況を出力(デバッグ)
CAShow(_graph);
// AUGraphを初期化
ret = AUGraphInitialize(_graph);
// AUiPodTimeOtherのパラメータを初期化
[self initAUiPodTimeOther];
return ret;
}
AUiPodTimeOther Unit を追加したことで、各Unitのオーディオフォーマットを合わせることを考慮しておく必要があります。細かな説明は省きますが、「ASBD(Audio Stream Basic Description)の設定」の箇所を確認してみてください。
パラメータの設定
ここからは追加の内容です。AUiPodTimeOther Unit のパラメータを設定するための実装になります。
初期化処理
- (void)initAUiPodTimeOther
{
// AudioUnitGetProperty で取得する paramList のサイズを取得
UInt32 size = sizeof(UInt32);
AudioUnitGetPropertyInfo(_aUiPodTimeUnit,
kAudioUnitProperty_ParameterList,
kAudioUnitScope_Global,
0,
&size,
NULL);
int numOfParams = size / sizeof(AudioUnitParameterID);
NSLog(@"numOfParams = %d", numOfParams);
// paramList の各IDを取得
AudioUnitParameterID paramList[numOfParams];
AudioUnitGetProperty(_aUiPodTimeUnit,
kAudioUnitProperty_ParameterList,
kAudioUnitScope_Global,
0,
paramList,
&size);
_paramInfo = (AudioUnitParameterInfo *)malloc(numOfParams * sizeof(AudioUnitParameterInfo));
for (int i = 0; i < numOfParams; i++) {
NSLog(@"paramList[%d] = %d", i, (unsigned int)paramList[i]);
_paramId = paramList[i];
// 各IDのパラメータを取得
size = sizeof(_paramInfo[i]);
AudioUnitGetProperty(_aUiPodTimeUnit,
kAudioUnitProperty_ParameterInfo,
kAudioUnitScope_Global,
paramList[i],
&_paramInfo[i],
&size);
NSLog(@"paramInfo.name = %s", _paramInfo[i].name);
NSLog(@"paramInfo.minValue = %f", _paramInfo[i].minValue);
NSLog(@"paramInfo.maxValue = %f", _paramInfo[i].maxValue);
NSLog(@"paramInfo.defaultValue = %f", _paramInfo[i].defaultValue);
// init
AudioUnitSetParameter(_aUiPodTimeUnit,
paramList[i],
kAudioUnitScope_Global,
0,
_paramInfo[i].defaultValue,
0);
}
}
paramList という変数に AUiPodTimeOther Unit に関するパラメータがenum型で入ります。
一般的なAudioUnitのパラメータ情報はAudioUnit/AudioUnitParameters.h
に定義されているのですが、なぜか今回の対象の AUiPodTimeOther Unit は定義されていません。ただ、上記のように実装することで、あらゆるAudioUnitのパラメータ情報を取得することができますので、あまり気にすることはないかと思います。
今回の場合で initAUiPodTimeOther 関数を処理した際の NSLog の出力結果は以下のようになります。
numOfParams = 1
paramList[0] = 0
paramInfo.name = rate
paramInfo.minValue = 0.500000
paramInfo.maxValue = 2.000000
paramInfo.defaultValue = 1.000000
つまり、0.5〜2.0倍の範囲で再生速度が変更可能であることがわかります。
パラメータの値の代入
以下の関数を呼び出すことで、再生速度が変更できます。スライダーで操作するのが良いかと思います。
以下の実装は簡略化していますので、実際にはエラー処理を入れた方が良いと思います。(サンプルコードを参照してください)
- (void)setPlaybackRate:(Float32)value
{
AudioUnitSetParameter(_aUiPodTimeUnit,
_paramId,
kAudioUnitScope_Global,
0,
value,
0);
}
以上です。
今回のサンプルプログラムは以下にあります。(TimeStretchAudioIO)
https://github.com/JunichiMinamino/AudioObjCSample
あと、今回の実装をもとに作成したアプリもありますので、よろしければ試してみてください。
タイムストレッチ(再生速度変更)/イコライザー
https://itunes.apple.com/jp/app/time-stretcher/id637376117?mt=8