summary refs log tree commit diff stats
path: root/libs/CocosDenshion
diff options
context:
space:
mode:
authorStarla Insigna <starla4444@gmail.com>2011-07-30 11:19:14 -0400
committerStarla Insigna <starla4444@gmail.com>2011-07-30 11:19:14 -0400
commit9cd57b731ab1c666d4a1cb725538fdc137763d12 (patch)
tree5bac45ae5157a1cb10c6e45500cbf72789917980 /libs/CocosDenshion
downloadcartcollect-9cd57b731ab1c666d4a1cb725538fdc137763d12.tar.gz
cartcollect-9cd57b731ab1c666d4a1cb725538fdc137763d12.tar.bz2
cartcollect-9cd57b731ab1c666d4a1cb725538fdc137763d12.zip
Initial commit (version 0.2.1)
Diffstat (limited to 'libs/CocosDenshion')
-rwxr-xr-xlibs/CocosDenshion/CDAudioManager.h243
-rwxr-xr-xlibs/CocosDenshion/CDAudioManager.m887
-rwxr-xr-xlibs/CocosDenshion/CDConfig.h60
-rwxr-xr-xlibs/CocosDenshion/CDOpenALSupport.h77
-rwxr-xr-xlibs/CocosDenshion/CDOpenALSupport.m246
-rwxr-xr-xlibs/CocosDenshion/CocosDenshion.h440
-rwxr-xr-xlibs/CocosDenshion/CocosDenshion.m1602
-rwxr-xr-xlibs/CocosDenshion/SimpleAudioEngine.h90
-rwxr-xr-xlibs/CocosDenshion/SimpleAudioEngine.m220
9 files changed, 3865 insertions, 0 deletions
diff --git a/libs/CocosDenshion/CDAudioManager.h b/libs/CocosDenshion/CDAudioManager.h new file mode 100755 index 0000000..2475929 --- /dev/null +++ b/libs/CocosDenshion/CDAudioManager.h
@@ -0,0 +1,243 @@
1/*
2 Copyright (c) 2010 Steve Oldmeadow
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
10
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 THE SOFTWARE.
21
22 $Id$
23 */
24
25#import "CocosDenshion.h"
26#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 30000
27 #import <AVFoundation/AVFoundation.h>
28#else
29 #import "CDXMacOSXSupport.h"
30#endif
31
32/** Different modes of the engine */
33typedef enum {
34 kAMM_FxOnly, //!Other apps will be able to play audio
35 kAMM_FxPlusMusic, //!Only this app will play audio
36 kAMM_FxPlusMusicIfNoOtherAudio, //!If another app is playing audio at start up then allow it to continue and don't play music
37 kAMM_MediaPlayback, //!This app takes over audio e.g music player app
38 kAMM_PlayAndRecord //!App takes over audio and has input and output
39} tAudioManagerMode;
40
41/** Possible states of the engine */
42typedef enum {
43 kAMStateUninitialised, //!Audio manager has not been initialised - do not use
44 kAMStateInitialising, //!Audio manager is in the process of initialising - do not use
45 kAMStateInitialised //!Audio manager is initialised - safe to use
46} tAudioManagerState;
47
48typedef enum {
49 kAMRBDoNothing, //Audio manager will not do anything on resign or becoming active
50 kAMRBStopPlay, //Background music is stopped on resign and resumed on become active
51 kAMRBStop //Background music is stopped on resign but not resumed - maybe because you want to do this from within your game
52} tAudioManagerResignBehavior;
53
54/** Notifications */
55extern NSString * const kCDN_AudioManagerInitialised;
56
57@interface CDAsynchInitialiser : NSOperation {}
58@end
59
60/** CDAudioManager supports two long audio source channels called left and right*/
61typedef enum {
62 kASC_Left = 0,
63 kASC_Right = 1
64} tAudioSourceChannel;
65
66typedef enum {
67 kLAS_Init,
68 kLAS_Loaded,
69 kLAS_Playing,
70 kLAS_Paused,
71 kLAS_Stopped,
72} tLongAudioSourceState;
73
74@class CDLongAudioSource;
75@protocol CDLongAudioSourceDelegate <NSObject>
76@optional
77/** The audio source completed playing */
78- (void) cdAudioSourceDidFinishPlaying:(CDLongAudioSource *) audioSource;
79/** The file used to load the audio source has changed */
80- (void) cdAudioSourceFileDidChange:(CDLongAudioSource *) audioSource;
81@end
82
83/**
84 CDLongAudioSource represents an audio source that has a long duration which makes
85 it costly to load into memory for playback as an effect using CDSoundEngine. Examples
86 include background music and narration tracks. The audio file may or may not be compressed.
87 Bear in mind that current iDevices can only use hardware to decode a single compressed
88 audio file at a time and playing multiple compressed files will result in a performance drop
89 as software decompression will take place.
90 @since v0.99
91 */
92@interface CDLongAudioSource : NSObject <AVAudioPlayerDelegate, CDAudioInterruptProtocol>{
93 AVAudioPlayer *audioSourcePlayer;
94 NSString *audioSourceFilePath;
95 NSInteger numberOfLoops;
96 float volume;
97 id<CDLongAudioSourceDelegate> delegate;
98 BOOL mute;
99 BOOL enabled_;
100 BOOL backgroundMusic;
101@public
102 BOOL systemPaused;//Used for auto resign handling
103 NSTimeInterval systemPauseLocation;//Used for auto resign handling
104@protected
105 tLongAudioSourceState state;
106}
107@property (readonly) AVAudioPlayer *audioSourcePlayer;
108@property (readonly) NSString *audioSourceFilePath;
109@property (readwrite, nonatomic) NSInteger numberOfLoops;
110@property (readwrite, nonatomic) float volume;
111@property (assign) id<CDLongAudioSourceDelegate> delegate;
112/* This long audio source functions as background music */
113@property (readwrite, nonatomic) BOOL backgroundMusic;
114
115/** Loads the file into the audio source */
116-(void) load:(NSString*) filePath;
117/** Plays the audio source */
118-(void) play;
119/** Stops playing the audio soruce */
120-(void) stop;
121/** Pauses the audio source */
122-(void) pause;
123/** Rewinds the audio source */
124-(void) rewind;
125/** Resumes playing the audio source if it was paused */
126-(void) resume;
127/** Returns whether or not the audio source is playing */
128-(BOOL) isPlaying;
129
130@end
131
132/**
133 CDAudioManager manages audio requirements for a game. It provides access to a CDSoundEngine object
134 for playing sound effects. It provides access to two CDLongAudioSource object (left and right channel)
135 for playing long duration audio such as background music and narration tracks. Additionally it manages
136 the audio session to take care of things like audio session interruption and interacting with the audio
137 of other apps that are running on the device.
138
139 Requirements:
140 - Firmware: OS 2.2 or greater
141 - Files: CDAudioManager.*, CocosDenshion.*
142 - Frameworks: OpenAL, AudioToolbox, AVFoundation
143 @since v0.8
144 */
145@interface CDAudioManager : NSObject <CDLongAudioSourceDelegate, CDAudioInterruptProtocol, AVAudioSessionDelegate> {
146 CDSoundEngine *soundEngine;
147 CDLongAudioSource *backgroundMusic;
148 NSMutableArray *audioSourceChannels;
149 NSString* _audioSessionCategory;
150 BOOL _audioWasPlayingAtStartup;
151 tAudioManagerMode _mode;
152 SEL backgroundMusicCompletionSelector;
153 id backgroundMusicCompletionListener;
154 BOOL willPlayBackgroundMusic;
155 BOOL _mute;
156 BOOL _resigned;
157 BOOL _interrupted;
158 BOOL _audioSessionActive;
159 BOOL enabled_;
160
161 //For handling resign/become active
162 BOOL _isObservingAppEvents;
163 tAudioManagerResignBehavior _resignBehavior;
164}
165
166@property (readonly) CDSoundEngine *soundEngine;
167@property (readonly) CDLongAudioSource *backgroundMusic;
168@property (readonly) BOOL willPlayBackgroundMusic;
169
170/** Returns the shared singleton */
171+ (CDAudioManager *) sharedManager;
172+ (tAudioManagerState) sharedManagerState;
173/** Configures the shared singleton with a mode*/
174+ (void) configure: (tAudioManagerMode) mode;
175/** Initializes the engine asynchronously with a mode */
176+ (void) initAsynchronously: (tAudioManagerMode) mode;
177/** Initializes the engine synchronously with a mode, channel definition and a total number of channels */
178- (id) init: (tAudioManagerMode) mode;
179-(void) audioSessionInterrupted;
180-(void) audioSessionResumed;
181-(void) setResignBehavior:(tAudioManagerResignBehavior) resignBehavior autoHandle:(BOOL) autoHandle;
182/** Returns true is audio is muted at a hardware level e.g user has ringer switch set to off */
183-(BOOL) isDeviceMuted;
184/** Returns true if another app is playing audio such as the iPod music player */
185-(BOOL) isOtherAudioPlaying;
186/** Sets the way the audio manager interacts with the operating system such as whether it shares output with other apps or obeys the mute switch */
187-(void) setMode:(tAudioManagerMode) mode;
188/** Shuts down the shared audio manager instance so that it can be reinitialised */
189+(void) end;
190
191/** Call if you want to use built in resign behavior but need to do some additional audio processing on resign active. */
192- (void) applicationWillResignActive;
193/** Call if you want to use built in resign behavior but need to do some additional audio processing on become active. */
194- (void) applicationDidBecomeActive;
195
196//New AVAudioPlayer API
197/** Loads the data from the specified file path to the channel's audio source */
198-(CDLongAudioSource*) audioSourceLoad:(NSString*) filePath channel:(tAudioSourceChannel) channel;
199/** Retrieves the audio source for the specified channel */
200-(CDLongAudioSource*) audioSourceForChannel:(tAudioSourceChannel) channel;
201
202//Legacy AVAudioPlayer API
203/** Plays music in background. The music can be looped or not
204 It is recommended to use .aac files as background music since they are decoded by the device (hardware).
205 */
206-(void) playBackgroundMusic:(NSString*) filePath loop:(BOOL) loop;
207/** Preloads a background music */
208-(void) preloadBackgroundMusic:(NSString*) filePath;
209/** Stops playing the background music */
210-(void) stopBackgroundMusic;
211/** Pauses the background music */
212-(void) pauseBackgroundMusic;
213/** Rewinds the background music */
214-(void) rewindBackgroundMusic;
215/** Resumes playing the background music */
216-(void) resumeBackgroundMusic;
217/** Returns whether or not the background music is playing */
218-(BOOL) isBackgroundMusicPlaying;
219
220-(void) setBackgroundMusicCompletionListener:(id) listener selector:(SEL) selector;
221
222@end
223
224/** Fader for long audio source objects */
225@interface CDLongAudioSourceFader : CDPropertyModifier{}
226@end
227
228static const int kCDNoBuffer = -1;
229
230/** Allows buffers to be associated with file names */
231@interface CDBufferManager:NSObject{
232 NSMutableDictionary* loadedBuffers;
233 NSMutableArray *freedBuffers;
234 CDSoundEngine *soundEngine;
235 int nextBufferId;
236}
237
238-(id) initWithEngine:(CDSoundEngine *) theSoundEngine;
239-(int) bufferForFile:(NSString*) filePath create:(BOOL) create;
240-(void) releaseBufferForFile:(NSString *) filePath;
241
242@end
243
diff --git a/libs/CocosDenshion/CDAudioManager.m b/libs/CocosDenshion/CDAudioManager.m new file mode 100755 index 0000000..0929f3c --- /dev/null +++ b/libs/CocosDenshion/CDAudioManager.m
@@ -0,0 +1,887 @@
1/*
2 Copyright (c) 2010 Steve Oldmeadow
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
10
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 THE SOFTWARE.
21
22 $Id$
23 */
24
25
26#import "CDAudioManager.h"
27
28NSString * const kCDN_AudioManagerInitialised = @"kCDN_AudioManagerInitialised";
29
30//NSOperation object used to asynchronously initialise
31@implementation CDAsynchInitialiser
32
33-(void) main {
34 [super main];
35 [CDAudioManager sharedManager];
36}
37
38@end
39
40@implementation CDLongAudioSource
41
42@synthesize audioSourcePlayer, audioSourceFilePath, delegate, backgroundMusic;
43
44-(id) init {
45 if ((self = [super init])) {
46 state = kLAS_Init;
47 volume = 1.0f;
48 mute = NO;
49 enabled_ = YES;
50 }
51 return self;
52}
53
54-(void) dealloc {
55 CDLOGINFO(@"Denshion::CDLongAudioSource - deallocating %@", self);
56 [audioSourcePlayer release];
57 [audioSourceFilePath release];
58 [super dealloc];
59}
60
61-(void) load:(NSString*) filePath {
62 //We have alread loaded a file previously, check if we are being asked to load the same file
63 if (state == kLAS_Init || ![filePath isEqualToString:audioSourceFilePath]) {
64 CDLOGINFO(@"Denshion::CDLongAudioSource - Loading new audio source %@",filePath);
65 //New file
66 if (state != kLAS_Init) {
67 [audioSourceFilePath release];//Release old file path
68 [audioSourcePlayer release];//Release old AVAudioPlayer, they can't be reused
69 }
70 audioSourceFilePath = [filePath copy];
71 NSError *error = nil;
72 NSString *path = [CDUtilities fullPathFromRelativePath:audioSourceFilePath];
73 audioSourcePlayer = [(AVAudioPlayer*)[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:&error];
74 if (error == nil) {
75 [audioSourcePlayer prepareToPlay];
76 audioSourcePlayer.delegate = self;
77 if (delegate && [delegate respondsToSelector:@selector(cdAudioSourceFileDidChange:)]) {
78 //Tell our delegate the file has changed
79 [delegate cdAudioSourceFileDidChange:self];
80 }
81 } else {
82 CDLOG(@"Denshion::CDLongAudioSource - Error initialising audio player: %@",error);
83 }
84 } else {
85 //Same file - just return it to a consistent state
86 [self pause];
87 [self rewind];
88 }
89 audioSourcePlayer.volume = volume;
90 audioSourcePlayer.numberOfLoops = numberOfLoops;
91 state = kLAS_Loaded;
92}
93
94-(void) play {
95 if (enabled_) {
96 self->systemPaused = NO;
97 [audioSourcePlayer play];
98 } else {
99 CDLOGINFO(@"Denshion::CDLongAudioSource long audio source didn't play because it is disabled");
100 }
101}
102
103-(void) stop {
104 [audioSourcePlayer stop];
105}
106
107-(void) pause {
108 [audioSourcePlayer pause];
109}
110
111-(void) rewind {
112 [audioSourcePlayer setCurrentTime:0];
113}
114
115-(void) resume {
116 [audioSourcePlayer play];
117}
118
119-(BOOL) isPlaying {
120 if (state != kLAS_Init) {
121 return [audioSourcePlayer isPlaying];
122 } else {
123 return NO;
124 }
125}
126
127-(void) setVolume:(float) newVolume
128{
129 volume = newVolume;
130 if (state != kLAS_Init && !mute) {
131 audioSourcePlayer.volume = newVolume;
132 }
133}
134
135-(float) volume
136{
137 return volume;
138}
139
140#pragma mark Audio Interrupt Protocol
141-(BOOL) mute
142{
143 return mute;
144}
145
146-(void) setMute:(BOOL) muteValue
147{
148 if (mute != muteValue) {
149 if (mute) {
150 //Turn sound back on
151 audioSourcePlayer.volume = volume;
152 } else {
153 audioSourcePlayer.volume = 0.0f;
154 }
155 mute = muteValue;
156 }
157}
158
159-(BOOL) enabled
160{
161 return enabled_;
162}
163
164-(void) setEnabled:(BOOL)enabledValue
165{
166 if (enabledValue != enabled_) {
167 enabled_ = enabledValue;
168 if (!enabled_) {
169 //"Stop" the sounds
170 [self pause];
171 [self rewind];
172 }
173 }
174}
175
176-(NSInteger) numberOfLoops {
177 return numberOfLoops;
178}
179
180-(void) setNumberOfLoops:(NSInteger) loopCount
181{
182 audioSourcePlayer.numberOfLoops = loopCount;
183 numberOfLoops = loopCount;
184}
185
186- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
187 CDLOGINFO(@"Denshion::CDLongAudioSource - audio player finished");
188#if TARGET_IPHONE_SIMULATOR
189 CDLOGINFO(@"Denshion::CDLongAudioSource - workaround for OpenAL clobbered audio issue");
190 //This is a workaround for an issue in all simulators (tested to 3.1.2). Problem is
191 //that OpenAL audio playback is clobbered when an AVAudioPlayer stops. Workaround
192 //is to keep the player playing on an endless loop with 0 volume and then when
193 //it is played again reset the volume and set loop count appropriately.
194 //NB: this workaround is not foolproof but it is good enough for most situations.
195 player.numberOfLoops = -1;
196 player.volume = 0;
197 [player play];
198#endif
199 if (delegate && [delegate respondsToSelector:@selector(cdAudioSourceDidFinishPlaying:)]) {
200 [delegate cdAudioSourceDidFinishPlaying:self];
201 }
202}
203
204-(void)audioPlayerBeginInterruption:(AVAudioPlayer *)player {
205 CDLOGINFO(@"Denshion::CDLongAudioSource - audio player interrupted");
206}
207
208-(void)audioPlayerEndInterruption:(AVAudioPlayer *)player {
209 CDLOGINFO(@"Denshion::CDLongAudioSource - audio player resumed");
210 if (self.backgroundMusic) {
211 //Check if background music can play as rules may have changed during
212 //the interruption. This is to address a specific issue in 4.x when
213 //fast task switching
214 if([CDAudioManager sharedManager].willPlayBackgroundMusic) {
215 [player play];
216 }
217 } else {
218 [player play];
219 }
220}
221
222@end
223
224
225@interface CDAudioManager (PrivateMethods)
226-(BOOL) audioSessionSetActive:(BOOL) active;
227-(BOOL) audioSessionSetCategory:(NSString*) category;
228-(void) badAlContextHandler;
229@end
230
231
232@implementation CDAudioManager
233#define BACKGROUND_MUSIC_CHANNEL kASC_Left
234
235@synthesize soundEngine, willPlayBackgroundMusic;
236static CDAudioManager *sharedManager;
237static tAudioManagerState _sharedManagerState = kAMStateUninitialised;
238static tAudioManagerMode configuredMode;
239static BOOL configured = FALSE;
240
241-(BOOL) audioSessionSetActive:(BOOL) active {
242 NSError *activationError = nil;
243 if ([[AVAudioSession sharedInstance] setActive:active error:&activationError]) {
244 _audioSessionActive = active;
245 CDLOGINFO(@"Denshion::CDAudioManager - Audio session set active %i succeeded", active);
246 return YES;
247 } else {
248 //Failed
249 CDLOG(@"Denshion::CDAudioManager - Audio session set active %i failed with error %@", active, activationError);
250 return NO;
251 }
252}
253
254-(BOOL) audioSessionSetCategory:(NSString*) category {
255 NSError *categoryError = nil;
256 if ([[AVAudioSession sharedInstance] setCategory:category error:&categoryError]) {
257 CDLOGINFO(@"Denshion::CDAudioManager - Audio session set category %@ succeeded", category);
258 return YES;
259 } else {
260 //Failed
261 CDLOG(@"Denshion::CDAudioManager - Audio session set category %@ failed with error %@", category, categoryError);
262 return NO;
263 }
264}
265
266// Init
267+ (CDAudioManager *) sharedManager
268{
269 @synchronized(self) {
270 if (!sharedManager) {
271 if (!configured) {
272 //Set defaults here
273 configuredMode = kAMM_FxPlusMusicIfNoOtherAudio;
274 }
275 sharedManager = [[CDAudioManager alloc] init:configuredMode];
276 _sharedManagerState = kAMStateInitialised;//This is only really relevant when using asynchronous initialisation
277 [[NSNotificationCenter defaultCenter] postNotificationName:kCDN_AudioManagerInitialised object:nil];
278 }
279 }
280 return sharedManager;
281}
282
283+ (tAudioManagerState) sharedManagerState {
284 return _sharedManagerState;
285}
286
287/**
288 * Call this to set up audio manager asynchronously. Initialisation is finished when sharedManagerState == kAMStateInitialised
289 */
290+ (void) initAsynchronously: (tAudioManagerMode) mode {
291 @synchronized(self) {
292 if (_sharedManagerState == kAMStateUninitialised) {
293 _sharedManagerState = kAMStateInitialising;
294 [CDAudioManager configure:mode];
295 CDAsynchInitialiser *initOp = [[[CDAsynchInitialiser alloc] init] autorelease];
296 NSOperationQueue *opQ = [[[NSOperationQueue alloc] init] autorelease];
297 [opQ addOperation:initOp];
298 }
299 }
300}
301
302+ (id) alloc
303{
304 @synchronized(self) {
305 NSAssert(sharedManager == nil, @"Attempted to allocate a second instance of a singleton.");
306 return [super alloc];
307 }
308 return nil;
309}
310
311/*
312 * Call this method before accessing the shared manager in order to configure the shared audio manager
313 */
314+ (void) configure: (tAudioManagerMode) mode {
315 configuredMode = mode;
316 configured = TRUE;
317}
318
319-(BOOL) isOtherAudioPlaying {
320 UInt32 isPlaying = 0;
321 UInt32 varSize = sizeof(isPlaying);
322 AudioSessionGetProperty (kAudioSessionProperty_OtherAudioIsPlaying, &varSize, &isPlaying);
323 return (isPlaying != 0);
324}
325
326-(void) setMode:(tAudioManagerMode) mode {
327
328 _mode = mode;
329 switch (_mode) {
330
331 case kAMM_FxOnly:
332 //Share audio with other app
333 CDLOGINFO(@"Denshion::CDAudioManager - Audio will be shared");
334 //_audioSessionCategory = kAudioSessionCategory_AmbientSound;
335 _audioSessionCategory = AVAudioSessionCategoryAmbient;
336 willPlayBackgroundMusic = NO;
337 break;
338
339 case kAMM_FxPlusMusic:
340 //Use audio exclusively - if other audio is playing it will be stopped
341 CDLOGINFO(@"Denshion::CDAudioManager - Audio will be exclusive");
342 //_audioSessionCategory = kAudioSessionCategory_SoloAmbientSound;
343 _audioSessionCategory = AVAudioSessionCategorySoloAmbient;
344 willPlayBackgroundMusic = YES;
345 break;
346
347 case kAMM_MediaPlayback:
348 //Use audio exclusively, ignore mute switch and sleep
349 CDLOGINFO(@"Denshion::CDAudioManager - Media playback mode, audio will be exclusive");
350 //_audioSessionCategory = kAudioSessionCategory_MediaPlayback;
351 _audioSessionCategory = AVAudioSessionCategoryPlayback;
352 willPlayBackgroundMusic = YES;
353 break;
354
355 case kAMM_PlayAndRecord:
356 //Use audio exclusively, ignore mute switch and sleep, has inputs and outputs
357 CDLOGINFO(@"Denshion::CDAudioManager - Play and record mode, audio will be exclusive");
358 //_audioSessionCategory = kAudioSessionCategory_PlayAndRecord;
359 _audioSessionCategory = AVAudioSessionCategoryPlayAndRecord;
360 willPlayBackgroundMusic = YES;
361 break;
362
363 default:
364 //kAudioManagerFxPlusMusicIfNoOtherAudio
365 if ([self isOtherAudioPlaying]) {
366 CDLOGINFO(@"Denshion::CDAudioManager - Other audio is playing audio will be shared");
367 //_audioSessionCategory = kAudioSessionCategory_AmbientSound;
368 _audioSessionCategory = AVAudioSessionCategoryAmbient;
369 willPlayBackgroundMusic = NO;
370 } else {
371 CDLOGINFO(@"Denshion::CDAudioManager - Other audio is not playing audio will be exclusive");
372 //_audioSessionCategory = kAudioSessionCategory_SoloAmbientSound;
373 _audioSessionCategory = AVAudioSessionCategorySoloAmbient;
374 willPlayBackgroundMusic = YES;
375 }
376
377 break;
378 }
379
380 [self audioSessionSetCategory:_audioSessionCategory];
381
382}
383
384/**
385 * This method is used to work around various bugs introduced in 4.x OS versions. In some circumstances the
386 * audio session is interrupted but never resumed, this results in the loss of OpenAL audio when following
387 * standard practices. If we detect this situation then we will attempt to resume the audio session ourselves.
388 * Known triggers: lock the device then unlock it (iOS 4.2 gm), playback a song using MPMediaPlayer (iOS 4.0)
389 */
390- (void) badAlContextHandler {
391 if (_interrupted && alcGetCurrentContext() == NULL) {
392 CDLOG(@"Denshion::CDAudioManager - bad OpenAL context detected, attempting to resume audio session");
393 [self audioSessionResumed];
394 }
395}
396
397- (id) init: (tAudioManagerMode) mode {
398 if ((self = [super init])) {
399
400 //Initialise the audio session
401 AVAudioSession* session = [AVAudioSession sharedInstance];
402 session.delegate = self;
403
404 _mode = mode;
405 backgroundMusicCompletionSelector = nil;
406 _isObservingAppEvents = FALSE;
407 _mute = NO;
408 _resigned = NO;
409 _interrupted = NO;
410 enabled_ = YES;
411 _audioSessionActive = NO;
412 [self setMode:mode];
413 soundEngine = [[CDSoundEngine alloc] init];
414
415 //Set up audioSource channels
416 audioSourceChannels = [[NSMutableArray alloc] init];
417 CDLongAudioSource *leftChannel = [[CDLongAudioSource alloc] init];
418 leftChannel.backgroundMusic = YES;
419 CDLongAudioSource *rightChannel = [[CDLongAudioSource alloc] init];
420 rightChannel.backgroundMusic = NO;
421 [audioSourceChannels insertObject:leftChannel atIndex:kASC_Left];
422 [audioSourceChannels insertObject:rightChannel atIndex:kASC_Right];
423 [leftChannel release];
424 [rightChannel release];
425 //Used to support legacy APIs
426 backgroundMusic = [self audioSourceForChannel:BACKGROUND_MUSIC_CHANNEL];
427 backgroundMusic.delegate = self;
428
429 //Add handler for bad al context messages, these are posted by the sound engine.
430 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(badAlContextHandler) name:kCDN_BadAlContext object:nil];
431
432 }
433 return self;
434}
435
436-(void) dealloc {
437 CDLOGINFO(@"Denshion::CDAudioManager - deallocating");
438 [self stopBackgroundMusic];
439 [soundEngine release];
440 [[NSNotificationCenter defaultCenter] removeObserver:self];
441 [self audioSessionSetActive:NO];
442 [audioSourceChannels release];
443 [super dealloc];
444}
445
446/** Retrieves the audio source for the specified channel */
447-(CDLongAudioSource*) audioSourceForChannel:(tAudioSourceChannel) channel
448{
449 return (CDLongAudioSource*)[audioSourceChannels objectAtIndex:channel];
450}
451
452/** Loads the data from the specified file path to the channel's audio source */
453-(CDLongAudioSource*) audioSourceLoad:(NSString*) filePath channel:(tAudioSourceChannel) channel
454{
455 CDLongAudioSource *audioSource = [self audioSourceForChannel:channel];
456 if (audioSource) {
457 [audioSource load:filePath];
458 }
459 return audioSource;
460}
461
462-(BOOL) isBackgroundMusicPlaying {
463 return [self.backgroundMusic isPlaying];
464}
465
466//NB: originally I tried using a route change listener and intended to store the current route,
467//however, on a 3gs running 3.1.2 no route change is generated when the user switches the
468//ringer mute switch to off (i.e. enables sound) therefore polling is the only reliable way to
469//determine ringer switch state
470-(BOOL) isDeviceMuted {
471
472#if TARGET_IPHONE_SIMULATOR
473 //Calling audio route stuff on the simulator causes problems
474 return NO;
475#else
476 CFStringRef newAudioRoute;
477 UInt32 propertySize = sizeof (CFStringRef);
478
479 AudioSessionGetProperty (
480 kAudioSessionProperty_AudioRoute,
481 &propertySize,
482 &newAudioRoute
483 );
484
485 if (newAudioRoute == NULL) {
486 //Don't expect this to happen but playing safe otherwise a null in the CFStringCompare will cause a crash
487 return YES;
488 } else {
489 CFComparisonResult newDeviceIsMuted = CFStringCompare (
490 newAudioRoute,
491 (CFStringRef) @"",
492 0
493 );
494
495 return (newDeviceIsMuted == kCFCompareEqualTo);
496 }
497#endif
498}
499
500#pragma mark Audio Interrupt Protocol
501
502-(BOOL) mute {
503 return _mute;
504}
505
506-(void) setMute:(BOOL) muteValue {
507 if (muteValue != _mute) {
508 _mute = muteValue;
509 [soundEngine setMute:muteValue];
510 for( CDLongAudioSource *audioSource in audioSourceChannels) {
511 audioSource.mute = muteValue;
512 }
513 }
514}
515
516-(BOOL) enabled {
517 return enabled_;
518}
519
520-(void) setEnabled:(BOOL) enabledValue {
521 if (enabledValue != enabled_) {
522 enabled_ = enabledValue;
523 [soundEngine setEnabled:enabled_];
524 for( CDLongAudioSource *audioSource in audioSourceChannels) {
525 audioSource.enabled = enabled_;
526 }
527 }
528}
529
530-(CDLongAudioSource*) backgroundMusic
531{
532 return backgroundMusic;
533}
534
535//Load background music ready for playing
536-(void) preloadBackgroundMusic:(NSString*) filePath
537{
538 [self.backgroundMusic load:filePath];
539}
540
541-(void) playBackgroundMusic:(NSString*) filePath loop:(BOOL) loop
542{
543 [self.backgroundMusic load:filePath];
544
545 if (!willPlayBackgroundMusic || _mute) {
546 CDLOGINFO(@"Denshion::CDAudioManager - play bgm aborted because audio is not exclusive or sound is muted");
547 return;
548 }
549
550 if (loop) {
551 [self.backgroundMusic setNumberOfLoops:-1];
552 } else {
553 [self.backgroundMusic setNumberOfLoops:0];
554 }
555 [self.backgroundMusic play];
556}
557
558-(void) stopBackgroundMusic
559{
560 [self.backgroundMusic stop];
561}
562
563-(void) pauseBackgroundMusic
564{
565 [self.backgroundMusic pause];
566}
567
568-(void) resumeBackgroundMusic
569{
570 if (!willPlayBackgroundMusic || _mute) {
571 CDLOGINFO(@"Denshion::CDAudioManager - resume bgm aborted because audio is not exclusive or sound is muted");
572 return;
573 }
574
575 [self.backgroundMusic resume];
576}
577
578-(void) rewindBackgroundMusic
579{
580 [self.backgroundMusic rewind];
581}
582
583-(void) setBackgroundMusicCompletionListener:(id) listener selector:(SEL) selector {
584 backgroundMusicCompletionListener = listener;
585 backgroundMusicCompletionSelector = selector;
586}
587
588/*
589 * Call this method to have the audio manager automatically handle application resign and
590 * become active. Pass a tAudioManagerResignBehavior to indicate the desired behavior
591 * for resigning and becoming active again.
592 *
593 * If autohandle is YES then the applicationWillResignActive and applicationDidBecomActive
594 * methods are automatically called, otherwise you must call them yourself at the appropriate time.
595 *
596 * Based on idea of Dominique Bongard
597 */
598-(void) setResignBehavior:(tAudioManagerResignBehavior) resignBehavior autoHandle:(BOOL) autoHandle {
599
600 if (!_isObservingAppEvents && autoHandle) {
601 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:@"UIApplicationWillResignActiveNotification" object:nil];
602 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:@"UIApplicationDidBecomeActiveNotification" object:nil];
603 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate:) name:@"UIApplicationWillTerminateNotification" object:nil];
604 _isObservingAppEvents = TRUE;
605 }
606 _resignBehavior = resignBehavior;
607}
608
609- (void) applicationWillResignActive {
610 self->_resigned = YES;
611
612 //Set the audio sesssion to one that allows sharing so that other audio won't be clobbered on resume
613 [self audioSessionSetCategory:AVAudioSessionCategoryAmbient];
614
615 switch (_resignBehavior) {
616
617 case kAMRBStopPlay:
618
619 for( CDLongAudioSource *audioSource in audioSourceChannels) {
620 if (audioSource.isPlaying) {
621 audioSource->systemPaused = YES;
622 audioSource->systemPauseLocation = audioSource.audioSourcePlayer.currentTime;
623 [audioSource stop];
624 } else {
625 //Music is either paused or stopped, if it is paused it will be restarted
626 //by OS so we will stop it.
627 audioSource->systemPaused = NO;
628 [audioSource stop];
629 }
630 }
631 break;
632
633 case kAMRBStop:
634 //Stop music regardless of whether it is playing or not because if it was paused
635 //then the OS would resume it
636 for( CDLongAudioSource *audioSource in audioSourceChannels) {
637 [audioSource stop];
638 }
639
640 default:
641 break;
642
643 }
644 CDLOGINFO(@"Denshion::CDAudioManager - handled resign active");
645}
646
647//Called when application resigns active only if setResignBehavior has been called
648- (void) applicationWillResignActive:(NSNotification *) notification
649{
650 [self applicationWillResignActive];
651}
652
653- (void) applicationDidBecomeActive {
654
655 if (self->_resigned) {
656 _resigned = NO;
657 //Reset the mode incase something changed with audio while we were inactive
658 [self setMode:_mode];
659 switch (_resignBehavior) {
660
661 case kAMRBStopPlay:
662
663 //Music had been stopped but stop maintains current time
664 //so playing again will continue from where music was before resign active.
665 //We check if music can be played because while we were inactive the user might have
666 //done something that should force music to not play such as starting a track in the iPod
667 if (self.willPlayBackgroundMusic) {
668 for( CDLongAudioSource *audioSource in audioSourceChannels) {
669 if (audioSource->systemPaused) {
670 [audioSource resume];
671 audioSource->systemPaused = NO;
672 }
673 }
674 }
675 break;
676
677 default:
678 break;
679
680 }
681 CDLOGINFO(@"Denshion::CDAudioManager - audio manager handled become active");
682 }
683}
684
685//Called when application becomes active only if setResignBehavior has been called
686- (void) applicationDidBecomeActive:(NSNotification *) notification
687{
688 [self applicationDidBecomeActive];
689}
690
691//Called when application terminates only if setResignBehavior has been called
692- (void) applicationWillTerminate:(NSNotification *) notification
693{
694 CDLOGINFO(@"Denshion::CDAudioManager - audio manager handling terminate");
695 [self stopBackgroundMusic];
696}
697
698/** The audio source completed playing */
699- (void) cdAudioSourceDidFinishPlaying:(CDLongAudioSource *) audioSource {
700 CDLOGINFO(@"Denshion::CDAudioManager - audio manager got told background music finished");
701 if (backgroundMusicCompletionSelector != nil) {
702 [backgroundMusicCompletionListener performSelector:backgroundMusicCompletionSelector];
703 }
704}
705
706-(void) beginInterruption {
707 CDLOGINFO(@"Denshion::CDAudioManager - begin interruption");
708 [self audioSessionInterrupted];
709}
710
711-(void) endInterruption {
712 CDLOGINFO(@"Denshion::CDAudioManager - end interruption");
713 [self audioSessionResumed];
714}
715
716#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000
717-(void) endInterruptionWithFlags:(NSUInteger)flags {
718 CDLOGINFO(@"Denshion::CDAudioManager - interruption ended with flags %i",flags);
719 if (flags == AVAudioSessionInterruptionFlags_ShouldResume) {
720 [self audioSessionResumed];
721 }
722}
723#endif
724
725-(void)audioSessionInterrupted
726{
727 if (!_interrupted) {
728 CDLOGINFO(@"Denshion::CDAudioManager - Audio session interrupted");
729 _interrupted = YES;
730
731 // Deactivate the current audio session
732 [self audioSessionSetActive:NO];
733
734 if (alcGetCurrentContext() != NULL) {
735 CDLOGINFO(@"Denshion::CDAudioManager - Setting OpenAL context to NULL");
736
737 ALenum error = AL_NO_ERROR;
738
739 // set the current context to NULL will 'shutdown' openAL
740 alcMakeContextCurrent(NULL);
741
742 if((error = alGetError()) != AL_NO_ERROR) {
743 CDLOG(@"Denshion::CDAudioManager - Error making context current %x\n", error);
744 }
745 #pragma unused(error)
746 }
747 }
748}
749
750-(void)audioSessionResumed
751{
752 if (_interrupted) {
753 CDLOGINFO(@"Denshion::CDAudioManager - Audio session resumed");
754 _interrupted = NO;
755
756 BOOL activationResult = NO;
757 // Reactivate the current audio session
758 activationResult = [self audioSessionSetActive:YES];
759
760 //This code is to handle a problem with iOS 4.0 and 4.01 where reactivating the session can fail if
761 //task switching is performed too rapidly. A test case that reliably reproduces the issue is to call the
762 //iPhone and then hang up after two rings (timing may vary ;))
763 //Basically we keep waiting and trying to let the OS catch up with itself but the number of tries is
764 //limited.
765 if (!activationResult) {
766 CDLOG(@"Denshion::CDAudioManager - Failure reactivating audio session, will try wait-try cycle");
767 int activateCount = 0;
768 while (!activationResult && activateCount < 10) {
769 [NSThread sleepForTimeInterval:0.5];
770 activationResult = [self audioSessionSetActive:YES];
771 activateCount++;
772 CDLOGINFO(@"Denshion::CDAudioManager - Reactivation attempt %i status = %i",activateCount,activationResult);
773 }
774 }
775
776 if (alcGetCurrentContext() == NULL) {
777 CDLOGINFO(@"Denshion::CDAudioManager - Restoring OpenAL context");
778 ALenum error = AL_NO_ERROR;
779 // Restore open al context
780 alcMakeContextCurrent([soundEngine openALContext]);
781 if((error = alGetError()) != AL_NO_ERROR) {
782 CDLOG(@"Denshion::CDAudioManager - Error making context current%x\n", error);
783 }
784 #pragma unused(error)
785 }
786 }
787}
788
789+(void) end {
790 [sharedManager release];
791 sharedManager = nil;
792}
793
794@end
795
796///////////////////////////////////////////////////////////////////////////////////////
797@implementation CDLongAudioSourceFader
798
799-(void) _setTargetProperty:(float) newVal {
800 ((CDLongAudioSource*)target).volume = newVal;
801}
802
803-(float) _getTargetProperty {
804 return ((CDLongAudioSource*)target).volume;
805}
806
807-(void) _stopTarget {
808 //Pause instead of stop as stop releases resources and causes problems in the simulator
809 [((CDLongAudioSource*)target) pause];
810}
811
812-(Class) _allowableType {
813 return [CDLongAudioSource class];
814}
815
816@end
817///////////////////////////////////////////////////////////////////////////////////////
818@implementation CDBufferManager
819
820-(id) initWithEngine:(CDSoundEngine *) theSoundEngine {
821 if ((self = [super init])) {
822 soundEngine = theSoundEngine;
823 loadedBuffers = [[NSMutableDictionary alloc] initWithCapacity:CD_BUFFERS_START];
824 freedBuffers = [[NSMutableArray alloc] init];
825 nextBufferId = 0;
826 }
827 return self;
828}
829
830-(void) dealloc {
831 [loadedBuffers release];
832 [freedBuffers release];
833 [super dealloc];
834}
835
836-(int) bufferForFile:(NSString*) filePath create:(BOOL) create {
837
838 NSNumber* soundId = (NSNumber*)[loadedBuffers objectForKey:filePath];
839 if(soundId == nil)
840 {
841 if (create) {
842 NSNumber* bufferId = nil;
843 //First try to get a buffer from the free buffers
844 if ([freedBuffers count] > 0) {
845 bufferId = [[[freedBuffers lastObject] retain] autorelease];
846 [freedBuffers removeLastObject];
847 CDLOGINFO(@"Denshion::CDBufferManager reusing buffer id %i",[bufferId intValue]);
848 } else {
849 bufferId = [[NSNumber alloc] initWithInt:nextBufferId];
850 [bufferId autorelease];
851 CDLOGINFO(@"Denshion::CDBufferManager generating new buffer id %i",[bufferId intValue]);
852 nextBufferId++;
853 }
854
855 if ([soundEngine loadBuffer:[bufferId intValue] filePath:filePath]) {
856 //File successfully loaded
857 CDLOGINFO(@"Denshion::CDBufferManager buffer loaded %@ %@",bufferId,filePath);
858 [loadedBuffers setObject:bufferId forKey:filePath];
859 return [bufferId intValue];
860 } else {
861 //File didn't load, put buffer id on free list
862 [freedBuffers addObject:bufferId];
863 return kCDNoBuffer;
864 }
865 } else {
866 //No matching buffer was found
867 return kCDNoBuffer;
868 }
869 } else {
870 return [soundId intValue];
871 }
872}
873
874-(void) releaseBufferForFile:(NSString *) filePath {
875 int bufferId = [self bufferForFile:filePath create:NO];
876 if (bufferId != kCDNoBuffer) {
877 [soundEngine unloadBuffer:bufferId];
878 [loadedBuffers removeObjectForKey:filePath];
879 NSNumber *freedBufferId = [[NSNumber alloc] initWithInt:bufferId];
880 [freedBufferId autorelease];
881 [freedBuffers addObject:freedBufferId];
882 }
883}
884@end
885
886
887
diff --git a/libs/CocosDenshion/CDConfig.h b/libs/CocosDenshion/CDConfig.h new file mode 100755 index 0000000..2bd8f76 --- /dev/null +++ b/libs/CocosDenshion/CDConfig.h
@@ -0,0 +1,60 @@
1/*
2 Copyright (c) 2010 Steve Oldmeadow
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
10
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 THE SOFTWARE.
21
22 $Id$
23 */
24#define COCOSDENSHION_VERSION "Aphex.rc"
25
26
27/**
28 If enabled code useful for debugging such as parameter check assertions will be performed.
29 If you experience any problems you should enable this and test your code with a debug build.
30 */
31//#define CD_DEBUG 1
32
33/**
34 The total number of sounds/buffers that can be loaded assuming memory is sufficient
35 */
36//Number of buffers slots that will be initially created
37#define CD_BUFFERS_START 64
38//Number of buffers that will be added
39#define CD_BUFFERS_INCREMENT 16
40
41/**
42 If enabled, OpenAL code will use static buffers. When static buffers are used the audio
43 data is managed outside of OpenAL, this eliminates a memcpy operation which leads to
44 higher performance when loading sounds.
45
46 However, the downside is that when the audio data is freed you must
47 be certain that it is no longer being accessed otherwise your app will crash. Testing on OS 2.2.1
48 and 3.1.2 has shown that this may occur if a buffer is being used by a source with state = AL_PLAYING
49 when the buffer is deleted. If the data is freed too quickly after the source is stopped then
50 a crash will occur. The implemented workaround is that when static buffers are used the unloadBuffer code will wait for
51 any playing sources to finish playing before the associated buffer and data are deleted, however, this delay may negate any
52 performance gains that are achieved during loading.
53
54 Performance tests on a 1st gen iPod running OS 2.2.1 loading the CocosDenshionDemo sounds were ~0.14 seconds without
55 static buffers and ~0.12 seconds when using static buffers.
56
57 */
58//#define CD_USE_STATIC_BUFFERS 1
59
60
diff --git a/libs/CocosDenshion/CDOpenALSupport.h b/libs/CocosDenshion/CDOpenALSupport.h new file mode 100755 index 0000000..661c69e --- /dev/null +++ b/libs/CocosDenshion/CDOpenALSupport.h
@@ -0,0 +1,77 @@
1/*
2
3 Disclaimer: IMPORTANT: This Apple software is supplied to you by
4 Apple Inc. ("Apple") in consideration of your agreement to the
5 following terms, and your use, installation, modification or
6 redistribution of this Apple software constitutes acceptance of these
7 terms. If you do not agree with these terms, please do not use,
8 install, modify or redistribute this Apple software.
9
10 In consideration of your agreement to abide by the following terms, and
11 subject to these terms, Apple grants you a personal, non-exclusive
12 license, under Apple's copyrights in this original Apple software (the
13 "Apple Software"), to use, reproduce, modify and redistribute the Apple
14 Software, with or without modifications, in source and/or binary forms;
15 provided that if you redistribute the Apple Software in its entirety and
16 without modifications, you must retain this notice and the following
17 text and disclaimers in all such redistributions of the Apple Software.
18 Neither the name, trademarks, service marks or logos of Apple Inc.
19 may be used to endorse or promote products derived from the Apple
20 Software without specific prior written permission from Apple. Except
21 as expressly stated in this notice, no other rights or licenses, express
22 or implied, are granted by Apple herein, including but not limited to
23 any patent rights that may be infringed by your derivative works or by
24 other works in which the Apple Software may be incorporated.
25
26 The Apple Software is provided by Apple on an "AS IS" basis. APPLE
27 MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
28 THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
29 FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
30 OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
31
32 IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
33 OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
34 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
35 INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
36 MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
37 AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
38 STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
39 POSSIBILITY OF SUCH DAMAGE.
40
41 Copyright (C) 2009 Apple Inc. All Rights Reserved.
42
43 $Id$
44 */
45
46/*
47 This file contains code from version 1.1 and 1.4 of MyOpenALSupport.h taken from Apple's oalTouch version.
48 The 1.4 version code is used for loading IMA4 files, however, this code causes very noticeable clicking
49 when used to load wave files that are looped so the 1.1 version code is used specifically for loading
50 wav files.
51 */
52
53#ifndef __CD_OPENAL_H
54#define __CD_OPENAL_H
55
56#ifdef __cplusplus
57extern "C" {
58#endif
59
60
61#import <OpenAL/al.h>
62#import <OpenAL/alc.h>
63#import <CoreFoundation/CFURL.h>
64
65
66//Taken from oalTouch MyOpenALSupport 1.1
67void* CDloadWaveAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei* outSampleRate);
68void* CDloadCafAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei* outSampleRate);
69void* CDGetOpenALAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei* outSampleRate);
70
71#ifdef __cplusplus
72}
73#endif
74
75#endif
76
77
diff --git a/libs/CocosDenshion/CDOpenALSupport.m b/libs/CocosDenshion/CDOpenALSupport.m new file mode 100755 index 0000000..ab0df8e --- /dev/null +++ b/libs/CocosDenshion/CDOpenALSupport.m
@@ -0,0 +1,246 @@
1/*
2
3 Disclaimer: IMPORTANT: This Apple software is supplied to you by
4 Apple Inc. ("Apple") in consideration of your agreement to the
5 following terms, and your use, installation, modification or
6 redistribution of this Apple software constitutes acceptance of these
7 terms. If you do not agree with these terms, please do not use,
8 install, modify or redistribute this Apple software.
9
10 In consideration of your agreement to abide by the following terms, and
11 subject to these terms, Apple grants you a personal, non-exclusive
12 license, under Apple's copyrights in this original Apple software (the
13 "Apple Software"), to use, reproduce, modify and redistribute the Apple
14 Software, with or without modifications, in source and/or binary forms;
15 provided that if you redistribute the Apple Software in its entirety and
16 without modifications, you must retain this notice and the following
17 text and disclaimers in all such redistributions of the Apple Software.
18 Neither the name, trademarks, service marks or logos of Apple Inc.
19 may be used to endorse or promote products derived from the Apple
20 Software without specific prior written permission from Apple. Except
21 as expressly stated in this notice, no other rights or licenses, express
22 or implied, are granted by Apple herein, including but not limited to
23 any patent rights that may be infringed by your derivative works or by
24 other works in which the Apple Software may be incorporated.
25
26 The Apple Software is provided by Apple on an "AS IS" basis. APPLE
27 MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
28 THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
29 FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
30 OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
31
32 IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
33 OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
34 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
35 INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
36 MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
37 AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
38 STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
39 POSSIBILITY OF SUCH DAMAGE.
40
41 Copyright (C) 2009 Apple Inc. All Rights Reserved.
42
43 $Id: CDOpenALSupport.h 16 2010-03-11 06:22:10Z steveoldmeadow $
44 */
45
46#import "CDOpenALSupport.h"
47#import "CocosDenshion.h"
48#import <AudioToolbox/AudioToolbox.h>
49#import <AudioToolbox/ExtendedAudioFile.h>
50
51//Taken from oalTouch MyOpenALSupport 1.1
52void* CDloadWaveAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei* outSampleRate)
53{
54 OSStatus err = noErr;
55 UInt64 fileDataSize = 0;
56 AudioStreamBasicDescription theFileFormat;
57 UInt32 thePropertySize = sizeof(theFileFormat);
58 AudioFileID afid = 0;
59 void* theData = NULL;
60
61 // Open a file with ExtAudioFileOpen()
62 err = AudioFileOpenURL(inFileURL, kAudioFileReadPermission, 0, &afid);
63 if(err) { CDLOG(@"MyGetOpenALAudioData: AudioFileOpenURL FAILED, Error = %ld\n", err); goto Exit; }
64
65 // Get the audio data format
66 err = AudioFileGetProperty(afid, kAudioFilePropertyDataFormat, &thePropertySize, &theFileFormat);
67 if(err) { CDLOG(@"MyGetOpenALAudioData: AudioFileGetProperty(kAudioFileProperty_DataFormat) FAILED, Error = %ld\n", err); goto Exit; }
68
69 if (theFileFormat.mChannelsPerFrame > 2) {
70 CDLOG(@"MyGetOpenALAudioData - Unsupported Format, channel count is greater than stereo\n"); goto Exit;
71 }
72
73 if ((theFileFormat.mFormatID != kAudioFormatLinearPCM) || (!TestAudioFormatNativeEndian(theFileFormat))) {
74 CDLOG(@"MyGetOpenALAudioData - Unsupported Format, must be little-endian PCM\n"); goto Exit;
75 }
76
77 if ((theFileFormat.mBitsPerChannel != 8) && (theFileFormat.mBitsPerChannel != 16)) {
78 CDLOG(@"MyGetOpenALAudioData - Unsupported Format, must be 8 or 16 bit PCM\n"); goto Exit;
79 }
80
81
82 thePropertySize = sizeof(fileDataSize);
83 err = AudioFileGetProperty(afid, kAudioFilePropertyAudioDataByteCount, &thePropertySize, &fileDataSize);
84 if(err) { CDLOG(@"MyGetOpenALAudioData: AudioFileGetProperty(kAudioFilePropertyAudioDataByteCount) FAILED, Error = %ld\n", err); goto Exit; }
85
86 // Read all the data into memory
87 UInt32 dataSize = (UInt32)fileDataSize;
88 theData = malloc(dataSize);
89 if (theData)
90 {
91 AudioFileReadBytes(afid, false, 0, &dataSize, theData);
92 if(err == noErr)
93 {
94 // success
95 *outDataSize = (ALsizei)dataSize;
96 //This fix was added by me, however, 8 bit sounds have a clipping sound at the end so aren't really usable (SO)
97 if (theFileFormat.mBitsPerChannel == 16) {
98 *outDataFormat = (theFileFormat.mChannelsPerFrame > 1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16;
99 } else {
100 *outDataFormat = (theFileFormat.mChannelsPerFrame > 1) ? AL_FORMAT_STEREO8 : AL_FORMAT_MONO8;
101 }
102 *outSampleRate = (ALsizei)theFileFormat.mSampleRate;
103 }
104 else
105 {
106 // failure
107 free (theData);
108 theData = NULL; // make sure to return NULL
109 CDLOG(@"MyGetOpenALAudioData: ExtAudioFileRead FAILED, Error = %ld\n", err); goto Exit;
110 }
111 }
112
113Exit:
114 // Dispose the ExtAudioFileRef, it is no longer needed
115 if (afid) AudioFileClose(afid);
116 return theData;
117}
118
119//Taken from oalTouch MyOpenALSupport 1.4
120void* CDloadCafAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei* outSampleRate)
121{
122 OSStatus status = noErr;
123 BOOL abort = NO;
124 SInt64 theFileLengthInFrames = 0;
125 AudioStreamBasicDescription theFileFormat;
126 UInt32 thePropertySize = sizeof(theFileFormat);
127 ExtAudioFileRef extRef = NULL;
128 void* theData = NULL;
129 AudioStreamBasicDescription theOutputFormat;
130 UInt32 dataSize = 0;
131
132 // Open a file with ExtAudioFileOpen()
133 status = ExtAudioFileOpenURL(inFileURL, &extRef);
134 if (status != noErr)
135 {
136 CDLOG(@"MyGetOpenALAudioData: ExtAudioFileOpenURL FAILED, Error = %ld\n", status);
137 abort = YES;
138 }
139 if (abort)
140 goto Exit;
141
142 // Get the audio data format
143 status = ExtAudioFileGetProperty(extRef, kExtAudioFileProperty_FileDataFormat, &thePropertySize, &theFileFormat);
144 if (status != noErr)
145 {
146 CDLOG(@"MyGetOpenALAudioData: ExtAudioFileGetProperty(kExtAudioFileProperty_FileDataFormat) FAILED, Error = %ld\n", status);
147 abort = YES;
148 }
149 if (abort)
150 goto Exit;
151
152 if (theFileFormat.mChannelsPerFrame > 2)
153 {
154 CDLOG(@"MyGetOpenALAudioData - Unsupported Format, channel count is greater than stereo\n");
155 abort = YES;
156 }
157 if (abort)
158 goto Exit;
159
160 // Set the client format to 16 bit signed integer (native-endian) data
161 // Maintain the channel count and sample rate of the original source format
162 theOutputFormat.mSampleRate = theFileFormat.mSampleRate;
163 theOutputFormat.mChannelsPerFrame = theFileFormat.mChannelsPerFrame;
164
165 theOutputFormat.mFormatID = kAudioFormatLinearPCM;
166 theOutputFormat.mBytesPerPacket = 2 * theOutputFormat.mChannelsPerFrame;
167 theOutputFormat.mFramesPerPacket = 1;
168 theOutputFormat.mBytesPerFrame = 2 * theOutputFormat.mChannelsPerFrame;
169 theOutputFormat.mBitsPerChannel = 16;
170 theOutputFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger;
171
172 // Set the desired client (output) data format
173 status = ExtAudioFileSetProperty(extRef, kExtAudioFileProperty_ClientDataFormat, sizeof(theOutputFormat), &theOutputFormat);
174 if (status != noErr)
175 {
176 CDLOG(@"MyGetOpenALAudioData: ExtAudioFileSetProperty(kExtAudioFileProperty_ClientDataFormat) FAILED, Error = %ld\n", status);
177 abort = YES;
178 }
179 if (abort)
180 goto Exit;
181
182 // Get the total frame count
183 thePropertySize = sizeof(theFileLengthInFrames);
184 status = ExtAudioFileGetProperty(extRef, kExtAudioFileProperty_FileLengthFrames, &thePropertySize, &theFileLengthInFrames);
185 if (status != noErr)
186 {
187 CDLOG(@"MyGetOpenALAudioData: ExtAudioFileGetProperty(kExtAudioFileProperty_FileLengthFrames) FAILED, Error = %ld\n", status);
188 abort = YES;
189 }
190 if (abort)
191 goto Exit;
192
193 // Read all the data into memory
194 dataSize = (UInt32) theFileLengthInFrames * theOutputFormat.mBytesPerFrame;
195 theData = malloc(dataSize);
196 if (theData)
197 {
198 AudioBufferList theDataBuffer;
199 theDataBuffer.mNumberBuffers = 1;
200 theDataBuffer.mBuffers[0].mDataByteSize = dataSize;
201 theDataBuffer.mBuffers[0].mNumberChannels = theOutputFormat.mChannelsPerFrame;
202 theDataBuffer.mBuffers[0].mData = theData;
203
204 // Read the data into an AudioBufferList
205 status = ExtAudioFileRead(extRef, (UInt32*)&theFileLengthInFrames, &theDataBuffer);
206 if(status == noErr)
207 {
208 // success
209 *outDataSize = (ALsizei)dataSize;
210 *outDataFormat = (theOutputFormat.mChannelsPerFrame > 1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16;
211 *outSampleRate = (ALsizei)theOutputFormat.mSampleRate;
212 }
213 else
214 {
215 // failure
216 free (theData);
217 theData = NULL; // make sure to return NULL
218 CDLOG(@"MyGetOpenALAudioData: ExtAudioFileRead FAILED, Error = %ld\n", status);
219 abort = YES;
220 }
221 }
222 if (abort)
223 goto Exit;
224
225Exit:
226 // Dispose the ExtAudioFileRef, it is no longer needed
227 if (extRef) ExtAudioFileDispose(extRef);
228 return theData;
229}
230
231void* CDGetOpenALAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei* outSampleRate) {
232
233 CFStringRef extension = CFURLCopyPathExtension(inFileURL);
234 CFComparisonResult isWavFile = 0;
235 if (extension != NULL) {
236 isWavFile = CFStringCompare (extension,(CFStringRef)@"wav", kCFCompareCaseInsensitive);
237 CFRelease(extension);
238 }
239
240 if (isWavFile == kCFCompareEqualTo) {
241 return CDloadWaveAudioData(inFileURL, outDataSize, outDataFormat, outSampleRate);
242 } else {
243 return CDloadCafAudioData(inFileURL, outDataSize, outDataFormat, outSampleRate);
244 }
245}
246
diff --git a/libs/CocosDenshion/CocosDenshion.h b/libs/CocosDenshion/CocosDenshion.h new file mode 100755 index 0000000..638d852 --- /dev/null +++ b/libs/CocosDenshion/CocosDenshion.h
@@ -0,0 +1,440 @@
1/*
2 Copyright (c) 2010 Steve Oldmeadow
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
10
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 THE SOFTWARE.
21
22 $Id$
23 */
24
25
26
27/**
28@file
29@b IMPORTANT
30There are 3 different ways of using CocosDenshion. Depending on which you choose you
31will need to include different files and frameworks.
32
33@par SimpleAudioEngine
34This is recommended for basic audio requirements. If you just want to play some sound fx
35and some background music and have no interest in learning the lower level workings then
36this is the interface to use.
37
38Requirements:
39 - Firmware: OS 2.2 or greater
40 - Files: SimpleAudioEngine.*, CocosDenshion.*
41 - Frameworks: OpenAL, AudioToolbox, AVFoundation
42
43@par CDAudioManager
44CDAudioManager is basically a thin wrapper around an AVAudioPlayer object used for playing
45background music and a CDSoundEngine object used for playing sound effects. It manages the
46audio session for you deals with audio session interruption. It is fairly low level and it
47is expected you have some understanding of the underlying technologies. For example, for
48many use cases regarding background music it is expected you will work directly with the
49backgroundMusic AVAudioPlayer which is exposed as a property.
50
51Requirements:
52 - Firmware: OS 2.2 or greater
53 - Files: CDAudioManager.*, CocosDenshion.*
54 - Frameworks: OpenAL, AudioToolbox, AVFoundation
55
56@par CDSoundEngine
57CDSoundEngine is a sound engine built upon OpenAL and derived from Apple's oalTouch
58example. It can playback up to 32 sounds simultaneously with control over pitch, pan
59and gain. It can be set up to handle audio session interruption automatically. You
60may decide to use CDSoundEngine directly instead of CDAudioManager or SimpleAudioEngine
61because you require OS 2.0 compatibility.
62
63Requirements:
64 - Firmware: OS 2.0 or greater
65 - Files: CocosDenshion.*
66 - Frameworks: OpenAL, AudioToolbox
67
68*/
69
70#import <OpenAL/al.h>
71#import <OpenAL/alc.h>
72#import <AudioToolbox/AudioToolbox.h>
73#import <Foundation/Foundation.h>
74#import "CDConfig.h"
75
76
77#if !defined(CD_DEBUG) || CD_DEBUG == 0
78#define CDLOG(...) do {} while (0)
79#define CDLOGINFO(...) do {} while (0)
80
81#elif CD_DEBUG == 1
82#define CDLOG(...) NSLog(__VA_ARGS__)
83#define CDLOGINFO(...) do {} while (0)
84
85#elif CD_DEBUG > 1
86#define CDLOG(...) NSLog(__VA_ARGS__)
87#define CDLOGINFO(...) NSLog(__VA_ARGS__)
88#endif // CD_DEBUG
89
90
91#import "CDOpenALSupport.h"
92
93//Tested source limit on 2.2.1 and 3.1.2 with up to 128 sources and appears to work. Older OS versions e.g 2.2 may support only 32
94#define CD_SOURCE_LIMIT 32 //Total number of sources we will ever want, may actually get less
95#define CD_NO_SOURCE 0xFEEDFAC //Return value indicating playback failed i.e. no source
96#define CD_IGNORE_AUDIO_SESSION 0xBEEFBEE //Used internally to indicate audio session will not be handled
97#define CD_MUTE 0xFEEDBAB //Return value indicating sound engine is muted or non functioning
98#define CD_NO_SOUND = -1;
99
100#define CD_SAMPLE_RATE_HIGH 44100
101#define CD_SAMPLE_RATE_MID 22050
102#define CD_SAMPLE_RATE_LOW 16000
103#define CD_SAMPLE_RATE_BASIC 8000
104#define CD_SAMPLE_RATE_DEFAULT 44100
105
106extern NSString * const kCDN_BadAlContext;
107extern NSString * const kCDN_AsynchLoadComplete;
108
109extern float const kCD_PitchDefault;
110extern float const kCD_PitchLowerOneOctave;
111extern float const kCD_PitchHigherOneOctave;
112extern float const kCD_PanDefault;
113extern float const kCD_PanFullLeft;
114extern float const kCD_PanFullRight;
115extern float const kCD_GainDefault;
116
117enum bufferState {
118 CD_BS_EMPTY = 0,
119 CD_BS_LOADED = 1,
120 CD_BS_FAILED = 2
121};
122
123typedef struct _sourceGroup {
124 int startIndex;
125 int currentIndex;
126 int totalSources;
127 bool enabled;
128 bool nonInterruptible;
129 int *sourceStatuses;//pointer into array of source status information
130} sourceGroup;
131
132typedef struct _bufferInfo {
133 ALuint bufferId;
134 int bufferState;
135 void* bufferData;
136 ALenum format;
137 ALsizei sizeInBytes;
138 ALsizei frequencyInHertz;
139} bufferInfo;
140
141typedef struct _sourceInfo {
142 bool usable;
143 ALuint sourceId;
144 ALuint attachedBufferId;
145} sourceInfo;
146
147#pragma mark CDAudioTransportProtocol
148
149@protocol CDAudioTransportProtocol <NSObject>
150/** Play the audio */
151-(BOOL) play;
152/** Pause the audio, retain resources */
153-(BOOL) pause;
154/** Stop the audio, release resources */
155-(BOOL) stop;
156/** Return playback to beginning */
157-(BOOL) rewind;
158@end
159
160#pragma mark CDAudioInterruptProtocol
161
162@protocol CDAudioInterruptProtocol <NSObject>
163/** Is audio mute */
164-(BOOL) mute;
165/** If YES then audio is silenced but not stopped, calls to start new audio will proceed but silently */
166-(void) setMute:(BOOL) muteValue;
167/** Is audio enabled */
168-(BOOL) enabled;
169/** If NO then all audio is stopped and any calls to start new audio will be ignored */
170-(void) setEnabled:(BOOL) enabledValue;
171@end
172
173#pragma mark CDUtilities
174/**
175 Collection of utilities required by CocosDenshion
176 */
177@interface CDUtilities : NSObject
178{
179}
180
181/** Fundamentally the same as the corresponding method is CCFileUtils but added to break binding to cocos2d */
182+(NSString*) fullPathFromRelativePath:(NSString*) relPath;
183
184@end
185
186
187#pragma mark CDSoundEngine
188
189/** CDSoundEngine is built upon OpenAL and works with SDK 2.0.
190 CDSoundEngine is a sound engine built upon OpenAL and derived from Apple's oalTouch
191 example. It can playback up to 32 sounds simultaneously with control over pitch, pan
192 and gain. It can be set up to handle audio session interruption automatically. You
193 may decide to use CDSoundEngine directly instead of CDAudioManager or SimpleAudioEngine
194 because you require OS 2.0 compatibility.
195
196 Requirements:
197 - Firmware: OS 2.0 or greater
198 - Files: CocosDenshion.*
199 - Frameworks: OpenAL, AudioToolbox
200
201 @since v0.8
202 */
203@class CDSoundSource;
204@interface CDSoundEngine : NSObject <CDAudioInterruptProtocol> {
205
206 bufferInfo *_buffers;
207 sourceInfo *_sources;
208 sourceGroup *_sourceGroups;
209 ALCcontext *context;
210 NSUInteger _sourceGroupTotal;
211 UInt32 _audioSessionCategory;
212 BOOL _handleAudioSession;
213 ALfloat _preMuteGain;
214 NSObject *_mutexBufferLoad;
215 BOOL mute_;
216 BOOL enabled_;
217
218 ALenum lastErrorCode_;
219 BOOL functioning_;
220 float asynchLoadProgress_;
221 BOOL getGainWorks_;
222
223 //For managing dynamic allocation of sources and buffers
224 int sourceTotal_;
225 int bufferTotal;
226
227}
228
229@property (readwrite, nonatomic) ALfloat masterGain;
230@property (readonly) ALenum lastErrorCode;//Last OpenAL error code that was generated
231@property (readonly) BOOL functioning;//Is the sound engine functioning
232@property (readwrite) float asynchLoadProgress;
233@property (readonly) BOOL getGainWorks;//Does getting the gain for a source work
234/** Total number of sources available */
235@property (readonly) int sourceTotal;
236/** Total number of source groups that have been defined */
237@property (readonly) NSUInteger sourceGroupTotal;
238
239/** Sets the sample rate for the audio mixer. For best performance this should match the sample rate of your audio content */
240+(void) setMixerSampleRate:(Float32) sampleRate;
241
242/** Initializes the engine with a group definition and a total number of groups */
243-(id)init;
244
245/** Plays a sound in a channel group with a pitch, pan and gain. The sound could played looped or not */
246-(ALuint) playSound:(int) soundId sourceGroupId:(int)sourceGroupId pitch:(float) pitch pan:(float) pan gain:(float) gain loop:(BOOL) loop;
247
248/** Creates and returns a sound source object for the specified sound within the specified source group.
249 */
250-(CDSoundSource *) soundSourceForSound:(int) soundId sourceGroupId:(int) sourceGroupId;
251
252/** Stops playing a sound */
253- (void) stopSound:(ALuint) sourceId;
254/** Stops playing a source group */
255- (void) stopSourceGroup:(int) sourceGroupId;
256/** Stops all playing sounds */
257-(void) stopAllSounds;
258-(void) defineSourceGroups:(NSArray*) sourceGroupDefinitions;
259-(void) defineSourceGroups:(int[]) sourceGroupDefinitions total:(NSUInteger) total;
260-(void) setSourceGroupNonInterruptible:(int) sourceGroupId isNonInterruptible:(BOOL) isNonInterruptible;
261-(void) setSourceGroupEnabled:(int) sourceGroupId enabled:(BOOL) enabled;
262-(BOOL) sourceGroupEnabled:(int) sourceGroupId;
263-(BOOL) loadBufferFromData:(int) soundId soundData:(ALvoid*) soundData format:(ALenum) format size:(ALsizei) size freq:(ALsizei) freq;
264-(BOOL) loadBuffer:(int) soundId filePath:(NSString*) filePath;
265-(void) loadBuffersAsynchronously:(NSArray *) loadRequests;
266-(BOOL) unloadBuffer:(int) soundId;
267-(ALCcontext *) openALContext;
268
269/** Returns the duration of the buffer in seconds or a negative value if the buffer id is invalid */
270-(float) bufferDurationInSeconds:(int) soundId;
271/** Returns the size of the buffer in bytes or a negative value if the buffer id is invalid */
272-(ALsizei) bufferSizeInBytes:(int) soundId;
273/** Returns the sampling frequency of the buffer in hertz or a negative value if the buffer id is invalid */
274-(ALsizei) bufferFrequencyInHertz:(int) soundId;
275
276/** Used internally, never call unless you know what you are doing */
277-(void) _soundSourcePreRelease:(CDSoundSource *) soundSource;
278
279@end
280
281#pragma mark CDSoundSource
282/** CDSoundSource is a wrapper around an OpenAL sound source.
283 It allows you to manipulate properties such as pitch, gain, pan and looping while the
284 sound is playing. CDSoundSource is based on the old CDSourceWrapper class but with much
285 added functionality.
286
287 @since v1.0
288 */
289@interface CDSoundSource : NSObject <CDAudioTransportProtocol, CDAudioInterruptProtocol> {
290 ALenum lastError;
291@public
292 ALuint _sourceId;
293 ALuint _sourceIndex;
294 CDSoundEngine* _engine;
295 int _soundId;
296 float _preMuteGain;
297 BOOL enabled_;
298 BOOL mute_;
299}
300@property (readwrite, nonatomic) float pitch;
301@property (readwrite, nonatomic) float gain;
302@property (readwrite, nonatomic) float pan;
303@property (readwrite, nonatomic) BOOL looping;
304@property (readonly) BOOL isPlaying;
305@property (readwrite, nonatomic) int soundId;
306/** Returns the duration of the attached buffer in seconds or a negative value if the buffer is invalid */
307@property (readonly) float durationInSeconds;
308
309/** Stores the last error code that occurred. Check against AL_NO_ERROR */
310@property (readonly) ALenum lastError;
311/** Do not init yourself, get an instance from the sourceForSound factory method on CDSoundEngine */
312-(id)init:(ALuint) theSourceId sourceIndex:(int) index soundEngine:(CDSoundEngine*) engine;
313
314@end
315
316#pragma mark CDAudioInterruptTargetGroup
317
318/** Container for objects that implement audio interrupt protocol i.e. they can be muted and enabled.
319 Setting mute and enabled for the group propagates to all children.
320 Designed to be used with your CDSoundSource objects to get them to comply with global enabled and mute settings
321 if that is what you want to do.*/
322@interface CDAudioInterruptTargetGroup : NSObject <CDAudioInterruptProtocol> {
323 BOOL mute_;
324 BOOL enabled_;
325 NSMutableArray *children_;
326}
327-(void) addAudioInterruptTarget:(NSObject<CDAudioInterruptProtocol>*) interruptibleTarget;
328@end
329
330#pragma mark CDAsynchBufferLoader
331
332/** CDAsynchBufferLoader
333 TODO
334 */
335@interface CDAsynchBufferLoader : NSOperation {
336 NSArray *_loadRequests;
337 CDSoundEngine *_soundEngine;
338}
339
340-(id) init:(NSArray *)loadRequests soundEngine:(CDSoundEngine *) theSoundEngine;
341
342@end
343
344#pragma mark CDBufferLoadRequest
345
346/** CDBufferLoadRequest */
347@interface CDBufferLoadRequest: NSObject
348{
349 NSString *filePath;
350 int soundId;
351 //id loader;
352}
353
354@property (readonly) NSString *filePath;
355@property (readonly) int soundId;
356
357- (id)init:(int) theSoundId filePath:(const NSString *) theFilePath;
358@end
359
360/** Interpolation type */
361typedef enum {
362 kIT_Linear, //!Straight linear interpolation fade
363 kIT_SCurve, //!S curved interpolation
364 kIT_Exponential //!Exponential interpolation
365} tCDInterpolationType;
366
367#pragma mark CDFloatInterpolator
368@interface CDFloatInterpolator: NSObject
369{
370 float start;
371 float end;
372 float lastValue;
373 tCDInterpolationType interpolationType;
374}
375@property (readwrite, nonatomic) float start;
376@property (readwrite, nonatomic) float end;
377@property (readwrite, nonatomic) tCDInterpolationType interpolationType;
378
379/** Return a value between min and max based on t which represents fractional progress where 0 is the start
380 and 1 is the end */
381-(float) interpolate:(float) t;
382-(id) init:(tCDInterpolationType) type startVal:(float) startVal endVal:(float) endVal;
383
384@end
385
386#pragma mark CDPropertyModifier
387
388/** Base class for classes that modify properties such as pitch, pan and gain */
389@interface CDPropertyModifier: NSObject
390{
391 CDFloatInterpolator *interpolator;
392 float startValue;
393 float endValue;
394 id target;
395 BOOL stopTargetWhenComplete;
396
397}
398@property (readwrite, nonatomic) BOOL stopTargetWhenComplete;
399@property (readwrite, nonatomic) float startValue;
400@property (readwrite, nonatomic) float endValue;
401@property (readwrite, nonatomic) tCDInterpolationType interpolationType;
402
403-(id) init:(id) theTarget interpolationType:(tCDInterpolationType) type startVal:(float) startVal endVal:(float) endVal;
404/** Set to a fractional value between 0 and 1 where 0 equals the start and 1 equals the end*/
405-(void) modify:(float) t;
406
407-(void) _setTargetProperty:(float) newVal;
408-(float) _getTargetProperty;
409-(void) _stopTarget;
410-(Class) _allowableType;
411
412@end
413
414#pragma mark CDSoundSourceFader
415
416/** Fader for CDSoundSource objects */
417@interface CDSoundSourceFader : CDPropertyModifier{}
418@end
419
420#pragma mark CDSoundSourcePanner
421
422/** Panner for CDSoundSource objects */
423@interface CDSoundSourcePanner : CDPropertyModifier{}
424@end
425
426#pragma mark CDSoundSourcePitchBender
427
428/** Pitch bender for CDSoundSource objects */
429@interface CDSoundSourcePitchBender : CDPropertyModifier{}
430@end
431
432#pragma mark CDSoundEngineFader
433
434/** Fader for CDSoundEngine objects */
435@interface CDSoundEngineFader : CDPropertyModifier{}
436@end
437
438
439
440
diff --git a/libs/CocosDenshion/CocosDenshion.m b/libs/CocosDenshion/CocosDenshion.m new file mode 100755 index 0000000..6956c3a --- /dev/null +++ b/libs/CocosDenshion/CocosDenshion.m
@@ -0,0 +1,1602 @@
1/*
2 Copyright (c) 2010 Steve Oldmeadow
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
10
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 THE SOFTWARE.
21
22 $Id$
23 */
24
25#import "CocosDenshion.h"
26
27ALvoid alBufferDataStaticProc(const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq);
28ALvoid alcMacOSXMixerOutputRateProc(const ALdouble value);
29
30
31typedef ALvoid AL_APIENTRY (*alBufferDataStaticProcPtr) (const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq);
32ALvoid alBufferDataStaticProc(const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq)
33{
34 static alBufferDataStaticProcPtr proc = NULL;
35
36 if (proc == NULL) {
37 proc = (alBufferDataStaticProcPtr) alcGetProcAddress(NULL, (const ALCchar*) "alBufferDataStatic");
38 }
39
40 if (proc)
41 proc(bid, format, data, size, freq);
42
43 return;
44}
45
46typedef ALvoid AL_APIENTRY (*alcMacOSXMixerOutputRateProcPtr) (const ALdouble value);
47ALvoid alcMacOSXMixerOutputRateProc(const ALdouble value)
48{
49 static alcMacOSXMixerOutputRateProcPtr proc = NULL;
50
51 if (proc == NULL) {
52 proc = (alcMacOSXMixerOutputRateProcPtr) alcGetProcAddress(NULL, (const ALCchar*) "alcMacOSXMixerOutputRate");
53 }
54
55 if (proc)
56 proc(value);
57
58 return;
59}
60
61NSString * const kCDN_BadAlContext = @"kCDN_BadAlContext";
62NSString * const kCDN_AsynchLoadComplete = @"kCDN_AsynchLoadComplete";
63float const kCD_PitchDefault = 1.0f;
64float const kCD_PitchLowerOneOctave = 0.5f;
65float const kCD_PitchHigherOneOctave = 2.0f;
66float const kCD_PanDefault = 0.0f;
67float const kCD_PanFullLeft = -1.0f;
68float const kCD_PanFullRight = 1.0f;
69float const kCD_GainDefault = 1.0f;
70
71@interface CDSoundEngine (PrivateMethods)
72-(BOOL) _initOpenAL;
73-(void) _testGetGain;
74-(void) _dumpSourceGroupsInfo;
75-(void) _getSourceIndexForSourceGroup;
76-(void) _freeSourceGroups;
77-(BOOL) _setUpSourceGroups:(int[]) definitions total:(NSUInteger) total;
78@end
79
80#pragma mark -
81#pragma mark CDUtilities
82
83@implementation CDUtilities
84
85+(NSString*) fullPathFromRelativePath:(NSString*) relPath
86{
87 // do not convert an absolute path (starting with '/')
88 if(([relPath length] > 0) && ([relPath characterAtIndex:0] == '/'))
89 {
90 return relPath;
91 }
92
93 NSMutableArray *imagePathComponents = [NSMutableArray arrayWithArray:[relPath pathComponents]];
94 NSString *file = [imagePathComponents lastObject];
95
96 [imagePathComponents removeLastObject];
97 NSString *imageDirectory = [NSString pathWithComponents:imagePathComponents];
98
99 NSString *fullpath = [[NSBundle mainBundle] pathForResource:file ofType:nil inDirectory:imageDirectory];
100 if (fullpath == nil)
101 fullpath = relPath;
102
103 return fullpath;
104}
105
106@end
107
108#pragma mark -
109#pragma mark CDSoundEngine
110
111@implementation CDSoundEngine
112
113static Float32 _mixerSampleRate;
114static BOOL _mixerRateSet = NO;
115
116@synthesize lastErrorCode = lastErrorCode_;
117@synthesize functioning = functioning_;
118@synthesize asynchLoadProgress = asynchLoadProgress_;
119@synthesize getGainWorks = getGainWorks_;
120@synthesize sourceTotal = sourceTotal_;
121
122+ (void) setMixerSampleRate:(Float32) sampleRate {
123 _mixerRateSet = YES;
124 _mixerSampleRate = sampleRate;
125}
126
127- (void) _testGetGain {
128 float testValue = 0.7f;
129 ALuint testSourceId = _sources[0].sourceId;
130 alSourcef(testSourceId, AL_GAIN, 0.0f);//Start from know value
131 alSourcef(testSourceId, AL_GAIN, testValue);
132 ALfloat gainVal;
133 alGetSourcef(testSourceId, AL_GAIN, &gainVal);
134 getGainWorks_ = (gainVal == testValue);
135}
136
137//Generate sources one at a time until we fail
138-(void) _generateSources {
139
140 _sources = (sourceInfo*)malloc( sizeof(_sources[0]) * CD_SOURCE_LIMIT);
141 BOOL hasFailed = NO;
142 sourceTotal_ = 0;
143 alGetError();//Clear error
144 while (!hasFailed && sourceTotal_ < CD_SOURCE_LIMIT) {
145 alGenSources(1, &(_sources[sourceTotal_].sourceId));
146 if (alGetError() == AL_NO_ERROR) {
147 //Now try attaching source to null buffer
148 alSourcei(_sources[sourceTotal_].sourceId, AL_BUFFER, 0);
149 if (alGetError() == AL_NO_ERROR) {
150 _sources[sourceTotal_].usable = true;
151 sourceTotal_++;
152 } else {
153 hasFailed = YES;
154 }
155 } else {
156 _sources[sourceTotal_].usable = false;
157 hasFailed = YES;
158 }
159 }
160 //Mark the rest of the sources as not usable
161 for (int i=sourceTotal_; i < CD_SOURCE_LIMIT; i++) {
162 _sources[i].usable = false;
163 }
164}
165
166-(void) _generateBuffers:(int) startIndex endIndex:(int) endIndex {
167 if (_buffers) {
168 alGetError();
169 for (int i=startIndex; i <= endIndex; i++) {
170 alGenBuffers(1, &_buffers[i].bufferId);
171 _buffers[i].bufferData = NULL;
172 if (alGetError() == AL_NO_ERROR) {
173 _buffers[i].bufferState = CD_BS_EMPTY;
174 } else {
175 _buffers[i].bufferState = CD_BS_FAILED;
176 CDLOG(@"Denshion::CDSoundEngine - buffer creation failed %i",i);
177 }
178 }
179 }
180}
181
182/**
183 * Internal method called during init
184 */
185- (BOOL) _initOpenAL
186{
187 //ALenum error;
188 context = NULL;
189 ALCdevice *newDevice = NULL;
190
191 //Set the mixer rate for the audio mixer
192 if (!_mixerRateSet) {
193 _mixerSampleRate = CD_SAMPLE_RATE_DEFAULT;
194 }
195 alcMacOSXMixerOutputRateProc(_mixerSampleRate);
196 CDLOGINFO(@"Denshion::CDSoundEngine - mixer output rate set to %0.2f",_mixerSampleRate);
197
198 // Create a new OpenAL Device
199 // Pass NULL to specify the system's default output device
200 newDevice = alcOpenDevice(NULL);
201 if (newDevice != NULL)
202 {
203 // Create a new OpenAL Context
204 // The new context will render to the OpenAL Device just created
205 context = alcCreateContext(newDevice, 0);
206 if (context != NULL)
207 {
208 // Make the new context the Current OpenAL Context
209 alcMakeContextCurrent(context);
210
211 // Create some OpenAL Buffer Objects
212 [self _generateBuffers:0 endIndex:bufferTotal-1];
213
214 // Create some OpenAL Source Objects
215 [self _generateSources];
216
217 }
218 } else {
219 return FALSE;//No device
220 }
221 alGetError();//Clear error
222 return TRUE;
223}
224
225- (void) dealloc {
226
227 ALCcontext *currentContext = NULL;
228 ALCdevice *device = NULL;
229
230 [self stopAllSounds];
231
232 CDLOGINFO(@"Denshion::CDSoundEngine - Deallocing sound engine.");
233 [self _freeSourceGroups];
234
235 // Delete the Sources
236 CDLOGINFO(@"Denshion::CDSoundEngine - deleting sources.");
237 for (int i=0; i < sourceTotal_; i++) {
238 alSourcei(_sources[i].sourceId, AL_BUFFER, 0);//Detach from current buffer
239 alDeleteSources(1, &(_sources[i].sourceId));
240 if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) {
241 CDLOG(@"Denshion::CDSoundEngine - Error deleting source! %x\n", lastErrorCode_);
242 }
243 }
244
245 // Delete the Buffers
246 CDLOGINFO(@"Denshion::CDSoundEngine - deleting buffers.");
247 for (int i=0; i < bufferTotal; i++) {
248 alDeleteBuffers(1, &_buffers[i].bufferId);
249#ifdef CD_USE_STATIC_BUFFERS
250 if (_buffers[i].bufferData) {
251 free(_buffers[i].bufferData);
252 }
253#endif
254 }
255 CDLOGINFO(@"Denshion::CDSoundEngine - free buffers.");
256 free(_buffers);
257 currentContext = alcGetCurrentContext();
258 //Get device for active context
259 device = alcGetContextsDevice(currentContext);
260 //Release context
261 CDLOGINFO(@"Denshion::CDSoundEngine - destroy context.");
262 alcDestroyContext(currentContext);
263 //Close device
264 CDLOGINFO(@"Denshion::CDSoundEngine - close device.");
265 alcCloseDevice(device);
266 CDLOGINFO(@"Denshion::CDSoundEngine - free sources.");
267 free(_sources);
268
269 //Release mutexes
270 [_mutexBufferLoad release];
271
272 [super dealloc];
273}
274
275-(NSUInteger) sourceGroupTotal {
276 return _sourceGroupTotal;
277}
278
279-(void) _freeSourceGroups
280{
281 CDLOGINFO(@"Denshion::CDSoundEngine freeing source groups");
282 if(_sourceGroups) {
283 for (int i=0; i < _sourceGroupTotal; i++) {
284 if (_sourceGroups[i].sourceStatuses) {
285 free(_sourceGroups[i].sourceStatuses);
286 CDLOGINFO(@"Denshion::CDSoundEngine freed source statuses %i",i);
287 }
288 }
289 free(_sourceGroups);
290 }
291}
292
293-(BOOL) _redefineSourceGroups:(int[]) definitions total:(NSUInteger) total
294{
295 if (_sourceGroups) {
296 //Stop all sounds
297 [self stopAllSounds];
298 //Need to free source groups
299 [self _freeSourceGroups];
300 }
301 return [self _setUpSourceGroups:definitions total:total];
302}
303
304-(BOOL) _setUpSourceGroups:(int[]) definitions total:(NSUInteger) total
305{
306 _sourceGroups = (sourceGroup *)malloc( sizeof(_sourceGroups[0]) * total);
307 if(!_sourceGroups) {
308 CDLOG(@"Denshion::CDSoundEngine - source groups memory allocation failed");
309 return NO;
310 }
311
312 _sourceGroupTotal = total;
313 int sourceCount = 0;
314 for (int i=0; i < _sourceGroupTotal; i++) {
315
316 _sourceGroups[i].startIndex = 0;
317 _sourceGroups[i].currentIndex = _sourceGroups[i].startIndex;
318 _sourceGroups[i].enabled = false;
319 _sourceGroups[i].nonInterruptible = false;
320 _sourceGroups[i].totalSources = definitions[i];
321 _sourceGroups[i].sourceStatuses = malloc(sizeof(_sourceGroups[i].sourceStatuses[0]) * _sourceGroups[i].totalSources);
322 if (_sourceGroups[i].sourceStatuses) {
323 for (int j=0; j < _sourceGroups[i].totalSources; j++) {
324 //First bit is used to indicate whether source is locked, index is shifted back 1 bit
325 _sourceGroups[i].sourceStatuses[j] = (sourceCount + j) << 1;
326 }
327 }
328 sourceCount += definitions[i];
329 }
330 return YES;
331}
332
333-(void) defineSourceGroups:(int[]) sourceGroupDefinitions total:(NSUInteger) total {
334 [self _redefineSourceGroups:sourceGroupDefinitions total:total];
335}
336
337-(void) defineSourceGroups:(NSArray*) sourceGroupDefinitions {
338 CDLOGINFO(@"Denshion::CDSoundEngine - source groups defined by NSArray.");
339 NSUInteger totalDefs = [sourceGroupDefinitions count];
340 int* defs = (int *)malloc( sizeof(int) * totalDefs);
341 int currentIndex = 0;
342 for (id currentDef in sourceGroupDefinitions) {
343 if ([currentDef isKindOfClass:[NSNumber class]]) {
344 defs[currentIndex] = (int)[(NSNumber*)currentDef integerValue];
345 CDLOGINFO(@"Denshion::CDSoundEngine - found definition %i.",defs[currentIndex]);
346 } else {
347 CDLOG(@"Denshion::CDSoundEngine - warning, did not understand source definition.");
348 defs[currentIndex] = 0;
349 }
350 currentIndex++;
351 }
352 [self _redefineSourceGroups:defs total:totalDefs];
353 free(defs);
354}
355
356- (id)init
357{
358 if ((self = [super init])) {
359
360 //Create mutexes
361 _mutexBufferLoad = [[NSObject alloc] init];
362
363 asynchLoadProgress_ = 0.0f;
364
365 bufferTotal = CD_BUFFERS_START;
366 _buffers = (bufferInfo *)malloc( sizeof(_buffers[0]) * bufferTotal);
367
368 // Initialize our OpenAL environment
369 if ([self _initOpenAL]) {
370 //Set up the default source group - a single group that contains all the sources
371 int sourceDefs[1];
372 sourceDefs[0] = self.sourceTotal;
373 [self _setUpSourceGroups:sourceDefs total:1];
374
375 functioning_ = YES;
376 //Synchronize premute gain
377 _preMuteGain = self.masterGain;
378 mute_ = NO;
379 enabled_ = YES;
380 //Test whether get gain works for sources
381 [self _testGetGain];
382 } else {
383 //Something went wrong with OpenAL
384 functioning_ = NO;
385 }
386 }
387
388 return self;
389}
390
391/**
392 * Delete the buffer identified by soundId
393 * @return true if buffer deleted successfully, otherwise false
394 */
395- (BOOL) unloadBuffer:(int) soundId
396{
397 //Ensure soundId is within array bounds otherwise memory corruption will occur
398 if (soundId < 0 || soundId >= bufferTotal) {
399 CDLOG(@"Denshion::CDSoundEngine - soundId is outside array bounds, maybe you need to increase CD_MAX_BUFFERS");
400 return FALSE;
401 }
402
403 //Before a buffer can be deleted any sources that are attached to it must be stopped
404 for (int i=0; i < sourceTotal_; i++) {
405 //Note: tried getting the AL_BUFFER attribute of the source instead but doesn't
406 //appear to work on a device - just returned zero.
407 if (_buffers[soundId].bufferId == _sources[i].attachedBufferId) {
408
409 CDLOG(@"Denshion::CDSoundEngine - Found attached source %i %i %i",i,_buffers[soundId].bufferId,_sources[i].sourceId);
410#ifdef CD_USE_STATIC_BUFFERS
411 //When using static buffers a crash may occur if a source is playing with a buffer that is about
412 //to be deleted even though we stop the source and successfully delete the buffer. Crash is confirmed
413 //on 2.2.1 and 3.1.2, however, it will only occur if a source is used rapidly after having its prior
414 //data deleted. To avoid any possibility of the crash we wait for the source to finish playing.
415 ALint state;
416
417 alGetSourcei(_sources[i].sourceId, AL_SOURCE_STATE, &state);
418
419 if (state == AL_PLAYING) {
420 CDLOG(@"Denshion::CDSoundEngine - waiting for source to complete playing before removing buffer data");
421 alSourcei(_sources[i].sourceId, AL_LOOPING, FALSE);//Turn off looping otherwise loops will never end
422 while (state == AL_PLAYING) {
423 alGetSourcei(_sources[i].sourceId, AL_SOURCE_STATE, &state);
424 usleep(10000);
425 }
426 }
427#endif
428 //Stop source and detach
429 alSourceStop(_sources[i].sourceId);
430 if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) {
431 CDLOG(@"Denshion::CDSoundEngine - error stopping source: %x\n", lastErrorCode_);
432 }
433
434 alSourcei(_sources[i].sourceId, AL_BUFFER, 0);//Attach to "NULL" buffer to detach
435 if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) {
436 CDLOG(@"Denshion::CDSoundEngine - error detaching buffer: %x\n", lastErrorCode_);
437 } else {
438 //Record that source is now attached to nothing
439 _sources[i].attachedBufferId = 0;
440 }
441 }
442 }
443
444 alDeleteBuffers(1, &_buffers[soundId].bufferId);
445 if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) {
446 CDLOG(@"Denshion::CDSoundEngine - error deleting buffer: %x\n", lastErrorCode_);
447 _buffers[soundId].bufferState = CD_BS_FAILED;
448 return FALSE;
449 } else {
450#ifdef CD_USE_STATIC_BUFFERS
451 //Free previous data, if alDeleteBuffer has returned without error then no
452 if (_buffers[soundId].bufferData) {
453 CDLOGINFO(@"Denshion::CDSoundEngine - freeing static data for soundId %i @ %i",soundId,_buffers[soundId].bufferData);
454 free(_buffers[soundId].bufferData);//Free the old data
455 _buffers[soundId].bufferData = NULL;
456 }
457#endif
458 }
459
460 alGenBuffers(1, &_buffers[soundId].bufferId);
461 if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) {
462 CDLOG(@"Denshion::CDSoundEngine - error regenerating buffer: %x\n", lastErrorCode_);
463 _buffers[soundId].bufferState = CD_BS_FAILED;
464 return FALSE;
465 } else {
466 //We now have an empty buffer
467 _buffers[soundId].bufferState = CD_BS_EMPTY;
468 CDLOGINFO(@"Denshion::CDSoundEngine - buffer %i successfully unloaded\n",soundId);
469 return TRUE;
470 }
471}
472
473/**
474 * Load buffers asynchronously
475 * Check asynchLoadProgress for progress. asynchLoadProgress represents fraction of completion. When it equals 1.0 loading
476 * is complete. NB: asynchLoadProgress is simply based on the number of load requests, it does not take into account
477 * file sizes.
478 * @param An array of CDBufferLoadRequest objects
479 */
480- (void) loadBuffersAsynchronously:(NSArray *) loadRequests {
481 @synchronized(self) {
482 asynchLoadProgress_ = 0.0f;
483 CDAsynchBufferLoader *loaderOp = [[[CDAsynchBufferLoader alloc] init:loadRequests soundEngine:self] autorelease];
484 NSOperationQueue *opQ = [[[NSOperationQueue alloc] init] autorelease];
485 [opQ addOperation:loaderOp];
486 }
487}
488
489-(BOOL) _resizeBuffers:(int) increment {
490
491 void * tmpBufferInfos = realloc( _buffers, sizeof(_buffers[0]) * (bufferTotal + increment) );
492
493 if(!tmpBufferInfos) {
494 free(tmpBufferInfos);
495 return NO;
496 } else {
497 _buffers = tmpBufferInfos;
498 int oldBufferTotal = bufferTotal;
499 bufferTotal = bufferTotal + increment;
500 [self _generateBuffers:oldBufferTotal endIndex:bufferTotal-1];
501 return YES;
502 }
503}
504
505-(BOOL) loadBufferFromData:(int) soundId soundData:(ALvoid*) soundData format:(ALenum) format size:(ALsizei) size freq:(ALsizei) freq {
506
507 @synchronized(_mutexBufferLoad) {
508
509 if (!functioning_) {
510 //OpenAL initialisation has previously failed
511 CDLOG(@"Denshion::CDSoundEngine - Loading buffer failed because sound engine state != functioning");
512 return FALSE;
513 }
514
515 //Ensure soundId is within array bounds otherwise memory corruption will occur
516 if (soundId < 0) {
517 CDLOG(@"Denshion::CDSoundEngine - soundId is negative");
518 return FALSE;
519 }
520
521 if (soundId >= bufferTotal) {
522 //Need to resize the buffers
523 int requiredIncrement = CD_BUFFERS_INCREMENT;
524 while (bufferTotal + requiredIncrement < soundId) {
525 requiredIncrement += CD_BUFFERS_INCREMENT;
526 }
527 CDLOGINFO(@"Denshion::CDSoundEngine - attempting to resize buffers by %i for sound %i",requiredIncrement,soundId);
528 if (![self _resizeBuffers:requiredIncrement]) {
529 CDLOG(@"Denshion::CDSoundEngine - buffer resize failed");
530 return FALSE;
531 }
532 }
533
534 if (soundData)
535 {
536 if (_buffers[soundId].bufferState != CD_BS_EMPTY) {
537 CDLOGINFO(@"Denshion::CDSoundEngine - non empty buffer, regenerating");
538 if (![self unloadBuffer:soundId]) {
539 //Deletion of buffer failed, delete buffer routine has set buffer state and lastErrorCode
540 return NO;
541 }
542 }
543
544#ifdef CD_DEBUG
545 //Check that sample rate matches mixer rate and warn if they do not
546 if (freq != (int)_mixerSampleRate) {
547 CDLOGINFO(@"Denshion::CDSoundEngine - WARNING sample rate does not match mixer sample rate performance may not be optimal.");
548 }
549#endif
550
551#ifdef CD_USE_STATIC_BUFFERS
552 alBufferDataStaticProc(_buffers[soundId].bufferId, format, soundData, size, freq);
553 _buffers[soundId].bufferData = data;//Save the pointer to the new data
554#else
555 alBufferData(_buffers[soundId].bufferId, format, soundData, size, freq);
556#endif
557 if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) {
558 CDLOG(@"Denshion::CDSoundEngine - error attaching audio to buffer: %x", lastErrorCode_);
559 _buffers[soundId].bufferState = CD_BS_FAILED;
560 return FALSE;
561 }
562 } else {
563 CDLOG(@"Denshion::CDSoundEngine Buffer data is null!");
564 _buffers[soundId].bufferState = CD_BS_FAILED;
565 return FALSE;
566 }
567
568 _buffers[soundId].format = format;
569 _buffers[soundId].sizeInBytes = size;
570 _buffers[soundId].frequencyInHertz = freq;
571 _buffers[soundId].bufferState = CD_BS_LOADED;
572 CDLOGINFO(@"Denshion::CDSoundEngine Buffer %i loaded format:%i freq:%i size:%i",soundId,format,freq,size);
573 return TRUE;
574 }//end mutex
575}
576
577/**
578 * Load sound data for later play back.
579 * @return TRUE if buffer loaded okay for play back otherwise false
580 */
581- (BOOL) loadBuffer:(int) soundId filePath:(NSString*) filePath
582{
583
584 ALvoid* data;
585 ALenum format;
586 ALsizei size;
587 ALsizei freq;
588
589 CDLOGINFO(@"Denshion::CDSoundEngine - Loading openAL buffer %i %@", soundId, filePath);
590
591 CFURLRef fileURL = nil;
592 NSString *path = [CDUtilities fullPathFromRelativePath:filePath];
593 if (path) {
594 fileURL = (CFURLRef)[[NSURL fileURLWithPath:path] retain];
595 }
596
597 if (fileURL)
598 {
599 data = CDGetOpenALAudioData(fileURL, &size, &format, &freq);
600 CFRelease(fileURL);
601 BOOL result = [self loadBufferFromData:soundId soundData:data format:format size:size freq:freq];
602#ifndef CD_USE_STATIC_BUFFERS
603 free(data);//Data can be freed here because alBufferData performs a memcpy
604#endif
605 return result;
606 } else {
607 CDLOG(@"Denshion::CDSoundEngine Could not find file!\n");
608 //Don't change buffer state here as it will be the same as before method was called
609 return FALSE;
610 }
611}
612
613-(BOOL) validateBufferId:(int) soundId {
614 if (soundId < 0 || soundId >= bufferTotal) {
615 CDLOGINFO(@"Denshion::CDSoundEngine - validateBufferId buffer outside range %i",soundId);
616 return NO;
617 } else if (_buffers[soundId].bufferState != CD_BS_LOADED) {
618 CDLOGINFO(@"Denshion::CDSoundEngine - validateBufferId invalide buffer state %i",soundId);
619 return NO;
620 } else {
621 return YES;
622 }
623}
624
625-(float) bufferDurationInSeconds:(int) soundId {
626 if ([self validateBufferId:soundId]) {
627 float factor = 0.0f;
628 switch (_buffers[soundId].format) {
629 case AL_FORMAT_MONO8:
630 factor = 1.0f;
631 break;
632 case AL_FORMAT_MONO16:
633 factor = 0.5f;
634 break;
635 case AL_FORMAT_STEREO8:
636 factor = 0.5f;
637 break;
638 case AL_FORMAT_STEREO16:
639 factor = 0.25f;
640 break;
641 }
642 return (float)_buffers[soundId].sizeInBytes/(float)_buffers[soundId].frequencyInHertz * factor;
643 } else {
644 return -1.0f;
645 }
646}
647
648-(ALsizei) bufferSizeInBytes:(int) soundId {
649 if ([self validateBufferId:soundId]) {
650 return _buffers[soundId].sizeInBytes;
651 } else {
652 return -1.0f;
653 }
654}
655
656-(ALsizei) bufferFrequencyInHertz:(int) soundId {
657 if ([self validateBufferId:soundId]) {
658 return _buffers[soundId].frequencyInHertz;
659 } else {
660 return -1.0f;
661 }
662}
663
664- (ALfloat) masterGain {
665 if (mute_) {
666 //When mute the real gain will always be 0 therefore return the preMuteGain value
667 return _preMuteGain;
668 } else {
669 ALfloat gain;
670 alGetListenerf(AL_GAIN, &gain);
671 return gain;
672 }
673}
674
675/**
676 * Overall gain setting multiplier. e.g 0.5 is half the gain.
677 */
678- (void) setMasterGain:(ALfloat) newGainValue {
679 if (mute_) {
680 _preMuteGain = newGainValue;
681 } else {
682 alListenerf(AL_GAIN, newGainValue);
683 }
684}
685
686#pragma mark CDSoundEngine AudioInterrupt protocol
687- (BOOL) mute {
688 return mute_;
689}
690
691/**
692 * Setting mute silences all sounds but playing sounds continue to advance playback
693 */
694- (void) setMute:(BOOL) newMuteValue {
695
696 if (newMuteValue == mute_) {
697 return;
698 }
699
700 mute_ = newMuteValue;
701 if (mute_) {
702 //Remember what the gain was
703 _preMuteGain = self.masterGain;
704 //Set gain to 0 - do not use the property as this will adjust preMuteGain when muted
705 alListenerf(AL_GAIN, 0.0f);
706 } else {
707 //Restore gain to what it was before being muted
708 self.masterGain = _preMuteGain;
709 }
710}
711
712- (BOOL) enabled {
713 return enabled_;
714}
715
716- (void) setEnabled:(BOOL)enabledValue
717{
718 if (enabled_ == enabledValue) {
719 return;
720 }
721 enabled_ = enabledValue;
722 if (enabled_ == NO) {
723 [self stopAllSounds];
724 }
725}
726
727-(void) _lockSource:(int) sourceIndex lock:(BOOL) lock {
728 BOOL found = NO;
729 for (int i=0; i < _sourceGroupTotal && !found; i++) {
730 if (_sourceGroups[i].sourceStatuses) {
731 for (int j=0; j < _sourceGroups[i].totalSources && !found; j++) {
732 //First bit is used to indicate whether source is locked, index is shifted back 1 bit
733 if((_sourceGroups[i].sourceStatuses[j] >> 1)==sourceIndex) {
734 if (lock) {
735 //Set first bit to lock this source
736 _sourceGroups[i].sourceStatuses[j] |= 1;
737 } else {
738 //Unset first bit to unlock this source
739 _sourceGroups[i].sourceStatuses[j] &= ~1;
740 }
741 found = YES;
742 }
743 }
744 }
745 }
746}
747
748-(int) _getSourceIndexForSourceGroup:(int)sourceGroupId
749{
750 //Ensure source group id is valid to prevent memory corruption
751 if (sourceGroupId < 0 || sourceGroupId >= _sourceGroupTotal) {
752 CDLOG(@"Denshion::CDSoundEngine invalid source group id %i",sourceGroupId);
753 return CD_NO_SOURCE;
754 }
755
756 int sourceIndex = -1;//Using -1 to indicate no source found
757 BOOL complete = NO;
758 ALint sourceState = 0;
759 sourceGroup *thisSourceGroup = &_sourceGroups[sourceGroupId];
760 thisSourceGroup->currentIndex = thisSourceGroup->startIndex;
761 while (!complete) {
762 //Iterate over sources looking for one that is not locked, first bit indicates if source is locked
763 if ((thisSourceGroup->sourceStatuses[thisSourceGroup->currentIndex] & 1) == 0) {
764 //This source is not locked
765 sourceIndex = thisSourceGroup->sourceStatuses[thisSourceGroup->currentIndex] >> 1;//shift back to get the index
766 if (thisSourceGroup->nonInterruptible) {
767 //Check if this source is playing, if so it can't be interrupted
768 alGetSourcei(_sources[sourceIndex].sourceId, AL_SOURCE_STATE, &sourceState);
769 if (sourceState != AL_PLAYING) {
770 //complete = YES;
771 //Set start index so next search starts at the next position
772 thisSourceGroup->startIndex = thisSourceGroup->currentIndex + 1;
773 break;
774 } else {
775 sourceIndex = -1;//The source index was no good because the source was playing
776 }
777 } else {
778 //complete = YES;
779 //Set start index so next search starts at the next position
780 thisSourceGroup->startIndex = thisSourceGroup->currentIndex + 1;
781 break;
782 }
783 }
784 thisSourceGroup->currentIndex++;
785 if (thisSourceGroup->currentIndex >= thisSourceGroup->totalSources) {
786 //Reset to the beginning
787 thisSourceGroup->currentIndex = 0;
788 }
789 if (thisSourceGroup->currentIndex == thisSourceGroup->startIndex) {
790 //We have looped around and got back to the start
791 complete = YES;
792 }
793 }
794
795 //Reset start index to beginning if beyond bounds
796 if (thisSourceGroup->startIndex >= thisSourceGroup->totalSources) {
797 thisSourceGroup->startIndex = 0;
798 }
799
800 if (sourceIndex >= 0) {
801 return sourceIndex;
802 } else {
803 return CD_NO_SOURCE;
804 }
805
806}
807
808/**
809 * Play a sound.
810 * @param soundId the id of the sound to play (buffer id).
811 * @param SourceGroupId the source group that will be used to play the sound.
812 * @param pitch pitch multiplier. e.g 1.0 is unaltered, 0.5 is 1 octave lower.
813 * @param pan stereo position. -1 is fully left, 0 is centre and 1 is fully right.
814 * @param gain gain multiplier. e.g. 1.0 is unaltered, 0.5 is half the gain
815 * @param loop should the sound be looped or one shot.
816 * @return the id of the source being used to play the sound or CD_MUTE if the sound engine is muted or non functioning
817 * or CD_NO_SOURCE if a problem occurs setting up the source
818 *
819 */
820- (ALuint)playSound:(int) soundId sourceGroupId:(int)sourceGroupId pitch:(float) pitch pan:(float) pan gain:(float) gain loop:(BOOL) loop {
821
822#ifdef CD_DEBUG
823 //Sanity check parameters - only in DEBUG
824 NSAssert(soundId >= 0, @"soundId can not be negative");
825 NSAssert(soundId < bufferTotal, @"soundId exceeds limit");
826 NSAssert(sourceGroupId >= 0, @"sourceGroupId can not be negative");
827 NSAssert(sourceGroupId < _sourceGroupTotal, @"sourceGroupId exceeds limit");
828 NSAssert(pitch > 0, @"pitch must be greater than zero");
829 NSAssert(pan >= -1 && pan <= 1, @"pan must be between -1 and 1");
830 NSAssert(gain >= 0, @"gain can not be negative");
831#endif
832 //If mute or initialisation has failed or buffer is not loaded then do nothing
833 if (!enabled_ || !functioning_ || _buffers[soundId].bufferState != CD_BS_LOADED || _sourceGroups[sourceGroupId].enabled) {
834#ifdef CD_DEBUG
835 if (!functioning_) {
836 CDLOGINFO(@"Denshion::CDSoundEngine - sound playback aborted because sound engine is not functioning");
837 } else if (_buffers[soundId].bufferState != CD_BS_LOADED) {
838 CDLOGINFO(@"Denshion::CDSoundEngine - sound playback aborted because buffer %i is not loaded", soundId);
839 }
840#endif
841 return CD_MUTE;
842 }
843
844 int sourceIndex = [self _getSourceIndexForSourceGroup:sourceGroupId];//This method ensures sourceIndex is valid
845
846 if (sourceIndex != CD_NO_SOURCE) {
847 ALint state;
848 ALuint source = _sources[sourceIndex].sourceId;
849 ALuint buffer = _buffers[soundId].bufferId;
850 alGetError();//Clear the error code
851 alGetSourcei(source, AL_SOURCE_STATE, &state);
852 if (state == AL_PLAYING) {
853 alSourceStop(source);
854 }
855 alSourcei(source, AL_BUFFER, buffer);//Attach to sound
856 alSourcef(source, AL_PITCH, pitch);//Set pitch
857 alSourcei(source, AL_LOOPING, loop);//Set looping
858 alSourcef(source, AL_GAIN, gain);//Set gain/volume
859 float sourcePosAL[] = {pan, 0.0f, 0.0f};//Set position - just using left and right panning
860 alSourcefv(source, AL_POSITION, sourcePosAL);
861 alGetError();//Clear the error code
862 alSourcePlay(source);
863 if((lastErrorCode_ = alGetError()) == AL_NO_ERROR) {
864 //Everything was okay
865 _sources[sourceIndex].attachedBufferId = buffer;
866 return source;
867 } else {
868 if (alcGetCurrentContext() == NULL) {
869 CDLOGINFO(@"Denshion::CDSoundEngine - posting bad OpenAL context message");
870 [[NSNotificationCenter defaultCenter] postNotificationName:kCDN_BadAlContext object:nil];
871 }
872 return CD_NO_SOURCE;
873 }
874 } else {
875 return CD_NO_SOURCE;
876 }
877}
878
879-(BOOL) _soundSourceAttachToBuffer:(CDSoundSource*) soundSource soundId:(int) soundId {
880 //Attach the source to the buffer
881 ALint state;
882 ALuint source = soundSource->_sourceId;
883 ALuint buffer = _buffers[soundId].bufferId;
884 alGetSourcei(source, AL_SOURCE_STATE, &state);
885 if (state == AL_PLAYING) {
886 alSourceStop(source);
887 }
888 alGetError();//Clear the error code
889 alSourcei(source, AL_BUFFER, buffer);//Attach to sound data
890 if((lastErrorCode_ = alGetError()) == AL_NO_ERROR) {
891 _sources[soundSource->_sourceIndex].attachedBufferId = buffer;
892 //_sourceBufferAttachments[soundSource->_sourceIndex] = buffer;//Keep track of which
893 soundSource->_soundId = soundId;
894 return YES;
895 } else {
896 return NO;
897 }
898}
899
900/**
901 * Get a sound source for the specified sound in the specified source group
902 */
903-(CDSoundSource *) soundSourceForSound:(int) soundId sourceGroupId:(int) sourceGroupId
904{
905 if (!functioning_) {
906 return nil;
907 }
908 //Check if a source is available
909 int sourceIndex = [self _getSourceIndexForSourceGroup:sourceGroupId];
910 if (sourceIndex != CD_NO_SOURCE) {
911 CDSoundSource *result = [[CDSoundSource alloc] init:_sources[sourceIndex].sourceId sourceIndex:sourceIndex soundEngine:self];
912 [self _lockSource:sourceIndex lock:YES];
913 //Try to attach to the buffer
914 if ([self _soundSourceAttachToBuffer:result soundId:soundId]) {
915 //Set to a known state
916 result.pitch = 1.0f;
917 result.pan = 0.0f;
918 result.gain = 1.0f;
919 result.looping = NO;
920 return [result autorelease];
921 } else {
922 //Release the sound source we just created, this will also unlock the source
923 [result release];
924 return nil;
925 }
926 } else {
927 //No available source within that source group
928 return nil;
929 }
930}
931
932-(void) _soundSourcePreRelease:(CDSoundSource *) soundSource {
933 CDLOGINFO(@"Denshion::CDSoundEngine _soundSourcePreRelease %i",soundSource->_sourceIndex);
934 //Unlock the sound source's source
935 [self _lockSource:soundSource->_sourceIndex lock:NO];
936}
937
938/**
939 * Stop all sounds playing within a source group
940 */
941- (void) stopSourceGroup:(int) sourceGroupId {
942
943 if (!functioning_ || sourceGroupId >= _sourceGroupTotal || sourceGroupId < 0) {
944 return;
945 }
946 int sourceCount = _sourceGroups[sourceGroupId].totalSources;
947 for (int i=0; i < sourceCount; i++) {
948 int sourceIndex = _sourceGroups[sourceGroupId].sourceStatuses[i] >> 1;
949 alSourceStop(_sources[sourceIndex].sourceId);
950 }
951 alGetError();//Clear error in case we stopped any sounds that couldn't be stopped
952}
953
954/**
955 * Stop a sound playing.
956 * @param sourceId an OpenAL source identifier i.e. the return value of playSound
957 */
958- (void)stopSound:(ALuint) sourceId {
959 if (!functioning_) {
960 return;
961 }
962 alSourceStop(sourceId);
963 alGetError();//Clear error in case we stopped any sounds that couldn't be stopped
964}
965
966- (void) stopAllSounds {
967 for (int i=0; i < sourceTotal_; i++) {
968 alSourceStop(_sources[i].sourceId);
969 }
970 alGetError();//Clear error in case we stopped any sounds that couldn't be stopped
971}
972
973/**
974 * Set a source group as non interruptible. Default is that source groups are interruptible.
975 * Non interruptible means that if a request to play a sound is made for a source group and there are
976 * no free sources available then the play request will be ignored and CD_NO_SOURCE will be returned.
977 */
978- (void) setSourceGroupNonInterruptible:(int) sourceGroupId isNonInterruptible:(BOOL) isNonInterruptible {
979 //Ensure source group id is valid to prevent memory corruption
980 if (sourceGroupId < 0 || sourceGroupId >= _sourceGroupTotal) {
981 CDLOG(@"Denshion::CDSoundEngine setSourceGroupNonInterruptible invalid source group id %i",sourceGroupId);
982 return;
983 }
984
985 if (isNonInterruptible) {
986 _sourceGroups[sourceGroupId].nonInterruptible = true;
987 } else {
988 _sourceGroups[sourceGroupId].nonInterruptible = false;
989 }
990}
991
992/**
993 * Set the mute property for a source group. If mute is turned on any sounds in that source group
994 * will be stopped and further sounds in that source group will play. However, turning mute off
995 * will not restart any sounds that were playing when mute was turned on. Also the mute setting
996 * for the sound engine must be taken into account. If the sound engine is mute no sounds will play
997 * no matter what the source group mute setting is.
998 */
999- (void) setSourceGroupEnabled:(int) sourceGroupId enabled:(BOOL) enabled {
1000 //Ensure source group id is valid to prevent memory corruption
1001 if (sourceGroupId < 0 || sourceGroupId >= _sourceGroupTotal) {
1002 CDLOG(@"Denshion::CDSoundEngine setSourceGroupEnabled invalid source group id %i",sourceGroupId);
1003 return;
1004 }
1005
1006 if (enabled) {
1007 _sourceGroups[sourceGroupId].enabled = true;
1008 [self stopSourceGroup:sourceGroupId];
1009 } else {
1010 _sourceGroups[sourceGroupId].enabled = false;
1011 }
1012}
1013
1014/**
1015 * Return the mute property for the source group identified by sourceGroupId
1016 */
1017- (BOOL) sourceGroupEnabled:(int) sourceGroupId {
1018 return _sourceGroups[sourceGroupId].enabled;
1019}
1020
1021-(ALCcontext *) openALContext {
1022 return context;
1023}
1024
1025- (void) _dumpSourceGroupsInfo {
1026#ifdef CD_DEBUG
1027 CDLOGINFO(@"-------------- source Group Info --------------");
1028 for (int i=0; i < _sourceGroupTotal; i++) {
1029 CDLOGINFO(@"Group: %i start:%i total:%i",i,_sourceGroups[i].startIndex, _sourceGroups[i].totalSources);
1030 CDLOGINFO(@"----- mute:%i nonInterruptible:%i",_sourceGroups[i].enabled, _sourceGroups[i].nonInterruptible);
1031 CDLOGINFO(@"----- Source statuses ----");
1032 for (int j=0; j < _sourceGroups[i].totalSources; j++) {
1033 CDLOGINFO(@"Source status:%i index=%i locked=%i",j,_sourceGroups[i].sourceStatuses[j] >> 1, _sourceGroups[i].sourceStatuses[j] & 1);
1034 }
1035 }
1036#endif
1037}
1038
1039@end
1040
1041///////////////////////////////////////////////////////////////////////////////////////
1042@implementation CDSoundSource
1043
1044@synthesize lastError;
1045
1046//Macro for handling the al error code
1047#define CDSOUNDSOURCE_UPDATE_LAST_ERROR (lastError = alGetError())
1048#define CDSOUNDSOURCE_ERROR_HANDLER ( CDSOUNDSOURCE_UPDATE_LAST_ERROR == AL_NO_ERROR)
1049
1050-(id)init:(ALuint) theSourceId sourceIndex:(int) index soundEngine:(CDSoundEngine*) engine {
1051 if ((self = [super init])) {
1052 _sourceId = theSourceId;
1053 _engine = engine;
1054 _sourceIndex = index;
1055 enabled_ = YES;
1056 mute_ = NO;
1057 _preMuteGain = self.gain;
1058 }
1059 return self;
1060}
1061
1062-(void) dealloc
1063{
1064 CDLOGINFO(@"Denshion::CDSoundSource deallocated %i",self->_sourceIndex);
1065
1066 //Notify sound engine we are about to release
1067 [_engine _soundSourcePreRelease:self];
1068 [super dealloc];
1069}
1070
1071- (void) setPitch:(float) newPitchValue {
1072 alSourcef(_sourceId, AL_PITCH, newPitchValue);
1073 CDSOUNDSOURCE_UPDATE_LAST_ERROR;
1074}
1075
1076- (void) setGain:(float) newGainValue {
1077 if (!mute_) {
1078 alSourcef(_sourceId, AL_GAIN, newGainValue);
1079 } else {
1080 _preMuteGain = newGainValue;
1081 }
1082 CDSOUNDSOURCE_UPDATE_LAST_ERROR;
1083}
1084
1085- (void) setPan:(float) newPanValue {
1086 float sourcePosAL[] = {newPanValue, 0.0f, 0.0f};//Set position - just using left and right panning
1087 alSourcefv(_sourceId, AL_POSITION, sourcePosAL);
1088 CDSOUNDSOURCE_UPDATE_LAST_ERROR;
1089
1090}
1091
1092- (void) setLooping:(BOOL) newLoopingValue {
1093 alSourcei(_sourceId, AL_LOOPING, newLoopingValue);
1094 CDSOUNDSOURCE_UPDATE_LAST_ERROR;
1095
1096}
1097
1098- (BOOL) isPlaying {
1099 ALint state;
1100 alGetSourcei(_sourceId, AL_SOURCE_STATE, &state);
1101 CDSOUNDSOURCE_UPDATE_LAST_ERROR;
1102 return (state == AL_PLAYING);
1103}
1104
1105- (float) pitch {
1106 ALfloat pitchVal;
1107 alGetSourcef(_sourceId, AL_PITCH, &pitchVal);
1108 CDSOUNDSOURCE_UPDATE_LAST_ERROR;
1109 return pitchVal;
1110}
1111
1112- (float) pan {
1113 ALfloat sourcePosAL[] = {0.0f,0.0f,0.0f};
1114 alGetSourcefv(_sourceId, AL_POSITION, sourcePosAL);
1115 CDSOUNDSOURCE_UPDATE_LAST_ERROR;
1116 return sourcePosAL[0];
1117}
1118
1119- (float) gain {
1120 if (!mute_) {
1121 ALfloat val;
1122 alGetSourcef(_sourceId, AL_GAIN, &val);
1123 CDSOUNDSOURCE_UPDATE_LAST_ERROR;
1124 return val;
1125 } else {
1126 return _preMuteGain;
1127 }
1128}
1129
1130- (BOOL) looping {
1131 ALfloat val;
1132 alGetSourcef(_sourceId, AL_LOOPING, &val);
1133 CDSOUNDSOURCE_UPDATE_LAST_ERROR;
1134 return val;
1135}
1136
1137-(BOOL) stop {
1138 alSourceStop(_sourceId);
1139 return CDSOUNDSOURCE_ERROR_HANDLER;
1140}
1141
1142-(BOOL) play {
1143 if (enabled_) {
1144 alSourcePlay(_sourceId);
1145 CDSOUNDSOURCE_UPDATE_LAST_ERROR;
1146 if (lastError != AL_NO_ERROR) {
1147 if (alcGetCurrentContext() == NULL) {
1148 CDLOGINFO(@"Denshion::CDSoundSource - posting bad OpenAL context message");
1149 [[NSNotificationCenter defaultCenter] postNotificationName:kCDN_BadAlContext object:nil];
1150 }
1151 return NO;
1152 } else {
1153 return YES;
1154 }
1155 } else {
1156 return NO;
1157 }
1158}
1159
1160-(BOOL) pause {
1161 alSourcePause(_sourceId);
1162 return CDSOUNDSOURCE_ERROR_HANDLER;
1163}
1164
1165-(BOOL) rewind {
1166 alSourceRewind(_sourceId);
1167 return CDSOUNDSOURCE_ERROR_HANDLER;
1168}
1169
1170-(void) setSoundId:(int) soundId {
1171 [_engine _soundSourceAttachToBuffer:self soundId:soundId];
1172}
1173
1174-(int) soundId {
1175 return _soundId;
1176}
1177
1178-(float) durationInSeconds {
1179 return [_engine bufferDurationInSeconds:_soundId];
1180}
1181
1182#pragma mark CDSoundSource AudioInterrupt protocol
1183- (BOOL) mute {
1184 return mute_;
1185}
1186
1187/**
1188 * Setting mute silences all sounds but playing sounds continue to advance playback
1189 */
1190- (void) setMute:(BOOL) newMuteValue {
1191
1192 if (newMuteValue == mute_) {
1193 return;
1194 }
1195
1196 if (newMuteValue) {
1197 //Remember what the gain was
1198 _preMuteGain = self.gain;
1199 self.gain = 0.0f;
1200 mute_ = newMuteValue;//Make sure this is done after setting the gain property as the setter behaves differently depending on mute value
1201 } else {
1202 //Restore gain to what it was before being muted
1203 mute_ = newMuteValue;
1204 self.gain = _preMuteGain;
1205 }
1206}
1207
1208- (BOOL) enabled {
1209 return enabled_;
1210}
1211
1212- (void) setEnabled:(BOOL)enabledValue
1213{
1214 if (enabled_ == enabledValue) {
1215 return;
1216 }
1217 enabled_ = enabledValue;
1218 if (enabled_ == NO) {
1219 [self stop];
1220 }
1221}
1222
1223@end
1224
1225////////////////////////////////////////////////////////////////////////////
1226#pragma mark -
1227#pragma mark CDAudioInterruptTargetGroup
1228
1229@implementation CDAudioInterruptTargetGroup
1230
1231-(id) init {
1232 if ((self = [super init])) {
1233 children_ = [[NSMutableArray alloc] initWithCapacity:32];
1234 enabled_ = YES;
1235 mute_ = NO;
1236 }
1237 return self;
1238}
1239
1240-(void) addAudioInterruptTarget:(NSObject<CDAudioInterruptProtocol>*) interruptibleTarget {
1241 //Synchronize child with group settings;
1242 [interruptibleTarget setMute:mute_];
1243 [interruptibleTarget setEnabled:enabled_];
1244 [children_ addObject:interruptibleTarget];
1245}
1246
1247-(void) removeAudioInterruptTarget:(NSObject<CDAudioInterruptProtocol>*) interruptibleTarget {
1248 [children_ removeObjectIdenticalTo:interruptibleTarget];
1249}
1250
1251- (BOOL) mute {
1252 return mute_;
1253}
1254
1255/**
1256 * Setting mute silences all sounds but playing sounds continue to advance playback
1257 */
1258- (void) setMute:(BOOL) newMuteValue {
1259
1260 if (newMuteValue == mute_) {
1261 return;
1262 }
1263
1264 for (NSObject<CDAudioInterruptProtocol>* target in children_) {
1265 [target setMute:newMuteValue];
1266 }
1267}
1268
1269- (BOOL) enabled {
1270 return enabled_;
1271}
1272
1273- (void) setEnabled:(BOOL)enabledValue
1274{
1275 if (enabledValue == enabled_) {
1276 return;
1277 }
1278
1279 for (NSObject<CDAudioInterruptProtocol>* target in children_) {
1280 [target setEnabled:enabledValue];
1281 }
1282}
1283
1284@end
1285
1286
1287
1288////////////////////////////////////////////////////////////////////////////
1289
1290#pragma mark -
1291#pragma mark CDAsynchBufferLoader
1292
1293@implementation CDAsynchBufferLoader
1294
1295-(id) init:(NSArray *)loadRequests soundEngine:(CDSoundEngine *) theSoundEngine {
1296 if ((self = [super init])) {
1297 _loadRequests = loadRequests;
1298 [_loadRequests retain];
1299 _soundEngine = theSoundEngine;
1300 [_soundEngine retain];
1301 }
1302 return self;
1303}
1304
1305-(void) main {
1306 CDLOGINFO(@"Denshion::CDAsynchBufferLoader - loading buffers");
1307 [super main];
1308 _soundEngine.asynchLoadProgress = 0.0f;
1309
1310 if ([_loadRequests count] > 0) {
1311 float increment = 1.0f / [_loadRequests count];
1312 //Iterate over load request and load
1313 for (CDBufferLoadRequest *loadRequest in _loadRequests) {
1314 [_soundEngine loadBuffer:loadRequest.soundId filePath:loadRequest.filePath];
1315 _soundEngine.asynchLoadProgress += increment;
1316 }
1317 }
1318
1319 //Completed
1320 _soundEngine.asynchLoadProgress = 1.0f;
1321 [[NSNotificationCenter defaultCenter] postNotificationName:kCDN_AsynchLoadComplete object:nil];
1322
1323}
1324
1325-(void) dealloc {
1326 [_loadRequests release];
1327 [_soundEngine release];
1328 [super dealloc];
1329}
1330
1331@end
1332
1333
1334///////////////////////////////////////////////////////////////////////////////////////
1335#pragma mark -
1336#pragma mark CDBufferLoadRequest
1337
1338@implementation CDBufferLoadRequest
1339
1340@synthesize filePath, soundId;
1341
1342-(id) init:(int) theSoundId filePath:(const NSString *) theFilePath {
1343 if ((self = [super init])) {
1344 soundId = theSoundId;
1345 filePath = [theFilePath copy];//TODO: is retain necessary or does copy set retain count
1346 [filePath retain];
1347 }
1348 return self;
1349}
1350
1351-(void) dealloc {
1352 [filePath release];
1353 [super dealloc];
1354}
1355
1356@end
1357
1358///////////////////////////////////////////////////////////////////////////////////////
1359#pragma mark -
1360#pragma mark CDFloatInterpolator
1361
1362@implementation CDFloatInterpolator
1363@synthesize start,end,interpolationType;
1364
1365-(float) interpolate:(float) t {
1366
1367 if (t < 1.0f) {
1368 switch (interpolationType) {
1369 case kIT_Linear:
1370 //Linear interpolation
1371 return ((end - start) * t) + start;
1372
1373 case kIT_SCurve:
1374 //Cubic s curve t^2 * (3 - 2t)
1375 return ((float)(t * t * (3.0 - (2.0 * t))) * (end - start)) + start;
1376
1377 case kIT_Exponential:
1378 //Formulas taken from EaseAction
1379 if (end > start) {
1380 //Fade in
1381 float logDelta = (t==0) ? 0 : powf(2, 10 * (t/1 - 1)) - 1 * 0.001f;
1382 return ((end - start) * logDelta) + start;
1383 } else {
1384 //Fade Out
1385 float logDelta = (-powf(2, -10 * t/1) + 1);
1386 return ((end - start) * logDelta) + start;
1387 }
1388 default:
1389 return 0.0f;
1390 }
1391 } else {
1392 return end;
1393 }
1394}
1395
1396-(id) init:(tCDInterpolationType) type startVal:(float) startVal endVal:(float) endVal {
1397 if ((self = [super init])) {
1398 start = startVal;
1399 end = endVal;
1400 interpolationType = type;
1401 }
1402 return self;
1403}
1404
1405@end
1406
1407///////////////////////////////////////////////////////////////////////////////////////
1408#pragma mark -
1409#pragma mark CDPropertyModifier
1410
1411@implementation CDPropertyModifier
1412
1413@synthesize stopTargetWhenComplete;
1414
1415-(id) init:(id) theTarget interpolationType:(tCDInterpolationType) type startVal:(float) startVal endVal:(float) endVal {
1416 if ((self = [super init])) {
1417 if (target) {
1418 //Release the previous target if there is one
1419 [target release];
1420 }
1421 target = theTarget;
1422#if CD_DEBUG
1423 //Check target is of the required type
1424 if (![theTarget isMemberOfClass:[self _allowableType]] ) {
1425 CDLOG(@"Denshion::CDPropertyModifier target is not of type %@",[self _allowableType]);
1426 NSAssert([theTarget isKindOfClass:[CDSoundEngine class]], @"CDPropertyModifier target not of required type");
1427 }
1428#endif
1429 [target retain];
1430 startValue = startVal;
1431 endValue = endVal;
1432 if (interpolator) {
1433 //Release previous interpolator if there is one
1434 [interpolator release];
1435 }
1436 interpolator = [[CDFloatInterpolator alloc] init:type startVal:startVal endVal:endVal];
1437 stopTargetWhenComplete = NO;
1438 }
1439 return self;
1440}
1441
1442-(void) dealloc {
1443 CDLOGINFO(@"Denshion::CDPropertyModifier deallocated %@",self);
1444 [target release];
1445 [interpolator release];
1446 [super dealloc];
1447}
1448
1449-(void) modify:(float) t {
1450 if (t < 1.0) {
1451 [self _setTargetProperty:[interpolator interpolate:t]];
1452 } else {
1453 //At the end
1454 [self _setTargetProperty:endValue];
1455 if (stopTargetWhenComplete) {
1456 [self _stopTarget];
1457 }
1458 }
1459}
1460
1461-(float) startValue {
1462 return startValue;
1463}
1464
1465-(void) setStartValue:(float) startVal
1466{
1467 startValue = startVal;
1468 interpolator.start = startVal;
1469}
1470
1471-(float) endValue {
1472 return startValue;
1473}
1474
1475-(void) setEndValue:(float) endVal
1476{
1477 endValue = endVal;
1478 interpolator.end = endVal;
1479}
1480
1481-(tCDInterpolationType) interpolationType {
1482 return interpolator.interpolationType;
1483}
1484
1485-(void) setInterpolationType:(tCDInterpolationType) interpolationType {
1486 interpolator.interpolationType = interpolationType;
1487}
1488
1489-(void) _setTargetProperty:(float) newVal {
1490
1491}
1492
1493-(float) _getTargetProperty {
1494 return 0.0f;
1495}
1496
1497-(void) _stopTarget {
1498
1499}
1500
1501-(Class) _allowableType {
1502 return [NSObject class];
1503}
1504@end
1505
1506///////////////////////////////////////////////////////////////////////////////////////
1507#pragma mark -
1508#pragma mark CDSoundSourceFader
1509
1510@implementation CDSoundSourceFader
1511
1512-(void) _setTargetProperty:(float) newVal {
1513 ((CDSoundSource*)target).gain = newVal;
1514}
1515
1516-(float) _getTargetProperty {
1517 return ((CDSoundSource*)target).gain;
1518}
1519
1520-(void) _stopTarget {
1521 [((CDSoundSource*)target) stop];
1522}
1523
1524-(Class) _allowableType {
1525 return [CDSoundSource class];
1526}
1527
1528@end
1529
1530///////////////////////////////////////////////////////////////////////////////////////
1531#pragma mark -
1532#pragma mark CDSoundSourcePanner
1533
1534@implementation CDSoundSourcePanner
1535
1536-(void) _setTargetProperty:(float) newVal {
1537 ((CDSoundSource*)target).pan = newVal;
1538}
1539
1540-(float) _getTargetProperty {
1541 return ((CDSoundSource*)target).pan;
1542}
1543
1544-(void) _stopTarget {
1545 [((CDSoundSource*)target) stop];
1546}
1547
1548-(Class) _allowableType {
1549 return [CDSoundSource class];
1550}
1551
1552@end
1553
1554///////////////////////////////////////////////////////////////////////////////////////
1555#pragma mark -
1556#pragma mark CDSoundSourcePitchBender
1557
1558@implementation CDSoundSourcePitchBender
1559
1560-(void) _setTargetProperty:(float) newVal {
1561 ((CDSoundSource*)target).pitch = newVal;
1562}
1563
1564-(float) _getTargetProperty {
1565 return ((CDSoundSource*)target).pitch;
1566}
1567
1568-(void) _stopTarget {
1569 [((CDSoundSource*)target) stop];
1570}
1571
1572-(Class) _allowableType {
1573 return [CDSoundSource class];
1574}
1575
1576@end
1577
1578///////////////////////////////////////////////////////////////////////////////////////
1579#pragma mark -
1580#pragma mark CDSoundEngineFader
1581
1582@implementation CDSoundEngineFader
1583
1584-(void) _setTargetProperty:(float) newVal {
1585 ((CDSoundEngine*)target).masterGain = newVal;
1586}
1587
1588-(float) _getTargetProperty {
1589 return ((CDSoundEngine*)target).masterGain;
1590}
1591
1592-(void) _stopTarget {
1593 [((CDSoundEngine*)target) stopAllSounds];
1594}
1595
1596-(Class) _allowableType {
1597 return [CDSoundEngine class];
1598}
1599
1600@end
1601
1602
diff --git a/libs/CocosDenshion/SimpleAudioEngine.h b/libs/CocosDenshion/SimpleAudioEngine.h new file mode 100755 index 0000000..35396c6 --- /dev/null +++ b/libs/CocosDenshion/SimpleAudioEngine.h
@@ -0,0 +1,90 @@
1/*
2 Copyright (c) 2010 Steve Oldmeadow
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
10
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 THE SOFTWARE.
21
22 $Id$
23 */
24
25
26#import "CDAudioManager.h"
27
28/**
29 A wrapper to the CDAudioManager object.
30 This is recommended for basic audio requirements. If you just want to play some sound fx
31 and some background music and have no interest in learning the lower level workings then
32 this is the interface to use.
33
34 Requirements:
35 - Firmware: OS 2.2 or greater
36 - Files: SimpleAudioEngine.*, CocosDenshion.*
37 - Frameworks: OpenAL, AudioToolbox, AVFoundation
38 @since v0.8
39 */
40@interface SimpleAudioEngine : NSObject <CDAudioInterruptProtocol> {
41
42 BOOL mute_;
43 BOOL enabled_;
44}
45
46/** Background music volume. Range is 0.0f to 1.0f. This will only have an effect if willPlayBackgroundMusic returns YES */
47@property (readwrite) float backgroundMusicVolume;
48/** Effects volume. Range is 0.0f to 1.0f */
49@property (readwrite) float effectsVolume;
50/** If NO it indicates background music will not be played either because no background music is loaded or the audio session does not permit it.*/
51@property (readonly) BOOL willPlayBackgroundMusic;
52
53/** returns the shared instance of the SimpleAudioEngine object */
54+ (SimpleAudioEngine*) sharedEngine;
55
56/** Preloads a music file so it will be ready to play as background music */
57-(void) preloadBackgroundMusic:(NSString*) filePath;
58
59/** plays background music in a loop*/
60-(void) playBackgroundMusic:(NSString*) filePath;
61/** plays background music, if loop is true the music will repeat otherwise it will be played once */
62-(void) playBackgroundMusic:(NSString*) filePath loop:(BOOL) loop;
63/** stops playing background music */
64-(void) stopBackgroundMusic;
65/** pauses the background music */
66-(void) pauseBackgroundMusic;
67/** resume background music that has been paused */
68-(void) resumeBackgroundMusic;
69/** rewind the background music */
70-(void) rewindBackgroundMusic;
71/** returns whether or not the background music is playing */
72-(BOOL) isBackgroundMusicPlaying;
73
74/** plays an audio effect with a file path*/
75-(ALuint) playEffect:(NSString*) filePath;
76/** stop a sound that is playing, note you must pass in the soundId that is returned when you started playing the sound with playEffect */
77-(void) stopEffect:(ALuint) soundId;
78/** plays an audio effect with a file path, pitch, pan and gain */
79-(ALuint) playEffect:(NSString*) filePath pitch:(Float32) pitch pan:(Float32) pan gain:(Float32) gain;
80/** preloads an audio effect */
81-(void) preloadEffect:(NSString*) filePath;
82/** unloads an audio effect from memory */
83-(void) unloadEffect:(NSString*) filePath;
84/** Gets a CDSoundSource object set up to play the specified file. */
85-(CDSoundSource *) soundSourceForFile:(NSString*) filePath;
86
87/** Shuts down the shared audio engine instance so that it can be reinitialised */
88+(void) end;
89
90@end
diff --git a/libs/CocosDenshion/SimpleAudioEngine.m b/libs/CocosDenshion/SimpleAudioEngine.m new file mode 100755 index 0000000..cdff26c --- /dev/null +++ b/libs/CocosDenshion/SimpleAudioEngine.m
@@ -0,0 +1,220 @@
1/*
2 Copyright (c) 2010 Steve Oldmeadow
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
10
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 THE SOFTWARE.
21
22 $Id$
23 */
24
25#import "SimpleAudioEngine.h"
26
27@implementation SimpleAudioEngine
28
29static SimpleAudioEngine *sharedEngine = nil;
30static CDSoundEngine* soundEngine = nil;
31static CDAudioManager *am = nil;
32static CDBufferManager *bufferManager = nil;
33
34// Init
35+ (SimpleAudioEngine *) sharedEngine
36{
37 @synchronized(self) {
38 if (!sharedEngine)
39 sharedEngine = [[SimpleAudioEngine alloc] init];
40 }
41 return sharedEngine;
42}
43
44+ (id) alloc
45{
46 @synchronized(self) {
47 NSAssert(sharedEngine == nil, @"Attempted to allocate a second instance of a singleton.");
48 return [super alloc];
49 }
50 return nil;
51}
52
53-(id) init
54{
55 if((self=[super init])) {
56 am = [CDAudioManager sharedManager];
57 soundEngine = am.soundEngine;
58 bufferManager = [[CDBufferManager alloc] initWithEngine:soundEngine];
59 mute_ = NO;
60 enabled_ = YES;
61 }
62 return self;
63}
64
65// Memory
66- (void) dealloc
67{
68 am = nil;
69 soundEngine = nil;
70 bufferManager = nil;
71 [super dealloc];
72}
73
74+(void) end
75{
76 am = nil;
77 [CDAudioManager end];
78 [bufferManager release];
79 [sharedEngine release];
80 sharedEngine = nil;
81}
82
83#pragma mark SimpleAudioEngine - background music
84
85-(void) preloadBackgroundMusic:(NSString*) filePath {
86 [am preloadBackgroundMusic:filePath];
87}
88
89-(void) playBackgroundMusic:(NSString*) filePath
90{
91 [am playBackgroundMusic:filePath loop:TRUE];
92}
93
94-(void) playBackgroundMusic:(NSString*) filePath loop:(BOOL) loop
95{
96 [am playBackgroundMusic:filePath loop:loop];
97}
98
99-(void) stopBackgroundMusic
100{
101 [am stopBackgroundMusic];
102}
103
104-(void) pauseBackgroundMusic {
105 [am pauseBackgroundMusic];
106}
107
108-(void) resumeBackgroundMusic {
109 [am resumeBackgroundMusic];
110}
111
112-(void) rewindBackgroundMusic {
113 [am rewindBackgroundMusic];
114}
115
116-(BOOL) isBackgroundMusicPlaying {
117 return [am isBackgroundMusicPlaying];
118}
119
120-(BOOL) willPlayBackgroundMusic {
121 return [am willPlayBackgroundMusic];
122}
123
124#pragma mark SimpleAudioEngine - sound effects
125
126-(ALuint) playEffect:(NSString*) filePath
127{
128 return [self playEffect:filePath pitch:1.0f pan:0.0f gain:1.0f];
129}
130
131-(ALuint) playEffect:(NSString*) filePath pitch:(Float32) pitch pan:(Float32) pan gain:(Float32) gain
132{
133 int soundId = [bufferManager bufferForFile:filePath create:YES];
134 if (soundId != kCDNoBuffer) {
135 return [soundEngine playSound:soundId sourceGroupId:0 pitch:pitch pan:pan gain:gain loop:false];
136 } else {
137 return CD_MUTE;
138 }
139}
140
141-(void) stopEffect:(ALuint) soundId {
142 [soundEngine stopSound:soundId];
143}
144
145-(void) preloadEffect:(NSString*) filePath
146{
147 int soundId = [bufferManager bufferForFile:filePath create:YES];
148 if (soundId == kCDNoBuffer) {
149 CDLOG(@"Denshion::SimpleAudioEngine sound failed to preload %@",filePath);
150 }
151}
152
153-(void) unloadEffect:(NSString*) filePath
154{
155 CDLOGINFO(@"Denshion::SimpleAudioEngine unloadedEffect %@",filePath);
156 [bufferManager releaseBufferForFile:filePath];
157}
158
159#pragma mark Audio Interrupt Protocol
160-(BOOL) mute
161{
162 return mute_;
163}
164
165-(void) setMute:(BOOL) muteValue
166{
167 if (mute_ != muteValue) {
168 mute_ = muteValue;
169 am.mute = mute_;
170 }
171}
172
173-(BOOL) enabled
174{
175 return enabled_;
176}
177
178-(void) setEnabled:(BOOL) enabledValue
179{
180 if (enabled_ != enabledValue) {
181 enabled_ = enabledValue;
182 am.enabled = enabled_;
183 }
184}
185
186
187#pragma mark SimpleAudioEngine - BackgroundMusicVolume
188-(float) backgroundMusicVolume
189{
190 return am.backgroundMusic.volume;
191}
192
193-(void) setBackgroundMusicVolume:(float) volume
194{
195 am.backgroundMusic.volume = volume;
196}
197
198#pragma mark SimpleAudioEngine - EffectsVolume
199-(float) effectsVolume
200{
201 return am.soundEngine.masterGain;
202}
203
204-(void) setEffectsVolume:(float) volume
205{
206 am.soundEngine.masterGain = volume;
207}
208
209-(CDSoundSource *) soundSourceForFile:(NSString*) filePath {
210 int soundId = [bufferManager bufferForFile:filePath create:YES];
211 if (soundId != kCDNoBuffer) {
212 CDSoundSource *result = [soundEngine soundSourceForSound:soundId sourceGroupId:0];
213 CDLOGINFO(@"Denshion::SimpleAudioEngine sound source created for %@",filePath);
214 return result;
215 } else {
216 return nil;
217 }
218}
219
220@end