Ho Can I Smooth Audio Level Metering for a More Realistic Analog VU Meter?
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.
- Create an array that holds 7-8 values.
- every time you get a reading, add it to the array and pop the 7th value of (eg only hold the last seven values.
- Find the average of the array (sum the array / divide by the number of elements in the array.
- 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.
精彩评论