Q:

Synchronizing UIView animation speed with audio playback

I have a vertical one-pixel view that I want to move over a grid that represents notes being played. Depending on the BPM (beats per minute) of the song the animation should be slower or faster. The notes are squeezed next to each other and have a width of 25px.
The way I'm doing this right now is by moving the view 1px to the right every time x samples have passed:
int bpm = 90;
int interval = (44100 * 60.0f / song.bpm) / 25.0f; // = 1176
// sample rate=44100 multiplied by 60 (seconds) divided by number of pixels

So with BPM=90 I move the UIView 1px every 1176 samples played. This works fairly well but gives me problems if the BPM is higher than 180. At that point the view is behind on the music that is played and when BPM goes up to 240 the view is way off.
How can I do this with the built in animation features of Views and base the animation speed on the calculations I posted above? I want to start the animation once the music starts playing so I don't need to manually move it pixel by pixel.

A:

If I understood correctly, what you want to do is:

Draw a static background image.
Draw a vertical line on top of that which moves from left to right.
Synchronize the line's x position with the audio playback time.

There are two problems to solve:

Drawing performance
Audio synchronization

To get the drawing performance right there are two implementations to consider:

Using Core Animation layers and animation (much easier to implement than 2.)
Using OpenGL to draw the whole view (possibly a little faster and a little less latency than 1.)

Implementing the view in OpenGL is too much code to post as an example, but here's code for the Core Animation option:
// In the UIViewController:

- (CALayer *)markerLayer
{
    static NSString *uniqueName = @"com.mydomain.MarkerLayer";
    for (CALayer *layer in self.view.layer.sublayers) {
        if ([layer.name isEqualToString:uniqueName])
            return layer;
    }

    CALayer *markerLayer = [CALayer layer];
    markerLayer.backgroundColor = [UIColor whiteColor].CGColor;
    markerLayer.name = uniqueName;
    markerLayer.anchorPoint = CGPointZero;
    [self.view.layer addSublayer:markerLayer];
    return markerLayer;
}

- (void)startAnimationWithDuration:(NSTimeInterval)duration
{
    [CATransaction begin];
    [CATransaction setDisableActions:YES];

    CALayer *markerLayer = [self markerLayer];

    markerLayer.bounds = (CGRect){.size = {1, self.view.bounds.size.height}};
    markerLayer.position = CGPointZero;
    [CATransaction commit];

    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position.x"];
    animation.fromValue = @0.0f;
    animation.toValue = [NSNumber numberWithFloat:self.view.bounds.size.width];
    animation.duration = duration;
    [markerLayer addAnimation:animation forKey:@"audioAnimation"];
}

Audio synchronization is very much depending on the way you are playing it, for example using AVFoundation or Core Audio. To get precise synchronization you would need some kind of callback from the audio engine that tells you where exactly the playback is right now or when it did start.
Maybe it is enough to just call startAnimationWithDuration: immediately before you start the audio but that seems kind of fragile and depends on the latency of the audio framework.

