문제

I have built an emulated Analog VU Meter for a recording app and have everything hooked up properly and working the way I expect except for one aspect. If you watch this 13-second video of the VU meter in action, you will see that the needle bounces all over the place and is not really what would happen in a real VU meter. For an example of what I am looking for, try out the Apple "Voice Memos" app and see.

My logic so far is easy:

#define VU_METER_FREQUENCY                                      1.0/5.0

- (void)someMethod {
    _updateTimer = [NSTimer 
                    scheduledTimerWithTimeInterval:VU_METER_FREQUENCY
                    target:self 
                    selector:@selector(_refresh) 
                    userInfo:nil 
                    repeats:YES];
}

- (void)_refresh {  
    // if we have no queue, but still have levels, gradually bring them down
    if (_delegate == nil) {
        CFAbsoluteTime thisFire = CFAbsoluteTimeGetCurrent();
        // calculate how much time passed since the last draw
        CFAbsoluteTime timePassed = thisFire - _peakFalloffLastFire;
        needleValue = needleValue - timePassed * VU_METER_LEVEL_FALL_OFF_PER_SECOND;
        if (needleValue < VU_METER_MIN_DB) {
            needleValue = VU_METER_MIN_DB;
            TT_INVALIDATE_TIMER(_updateTimer);
        }
        _peakFalloffLastFire = thisFire;
    } else {
        prevNeedleValue = needleValue;
        needleValue = [_delegate currentDB];
    }
    [self updateNeedle];
}

- (void)updateNeedle {
    [UIView beginAnimations:nil context:NULL]; // arguments are optional
    [UIView setAnimationDuration:VU_METER_FREQUENCY];
    [UIView setAnimationCurve:(needleValue > prevNeedleValue ? UIViewAnimationCurveEaseOut : UIViewAnimationCurveEaseIn)];
    CGFloat radAngle = [self radianAngleForValue:needleValue];
    self.needle.transform = CGAffineTransformMakeRotation(radAngle);
    [UIView commitAnimations];
}

Basically, I setup a timer to run at VU_METER_FREQUENCY and update the needle rotation using a UIView animation with easing that is preferential to keep the needle higher. I am looking for a way to adjust this somehow to provide a smoother needle, with my benchmark being as close as possible to Apple's analog VU Meter. To get the needleValue, I am using AudioQueue's mAveragePower and querying it every time currentDB is called. How can I smooth this?

도움이 되었습니까?

해결책

One thing I would suggest is changing this.

#define VU_METER_FREQUENCY    1.0/5.0

That says update 5x a second, the issue is that I think apple will hold 0.2s of samples, so you really are getting an average of the sounds, hence the meter is not really following the highs and lows of the sounds but more of a lower average.

I think this setting can go as high as 1.0/60 (60hz).

As for making the meter smooth, that is a little tricker.

You could do something like this.

  1. Create an array that holds 7-8 values.
  2. every time you get a reading, add it to the array and pop the 7th value of (eg only hold the last seven values.
  3. Find the average of the array (sum the array / divide by the number of elements in the array.
  4. Display this average.

So its a bit like filling up a pipe, and once you stop filling it will take some time for it to empty and needle will slowly fall down.

OR you could only allow the needle to fall down only so much every cycle.

Lets say the needle swings be 0 (lowest value) and 1 (highest value far right hand side). Lets also say you sample at 20hz (20x times a second).

Every time you update the position only allow the needle to rise say 0.1 of a value max and fall only 0.05 of value.

you could do something like this and play with the values to get it nice and smooth.

if newValue>currentMeterValue
   currentMeterValue = Min(currentMeterValue + 0.1, newValue);
else
   currentMeterValue = Max(currentMeterValue - 0.05, newValue);

OR

You simply move the meter at a rate proportionally to the distance between each value (this should smooth it nicely) and actually be close to real meter with a spring pushing against the needle which is powered by an electromagnet.

   currentMeterValue += (newValue - currentMeterValue)/4.0;

다른 팁

According to Wikipedia, the behavior of a VUMeter is defined in in ANSI specification C16.5-1942. The needle full rise and fall time is supposed to be 300 mSec, averaging loudness over that duration.

I would try a 1-pole low-pass filter on the needle angle to approximate that angular rate, and animate the meter manually on a frame-by-frame basis using CADisplayLink based drawRect animation. View animation might not give the same responsiveness.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top