LoginSignup
11
12

More than 5 years have passed since last update.

AUGraphを用いたリアルタイム・タイムストレッチ

Last updated at Posted at 2016-02-26

[追記 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

11
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
12