ios: added MXAudioPlayerFadeOperation to allow easy fade in and out of background music
authorkoda
Thu, 09 Feb 2012 17:28:05 +0100
changeset 6656 6aeaba3ee584
parent 6655 1edd500b2471
child 6657 e1125559359f
ios: added MXAudioPlayerFadeOperation to allow easy fade in and out of background music
project_files/HedgewarsMobile/Classes/AudioManagerController.h
project_files/HedgewarsMobile/Classes/AudioManagerController.m
project_files/HedgewarsMobile/Classes/GameInterfaceBridge.m
project_files/HedgewarsMobile/Hedgewars.xcodeproj/project.pbxproj
project_files/HedgewarsMobile/MXAudioPlayerFadeOperation.h
project_files/HedgewarsMobile/MXAudioPlayerFadeOperation.m
--- a/project_files/HedgewarsMobile/Classes/AudioManagerController.h	Thu Feb 09 16:31:02 2012 +0100
+++ b/project_files/HedgewarsMobile/Classes/AudioManagerController.h	Thu Feb 09 17:28:05 2012 +0100
@@ -30,6 +30,9 @@
 +(void) pauseBackgroundMusic;
 +(void) stopBackgroundMusic;
 
++(void) fadeInBackgroundMusic;
++(void) fadeOutBackgroundMusic;
+
 +(void) playClickSound;
 +(void) playBackSound;
 +(void) playSelectSound;
--- a/project_files/HedgewarsMobile/Classes/AudioManagerController.m	Thu Feb 09 16:31:02 2012 +0100
+++ b/project_files/HedgewarsMobile/Classes/AudioManagerController.m	Thu Feb 09 17:28:05 2012 +0100
@@ -22,13 +22,18 @@
 #import "AudioManagerController.h"
 #import "AVFoundation/AVAudioPlayer.h"
 #import <AudioToolbox/AudioToolbox.h>
-
+#import "MXAudioPlayerFadeOperation.h"
 
 static AVAudioPlayer *backgroundMusic = nil;
 static SystemSoundID clickSound = -1;
 static SystemSoundID backSound = -1;
 static SystemSoundID selSound = -1;
 
+static NSOperationQueue *audioFaderQueue = nil;
+static MXAudioPlayerFadeOperation *fadeIn = nil;
+static MXAudioPlayerFadeOperation *fadeOut = nil;
+
+
 @implementation AudioManagerController
 
 #pragma mark -
@@ -38,7 +43,7 @@
     backgroundMusic = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:musicString] error:nil];
 
     backgroundMusic.delegate = nil;
-    backgroundMusic.volume = 0.4f;
+    backgroundMusic.volume = 0;
     backgroundMusic.numberOfLoops = -1;
     [backgroundMusic prepareToPlay];
 }
@@ -50,6 +55,7 @@
     if (backgroundMusic == nil)
         [AudioManagerController loadBackgroundMusic];
 
+    backgroundMusic.volume = 0.45f;
     [backgroundMusic play];
 }
 
@@ -61,6 +67,28 @@
     [backgroundMusic stop];
 }
 
++(void) fadeOutBackgroundMusic {
+    if (audioFaderQueue == nil)
+        audioFaderQueue = [[NSOperationQueue alloc] init];
+    if (backgroundMusic == nil)
+        [AudioManagerController loadBackgroundMusic];
+    if (fadeOut == nil)
+        fadeOut = [[MXAudioPlayerFadeOperation alloc] initFadeWithAudioPlayer:backgroundMusic toVolume:0.0 overDuration:3.0];
+
+    [audioFaderQueue addOperation:fadeOut];
+}
+
++(void) fadeInBackgroundMusic {
+    if (audioFaderQueue == nil)
+        audioFaderQueue = [[NSOperationQueue alloc] init];
+    if (backgroundMusic == nil)
+        [AudioManagerController loadBackgroundMusic];
+    if (fadeIn == nil)
+        fadeIn = [[MXAudioPlayerFadeOperation alloc] initFadeWithAudioPlayer:backgroundMusic toVolume:0.45 overDuration:2.0];
+
+    [audioFaderQueue addOperation:fadeIn];
+}
+
 #pragma mark -
 #pragma mark sound effects control
 +(SystemSoundID) loadSound:(NSString *)snd {
@@ -111,6 +139,9 @@
 +(void) releaseCache {
     [backgroundMusic stop];
     [backgroundMusic release], backgroundMusic = nil;
+    [fadeOut release], fadeOut = nil;
+    [fadeIn release], fadeIn = nil;
+    [audioFaderQueue release], audioFaderQueue = nil;
     AudioServicesDisposeSystemSoundID(clickSound), clickSound = -1;
     AudioServicesDisposeSystemSoundID(backSound), backSound = -1;
     AudioServicesDisposeSystemSoundID(selSound), selSound = -1;
--- a/project_files/HedgewarsMobile/Classes/GameInterfaceBridge.m	Thu Feb 09 16:31:02 2012 +0100
+++ b/project_files/HedgewarsMobile/Classes/GameInterfaceBridge.m	Thu Feb 09 17:28:05 2012 +0100
@@ -36,7 +36,7 @@
 // prepares the controllers for hosting a game
 -(void) earlyEngineLaunch:(NSDictionary *)optionsOrNil {
     [self retain];
-    [AudioManagerController stopBackgroundMusic];
+    [AudioManagerController fadeOutBackgroundMusic];
 
     EngineProtocolNetwork *engineProtocol = [[EngineProtocolNetwork alloc] init];
     self.proto = engineProtocol;
@@ -107,7 +107,7 @@
         [[NSFileManager defaultManager] removeItemAtPath:self.savePath error:nil];
 
     // restart music and we're done
-    [AudioManagerController playBackgroundMusic];
+    [AudioManagerController fadeInBackgroundMusic];
     [HWUtils setGameStatus:gsNone];
     [HWUtils setGameType:gtNone];
     [self release];
--- a/project_files/HedgewarsMobile/Hedgewars.xcodeproj/project.pbxproj	Thu Feb 09 16:31:02 2012 +0100
+++ b/project_files/HedgewarsMobile/Hedgewars.xcodeproj/project.pbxproj	Thu Feb 09 17:28:05 2012 +0100
@@ -89,6 +89,7 @@
 		615AD96212073B4D00F2FF04 /* startGameButton.png in Resources */ = {isa = PBXBuildFile; fileRef = 615AD96112073B4D00F2FF04 /* startGameButton.png */; };
 		615AD9E9120764CA00F2FF04 /* backButton.png in Resources */ = {isa = PBXBuildFile; fileRef = 615AD9E8120764CA00F2FF04 /* backButton.png */; };
 		615AD9EB1207654E00F2FF04 /* helpButton.png in Resources */ = {isa = PBXBuildFile; fileRef = 615AD9EA1207654E00F2FF04 /* helpButton.png */; };
+		615E755A14E41E8C00FBA131 /* MXAudioPlayerFadeOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 615E755914E41E8C00FBA131 /* MXAudioPlayerFadeOperation.m */; };
 		615FEAE212A2A6640098EE92 /* localplayButton~ipad.png in Resources */ = {isa = PBXBuildFile; fileRef = 615FEADF12A2A6640098EE92 /* localplayButton~ipad.png */; };
 		615FEAE312A2A6640098EE92 /* localplayButton~iphone.png in Resources */ = {isa = PBXBuildFile; fileRef = 615FEAE012A2A6640098EE92 /* localplayButton~iphone.png */; };
 		6163EE7E11CC2600001C0453 /* SingleWeaponViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6163EE7D11CC2600001C0453 /* SingleWeaponViewController.m */; };
@@ -437,6 +438,8 @@
 		615AD96112073B4D00F2FF04 /* startGameButton.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = startGameButton.png; path = Resources/Frontend/startGameButton.png; sourceTree = "<group>"; };
 		615AD9E8120764CA00F2FF04 /* backButton.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = backButton.png; path = Resources/Frontend/backButton.png; sourceTree = "<group>"; };
 		615AD9EA1207654E00F2FF04 /* helpButton.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = helpButton.png; path = Resources/Frontend/helpButton.png; sourceTree = "<group>"; };
+		615E755814E41E8C00FBA131 /* MXAudioPlayerFadeOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXAudioPlayerFadeOperation.h; sourceTree = "<group>"; };
+		615E755914E41E8C00FBA131 /* MXAudioPlayerFadeOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXAudioPlayerFadeOperation.m; sourceTree = "<group>"; };
 		615FEAD912A2A4C10098EE92 /* checkbox@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "checkbox@2x.png"; path = "Resources/Icons/checkbox@2x.png"; sourceTree = "<group>"; };
 		615FEADE12A2A6640098EE92 /* localplayButton@2x~iphone.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "localplayButton@2x~iphone.png"; path = "Resources/Frontend/localplayButton@2x~iphone.png"; sourceTree = "<group>"; };
 		615FEADF12A2A6640098EE92 /* localplayButton~ipad.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "localplayButton~ipad.png"; path = "Resources/Frontend/localplayButton~ipad.png"; sourceTree = "<group>"; };
@@ -1162,6 +1165,8 @@
 		61F8535314578999002CA294 /* Helpers */ = {
 			isa = PBXGroup;
 			children = (
+				615E755814E41E8C00FBA131 /* MXAudioPlayerFadeOperation.h */,
+				615E755914E41E8C00FBA131 /* MXAudioPlayerFadeOperation.m */,
 				61C28D3D142D380400DA16C2 /* AudioManagerController.h */,
 				61C28D3E142D380400DA16C2 /* AudioManagerController.m */,
 				6165922411CA9BD500D6E256 /* CGPointUtils.h */,
@@ -1734,6 +1739,7 @@
 				61D08D7514AEA7FE0007C078 /* uGearsList.pas in Sources */,
 				61D08D7614AEA7FE0007C078 /* uGearsUtils.pas in Sources */,
 				610C8E3714E018D200CF5C4C /* ValueTrackingSliderView.m in Sources */,
+				615E755A14E41E8C00FBA131 /* MXAudioPlayerFadeOperation.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/HedgewarsMobile/MXAudioPlayerFadeOperation.h	Thu Feb 09 17:28:05 2012 +0100
@@ -0,0 +1,55 @@
+//  MXAudioPlayerFadeOperation.h
+//
+//  Created by Andrew Mackenzie-Ross on 30/11/10.
+//  mackross.net
+//
+
+#import <Foundation/Foundation.h>
+
+@class AVAudioPlayer;
+@interface MXAudioPlayerFadeOperation : NSOperation {
+  AVAudioPlayer *_audioPlayer;
+  NSTimeInterval _fadeDuration;
+  NSTimeInterval _delay;
+  float _finishVolume;
+  BOOL _pauseAfterFade;
+  BOOL _stopAfterFade;
+  BOOL _playBeforeFade;
+}
+
+// The AVAudioPlayer that the volume fade will be applied to. 
+// Retained until the fade is completed.
+// Must be set with init method.
+@property (nonatomic, retain, readonly) AVAudioPlayer *audioPlayer; 
+
+// The duration of the volume fade. 
+// Default value is 1.0
+@property (nonatomic, assign) NSTimeInterval fadeDuration; 
+
+// The delay before the volume fade begins. 
+// Default value is 0.0
+@property (nonatomic, assign) NSTimeInterval delay; 
+
+// The volume that will be faded to. 
+// Default value is 0.0
+@property (nonatomic, assign) float finishVolume; 
+
+// If YES, audio player will be sent a pause message when the fade has completed. 
+// Default value is NO, however, if finishVolume is 0.0, default is YES
+@property (nonatomic, assign) BOOL pauseAfterFade; 
+
+// If YES, when the fade has completed the audio player will be sent a stop message.
+// Default value is NO.
+@property (nonatomic, assign) BOOL stopAfterFade;
+
+// If YES, audio player will be sent a play message after the delay.
+// Default value is YES.
+@property (nonatomic, assign) BOOL playBeforeFade;
+
+// Init Methods
+- (id)initFadeWithAudioPlayer:(AVAudioPlayer*)player toVolume:(float)volume overDuration:(NSTimeInterval)duration withDelay:(NSTimeInterval)timeDelay;
+- (id)initFadeWithAudioPlayer:(AVAudioPlayer*)player toVolume:(float)volume overDuration:(NSTimeInterval)duration;
+- (id)initFadeWithAudioPlayer:(AVAudioPlayer*)player toVolume:(float)volume;
+- (id)initFadeWithAudioPlayer:(AVAudioPlayer*)player;
+
+@end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/HedgewarsMobile/MXAudioPlayerFadeOperation.m	Thu Feb 09 17:28:05 2012 +0100
@@ -0,0 +1,133 @@
+//  MXAudioPlayerFadeOperation.m
+//
+//  Created by Andrew Mackenzie-Ross on 30/11/10.
+//  mackross.net.
+//
+
+#import "MXAudioPlayerFadeOperation.h"
+#import <AVFoundation/AVFoundation.h>
+
+#define SKVolumeChangesPerSecond 15
+
+@interface MXAudioPlayerFadeOperation ()
+@property (nonatomic, retain, readwrite) AVAudioPlayer *audioPlayer; 
+- (void)beginFadeOperation;
+- (void)finishFadeOperation;
+@end
+
+@implementation MXAudioPlayerFadeOperation
+#pragma mark -
+#pragma mark Properties
+@synthesize audioPlayer = _audioPlayer;
+@synthesize fadeDuration = _fadeDuration;
+@synthesize finishVolume = _finishVolume;
+@synthesize playBeforeFade = _playBeforeFade;
+@synthesize pauseAfterFade = _pauseAfterFade;
+@synthesize stopAfterFade = _stopAfterFade;
+@synthesize delay = _delay;
+
+#pragma mark -
+#pragma mark Accessors
+- (AVAudioPlayer *)audioPlayer {
+  AVAudioPlayer *result;
+  @synchronized(self) {
+    result = [_audioPlayer retain];
+  }
+  return [result autorelease];
+}
+
+- (void)setAudioPlayer:(AVAudioPlayer *)anAudioPlayer {
+  @synchronized(self) {
+    if (_audioPlayer != anAudioPlayer) {
+      [_audioPlayer release];
+      _audioPlayer = [anAudioPlayer retain];
+    }
+  }
+}
+
+#pragma mark -
+#pragma mark NSOperation
+-(id) initFadeWithAudioPlayer:(AVAudioPlayer*)player toVolume:(float)volume overDuration:(NSTimeInterval)duration withDelay:(NSTimeInterval)timeDelay {
+  if (self = [super init]) {
+    self.audioPlayer = player;
+    [player prepareToPlay];
+    _fadeDuration = duration;
+    _finishVolume = volume;
+    _playBeforeFade = YES;
+    _stopAfterFade = NO;
+    _pauseAfterFade = (volume == 0.0) ? YES : NO;
+    _delay = timeDelay;
+  }
+  return self;
+}
+
+- (id)initFadeWithAudioPlayer:(AVAudioPlayer*)player toVolume:(float)volume overDuration:(NSTimeInterval)duration {
+  return [self initFadeWithAudioPlayer:player toVolume:volume overDuration:duration withDelay:0.0];
+}
+
+- (id)initFadeWithAudioPlayer:(AVAudioPlayer*)player toVolume:(float)volume {
+  return [self initFadeWithAudioPlayer:player toVolume:volume overDuration:1.0];
+}
+
+- (id)initFadeWithAudioPlayer:(AVAudioPlayer*)player {
+  return [self initFadeWithAudioPlayer:player toVolume:0.0];
+}
+
+- (id) init {
+  ALog(@"Failed to init class (%@) with AVAudioPlayer instance, use initFadeWithAudioPlayer:",[self class]);
+  return nil;
+}
+
+- (void)main {
+  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+  [NSThread sleepForTimeInterval:_delay];
+  if ([self.audioPlayer isKindOfClass:[AVAudioPlayer class]]) {
+    [self beginFadeOperation];
+  }
+  else {
+    ALog(@"AudioPlayerFadeOperation began with invalid AVAudioPlayer");
+  }
+  
+  [pool release];
+}
+
+- (void)beginFadeOperation {
+  if (![self.audioPlayer isPlaying] && _playBeforeFade) [self.audioPlayer play];
+  
+  if (_fadeDuration != 0.0) {
+    
+    NSTimeInterval sleepInterval = (1.0 / SKVolumeChangesPerSecond);
+    NSTimeInterval startTime = [[NSDate date] timeIntervalSinceReferenceDate];
+    NSTimeInterval now = startTime;
+    
+    float startVolume = [self.audioPlayer volume];
+    
+    while (now < (startTime + _fadeDuration)) {
+      float ratioOfFadeCompleted = (now - startTime)/_fadeDuration;
+      float volume = (_finishVolume * ratioOfFadeCompleted) + (startVolume * (1-ratioOfFadeCompleted));
+      [self.audioPlayer setVolume:volume];
+      [NSThread sleepForTimeInterval:sleepInterval];
+      now = [[NSDate date] timeIntervalSinceReferenceDate];
+    }
+    
+    [self.audioPlayer setVolume:_finishVolume];
+    [self finishFadeOperation];
+  }
+  else {
+    [self.audioPlayer setVolume:_finishVolume];
+    [self finishFadeOperation];
+  }
+}
+
+- (void)finishFadeOperation {
+  if ([self.audioPlayer isPlaying] && _pauseAfterFade) [self.audioPlayer pause];
+  if ([self.audioPlayer isPlaying] && _stopAfterFade) [self.audioPlayer stop];
+}
+
+- (void)dealloc {
+  releaseAndNil(_audioPlayer);
+  [super dealloc];
+}
+
+@end
+