summary refs log tree commit diff stats
path: root/libs/CocosDenshion/CocosDenshion.m
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/CocosDenshion.m
downloadcartcollect-9cd57b731ab1c666d4a1cb725538fdc137763d12.tar.gz
cartcollect-9cd57b731ab1c666d4a1cb725538fdc137763d12.tar.bz2
cartcollect-9cd57b731ab1c666d4a1cb725538fdc137763d12.zip
Initial commit (version 0.2.1)
Diffstat (limited to 'libs/CocosDenshion/CocosDenshion.m')
-rwxr-xr-xlibs/CocosDenshion/CocosDenshion.m1602
1 files changed, 1602 insertions, 0 deletions
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