AVAudioPlayer fade volume out

IphoneAvaudioplayerVolume

Iphone Problem Overview


I have an AVAudioPlayer playing some audio (duh!)

The audio is initiated when the user presses a button. When they release it I want the audio to fade out.

I am using Interface builder...so I am trying to hook up a function on "touch up inside" that fades the audio out over 1 sec then stops.

Any ideas?

Thanks

Iphone Solutions


Solution 1 - Iphone

Here's how I'm doing it:

-(void)doVolumeFade
{  
    if (self.player.volume > 0.1) {
        self.player.volume = self.player.volume - 0.1;
        [self performSelector:@selector(doVolumeFade) withObject:nil afterDelay:0.1];		
     } else {
        // Stop and get the sound ready for playing again
        [self.player stop];
        self.player.currentTime = 0;
        [self.player prepareToPlay];
        self.player.volume = 1.0;
    }
}

.

11 years later: do note that setVolume#fadeDuration now exists!

Solution 2 - Iphone

Swift has an AVAudioPlayer method you can use for fading out which was included as of iOS 10.0:

var audioPlayer = AVAudioPlayer()
...
audioPlayer.setVolume(0, fadeDuration: 3)

Solution 3 - Iphone

I tackled this problem using an NSOperation subclass so fading the volume doesn't block the main thread. It also allows fades to be queued and and forgotten about. This is especially useful for playing one shot sounds with fade-in and fade-out effects as they are dealloced after the last fade is completed.

// Example of MXAudioPlayerFadeOperation in NSOperationQueue
NSOperationQueue *audioFaderQueue = [[NSOperationQueue alloc] init];
[audioFaderQueue setMaxConcurrentOperationCount:1]; // Execute fades serially.




NSString *filePath = [[NSBundle mainBundle] pathForResource:@"bg" ofType:@"mp3"]; // path to bg.mp3
AVAudioPlayer *player = [[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:filePath] error:NULL] autorelease];
[player setNumberOfLoops:-1];
[player setVolume:0.0];




// Note that delay is delay after last fade due to the Operation Queue working serially.
MXAudioPlayerFadeOperation *fadeIn = [[MXAudioPlayerFadeOperation alloc] initFadeWithAudioPlayer:player toVolume:1.0 overDuration:3.0];
[fadeIn setDelay:2.0];
MXAudioPlayerFadeOperation *fadeDown = [[MXAudioPlayerFadeOperation alloc] initFadeWithAudioPlayer:player toVolume:0.1 overDuration:3.0];
[fadeDown setDelay:0.0];
MXAudioPlayerFadeOperation *fadeUp = [[MXAudioPlayerFadeOperation alloc] initFadeWithAudioPlayer:player toVolume:1.0 overDuration:4.0];
[fadeUp setDelay:0.0];
MXAudioPlayerFadeOperation *fadeOut = [[MXAudioPlayerFadeOperation alloc] initFadeWithAudioPlayer:player toVolume:0.0 overDuration:3.0];
[fadeOut setDelay:2.0];




[audioFaderQueue addOperation:fadeIn]; // 2.0s - 5.0s
[audioFaderQueue addOperation:fadeDown]; // 5.0s - 8.0s
[audioFaderQueue addOperation:fadeUp]; // 8.0s - 12.0s
[audioFaderQueue addOperation:fadeOut]; // 14.0s - 17.0s




[fadeIn release];
[fadeDown release];
[fadeUp release];
[fadeOut release];

[fadeIn release]; [fadeDown release]; [fadeUp release]; [fadeOut release];

For MXAudioPlayerFadeOperation class code see [this post][1].

[1]: http://www.mackross.net/2010/12/fading-audio-with-avaudioplayer/ "my blog post"

Solution 4 - Iphone

I ended up combining some of the answer together and converted it to Swift ending up in this method:

func fadeVolumeAndPause(){
    if self.player?.volume > 0.1 {
        self.player?.volume = self.player!.volume - 0.1
        
        var dispatchTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC)))
        dispatch_after(dispatchTime, dispatch_get_main_queue(), {
            self.fadeVolumeAndPause()
        })
        
    } else {
        self.player?.pause()
        self.player?.volume = 1.0
    }
}

Solution 5 - Iphone

These are all good answers, however they don't deal with specifying the rate of fade (or applying a logarithmic curve to the fade, which is sometimes desirable), or specifying the number of dB's reduction from unity that you are fading to.

this is an except from one of my apps, with a few "bells and whistles" removed, that are not relevant to this question.

enjoy!

#define linearToDecibels(linear) (MIN(10,MAX(-100,20.0 * log10(linear))))
#define decibelsToLinear(decibels) (pow (10, (0.05 * decibels)))

#define fadeInfoId(n) [fadeInfo objectForKey:@#n]
#define fadeInfoObject(NSObject,n) ((NSObject*) fadeInfoId(n))
#define fadeInfoFloat(n) [fadeInfoId(n) floatValue]
#define useFadeInfoObject(n) * n = fadeInfoId(n)
#define useFadeInfoFloat(n) n = fadeInfoFloat(n)
#define setFadeInfoId(n,x) [fadeInfo setObject:x forKey:@#n]
#define setFadeInfoFloat(n,x) setFadeInfoId(n,[NSNumber numberWithFloat:x])
#define setFadeInfoFlag(n) setFadeInfoId(n,[NSNumber numberWithBool:YES])

#define saveFadeInfoId(n) setFadeInfoId(n,n)
#define saveFadeInfoFloat(n) setFadeInfoFloat(n,n)

#define fadeAVAudioPlayer_default           nil
#define fadeAVAudioPlayer_linearFade        @"linearFade"
#define fadeAVAudioPlayer_fadeToStop        @"fadeToStop"
#define fadeAVAudioPlayer_linearFadeToStop  @"linearFadeToStop"





-(void) fadeAVAudioPlayerTimerEvent:(NSTimer *) timer {
    NSMutableDictionary *fadeInfo = timer.userInfo;
    NSTimeInterval elapsed = 0 - [fadeInfoObject(NSDate,startTime) timeIntervalSinceNow];
    NSTimeInterval useFadeInfoFloat(fadeTime);
    float          useFadeInfoFloat(fadeToLevel);
    AVAudioPlayer  useFadeInfoObject(player);
    double linear;
    if (elapsed>fadeTime) {
        
        if (fadeInfoId(stopPlaybackAtFadeTime)) {
            [player stop];
            linear = fadeInfoFloat(fadeFromLevel);
            
        } else {
            
            linear = fadeToLevel;
        }
        [timer invalidate];
        [fadeInfo release];
        
    } else {
        
        
        if (fadeInfoId(linearCurve)) {
            float useFadeInfoFloat(fadeFromLevel);
            float fadeDelta = fadeToLevel-fadeFromLevel;
            linear = fadeFromLevel + (fadeDelta * (elapsed/fadeTime));
        } else {
            float useFadeInfoFloat(fadeToDB);
            float useFadeInfoFloat(fadeFromDB);
            
            float fadeDelta = fadeToDB-fadeFromDB;
            float decibels = fadeFromDB + (fadeDelta * (elapsed/fadeTime));
            linear = decibelsToLinear(decibels);
        }       
    }
    
    [player setVolume: linear];
    
    //[self displayFaderLevelForMedia:player];
    //[self updateMediaVolumeLabel:player];
}


-(void) fadeAVAudioPlayerLinear:(AVAudioPlayer *)player over:(NSTimeInterval) fadeTime fadeToLevel:(float) fadeToLevel fadeMode:(NSString*)fadeMode {
    NSMutableDictionary *fadeInfo = [[NSMutableDictionary alloc ]init];
    saveFadeInfoId(player);
    float fadeFromLevel = player.volume;// to optimize macros put value in var, so we don't call method 3 times.
    float fadeFromDB = linearToDecibels(fadeFromLevel);
    float fadeToDB   = linearToDecibels(fadeToLevel);
    
    saveFadeInfoFloat(fadeFromLevel);
    saveFadeInfoFloat(fadeToLevel);
    saveFadeInfoFloat(fadeToDB);
    saveFadeInfoFloat(fadeFromDB);
    saveFadeInfoFloat(fadeTime);
    
    setFadeInfoId(startTime,[NSDate date]);
    if([fadeMode isEqualToString:fadeAVAudioPlayer_fadeToStop]||[fadeMode isEqualToString:fadeAVAudioPlayer_linearFadeToStop]){ 
        setFadeInfoFlag(stopPlaybackAtFadeTime);
    }
    if([fadeMode isEqualToString:fadeAVAudioPlayer_linearFade]||[fadeMode isEqualToString:fadeAVAudioPlayer_linearFadeToStop]){ 
        setFadeInfoFlag(linearCurve);
    }
    
    [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(fadeAVAudioPlayerTimerEvent:) userInfo:fadeInfo repeats:YES];
}

-(void) fadeAVAudioPlayer:(AVAudioPlayer *)player over:(NSTimeInterval) fadeTime fadeToDB:(float) fadeToDB fadeMode:(NSString*)fadeMode {
    [self fadeAVAudioPlayerLinear:player over:fadeTime fadeToLevel:decibelsToLinear(fadeToDB) fadeMode:fadeMode ];
}

-(void) fadeoutAVAudioPlayer:(AVAudioPlayer *)player {
    [self fadeAVAudioPlayerLinear:player over:5.0 fadeToLevel:0 fadeMode:fadeAVAudioPlayer_default];
}

-(void) fadeinAVAudioPlayer:(AVAudioPlayer *)player {
    [self fadeAVAudioPlayerLinear:player over:5.0 fadeToLevel:0 fadeMode:fadeAVAudioPlayer_default];
}

Solution 6 - Iphone

An extension for swift 3 inspired from the most voted answer. For those of you who like copy-pasting :)

extension AVAudioPlayer {
    func fadeOut() {
        if volume > 0.1 {
            // Fade
            volume -= 0.1
            perform(#selector(fadeOut), with: nil, afterDelay: 0.1)
        } else {
            // Stop and get the sound ready for playing again
            stop()
            prepareToPlay()
            volume = 1
        }
    }
}

Solution 7 - Iphone

I wrote a helper class in Swift for fading AvAudioPlayer in and out. You can use logarithmic volume function for more gradual fading effect.

let player = AVAudioPlayer(contentsOfURL: soundURL, error: nil)

let fader = iiFaderForAvAudioPlayer(player: player)
fader.fadeIn()
fader.fadeOut()

Here a demo app: https://github.com/evgenyneu/sound-fader-ios

Solution 8 - Iphone

Swift 3

I like Ambroise Collon answer's , so i voted up but Swift is statically typed so the performSelector: methods are to fall by the wayside, maybe an alternative could be dispatch async (in this version I've added also the destination volume as parameter)

func dispatchDelay(delay:Double, closure:@escaping ()->()) {
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delay, execute: closure)
}

extension AVAudioPlayer {
    func fadeOut(vol:Float) {
        if volume > vol {
            //print("vol is : \(vol) and volume is: \(volume)")
            dispatchDelay(delay: 0.1, closure: {
                [weak self] in
                guard let strongSelf = self else { return }
                strongSelf.volume -= 0.01
                strongSelf.fadeOut(vol: vol)
            })
        } else {
            volume = vol
        }
    }
    func fadeIn(vol:Float) {
        if volume < vol {
            dispatchDelay(delay: 0.1, closure: {
                [weak self] in
                guard let strongSelf = self else { return }
                strongSelf.volume += 0.01
                strongSelf.fadeIn(vol: vol)
            })
        } else {
            volume = vol
        }
    }
}

Solution 9 - Iphone

This seems to me to be a descent use of an NSOperationQueue.

Hence here is my solution:

-(void) fadeIn
{
    if (self.currentPlayer.volume >= 1.0f) return;
    else {
        self.currentPlayer.volume+=0.10;
        __weak typeof (self) weakSelf = self;
        [NSThread sleepForTimeInterval:0.2f];
        [self.fadingQueue addOperationWithBlock:^{
            NSLog(@"fading in %.2f", self.currentPlayer.volume);
            [weakSelf fadeIn];
        }];
    }
}
-(void) fadeOut
{
    if (self.currentPlayer.volume <= 0.0f) return;
    else {
        self.currentPlayer.volume -=0.1;
        __weak typeof (self) weakSelf = self;
        [NSThread sleepForTimeInterval:0.2f];
        [self.fadingQueue addOperationWithBlock:^{
            NSLog(@"fading out %.2f", self.currentPlayer.volume);
            [weakSelf fadeOut];
        }];
    }
}

Solution 10 - Iphone

How about this: (if time passed in is negative then fade out the sound, otherwise fade in)

- (void) fadeInOutVolumeOverTime: (NSNumber *)time
{
#define fade_out_steps	0.1
	float			theVolume = player.volume;
	NSTimeInterval	theTime = [time doubleValue];
	int				sign = (theTime >= 0) ? 1 : -1;
    	
// before we call this, if we are fading out, we save the volume
// so that we can restore back to that level in the fade in
	if ((sign == 1) &&
			((theVolume >= savedVolume) ||
							(theTime == 0))) {
		player.volume = savedVolume;
	}
	else if ((sign == -1) && (theVolume <= 0)) {
		NSLog(@"fading");
		[player pause];
		[self performSelector:@selector(fadeInOutVolumeOverTime:) withObject:[NSNumber numberWithDouble:0] afterDelay:1.0];

	}
	else {
		theTime *= fade_out_steps;
		player.volume = theVolume + fade_out_steps * sign;
		[self performSelector:@selector(fadeInOutVolumeOverTime:) withObject:time afterDelay:fabs(theTime)];
	}
}

Solution 11 - Iphone

Swift solution:

The top rated answer here is great but it gives a stuttering effect as the volume step of 0.1 is too much. Using 0.01 gives a smoother fade effect to hear.

Using this code you can specify how long you want the fade transition to last.

let fadeVolumeStep: Float = 0.01

let fadeTime = 0.5 // Fade time in seconds

var fadeVolumeStepTime: Double {
     return fadeTime / Double(1.0 / fadeVolumeStep)
}

func fadeOut() {
    guard let player = self.player else {
        return
    }
    
    if !player.playing { return }
    
    func fadeOutPlayer() {
        if player.volume > fadeVolumeStep {
            player.volume -= fadeVolumeStep
            delay(time: fadeVolumeStepTime, closure: {
                fadeOutPlayer()
            })
        } else {
            player.stop()
            player.currentTime = 0
            player.prepareToPlay()
        }
    }
    
    fadeOutPlayer()
}

func fadeIn() {
    guard let player = self.player else {
        return
    }
    
    if player.playing { return }
    player.volume = 0
    player.play()
    
    func fadeInPlayer() {
        if player.volume <= 1 - fadeVolumeStep {
            player.volume += fadeVolumeStep
            delay(time: fadeVolumeStepTime, closure: {
                fadeInPlayer()
            })
        } else {
            player.volume = 1
        }
    }
    
    fadeInPlayer()
}

func delay(time delay:Double, closure:()->()) {
    dispatch_after(
        dispatch_time(
            DISPATCH_TIME_NOW,
            Int64(delay * Double(NSEC_PER_SEC))
        ),
        dispatch_get_main_queue(), closure)
}

you can adjust the time using fadeTime constant.

Solution 12 - Iphone

In Objective-C try this:

NSURL *trackURL = [[NSURL alloc]initFileURLWithPath:@"path to audio track"];

// fade duration in seconds
NSTimeInterval fadeDuration = 0.3;

// duration of the audio track in seconds
NSTimeInterval audioTrackDuration = 5.0; 

AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc]  initWithContentsOfURL:trackURL error:nil];
[audioPlayer play];

// first we set the volume to 1 - highest
[audioPlayer setVolume:1.0]; 

// then to 0 - lowest
[musicAudioPlayer setVolume:0 fadeDuration:audioTrackDuration - fadeDuration];

Solution 13 - Iphone

A very good solution to this problem is available from Nick Lockwood at objc.io. It leverages Core Animation to time volume changes which can come in handy if one needs to synchronise UI animations with audio volume changes, including interactive animations.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJonathan View Question on Stackoverflow
Solution 1 - IphoneBdebeezView Answer on Stackoverflow
Solution 2 - IphonenikolayView Answer on Stackoverflow
Solution 3 - IphonemackrossView Answer on Stackoverflow
Solution 4 - IphoneAntoineView Answer on Stackoverflow
Solution 5 - IphoneunsynchronizedView Answer on Stackoverflow
Solution 6 - IphoneAmbroise CollonView Answer on Stackoverflow
Solution 7 - IphoneEvgeniiView Answer on Stackoverflow
Solution 8 - IphoneAlessandro OrnanoView Answer on Stackoverflow
Solution 9 - IphoneJohn LaBargeView Answer on Stackoverflow
Solution 10 - IphonemahboudzView Answer on Stackoverflow
Solution 11 - IphoneIshan HandaView Answer on Stackoverflow
Solution 12 - IphonestellzView Answer on Stackoverflow
Solution 13 - IphoneLeon DeriglazovView Answer on Stackoverflow