UIImageView animation based on volume level feedback

558 views Asked by At

I have a requirement to create a audio level visualizer with a custom pattern. I have the image set as png's. My current approach is like this

1) Get the audio level of the microphone

2) Load the relevant image to the UIImageView based on the volume level.

// audio level timer
self.levelTimer = [NSTimer scheduledTimerWithTimeInterval: 0.001 target: self selector: @selector(levelTimerCallback:) userInfo: nil repeats: YES];

ImageView

- (void)levelTimerCallback:(NSTimer *)timer {
[self.audioRecorder updateMeters];
const double ALPHA = 0.05;
double peakPowerForChannel = pow(10, (0.05 * [self.audioRecorder peakPowerForChannel:0]));
lowPassResults = ALPHA * peakPowerForChannel + (1.0 - ALPHA) * lowPassResults;

//NSLog(@"Average input: %f Peak input: %f Low pass results: %f", [self.audioRecorder averagePowerForChannel:0], [self.audioRecorder peakPowerForChannel:0], lowPassResults);

if (lowPassResults > 0.0 && lowPassResults <= 0.05){
    imgViewRecordAnimation.image = nil;
    imgViewRecordAnimation.image = [UIImage imageNamed:@"anim1"];
}
if (lowPassResults > 0.06 && lowPassResults <= 0.10){
    imgViewRecordAnimation.image = nil;
    imgViewRecordAnimation.image = [UIImage imageNamed:@"anim2"];
}
if (lowPassResults > 0.11 && lowPassResults <= 0.15){
    imgViewRecordAnimation.image = nil;
    imgViewRecordAnimation.image = [UIImage imageNamed:@"anim3"];
}
if (lowPassResults > 0.16 && lowPassResults <= 0.20){
    imgViewRecordAnimation.image = nil;
    imgViewRecordAnimation.image = [UIImage imageNamed:@"anim4"];
}
if (lowPassResults > 0.21 && lowPassResults <= 0.25){
    imgViewRecordAnimation.image = nil;
    imgViewRecordAnimation.image = [UIImage imageNamed:@"anim5"];
}
if (lowPassResults > 0.26 && lowPassResults <= 0.30){
    imgViewRecordAnimation.image = nil;
    imgViewRecordAnimation.image = [UIImage imageNamed:@"anim6"];
}
if (lowPassResults > 0.31 && lowPassResults <= 0.35){
    imgViewRecordAnimation.image = nil;
    imgViewRecordAnimation.image = [UIImage imageNamed:@"anim7"];
}
if (lowPassResults > 0.36 && lowPassResults <= 0.40){
    imgViewRecordAnimation.image = nil;
    imgViewRecordAnimation.image = [UIImage imageNamed:@"anim8"];
}
if (lowPassResults > 0.41 && lowPassResults <= 0.45){
    imgViewRecordAnimation.image = nil;
    imgViewRecordAnimation.image = [UIImage imageNamed:@"anim9"];
}
if (lowPassResults > 0.46 && lowPassResults <= 0.50){
    imgViewRecordAnimation.image = nil;
    imgViewRecordAnimation.image = [UIImage imageNamed:@"anim10"];
}
if (lowPassResults > 0.51 && lowPassResults <= 0.55){
    imgViewRecordAnimation.image = nil;
    imgViewRecordAnimation.image = [UIImage imageNamed:@"anim11"];
}
if (lowPassResults > 0.56 && lowPassResults <= 0.60){
    imgViewRecordAnimation.image = nil;
    imgViewRecordAnimation.image = [UIImage imageNamed:@"anim12"];
}
if (lowPassResults > 0.61 && lowPassResults <= 0.65){
    imgViewRecordAnimation.image = nil;
    imgViewRecordAnimation.image = [UIImage imageNamed:@"anim13"];
}
if (lowPassResults > 0.66 && lowPassResults <= 0.70){
    imgViewRecordAnimation.image = nil;
    imgViewRecordAnimation.image = [UIImage imageNamed:@"anim14"];
}
if (lowPassResults > 0.71 && lowPassResults <= 0.75){
    imgViewRecordAnimation.image = nil;
    imgViewRecordAnimation.image = [UIImage imageNamed:@"anim15"];
}
if (lowPassResults > 0.76 && lowPassResults <= 0.80){
    imgViewRecordAnimation.image = nil;
    imgViewRecordAnimation.image = [UIImage imageNamed:@"anim16"];
}
if (lowPassResults > 0.81 && lowPassResults <= 0.85){
    imgViewRecordAnimation.image = nil;
    imgViewRecordAnimation.image = [UIImage imageNamed:@"anim17"];
}
if (lowPassResults > 0.86 && lowPassResults <= 0.90){
    imgViewRecordAnimation.image = nil;
    imgViewRecordAnimation.image = [UIImage imageNamed:@"anim18"];
}
if (lowPassResults > 0.86){
    imgViewRecordAnimation.image = nil;
    imgViewRecordAnimation.image = [UIImage imageNamed:@"anim19"];
}

}

But the output is not smooth as a real visualizer animation. What should be the best approach? Share there's any better ways to do this.

Couple of images

enter image description here

enter image description here

enter image description here

2

There are 2 answers

2
Trident On

If you feel the image changes is looking jerky, you better apply animations on them. By the way, you do not need to set nil to imgViewRecordAnimation.image, as you are setting proper images after that all the times, which will introduce unnecessary delay. You can also make all the if statements to else if, otherwise each time all the if statements get executed.

Coming to the solution, you can simply follow the link that @MileAtNobel posted. Or if you do not want to do much code changes, you can apply simple animation on backgroundColor property. You can remove the timer also, check the below code and see if the code is working good. I also tried to optimize the code with close resemblance, I hope this improves the performance.

BOOL flagToContinue ; //assign NO to this flag to stop updating the images

-(void)levelTimerCallback
{
    NSLog(@"Volume Logic Begins") ;//Volume level logic begins
    [self.audioRecorder updateMeters];
    const double ALPHA = 0.05;
    double peakPowerForChannel = pow(10, (0.05 * [self.audioRecorder peakPowerForChannel:0]));
    lowPassResults = ALPHA * peakPowerForChannel + (1.0 - ALPHA) * lowPassResults ;
    NSLog(@"Volume Logic ends") ;//Volume level logic ends

    [UIView animateWithDuration:0.001f
              delay:0.0f
            options: UIViewAnimationOptionCurveLinear
         animations:^{
           mgViewRecordAnimation.backgroundColor = [UIColor colorWithPatternImage:[NSString stringWithFormat:@"anim%d", (int)ceil(lowPassResults * 20)]];
         }
         completion:^(BOOL finished) {
           if(finished && flagToContinue) [self levelTimerCallback];
    }];
}
0
shoan On

Ok, I will not give you a direct solution, but will try to suggest a solution, which you will have to try. So, instead of changing the image of the imageView, write a custom control. Create a subclass of a view. Override the drawrect method. In that, either you can draw the required pattern depending upon the control value or you draw the image itself. But the first approach is recommended. Custom drawing is smooth as compared to changing the image. Please try it out and let me know it this works.