From 56a821f53a63cbc0df192d635405b66559f57725 Mon Sep 17 00:00:00 2001 From: demosthenese Date: Wed, 30 Mar 2011 18:56:09 +0000 Subject: [PATCH] Added SoundManager project --- LICENCE.txt | 20 ++++ README.txt | 80 +++++++++++++ RELEASE NOTES.txt | 3 + SoundManager.h | 63 ++++++++++ SoundManager.m | 298 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 464 insertions(+) create mode 100755 LICENCE.txt create mode 100755 README.txt create mode 100755 RELEASE NOTES.txt create mode 100644 SoundManager.h create mode 100644 SoundManager.m diff --git a/LICENCE.txt b/LICENCE.txt new file mode 100755 index 0000000..8b9233e --- /dev/null +++ b/LICENCE.txt @@ -0,0 +1,20 @@ +SoundManager +version 1.0, March 30th, 2011 + +Copyright (C) 2011 Charcoal Design + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. \ No newline at end of file diff --git a/README.txt b/README.txt new file mode 100755 index 0000000..e32adcf --- /dev/null +++ b/README.txt @@ -0,0 +1,80 @@ +Purpose +-------------- + +SoundManager is a simple class for playing sound and music in iPhone or Mac app store apps. + + +Installation +-------------- + +To use the SoundManager class in an app, just drag the class files into your project. For iOS apps you will also need to add the AVFoundation framework. + + +Configuration +-------------- + +The SoundManager class has the following configuration constants: + +FILE_EXTENSION - the default file extension for sounds when not specified. +CROSSFADE_DURATION - the crossfade duration between music tracks. +MUSIC_VOLUME - the volume at which to play music tracks. +SOUND_VOLUME - the volume at which to play sounds. + + +Properties +-------------- + +@property (nonatomic, readonly) BOOL playingMusic; + +This readonly property reports if the SoundManager is currently playing music. + +@property (nonatomic, assign) BOOL allowsBackgroundMusic; + +This property is used to control the audio session on the iPhone to allow iPod music to be played in the background. It defaults to NO, so it should be set to YES before you attempt to play any sound or music if you do not want the iPod music to be interrupted. It does nothing on Mac OS currently. + + +Methods +-------------- + ++ (SoundManager *)sharedManager; + +This class method returns a singleton instance of the SoundManager. + +- (void)prepareToPlay; + +The prepareToPlay method preloads a random sound from your application bundle, which initialises the AVAudioPlayer. It should be called before you attempt to play any audio, ideally during the startup sequence, to eliminate the delay when you first play a sound or music track. This method currently does nothing on Mac OS. + +- (void)playSound:(NSString *)name; + +The play method will load and play a sound from the application bundle whose filename matches the name passed. You can include the file extension in the name, or omit it, in which case the SoundManager will look for a matching file with the extension specified in the FILE_EXTENSION constant (defaults to .caf). + +- (void)playMusic:(NSString *)name; + +This method plays a music track. The music will fade in from silent to the volume specified in MUSIC_VOLUME over a period of time specified by CROSSFADE_DURATION. The sound manager only allows one music track to be played at a time, so if an existing track is playing it will be faded out. + +- (void)stopMusic; + +This will fade out the currently playing music track over the period specified by CROSSFADE_DURATION. + + +Supported Formats +------------------- + +The iPhone can be quite picky about which sounds it will play. For best results, +use .caf files, which you can generate using the afconvert command line tool. Here are some common configurations: + +For background music (mono): + +/usr/bin/afconvert -f caff -d aac -c 1 {input_file_name} {output_file_name}.caf + +For background music (stereo): + +/usr/bin/afconvert -f caff -d aac {input_file_name} {output_file_name}.caf + +For sound effects (mono): + +/usr/bin/afconvert -f caff -d ima4 -c 1 {input_file_name} {output_file_name}.caf + +For sound effects (stereo): + +/usr/bin/afconvert -f caff -d ima4 {input_file_name} {output_file_name}.caf \ No newline at end of file diff --git a/RELEASE NOTES.txt b/RELEASE NOTES.txt new file mode 100755 index 0000000..3140c19 --- /dev/null +++ b/RELEASE NOTES.txt @@ -0,0 +1,3 @@ +Version 1.0 + +- Initial release \ No newline at end of file diff --git a/SoundManager.h b/SoundManager.h new file mode 100644 index 0000000..6c8342d --- /dev/null +++ b/SoundManager.h @@ -0,0 +1,63 @@ +// +// SoundManager.h +// SoundManager +// +// Created by Nick Lockwood on 29/01/2011. +// Copyright 2011 Charcoal Design. All rights reserved. +// + +#import + + +#define FILE_EXTENSION @"caf" +#define CROSSFADE_DURATION 3.0 +#define MUSIC_VOLUME 0.5 +#define SOUND_VOLUME 1.0 + + +@interface Sound : NSObject +{ + float targetVolume; + float volumeDelta; + NSTimeInterval lastTick; + NSTimer *timer; + Sound *selfReference; + NSURL *url; + id sound; +} + ++ (Sound *)soundWithName:(NSString *)name; ++ (Sound *)soundWithURL:(NSURL *)url; + +- (Sound *)initWithName:(NSString *)name; +- (Sound *)initWithURL:(NSURL *)url; + +@property (nonatomic, retain, readonly) NSURL *url; +@property (nonatomic, assign, readonly) BOOL playing; +@property (nonatomic, assign) float volume; + +- (void)fadeTo:(float)volume duration:(NSTimeInterval)duration; +- (void)fadeIn:(NSTimeInterval)duration; +- (void)fadeOut:(NSTimeInterval)duration; +- (void)play:(BOOL)loop; + +@end + + +@interface SoundManager : NSObject +{ + Sound *currentMusic; + BOOL allowsBackgroundMusic; +} + +@property (nonatomic, readonly) BOOL playingMusic; +@property (nonatomic, assign) BOOL allowsBackgroundMusic; + ++ (SoundManager *)sharedManager; + +- (void)prepareToPlay; +- (void)playMusic:(NSString *)name; +- (void)stopMusic; +- (void)playSound:(NSString *)name; + +@end \ No newline at end of file diff --git a/SoundManager.m b/SoundManager.m new file mode 100644 index 0000000..20a0f87 --- /dev/null +++ b/SoundManager.m @@ -0,0 +1,298 @@ +// +// SoundManager.m +// SoundManager +// +// Created by Nick Lockwood on 29/01/2011. +// Copyright 2011 Charcoal Design. All rights reserved. +// + +#import "SoundManager.h" + + +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED +#import +#import +@interface Sound() +#else +#import +@interface Sound() +#endif + +@property (nonatomic, assign) float targetVolume; +@property (nonatomic, assign) float volumeDelta; +@property (nonatomic, assign) NSTimeInterval lastTick; +@property (nonatomic, retain) NSTimer *timer; +@property (nonatomic, retain) Sound *selfReference; + +@end + + +@implementation Sound + +@synthesize targetVolume; +@synthesize volumeDelta; +@synthesize lastTick; +@synthesize timer; +@synthesize selfReference; +@synthesize url; + + ++ (Sound *)soundWithName:(NSString *)_name +{ + return [[[self alloc] initWithName:_name] autorelease]; +} + ++ (Sound *)soundWithURL:(NSURL *)url +{ + return [[[self alloc] initWithURL:url] autorelease]; +} + +- (Sound *)initWithName:(NSString *)_name; +{ + if ([[_name pathExtension] isEqualToString:@""]) + { + _name = [_name stringByAppendingPathExtension:FILE_EXTENSION]; + } + NSString *path = [[NSBundle mainBundle] pathForResource:_name ofType:nil]; + return [self initWithURL:[NSURL fileURLWithPath:path]]; +} + +- (Sound *)initWithURL:(NSURL *)_url; +{ + if ((self = [super init])) + { + url = [_url retain]; + +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED + sound = [[AVAudioPlayer alloc] initWithContentsOfURL:_url error:NULL]; +#else + sound = [[NSSound alloc] initWithContentsOfURL:_url byReference:YES]; +#endif + + } + return self; +} + +- (float)volume +{ + return [sound volume]; +} + +- (void)setVolume:(float)volume +{ + [sound setVolume:volume]; +} + +- (BOOL)playing +{ + return [sound isPlaying]; +} + +- (void)play:(BOOL)loop +{ + if (!self.playing) + { + self.selfReference = self; + [sound setDelegate:self]; + +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED + [sound setNumberOfLoops:loop? -1: 0]; + [(AVAudioPlayer *)sound play]; +#else + [sound setLoops:loop]; + [(NSSound *)sound play]; +#endif + + } +} + +- (void)stop +{ + +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED + [(AVAudioPlayer *)sound stop]; +#else + [(NSSound *)sound stop]; +#endif + + self.selfReference = nil; +} + +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED +- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag +#else +- (void)sound:(NSSound *)_sound didFinishPlaying:(BOOL)finishedPlaying +#endif +{ + [self stop]; +} + +- (void)fadeTo:(float)volume duration:(NSTimeInterval)duration +{ + targetVolume = volume; + volumeDelta = (volume - self.volume) / duration; + if (timer == nil) + { + lastTick = [[NSDate date] timeIntervalSinceReferenceDate]; + self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0/60.0 + target:self + selector:@selector(tick) + userInfo:nil + repeats:YES]; + } +} + +- (void)fadeIn:(NSTimeInterval)duration +{ + self.volume = 0.0; + [self fadeTo:1.0 duration:duration]; +} + +- (void)fadeOut:(NSTimeInterval)duration +{ + [self fadeTo:0.0 duration:duration]; +} + +- (void)tick +{ + NSTimeInterval now = [[NSDate date] timeIntervalSinceReferenceDate]; + float delta = (now - lastTick); + self.volume += delta * volumeDelta; + if (volumeDelta > 0 && self.volume >= targetVolume) + { + self.volume = targetVolume; + [timer invalidate]; + self.timer = nil; + } + else if (volumeDelta < 0 && self.volume <= targetVolume) + { + self.volume = targetVolume; + [timer invalidate]; + self.timer = nil; + } + if (self.volume == 0) + { + [self stop]; + } +} + +- (void)dealloc +{ + [timer invalidate]; + [timer release]; + [url release]; + [sound release]; + [super dealloc]; +} + +@end + + +static SoundManager *sharedManager = nil; + + +@interface SoundManager () + +@property (nonatomic, retain) Sound *currentMusic; + +@end + + +@implementation SoundManager + +@synthesize currentMusic; +@synthesize allowsBackgroundMusic; + + ++ (SoundManager *)sharedManager +{ + if (sharedManager == nil) + { + sharedManager = [[self alloc] init]; + } + return sharedManager; +} + +- (void)setAllowsBackgroundMusic:(BOOL)allow +{ + if (allowsBackgroundMusic != allow) + { + +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED + + //configure audio session + allowsBackgroundMusic = allow; + UInt32 sessionCategory = allow? kAudioSessionCategory_AmbientSound: kAudioSessionCategory_SoloAmbientSound; + AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(sessionCategory), &sessionCategory); +#endif + + } +} + +- (void)prepareToPlay +{ + +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSArray *extensions = [NSArray arrayWithObjects:@"caf", @"m4a", @"mp4", @"mp3", @"wav", @"aif", nil]; + NSArray *paths = nil; + for (NSString *extension in extensions) + { + paths = [[NSBundle mainBundle] pathsForResourcesOfType:FILE_EXTENSION inDirectory:nil]; + if ([paths count]) + { + break; + } + } + NSURL *url = [NSURL fileURLWithPath:[paths objectAtIndex:0]]; + AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:NULL]; + [player prepareToPlay]; + [player release]; + [pool drain]; + +#endif + +} + +- (void)playMusic:(NSString *)name +{ + Sound *music = [Sound soundWithName:name]; + if (![music.url isEqual:currentMusic.url]) + { + if (currentMusic && currentMusic.playing) + { + [currentMusic fadeOut:CROSSFADE_DURATION]; + } + self.currentMusic = music; + currentMusic.volume = 0.0; + [currentMusic play:YES]; + [currentMusic fadeTo:MUSIC_VOLUME duration:CROSSFADE_DURATION]; + } +} + +- (void)stopMusic +{ + [currentMusic fadeOut:CROSSFADE_DURATION]; + self.currentMusic = nil; +} + +- (void)playSound:(NSString *)name +{ + Sound *sound = [Sound soundWithName:name]; + sound.volume = SOUND_VOLUME; + [sound play:NO]; +} + +- (BOOL)playingMusic +{ + return currentMusic != nil; +} + +- (void)dealloc +{ + [currentMusic release]; + [super dealloc]; +} + +@end