title: Building a looping sequencer with AVAudioEngine
body: <p>I'm building a simple drum machine using the new AVAudioEngine capabilities in iOS 8, but am having trouble thinking through how I might build the looping mechanism.</p>
  
  <p>It seems like the only way to do this is to have a rotating buffer of <code>AVAudioPlayerNodes</code> and have some sort of job that constantly schedules buffer playbacks in the future. Is this correct? </p>
  
  <p>It would be nice if the nodes (and their buffers) only needed to be scheduled once. Then the nodes could all <code>reset</code>, then <code>play</code> again from the beginning every time the playhead reaches the end of the sequence.</p>
  
  <p>I've tried the latter by creating an empty sample buffer, scheduling it at the end of the sequencer, and attaching to its <code>completionHandler</code>, but it doesn't seem to work.</p>
  
  <pre><code>self.audioEngine = [[AVAudioEngine alloc] init];
  self.instrumentNodes = [[NSMutableArray alloc] initWithCapacity:12];
  
  AVAudioMixerNode *mixer = self.audioEngine.mainMixerNode;
  
  NSError *error;
  if (![self.audioEngine startAndReturnError:&amp;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 &lt; 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();
  }
  </code></pre>