Fade out volume of AVAudioPlayerNode programmatically
Asked Answered
M

3

7

I've got an AVAudioEngine setup with a AVAudioPlayerNode that is playing some background music.

I'm trying to find a best approach to create a volume fadeout on the node over a 2 second timeframe. I'm considering using CADisplayLink in order to do this. I was wondering if somebody had experience with this scenario and could advise me on their approach?

Mutate answered 7/10, 2015 at 10:48 Comment(0)
F
5

My approach is below. Note that I assign the timer to a member var so I can invalidate it at other points (viewWillDisappear, delloc, etc.). I was worried that it wouldn't sound smooth, but I tried it and it works fine, didn't need to use CADisplayLink.

- (void)fadeOutAudioWithDuration:(double)duration {
    double timerInterval = 0.1;
    NSNumber *volumeInterval = [NSNumber numberWithDouble:(timerInterval / duration)];
    self.fadeOutTimer = [NSTimer scheduledTimerWithTimeInterval:timerInterval target:self selector:@selector(fadeOutTimerDidFire:) userInfo:volumeInterval repeats:YES];
}

- (void)fadeOutTimerDidFire:(NSTimer *)timer {
    float volumeInterval = ((NSNumber *)timer.userInfo).floatValue;
    float currentVolume = self.audioEngine.mainMixerNode.outputVolume;
    float newValue = MAX(currentVolume - volumeInterval, 0.0f);
    self.audioEngine.mainMixerNode.outputVolume = newValue;
    if (newValue == 0.0f) {
        [timer invalidate];
    }
}
Fidler answered 1/7, 2016 at 13:53 Comment(1)
Hmm this can cause lag issues if your main thread is doing other things.Barley
T
4

In case anyone like me still looking for an answer:

  1. As from docs, AVAudioPlayerNode doesn't support volume property, only AVAudioMixerNode node does. So ensure you envelope your AVAudioPlayerNode into AVAudioMixerNode.

  2. Here's a code used to fade in, fade out and generally fade (Swift 5)

     typealias Completion = (() -> Void)
    
     let mixer = AVAudioMixerNode()
    
     func fade(from: Float, to: Float, duration: TimeInterval, completion: Completion?) {
         let stepTime = 0.01
         let times = duration / stepTime
         let step = (to - from) / Float(times)
         for i in 0...Int(times) {
             DispatchQueue.main.asyncAfter(deadline: .now() + Double(i) * stepTime) {
                 mixer.volume = from + Float(i) * step
    
                 if i == Int(times) {
                     completion?()
                 }
             }
         }
     }
    
     func fadeIn(duration: TimeInterval = 1.3, completion: Completion? = nil) {
         fade(from: 0, to: 1, duration: duration, completion: completion)
     }
    
     func fadeOut(duration: TimeInterval = 1.3, completion: Completion? = nil) {
         fade(from: 1, to: 0, duration: duration, completion: completion)
     }
    
Tincture answered 17/3, 2021 at 11:10 Comment(1)
Actually AVAudioMixerNode implements AVAudioMixing which has a volume property.Barley
H
3

You can use global gain in EQ.

for example

AVAudioUnitEQ *Volume;
Volume = [[AVAudioUnitEQ alloc] init];
[engine attachNode:Volume];
[engine connect:Volume to:engine.outputNode format:nil];

And then

Volume.globalGain = /*here your floatValue*/
Hypogynous answered 13/1, 2016 at 10:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.