2015-03-17 8 views
1

Я создаю простой барабанный станок, используя новые возможности AVAudioEngine в iOS 8, но у меня возникли проблемы с пониманием того, как я могу построить механизм циклирования.Построение циклического секвенсора с AVAudioEngine

Кажется, единственный способ сделать это - иметь вращающийся буфер AVAudioPlayerNodes и иметь какое-то задание, которое постоянно планирует воспроизведение буфера в будущем. Это верно?

Было бы хорошо, если бы узлы (и их буферы) нужно было планировать только один раз. Затем узлы могли бы все 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(); 
} 
+0

Вы должны добавить к настройкам при планировании своего буфера: AVAudioPlayerNodeBufferLoops. [playerNode scheduleBuffer: buffer atTime: [AVAudioTime timeWithSampleTime: sampleTime atRate: sampleRate] опции: AVAudioPlayerNodeBufferInterrupts | AVAudioPlayerNodeBufferLoops completeHandler: nil]; – Brett

ответ

0

Создание Loopable буфера либо путем считывания барабана петли/бит из файла (как вы делаете выше) или создать его программно.

Затем планируйте этот буфер с помощью опции AVAudioPlayerNodeBufferLoops, и он автоматически зациклится на вас.

У вас может быть несколько игровых узлов, но я думаю об этом в терминах треков (например, один трек в 4-дорожечном рекордере), т. Е. Для воспроизведения звуков одновременно.

Затем вы планируете буферы на этих игровых узлах. Таким образом, у вас может быть узел игрового счетчика и узел басового барабана и т. Д., Ваши буферы будут самими образцами.

Я написал тональный генератор DTMF (тональных сигналов телефона), который воспроизводит два синхронных сигнала синусоидальной волны в одно и то же время, для этого я использую два игрокаNodes каждый с буфером с синусоидальной волной.

В моем методе инициализации (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, если это поможет.

+0

Я был бы признателен, если вы указали код для заполнения буфера. Благодарю. – RonH