1

iOS 8 の新しい AVAudioEngine 機能を使用して単純なドラム マシンを構築していますが、ループ メカニズムを構築する方法を考えるのに苦労しています。

これを行う唯一の方法は、回転バッファをAVAudioPlayerNodes用意し、将来的にバッファの再生を常にスケジュールするある種のジョブを用意することです。これは正しいです?

ノード (およびそのバッファー) を 1 回だけスケジュールする必要があると便利です。次に、再生ヘッドがシーケンスの最後に到達するたびに、ノードはすべて最初から再生されresetます。play

空のサンプル バッファを作成し、シーケンサの最後にスケジュールし、その にアタッチして、後者を試しましたcompletionHandlerが、うまくいかないようです。

self.audioEngine = [[AVAudioEngine alloc] init];
self.instrumentNodes = [[NSMutableArray alloc] initWithCapacity:12];

AVAudioMixerNode *mixer = self.audioEngine.mainMixerNode;

NSError *error;
if (![self.audioEngine startAndReturnError:&error]) {
    NSLog(@"Error starting engine: %@", error);
} else {
    NSLog(@"Started engine");

    NSURL *url = [[NSBundle mainBundle] URLForResource:@"Bleep_C3" withExtension:@"caf"];
    AVAudioFile *file = [[AVAudioFile alloc] initForReading:url error:nil];

    AVAudioPCMBuffer *buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:file.processingFormat frameCapacity:(AVAudioFrameCount)file.length];
    [file readIntoBuffer:buffer error:nil];

    double sampleRate = buffer.format.sampleRate;

    AVAudioPCMBuffer *blankBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:buffer.format frameCapacity:0];
    AVAudioPlayerNode *loopingNode = [[AVAudioPlayerNode alloc] init];
    [self.audioEngine attachNode:loopingNode];
    [self.audioEngine connect:loopingNode to:mixer format:[mixer outputFormatForBus:0]];

    __block void (^schedule_loops)() = ^{
        for (NSUInteger i = 0; i < 16; i++) {

            double sampleTime = sampleRate * (0.2 * i);
            double loopTime = sampleRate * (0.2 * 16);

            AVAudioPlayerNode *playerNode = [[AVAudioPlayerNode alloc] init];
            [self.audioEngine attachNode:playerNode];
            [self.audioEngine connect:playerNode to:mixer format:[mixer outputFormatForBus:0]];

            [playerNode scheduleBuffer:buffer atTime:[AVAudioTime timeWithSampleTime:sampleTime atRate:sampleRate] options:AVAudioPlayerNodeBufferInterrupts completionHandler:^{
                [playerNode stop];
                [playerNode reset];
                [self.audioEngine disconnectNodeOutput:playerNode];
            }];

            [playerNode play];

            if (i == 15) {
                [loopingNode scheduleBuffer:blankBuffer atTime:[AVAudioTime timeWithSampleTime:loopTime atRate:sampleRate] options:AVAudioPlayerNodeBufferInterrupts completionHandler:^{
                    NSLog(@"Looping");
                    [loopingNode stop];
                    schedule_loops();
                }];
                [loopingNode play];
            }
        }
    };

    schedule_loops();
}
4

1 に答える 1

0

ファイルからドラム ループ/ビートを読み取るか (上記のように)、プログラムで作成して、ループ可能なバッファーを作成します。

次に、そのバッファを AVAudioPlayerNodeBufferLoops オプションでスケジュールすると、自動的にループします。

複数のプレーヤー ノードを持つことができますが、これらはトラック (4 トラック レコーダーの 1 つのトラックのようなもの)、つまり同時にサウンドを再生するという観点から考えています。

次に、これらのプレーヤー ノードでバッファーをスケジュールします。したがって、シンバル プレーヤー ノードやバスドラム プレーヤー ノードなどを持つことができ、バッファはサンプル サウンド自体になります。

2 つのループ可能な正弦波トーンを同時に再生する DTMF (電話トーン) トーン ジェネレーターを作成しました。これを行うには、それぞれループ可能な正弦波バッファーを持つ 2 つの playerNode を使用します。

私のinitメソッド(createAudioBufferWithLoopableSineWaveFrequency:pcmBufferを埋めるだけ):

@property (nonatomic, readonly) AVAudioPlayerNode *playerOneNode;
@property (nonatomic, readonly) AVAudioPlayerNode *playerTwoNode;    
@property (nonatomic, readonly) AVAudioPCMBuffer* pcmBufferOne;
@property (nonatomic, readonly) AVAudioPCMBuffer* pcmBufferTwo;

[...]

    _playerOneNode = [[AVAudioPlayerNode alloc] init];
    [_audioEngine attachNode:_playerOneNode];

    [_audioEngine connect:_playerOneNode to:_mixerNode format:[_playerOneNode outputFormatForBus:0]];

    _pcmBufferOne = [self createAudioBufferWithLoopableSineWaveFrequency:frequency1];

    _playerTwoNode = [[AVAudioPlayerNode alloc] init];
    [_audioEngine attachNode:_playerTwoNode];

    [_audioEngine connect:_playerTwoNode to:_mixerNode format:[_playerTwoNode outputFormatForBus:0]];

    _pcmBufferTwo = [self createAudioBufferWithLoopableSineWaveFrequency:frequency2];

私のプレイ方法では:

if (_playerOneNode && _pcmBufferOne)
{
    [_playerOneNode scheduleBuffer:_pcmBufferOne atTime:nil options:AVAudioPlayerNodeBufferLoops completionHandler:^{

    }];

    [_playerOneNode play];
}

if (_playerTwoNode && _pcmBufferTwo)
{
    [_playerTwoNode scheduleBuffer:_pcmBufferTwo atTime:nil options:AVAudioPlayerNodeBufferLoops completionHandler:^{

    }];

    [_playerTwoNode play];
}

私の停止方法:

[_playerOneNode stop];
[_playerTwoNode stop];

役に立ったら、私の github プロジェクトを紹介できます。

于 2015-04-21T01:41:02.717 に答える