/CocosDenshion/mac/CocosDenshion.m

https://bitbucket.org/Tsiannian/cocos2d-x · Objective C · 1640 lines · 1199 code · 247 blank · 194 comment · 239 complexity · 5c2eeb4f5982d2c11f9627f4a0092db6 MD5 · raw file

Large files are truncated click here to view the full file

  1. /*
  2. Copyright (c) 2010 Steve Oldmeadow
  3. Permission is hereby granted, free of charge, to any person obtaining a copy
  4. of this software and associated documentation files (the "Software"), to deal
  5. in the Software without restriction, including without limitation the rights
  6. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. copies of the Software, and to permit persons to whom the Software is
  8. furnished to do so, subject to the following conditions:
  9. The above copyright notice and this permission notice shall be included in
  10. all copies or substantial portions of the Software.
  11. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  12. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  13. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  14. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  15. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  16. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  17. THE SOFTWARE.
  18. $Id$
  19. */
  20. #import "CocosDenshion.h"
  21. ALvoid alBufferDataStaticProc(const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq);
  22. ALvoid alcMacOSXMixerOutputRateProc(const ALdouble value);
  23. typedef ALvoid AL_APIENTRY (*alBufferDataStaticProcPtr) (const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq);
  24. ALvoid alBufferDataStaticProc(const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq)
  25. {
  26. static alBufferDataStaticProcPtr proc = NULL;
  27. if (proc == NULL) {
  28. proc = (alBufferDataStaticProcPtr) alcGetProcAddress(NULL, (const ALCchar*) "alBufferDataStatic");
  29. }
  30. if (proc)
  31. proc(bid, format, data, size, freq);
  32. return;
  33. }
  34. typedef ALvoid AL_APIENTRY (*alcMacOSXMixerOutputRateProcPtr) (const ALdouble value);
  35. ALvoid alcMacOSXMixerOutputRateProc(const ALdouble value)
  36. {
  37. static alcMacOSXMixerOutputRateProcPtr proc = NULL;
  38. if (proc == NULL) {
  39. proc = (alcMacOSXMixerOutputRateProcPtr) alcGetProcAddress(NULL, (const ALCchar*) "alcMacOSXMixerOutputRate");
  40. }
  41. if (proc)
  42. proc(value);
  43. return;
  44. }
  45. NSString * const kCDN_BadAlContext = @"kCDN_BadAlContext";
  46. NSString * const kCDN_AsynchLoadComplete = @"kCDN_AsynchLoadComplete";
  47. float const kCD_PitchDefault = 1.0f;
  48. float const kCD_PitchLowerOneOctave = 0.5f;
  49. float const kCD_PitchHigherOneOctave = 2.0f;
  50. float const kCD_PanDefault = 0.0f;
  51. float const kCD_PanFullLeft = -1.0f;
  52. float const kCD_PanFullRight = 1.0f;
  53. float const kCD_GainDefault = 1.0f;
  54. @interface CDSoundEngine (PrivateMethods)
  55. -(BOOL) _initOpenAL;
  56. -(void) _testGetGain;
  57. -(void) _dumpSourceGroupsInfo;
  58. -(void) _getSourceIndexForSourceGroup;
  59. -(void) _freeSourceGroups;
  60. -(BOOL) _setUpSourceGroups:(int[]) definitions total:(NSUInteger) total;
  61. @end
  62. #pragma mark -
  63. #pragma mark CDUtilities
  64. @implementation CDUtilities
  65. +(NSString*) fullPathFromRelativePath:(NSString*) relPath
  66. {
  67. // do not convert an absolute path (starting with '/')
  68. if(([relPath length] > 0) && ([relPath characterAtIndex:0] == '/'))
  69. {
  70. return relPath;
  71. }
  72. NSMutableArray *imagePathComponents = [NSMutableArray arrayWithArray:[relPath pathComponents]];
  73. NSString *file = [imagePathComponents lastObject];
  74. [imagePathComponents removeLastObject];
  75. NSString *imageDirectory = [NSString pathWithComponents:imagePathComponents];
  76. NSString *fullpath = [[NSBundle mainBundle] pathForResource:file ofType:nil inDirectory:imageDirectory];
  77. if (fullpath == nil)
  78. fullpath = relPath;
  79. return fullpath;
  80. }
  81. @end
  82. #pragma mark -
  83. #pragma mark CDSoundEngine
  84. @implementation CDSoundEngine
  85. static Float32 _mixerSampleRate;
  86. static BOOL _mixerRateSet = NO;
  87. @synthesize lastErrorCode = lastErrorCode_;
  88. @synthesize functioning = functioning_;
  89. @synthesize asynchLoadProgress = asynchLoadProgress_;
  90. @synthesize getGainWorks = getGainWorks_;
  91. @synthesize sourceTotal = sourceTotal_;
  92. + (void) setMixerSampleRate:(Float32) sampleRate {
  93. _mixerRateSet = YES;
  94. _mixerSampleRate = sampleRate;
  95. }
  96. - (void) _testGetGain {
  97. float testValue = 0.7f;
  98. ALuint testSourceId = _sources[0].sourceId;
  99. alSourcef(testSourceId, AL_GAIN, 0.0f);//Start from know value
  100. alSourcef(testSourceId, AL_GAIN, testValue);
  101. ALfloat gainVal;
  102. alGetSourcef(testSourceId, AL_GAIN, &gainVal);
  103. getGainWorks_ = (gainVal == testValue);
  104. }
  105. //Generate sources one at a time until we fail
  106. -(void) _generateSources {
  107. _sources = (sourceInfo*)malloc( sizeof(_sources[0]) * CD_SOURCE_LIMIT);
  108. BOOL hasFailed = NO;
  109. sourceTotal_ = 0;
  110. alGetError();//Clear error
  111. while (!hasFailed && sourceTotal_ < CD_SOURCE_LIMIT) {
  112. alGenSources(1, &(_sources[sourceTotal_].sourceId));
  113. if (alGetError() == AL_NO_ERROR) {
  114. //Now try attaching source to null buffer
  115. alSourcei(_sources[sourceTotal_].sourceId, AL_BUFFER, 0);
  116. if (alGetError() == AL_NO_ERROR) {
  117. _sources[sourceTotal_].usable = true;
  118. sourceTotal_++;
  119. } else {
  120. hasFailed = YES;
  121. }
  122. } else {
  123. _sources[sourceTotal_].usable = false;
  124. hasFailed = YES;
  125. }
  126. }
  127. //Mark the rest of the sources as not usable
  128. for (int i=sourceTotal_; i < CD_SOURCE_LIMIT; i++) {
  129. _sources[i].usable = false;
  130. }
  131. }
  132. -(void) _generateBuffers:(int) startIndex endIndex:(int) endIndex {
  133. if (_buffers) {
  134. alGetError();
  135. for (int i=startIndex; i <= endIndex; i++) {
  136. alGenBuffers(1, &_buffers[i].bufferId);
  137. _buffers[i].bufferData = NULL;
  138. if (alGetError() == AL_NO_ERROR) {
  139. _buffers[i].bufferState = CD_BS_EMPTY;
  140. } else {
  141. _buffers[i].bufferState = CD_BS_FAILED;
  142. CDLOG(@"Denshion::CDSoundEngine - buffer creation failed %i",i);
  143. }
  144. }
  145. }
  146. }
  147. /**
  148. * Internal method called during init
  149. */
  150. - (BOOL) _initOpenAL
  151. {
  152. //ALenum error;
  153. context = NULL;
  154. ALCdevice *newDevice = NULL;
  155. //Set the mixer rate for the audio mixer
  156. if (!_mixerRateSet) {
  157. _mixerSampleRate = CD_SAMPLE_RATE_DEFAULT;
  158. }
  159. alcMacOSXMixerOutputRateProc(_mixerSampleRate);
  160. CDLOGINFO(@"Denshion::CDSoundEngine - mixer output rate set to %0.2f",_mixerSampleRate);
  161. // Create a new OpenAL Device
  162. // Pass NULL to specify the system's default output device
  163. newDevice = alcOpenDevice(NULL);
  164. if (newDevice != NULL)
  165. {
  166. // Create a new OpenAL Context
  167. // The new context will render to the OpenAL Device just created
  168. context = alcCreateContext(newDevice, 0);
  169. if (context != NULL)
  170. {
  171. // Make the new context the Current OpenAL Context
  172. alcMakeContextCurrent(context);
  173. // Create some OpenAL Buffer Objects
  174. [self _generateBuffers:0 endIndex:bufferTotal-1];
  175. // Create some OpenAL Source Objects
  176. [self _generateSources];
  177. }
  178. } else {
  179. return FALSE;//No device
  180. }
  181. alGetError();//Clear error
  182. return TRUE;
  183. }
  184. - (void) dealloc {
  185. ALCcontext *currentContext = NULL;
  186. ALCdevice *device = NULL;
  187. [self stopAllSounds];
  188. CDLOGINFO(@"Denshion::CDSoundEngine - Deallocing sound engine.");
  189. [self _freeSourceGroups];
  190. // Delete the Sources
  191. CDLOGINFO(@"Denshion::CDSoundEngine - deleting sources.");
  192. for (int i=0; i < sourceTotal_; i++) {
  193. alSourcei(_sources[i].sourceId, AL_BUFFER, 0);//Detach from current buffer
  194. alDeleteSources(1, &(_sources[i].sourceId));
  195. if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) {
  196. CDLOG(@"Denshion::CDSoundEngine - Error deleting source! %x\n", lastErrorCode_);
  197. }
  198. }
  199. // Delete the Buffers
  200. CDLOGINFO(@"Denshion::CDSoundEngine - deleting buffers.");
  201. for (int i=0; i < bufferTotal; i++) {
  202. alDeleteBuffers(1, &_buffers[i].bufferId);
  203. #ifdef CD_USE_STATIC_BUFFERS
  204. if (_buffers[i].bufferData) {
  205. free(_buffers[i].bufferData);
  206. }
  207. #endif
  208. }
  209. CDLOGINFO(@"Denshion::CDSoundEngine - free buffers.");
  210. free(_buffers);
  211. currentContext = alcGetCurrentContext();
  212. //Get device for active context
  213. device = alcGetContextsDevice(currentContext);
  214. //Release context
  215. CDLOGINFO(@"Denshion::CDSoundEngine - destroy context.");
  216. alcDestroyContext(currentContext);
  217. //Close device
  218. CDLOGINFO(@"Denshion::CDSoundEngine - close device.");
  219. alcCloseDevice(device);
  220. CDLOGINFO(@"Denshion::CDSoundEngine - free sources.");
  221. free(_sources);
  222. //Release mutexes
  223. [_mutexBufferLoad release];
  224. [super dealloc];
  225. }
  226. -(NSUInteger) sourceGroupTotal {
  227. return _sourceGroupTotal;
  228. }
  229. -(void) _freeSourceGroups
  230. {
  231. CDLOGINFO(@"Denshion::CDSoundEngine freeing source groups");
  232. if(_sourceGroups) {
  233. for (int i=0; i < _sourceGroupTotal; i++) {
  234. if (_sourceGroups[i].sourceStatuses) {
  235. free(_sourceGroups[i].sourceStatuses);
  236. CDLOGINFO(@"Denshion::CDSoundEngine freed source statuses %i",i);
  237. }
  238. }
  239. free(_sourceGroups);
  240. }
  241. }
  242. -(BOOL) _redefineSourceGroups:(int[]) definitions total:(NSUInteger) total
  243. {
  244. if (_sourceGroups) {
  245. //Stop all sounds
  246. [self stopAllSounds];
  247. //Need to free source groups
  248. [self _freeSourceGroups];
  249. }
  250. return [self _setUpSourceGroups:definitions total:total];
  251. }
  252. -(BOOL) _setUpSourceGroups:(int[]) definitions total:(NSUInteger) total
  253. {
  254. _sourceGroups = (sourceGroup *)malloc( sizeof(_sourceGroups[0]) * total);
  255. if(!_sourceGroups) {
  256. CDLOG(@"Denshion::CDSoundEngine - source groups memory allocation failed");
  257. return NO;
  258. }
  259. _sourceGroupTotal = total;
  260. int sourceCount = 0;
  261. for (int i=0; i < _sourceGroupTotal; i++) {
  262. _sourceGroups[i].startIndex = 0;
  263. _sourceGroups[i].currentIndex = _sourceGroups[i].startIndex;
  264. _sourceGroups[i].enabled = false;
  265. _sourceGroups[i].nonInterruptible = false;
  266. _sourceGroups[i].totalSources = definitions[i];
  267. _sourceGroups[i].sourceStatuses = malloc(sizeof(_sourceGroups[i].sourceStatuses[0]) * _sourceGroups[i].totalSources);
  268. if (_sourceGroups[i].sourceStatuses) {
  269. for (int j=0; j < _sourceGroups[i].totalSources; j++) {
  270. //First bit is used to indicate whether source is locked, index is shifted back 1 bit
  271. _sourceGroups[i].sourceStatuses[j] = (sourceCount + j) << 1;
  272. }
  273. }
  274. sourceCount += definitions[i];
  275. }
  276. return YES;
  277. }
  278. -(void) defineSourceGroups:(int[]) sourceGroupDefinitions total:(NSUInteger) total {
  279. [self _redefineSourceGroups:sourceGroupDefinitions total:total];
  280. }
  281. -(void) defineSourceGroups:(NSArray*) sourceGroupDefinitions {
  282. CDLOGINFO(@"Denshion::CDSoundEngine - source groups defined by NSArray.");
  283. NSUInteger totalDefs = [sourceGroupDefinitions count];
  284. int* defs = (int *)malloc( sizeof(int) * totalDefs);
  285. int currentIndex = 0;
  286. for (id currentDef in sourceGroupDefinitions) {
  287. if ([currentDef isKindOfClass:[NSNumber class]]) {
  288. defs[currentIndex] = (int)[(NSNumber*)currentDef integerValue];
  289. CDLOGINFO(@"Denshion::CDSoundEngine - found definition %i.",defs[currentIndex]);
  290. } else {
  291. CDLOG(@"Denshion::CDSoundEngine - warning, did not understand source definition.");
  292. defs[currentIndex] = 0;
  293. }
  294. currentIndex++;
  295. }
  296. [self _redefineSourceGroups:defs total:totalDefs];
  297. free(defs);
  298. }
  299. - (id)init
  300. {
  301. if ((self = [super init])) {
  302. //Create mutexes
  303. _mutexBufferLoad = [[NSObject alloc] init];
  304. asynchLoadProgress_ = 0.0f;
  305. bufferTotal = CD_BUFFERS_START;
  306. _buffers = (bufferInfo *)malloc( sizeof(_buffers[0]) * bufferTotal);
  307. // Initialize our OpenAL environment
  308. if ([self _initOpenAL]) {
  309. //Set up the default source group - a single group that contains all the sources
  310. int sourceDefs[1];
  311. sourceDefs[0] = self.sourceTotal;
  312. [self _setUpSourceGroups:sourceDefs total:1];
  313. functioning_ = YES;
  314. //Synchronize premute gain
  315. _preMuteGain = self.masterGain;
  316. mute_ = NO;
  317. enabled_ = YES;
  318. //Test whether get gain works for sources
  319. [self _testGetGain];
  320. } else {
  321. //Something went wrong with OpenAL
  322. functioning_ = NO;
  323. }
  324. }
  325. return self;
  326. }
  327. /**
  328. * Delete the buffer identified by soundId
  329. * @return true if buffer deleted successfully, otherwise false
  330. */
  331. - (BOOL) unloadBuffer:(int) soundId
  332. {
  333. //Ensure soundId is within array bounds otherwise memory corruption will occur
  334. if (soundId < 0 || soundId >= bufferTotal) {
  335. CDLOG(@"Denshion::CDSoundEngine - soundId is outside array bounds, maybe you need to increase CD_MAX_BUFFERS");
  336. return FALSE;
  337. }
  338. //Before a buffer can be deleted any sources that are attached to it must be stopped
  339. for (int i=0; i < sourceTotal_; i++) {
  340. //Note: tried getting the AL_BUFFER attribute of the source instead but doesn't
  341. //appear to work on a device - just returned zero.
  342. if (_buffers[soundId].bufferId == _sources[i].attachedBufferId) {
  343. CDLOG(@"Denshion::CDSoundEngine - Found attached source %i %i %i",i,_buffers[soundId].bufferId,_sources[i].sourceId);
  344. #ifdef CD_USE_STATIC_BUFFERS
  345. //When using static buffers a crash may occur if a source is playing with a buffer that is about
  346. //to be deleted even though we stop the source and successfully delete the buffer. Crash is confirmed
  347. //on 2.2.1 and 3.1.2, however, it will only occur if a source is used rapidly after having its prior
  348. //data deleted. To avoid any possibility of the crash we wait for the source to finish playing.
  349. ALint state;
  350. alGetSourcei(_sources[i].sourceId, AL_SOURCE_STATE, &state);
  351. if (state == AL_PLAYING) {
  352. CDLOG(@"Denshion::CDSoundEngine - waiting for source to complete playing before removing buffer data");
  353. alSourcei(_sources[i].sourceId, AL_LOOPING, FALSE);//Turn off looping otherwise loops will never end
  354. while (state == AL_PLAYING) {
  355. alGetSourcei(_sources[i].sourceId, AL_SOURCE_STATE, &state);
  356. usleep(10000);
  357. }
  358. }
  359. #endif
  360. //Stop source and detach
  361. alSourceStop(_sources[i].sourceId);
  362. if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) {
  363. CDLOG(@"Denshion::CDSoundEngine - error stopping source: %x\n", lastErrorCode_);
  364. }
  365. alSourcei(_sources[i].sourceId, AL_BUFFER, 0);//Attach to "NULL" buffer to detach
  366. if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) {
  367. CDLOG(@"Denshion::CDSoundEngine - error detaching buffer: %x\n", lastErrorCode_);
  368. } else {
  369. //Record that source is now attached to nothing
  370. _sources[i].attachedBufferId = 0;
  371. }
  372. }
  373. }
  374. alDeleteBuffers(1, &_buffers[soundId].bufferId);
  375. if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) {
  376. CDLOG(@"Denshion::CDSoundEngine - error deleting buffer: %x\n", lastErrorCode_);
  377. _buffers[soundId].bufferState = CD_BS_FAILED;
  378. return FALSE;
  379. } else {
  380. #ifdef CD_USE_STATIC_BUFFERS
  381. //Free previous data, if alDeleteBuffer has returned without error then no
  382. if (_buffers[soundId].bufferData) {
  383. CDLOGINFO(@"Denshion::CDSoundEngine - freeing static data for soundId %i @ %i",soundId,_buffers[soundId].bufferData);
  384. free(_buffers[soundId].bufferData);//Free the old data
  385. _buffers[soundId].bufferData = NULL;
  386. }
  387. #endif
  388. }
  389. alGenBuffers(1, &_buffers[soundId].bufferId);
  390. if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) {
  391. CDLOG(@"Denshion::CDSoundEngine - error regenerating buffer: %x\n", lastErrorCode_);
  392. _buffers[soundId].bufferState = CD_BS_FAILED;
  393. return FALSE;
  394. } else {
  395. //We now have an empty buffer
  396. _buffers[soundId].bufferState = CD_BS_EMPTY;
  397. CDLOGINFO(@"Denshion::CDSoundEngine - buffer %i successfully unloaded\n",soundId);
  398. return TRUE;
  399. }
  400. }
  401. /**
  402. * Load buffers asynchronously
  403. * Check asynchLoadProgress for progress. asynchLoadProgress represents fraction of completion. When it equals 1.0 loading
  404. * is complete. NB: asynchLoadProgress is simply based on the number of load requests, it does not take into account
  405. * file sizes.
  406. * @param An array of CDBufferLoadRequest objects
  407. */
  408. - (void) loadBuffersAsynchronously:(NSArray *) loadRequests {
  409. @synchronized(self) {
  410. asynchLoadProgress_ = 0.0f;
  411. CDAsynchBufferLoader *loaderOp = [[[CDAsynchBufferLoader alloc] init:loadRequests soundEngine:self] autorelease];
  412. NSOperationQueue *opQ = [[[NSOperationQueue alloc] init] autorelease];
  413. [opQ addOperation:loaderOp];
  414. }
  415. }
  416. -(BOOL) _resizeBuffers:(int) increment {
  417. void * tmpBufferInfos = realloc( _buffers, sizeof(_buffers[0]) * (bufferTotal + increment) );
  418. if(!tmpBufferInfos) {
  419. free(tmpBufferInfos);
  420. return NO;
  421. } else {
  422. _buffers = tmpBufferInfos;
  423. int oldBufferTotal = bufferTotal;
  424. bufferTotal = bufferTotal + increment;
  425. [self _generateBuffers:oldBufferTotal endIndex:bufferTotal-1];
  426. return YES;
  427. }
  428. }
  429. -(BOOL) loadBufferFromData:(int) soundId soundData:(ALvoid*) soundData format:(ALenum) format size:(ALsizei) size freq:(ALsizei) freq {
  430. @synchronized(_mutexBufferLoad) {
  431. if (!functioning_) {
  432. //OpenAL initialisation has previously failed
  433. CDLOG(@"Denshion::CDSoundEngine - Loading buffer failed because sound engine state != functioning");
  434. return FALSE;
  435. }
  436. //Ensure soundId is within array bounds otherwise memory corruption will occur
  437. if (soundId < 0) {
  438. CDLOG(@"Denshion::CDSoundEngine - soundId is negative");
  439. return FALSE;
  440. }
  441. if (soundId >= bufferTotal) {
  442. //Need to resize the buffers
  443. int requiredIncrement = CD_BUFFERS_INCREMENT;
  444. while (bufferTotal + requiredIncrement < soundId) {
  445. requiredIncrement += CD_BUFFERS_INCREMENT;
  446. }
  447. CDLOGINFO(@"Denshion::CDSoundEngine - attempting to resize buffers by %i for sound %i",requiredIncrement,soundId);
  448. if (![self _resizeBuffers:requiredIncrement]) {
  449. CDLOG(@"Denshion::CDSoundEngine - buffer resize failed");
  450. return FALSE;
  451. }
  452. }
  453. if (soundData)
  454. {
  455. if (_buffers[soundId].bufferState != CD_BS_EMPTY) {
  456. CDLOGINFO(@"Denshion::CDSoundEngine - non empty buffer, regenerating");
  457. if (![self unloadBuffer:soundId]) {
  458. //Deletion of buffer failed, delete buffer routine has set buffer state and lastErrorCode
  459. return NO;
  460. }
  461. }
  462. #ifdef CD_DEBUG
  463. //Check that sample rate matches mixer rate and warn if they do not
  464. if (freq != (int)_mixerSampleRate) {
  465. CDLOGINFO(@"Denshion::CDSoundEngine - WARNING sample rate does not match mixer sample rate performance may not be optimal.");
  466. }
  467. #endif
  468. #ifdef CD_USE_STATIC_BUFFERS
  469. alBufferDataStaticProc(_buffers[soundId].bufferId, format, soundData, size, freq);
  470. _buffers[soundId].bufferData = data;//Save the pointer to the new data
  471. #else
  472. alBufferData(_buffers[soundId].bufferId, format, soundData, size, freq);
  473. #endif
  474. if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) {
  475. CDLOG(@"Denshion::CDSoundEngine - error attaching audio to buffer: %x", lastErrorCode_);
  476. _buffers[soundId].bufferState = CD_BS_FAILED;
  477. return FALSE;
  478. }
  479. } else {
  480. CDLOG(@"Denshion::CDSoundEngine Buffer data is null!");
  481. _buffers[soundId].bufferState = CD_BS_FAILED;
  482. return FALSE;
  483. }
  484. _buffers[soundId].format = format;
  485. _buffers[soundId].sizeInBytes = size;
  486. _buffers[soundId].frequencyInHertz = freq;
  487. _buffers[soundId].bufferState = CD_BS_LOADED;
  488. CDLOGINFO(@"Denshion::CDSoundEngine Buffer %i loaded format:%i freq:%i size:%i",soundId,format,freq,size);
  489. return TRUE;
  490. }//end mutex
  491. }
  492. /**
  493. * Load sound data for later play back.
  494. * @return TRUE if buffer loaded okay for play back otherwise false
  495. */
  496. - (BOOL) loadBuffer:(int) soundId filePath:(NSString*) filePath
  497. {
  498. ALvoid* data;
  499. ALenum format;
  500. ALsizei size;
  501. ALsizei freq;
  502. CDLOGINFO(@"Denshion::CDSoundEngine - Loading openAL buffer %i %@", soundId, filePath);
  503. CFURLRef fileURL = nil;
  504. NSString *path = [CDUtilities fullPathFromRelativePath:filePath];
  505. if (path) {
  506. fileURL = (CFURLRef)[[NSURL fileURLWithPath:path] retain];
  507. }
  508. if (fileURL)
  509. {
  510. data = CDGetOpenALAudioData(fileURL, &size, &format, &freq);
  511. CFRelease(fileURL);
  512. BOOL result = [self loadBufferFromData:soundId soundData:data format:format size:size freq:freq];
  513. #ifndef CD_USE_STATIC_BUFFERS
  514. free(data);//Data can be freed here because alBufferData performs a memcpy
  515. #endif
  516. return result;
  517. } else {
  518. CDLOG(@"Denshion::CDSoundEngine Could not find file!\n");
  519. //Don't change buffer state here as it will be the same as before method was called
  520. return FALSE;
  521. }
  522. }
  523. -(BOOL) validateBufferId:(int) soundId {
  524. if (soundId < 0 || soundId >= bufferTotal) {
  525. CDLOGINFO(@"Denshion::CDSoundEngine - validateBufferId buffer outside range %i",soundId);
  526. return NO;
  527. } else if (_buffers[soundId].bufferState != CD_BS_LOADED) {
  528. CDLOGINFO(@"Denshion::CDSoundEngine - validateBufferId invalide buffer state %i",soundId);
  529. return NO;
  530. } else {
  531. return YES;
  532. }
  533. }
  534. -(float) bufferDurationInSeconds:(int) soundId {
  535. if ([self validateBufferId:soundId]) {
  536. float factor = 0.0f;
  537. switch (_buffers[soundId].format) {
  538. case AL_FORMAT_MONO8:
  539. factor = 1.0f;
  540. break;
  541. case AL_FORMAT_MONO16:
  542. factor = 0.5f;
  543. break;
  544. case AL_FORMAT_STEREO8:
  545. factor = 0.5f;
  546. break;
  547. case AL_FORMAT_STEREO16:
  548. factor = 0.25f;
  549. break;
  550. }
  551. return (float)_buffers[soundId].sizeInBytes/(float)_buffers[soundId].frequencyInHertz * factor;
  552. } else {
  553. return -1.0f;
  554. }
  555. }
  556. -(ALsizei) bufferSizeInBytes:(int) soundId {
  557. if ([self validateBufferId:soundId]) {
  558. return _buffers[soundId].sizeInBytes;
  559. } else {
  560. return -1.0f;
  561. }
  562. }
  563. -(ALsizei) bufferFrequencyInHertz:(int) soundId {
  564. if ([self validateBufferId:soundId]) {
  565. return _buffers[soundId].frequencyInHertz;
  566. } else {
  567. return -1.0f;
  568. }
  569. }
  570. - (ALfloat) masterGain {
  571. if (mute_) {
  572. //When mute the real gain will always be 0 therefore return the preMuteGain value
  573. return _preMuteGain;
  574. } else {
  575. ALfloat gain;
  576. alGetListenerf(AL_GAIN, &gain);
  577. return gain;
  578. }
  579. }
  580. /**
  581. * Overall gain setting multiplier. e.g 0.5 is half the gain.
  582. */
  583. - (void) setMasterGain:(ALfloat) newGainValue {
  584. if (mute_) {
  585. _preMuteGain = newGainValue;
  586. } else {
  587. alListenerf(AL_GAIN, newGainValue);
  588. }
  589. }
  590. #pragma mark CDSoundEngine AudioInterrupt protocol
  591. - (BOOL) mute {
  592. return mute_;
  593. }
  594. /**
  595. * Setting mute silences all sounds but playing sounds continue to advance playback
  596. */
  597. - (void) setMute:(BOOL) newMuteValue {
  598. if (newMuteValue == mute_) {
  599. return;
  600. }
  601. mute_ = newMuteValue;
  602. if (mute_) {
  603. //Remember what the gain was
  604. _preMuteGain = self.masterGain;
  605. //Set gain to 0 - do not use the property as this will adjust preMuteGain when muted
  606. alListenerf(AL_GAIN, 0.0f);
  607. } else {
  608. //Restore gain to what it was before being muted
  609. self.masterGain = _preMuteGain;
  610. }
  611. }
  612. - (BOOL) enabled {
  613. return enabled_;
  614. }
  615. - (void) setEnabled:(BOOL)enabledValue
  616. {
  617. if (enabled_ == enabledValue) {
  618. return;
  619. }
  620. enabled_ = enabledValue;
  621. if (enabled_ == NO) {
  622. [self stopAllSounds];
  623. }
  624. }
  625. -(void) _lockSource:(int) sourceIndex lock:(BOOL) lock {
  626. BOOL found = NO;
  627. for (int i=0; i < _sourceGroupTotal && !found; i++) {
  628. if (_sourceGroups[i].sourceStatuses) {
  629. for (int j=0; j < _sourceGroups[i].totalSources && !found; j++) {
  630. //First bit is used to indicate whether source is locked, index is shifted back 1 bit
  631. if((_sourceGroups[i].sourceStatuses[j] >> 1)==sourceIndex) {
  632. if (lock) {
  633. //Set first bit to lock this source
  634. _sourceGroups[i].sourceStatuses[j] |= 1;
  635. } else {
  636. //Unset first bit to unlock this source
  637. _sourceGroups[i].sourceStatuses[j] &= ~1;
  638. }
  639. found = YES;
  640. }
  641. }
  642. }
  643. }
  644. }
  645. -(int) _getSourceIndexForSourceGroup:(int)sourceGroupId
  646. {
  647. //Ensure source group id is valid to prevent memory corruption
  648. if (sourceGroupId < 0 || sourceGroupId >= _sourceGroupTotal) {
  649. CDLOG(@"Denshion::CDSoundEngine invalid source group id %i",sourceGroupId);
  650. return CD_NO_SOURCE;
  651. }
  652. int sourceIndex = -1;//Using -1 to indicate no source found
  653. BOOL complete = NO;
  654. ALint sourceState = 0;
  655. sourceGroup *thisSourceGroup = &_sourceGroups[sourceGroupId];
  656. thisSourceGroup->currentIndex = thisSourceGroup->startIndex;
  657. while (!complete) {
  658. //Iterate over sources looking for one that is not locked, first bit indicates if source is locked
  659. if ((thisSourceGroup->sourceStatuses[thisSourceGroup->currentIndex] & 1) == 0) {
  660. //This source is not locked
  661. sourceIndex = thisSourceGroup->sourceStatuses[thisSourceGroup->currentIndex] >> 1;//shift back to get the index
  662. if (thisSourceGroup->nonInterruptible) {
  663. //Check if this source is playing, if so it can't be interrupted
  664. alGetSourcei(_sources[sourceIndex].sourceId, AL_SOURCE_STATE, &sourceState);
  665. if (sourceState != AL_PLAYING) {
  666. //complete = YES;
  667. //Set start index so next search starts at the next position
  668. thisSourceGroup->startIndex = thisSourceGroup->currentIndex + 1;
  669. break;
  670. } else {
  671. sourceIndex = -1;//The source index was no good because the source was playing
  672. }
  673. } else {
  674. //complete = YES;
  675. //Set start index so next search starts at the next position
  676. thisSourceGroup->startIndex = thisSourceGroup->currentIndex + 1;
  677. break;
  678. }
  679. }
  680. thisSourceGroup->currentIndex++;
  681. if (thisSourceGroup->currentIndex >= thisSourceGroup->totalSources) {
  682. //Reset to the beginning
  683. thisSourceGroup->currentIndex = 0;
  684. }
  685. if (thisSourceGroup->currentIndex == thisSourceGroup->startIndex) {
  686. //We have looped around and got back to the start
  687. complete = YES;
  688. }
  689. }
  690. //Reset start index to beginning if beyond bounds
  691. if (thisSourceGroup->startIndex >= thisSourceGroup->totalSources) {
  692. thisSourceGroup->startIndex = 0;
  693. }
  694. if (sourceIndex >= 0) {
  695. return sourceIndex;
  696. } else {
  697. return CD_NO_SOURCE;
  698. }
  699. }
  700. /**
  701. * Play a sound.
  702. * @param soundId the id of the sound to play (buffer id).
  703. * @param SourceGroupId the source group that will be used to play the sound.
  704. * @param pitch pitch multiplier. e.g 1.0 is unaltered, 0.5 is 1 octave lower.
  705. * @param pan stereo position. -1 is fully left, 0 is centre and 1 is fully right.
  706. * @param gain gain multiplier. e.g. 1.0 is unaltered, 0.5 is half the gain
  707. * @param loop should the sound be looped or one shot.
  708. * @return the id of the source being used to play the sound or CD_MUTE if the sound engine is muted or non functioning
  709. * or CD_NO_SOURCE if a problem occurs setting up the source
  710. *
  711. */
  712. - (ALuint)playSound:(int) soundId sourceGroupId:(int)sourceGroupId pitch:(float) pitch pan:(float) pan gain:(float) gain loop:(BOOL) loop {
  713. #ifdef CD_DEBUG
  714. //Sanity check parameters - only in DEBUG
  715. NSAssert(soundId >= 0, @"soundId can not be negative");
  716. NSAssert(soundId < bufferTotal, @"soundId exceeds limit");
  717. NSAssert(sourceGroupId >= 0, @"sourceGroupId can not be negative");
  718. NSAssert(sourceGroupId < _sourceGroupTotal, @"sourceGroupId exceeds limit");
  719. NSAssert(pitch > 0, @"pitch must be greater than zero");
  720. NSAssert(pan >= -1 && pan <= 1, @"pan must be between -1 and 1");
  721. NSAssert(gain >= 0, @"gain can not be negative");
  722. #endif
  723. //If mute or initialisation has failed or buffer is not loaded then do nothing
  724. if (!enabled_ || !functioning_ || _buffers[soundId].bufferState != CD_BS_LOADED || _sourceGroups[sourceGroupId].enabled) {
  725. #ifdef CD_DEBUG
  726. if (!functioning_) {
  727. CDLOGINFO(@"Denshion::CDSoundEngine - sound playback aborted because sound engine is not functioning");
  728. } else if (_buffers[soundId].bufferState != CD_BS_LOADED) {
  729. CDLOGINFO(@"Denshion::CDSoundEngine - sound playback aborted because buffer %i is not loaded", soundId);
  730. }
  731. #endif
  732. return CD_MUTE;
  733. }
  734. int sourceIndex = [self _getSourceIndexForSourceGroup:sourceGroupId];//This method ensures sourceIndex is valid
  735. if (sourceIndex != CD_NO_SOURCE) {
  736. ALint state;
  737. ALuint source = _sources[sourceIndex].sourceId;
  738. ALuint buffer = _buffers[soundId].bufferId;
  739. alGetError();//Clear the error code
  740. alGetSourcei(source, AL_SOURCE_STATE, &state);
  741. if (state == AL_PLAYING) {
  742. alSourceStop(source);
  743. }
  744. alSourcei(source, AL_BUFFER, buffer);//Attach to sound
  745. alSourcef(source, AL_PITCH, pitch);//Set pitch
  746. alSourcei(source, AL_LOOPING, loop);//Set looping
  747. alSourcef(source, AL_GAIN, gain);//Set gain/volume
  748. float sourcePosAL[] = {pan, 0.0f, 0.0f};//Set position - just using left and right panning
  749. alSourcefv(source, AL_POSITION, sourcePosAL);
  750. alGetError();//Clear the error code
  751. alSourcePlay(source);
  752. if((lastErrorCode_ = alGetError()) == AL_NO_ERROR) {
  753. //Everything was okay
  754. _sources[sourceIndex].attachedBufferId = buffer;
  755. return source;
  756. } else {
  757. if (alcGetCurrentContext() == NULL) {
  758. CDLOGINFO(@"Denshion::CDSoundEngine - posting bad OpenAL context message");
  759. [[NSNotificationCenter defaultCenter] postNotificationName:kCDN_BadAlContext object:nil];
  760. }
  761. return CD_NO_SOURCE;
  762. }
  763. } else {
  764. return CD_NO_SOURCE;
  765. }
  766. }
  767. -(BOOL) _soundSourceAttachToBuffer:(CDSoundSource*) soundSource soundId:(int) soundId {
  768. //Attach the source to the buffer
  769. ALint state;
  770. ALuint source = soundSource->_sourceId;
  771. ALuint buffer = _buffers[soundId].bufferId;
  772. alGetSourcei(source, AL_SOURCE_STATE, &state);
  773. if (state == AL_PLAYING) {
  774. alSourceStop(source);
  775. }
  776. alGetError();//Clear the error code
  777. alSourcei(source, AL_BUFFER, buffer);//Attach to sound data
  778. if((lastErrorCode_ = alGetError()) == AL_NO_ERROR) {
  779. _sources[soundSource->_sourceIndex].attachedBufferId = buffer;
  780. //_sourceBufferAttachments[soundSource->_sourceIndex] = buffer;//Keep track of which
  781. soundSource->_soundId = soundId;
  782. return YES;
  783. } else {
  784. return NO;
  785. }
  786. }
  787. /**
  788. * Get a sound source for the specified sound in the specified source group
  789. */
  790. -(CDSoundSource *) soundSourceForSound:(int) soundId sourceGroupId:(int) sourceGroupId
  791. {
  792. if (!functioning_) {
  793. return nil;
  794. }
  795. //Check if a source is available
  796. int sourceIndex = [self _getSourceIndexForSourceGroup:sourceGroupId];
  797. if (sourceIndex != CD_NO_SOURCE) {
  798. CDSoundSource *result = [[CDSoundSource alloc] init:_sources[sourceIndex].sourceId sourceIndex:sourceIndex soundEngine:self];
  799. [self _lockSource:sourceIndex lock:YES];
  800. //Try to attach to the buffer
  801. if ([self _soundSourceAttachToBuffer:result soundId:soundId]) {
  802. //Set to a known state
  803. result.pitch = 1.0f;
  804. result.pan = 0.0f;
  805. result.gain = 1.0f;
  806. result.looping = NO;
  807. return [result autorelease];
  808. } else {
  809. //Release the sound source we just created, this will also unlock the source
  810. [result release];
  811. return nil;
  812. }
  813. } else {
  814. //No available source within that source group
  815. return nil;
  816. }
  817. }
  818. -(void) _soundSourcePreRelease:(CDSoundSource *) soundSource {
  819. CDLOGINFO(@"Denshion::CDSoundEngine _soundSourcePreRelease %i",soundSource->_sourceIndex);
  820. //Unlock the sound source's source
  821. [self _lockSource:soundSource->_sourceIndex lock:NO];
  822. }
  823. /**
  824. * Stop all sounds playing within a source group
  825. */
  826. - (void) stopSourceGroup:(int) sourceGroupId {
  827. if (!functioning_ || sourceGroupId >= _sourceGroupTotal || sourceGroupId < 0) {
  828. return;
  829. }
  830. int sourceCount = _sourceGroups[sourceGroupId].totalSources;
  831. for (int i=0; i < sourceCount; i++) {
  832. int sourceIndex = _sourceGroups[sourceGroupId].sourceStatuses[i] >> 1;
  833. alSourceStop(_sources[sourceIndex].sourceId);
  834. }
  835. alGetError();//Clear error in case we stopped any sounds that couldn't be stopped
  836. }
  837. /**
  838. * Stop a sound playing.
  839. * @param sourceId an OpenAL source identifier i.e. the return value of playSound
  840. */
  841. - (void)stopSound:(ALuint) sourceId {
  842. if (!functioning_) {
  843. return;
  844. }
  845. alSourceStop(sourceId);
  846. alGetError();//Clear error in case we stopped any sounds that couldn't be stopped
  847. }
  848. - (void) stopAllSounds {
  849. for (int i=0; i < sourceTotal_; i++) {
  850. alSourceStop(_sources[i].sourceId);
  851. }
  852. alGetError();//Clear error in case we stopped any sounds that couldn't be stopped
  853. }
  854. - (void) pauseSound:(ALuint) sourceId {
  855. if (!functioning_) {
  856. return;
  857. }
  858. alSourcePause(sourceId);
  859. alGetError();//Clear error in case we pause any sounds that couldn't be paused
  860. }
  861. - (void) pauseAllSounds {
  862. for (int i = 0; i < sourceTotal_; i++) {
  863. [self pauseSound:_sources[i].sourceId];
  864. }
  865. alGetError();//Clear error in case we stopped any sounds that couldn't be paused
  866. }
  867. - (void) resumeSound:(ALuint) soundId {
  868. if (!functioning_) {
  869. return;
  870. }
  871. // only resume a sound id that is paused
  872. ALint state;
  873. alGetSourcei(soundId, AL_SOURCE_STATE, &state);
  874. if (state != AL_PAUSED)
  875. {
  876. return;
  877. }
  878. alSourcePlay(soundId);
  879. alGetError();//Clear error in case we stopped any sounds that couldn't be resumed
  880. }
  881. - (void) resumeAllSounds {
  882. for (int i = 0; i < sourceTotal_; i++) {
  883. [self resumeSound:_sources[i].sourceId];
  884. }
  885. alGetError();//Clear error in case we stopped any sounds that couldn't be resumed
  886. }
  887. /**
  888. * Set a source group as non interruptible. Default is that source groups are interruptible.
  889. * Non interruptible means that if a request to play a sound is made for a source group and there are
  890. * no free sources available then the play request will be ignored and CD_NO_SOURCE will be returned.
  891. */
  892. - (void) setSourceGroupNonInterruptible:(int) sourceGroupId isNonInterruptible:(BOOL) isNonInterruptible {
  893. //Ensure source group id is valid to prevent memory corruption
  894. if (sourceGroupId < 0 || sourceGroupId >= _sourceGroupTotal) {
  895. CDLOG(@"Denshion::CDSoundEngine setSourceGroupNonInterruptible invalid source group id %i",sourceGroupId);
  896. return;
  897. }
  898. if (isNonInterruptible) {
  899. _sourceGroups[sourceGroupId].nonInterruptible = true;
  900. } else {
  901. _sourceGroups[sourceGroupId].nonInterruptible = false;
  902. }
  903. }
  904. /**
  905. * Set the mute property for a source group. If mute is turned on any sounds in that source group
  906. * will be stopped and further sounds in that source group will play. However, turning mute off
  907. * will not restart any sounds that were playing when mute was turned on. Also the mute setting
  908. * for the sound engine must be taken into account. If the sound engine is mute no sounds will play
  909. * no matter what the source group mute setting is.
  910. */
  911. - (void) setSourceGroupEnabled:(int) sourceGroupId enabled:(BOOL) enabled {
  912. //Ensure source group id is valid to prevent memory corruption
  913. if (sourceGroupId < 0 || sourceGroupId >= _sourceGroupTotal) {
  914. CDLOG(@"Denshion::CDSoundEngine setSourceGroupEnabled invalid source group id %i",sourceGroupId);
  915. return;
  916. }
  917. if (enabled) {
  918. _sourceGroups[sourceGroupId].enabled = true;
  919. [self stopSourceGroup:sourceGroupId];
  920. } else {
  921. _sourceGroups[sourceGroupId].enabled = false;
  922. }
  923. }
  924. /**
  925. * Return the mute property for the source group identified by sourceGroupId
  926. */
  927. - (BOOL) sourceGroupEnabled:(int) sourceGroupId {
  928. return _sourceGroups[sourceGroupId].enabled;
  929. }
  930. -(ALCcontext *) openALContext {
  931. return context;
  932. }
  933. - (void) _dumpSourceGroupsInfo {
  934. #ifdef CD_DEBUG
  935. CDLOGINFO(@"-------------- source Group Info --------------");
  936. for (int i=0; i < _sourceGroupTotal; i++) {
  937. CDLOGINFO(@"Group: %i start:%i total:%i",i,_sourceGroups[i].startIndex, _sourceGroups[i].totalSources);
  938. CDLOGINFO(@"----- mute:%i nonInterruptible:%i",_sourceGroups[i].enabled, _sourceGroups[i].nonInterruptible);
  939. CDLOGINFO(@"----- Source statuses ----");
  940. for (int j=0; j < _sourceGroups[i].totalSources; j++) {
  941. CDLOGINFO(@"Source status:%i index=%i locked=%i",j,_sourceGroups[i].sourceStatuses[j] >> 1, _sourceGroups[i].sourceStatuses[j] & 1);
  942. }
  943. }
  944. #endif
  945. }
  946. @end
  947. ///////////////////////////////////////////////////////////////////////////////////////
  948. @implementation CDSoundSource
  949. @synthesize lastError;
  950. //Macro for handling the al error code
  951. #define CDSOUNDSOURCE_UPDATE_LAST_ERROR (lastError = alGetError())
  952. #define CDSOUNDSOURCE_ERROR_HANDLER ( CDSOUNDSOURCE_UPDATE_LAST_ERROR == AL_NO_ERROR)
  953. -(id)init:(ALuint) theSourceId sourceIndex:(int) index soundEngine:(CDSoundEngine*) engine {
  954. if ((self = [super init])) {
  955. _sourceId = theSourceId;
  956. _engine = engine;
  957. _sourceIndex = index;
  958. enabled_ = YES;
  959. mute_ = NO;
  960. _preMuteGain = self.gain;
  961. }
  962. return self;
  963. }
  964. -(void) dealloc
  965. {
  966. CDLOGINFO(@"Denshion::CDSoundSource deallocated %i",self->_sourceIndex);
  967. //Notify sound engine we are about to release
  968. [_engine _soundSourcePreRelease:self];
  969. [super dealloc];
  970. }
  971. - (void) setPitch:(float) newPitchValue {
  972. alSourcef(_sourceId, AL_PITCH, newPitchValue);
  973. CDSOUNDSOURCE_UPDATE_LAST_ERROR;
  974. }
  975. - (void) setGain:(float) newGainValue {
  976. if (!mute_) {
  977. alSourcef(_sourceId, AL_GAIN, newGainValue);
  978. } else {
  979. _preMuteGain = newGainValue;
  980. }
  981. CDSOUNDSOURCE_UPDATE_LAST_ERROR;
  982. }
  983. - (void) setPan:(float) newPanValue {
  984. float sourcePosAL[] = {newPanValue, 0.0f, 0.0f};//Set position - just using left and right panning
  985. alSourcefv(_sourceId, AL_POSITION, sourcePosAL);
  986. CDSOUNDSOURCE_UPDATE_LAST_ERROR;
  987. }
  988. - (void) setLooping:(BOOL) newLoopingValue {
  989. alSourcei(_sourceId, AL_LOOPING, newLoopingValue);
  990. CDSOUNDSOURCE_UPDATE_LAST_ERROR;
  991. }
  992. - (BOOL) isPlaying {
  993. ALint state;
  994. alGetSourcei(_sourceId, AL_SOURCE_STATE, &state);
  995. CDSOUNDSOURCE_UPDATE_LAST_ERROR;
  996. return (state == AL_PLAYING);
  997. }
  998. - (float) pitch {
  999. ALfloat pitchVal;
  1000. alGetSourcef(_sourceId, AL_PITCH, &pitchVal);
  1001. CDSOUNDSOURCE_UPDATE_LAST_ERROR;
  1002. return pitchVal;
  1003. }
  1004. - (float) pan {
  1005. ALfloat sourcePosAL[] = {0.0f,0.0f,0.0f};
  1006. alGetSourcefv(_sourceId, AL_POSITION, sourcePosAL);
  1007. CDSOUNDSOURCE_UPDATE_LAST_ERROR;
  1008. return sourcePosAL[0];
  1009. }
  1010. - (float) gain {
  1011. if (!mute_) {
  1012. ALfloat val;
  1013. alGetSourcef(_sourceId, AL_GAIN, &val);
  1014. CDSOUNDSOURCE_UPDATE_LAST_ERROR;
  1015. return val;
  1016. } else {
  1017. return _preMuteGain;
  1018. }
  1019. }
  1020. - (BOOL) looping {
  1021. ALfloat val;
  1022. alGetSourcef(_sourceId, AL_LOOPING, &val);
  1023. CDSOUNDSOURCE_UPDATE_LAST_ERROR;
  1024. return val;
  1025. }
  1026. -(BOOL) stop {
  1027. alSourceStop(_sourceId);
  1028. return CDSOUNDSOURCE_ERROR_HANDLER;
  1029. }
  1030. -(BOOL) play {
  1031. if (enabled_) {
  1032. alSourcePlay(_sourceId);
  1033. CDSOUNDSOURCE_UPDATE_LAST_ERROR;
  1034. if (lastError != AL_NO_ERROR) {
  1035. if (alcGetCurrentContext() == NULL) {
  1036. CDLOGINFO(@"Denshion::CDSoundSource - posting bad OpenAL context message");
  1037. [[NSNotificationCenter defaultCenter] postNotificationName:kCDN_BadAlContext object:nil];
  1038. }
  1039. return NO;
  1040. } else {
  1041. return YES;
  1042. }
  1043. } else {
  1044. return NO;
  1045. }
  1046. }
  1047. -(BOOL) pause {
  1048. alSourcePause(_sourceId);
  1049. return CDSOUNDSOURCE_ERROR_HANDLER;
  1050. }
  1051. -(BOOL) rewind {
  1052. alSourceRewind(_sourceId);
  1053. return CDSOUNDSOURCE_ERROR_HANDLER;
  1054. }
  1055. -(void) setSoundId:(int) soundId {
  1056. [_engine _soundSourceAttachToBuffer:self soundId:soundId];
  1057. }
  1058. -(int) soundId {
  1059. return _soundId;
  1060. }
  1061. -(float) durationInSeconds {
  1062. return [_engine bufferDurationInSeconds:_soundId];
  1063. }
  1064. #pragma mark CDSoundSource AudioInterrupt protocol
  1065. - (BOOL) mute {
  1066. return mute_;
  1067. }
  1068. /**
  1069. * Setting mute silences all sounds but playing sounds continue to advance playback
  1070. */
  1071. - (void) setMute:(BOOL) newMuteValue {
  1072. if (newMuteValue == mute_) {
  1073. return;
  1074. }
  1075. if (newMuteValue) {
  1076. //Remember what the gain was
  1077. _preMuteGain = self.gain;
  1078. self.gain = 0.0f;
  1079. mute_ = newMuteValue;//Make sure this is done after setting the gain property as the setter behaves differently depending on mute value
  1080. } else {
  1081. //Restore gain to what it was before being muted
  1082. mute_ = newMuteValue;
  1083. self.gain = _preMuteGain;
  1084. }
  1085. }
  1086. - (BOOL) enabled {
  1087. return enabled_;
  1088. }
  1089. - (void) setEnabled:(BOOL)enabledValue
  1090. {
  1091. if (enabled_ == enabledValue) {
  1092. return;
  1093. }
  1094. enabled_ = enabledValue;
  1095. if (enabled_ == NO) {
  1096. [self stop];
  1097. }
  1098. }
  1099. @end
  1100. ////////////////////////////////////////////////////////////////////////////
  1101. #pragma mark -
  1102. #pragma mark CDAudioInterruptTargetGroup
  1103. @implementation CDAudioInterruptTargetGroup
  1104. -(id) init {
  1105. if ((self = [super init])) {
  1106. children_ = [[NSMutableArray alloc] initWithCapacity:32];
  1107. enabled_ = YES;
  1108. mute_ = NO;
  1109. }
  1110. return self;
  1111. }
  1112. -(void) addAudioInterruptTarget:(NSObject<CDAudioInterruptProtocol>*) interruptibleTarget {
  1113. //Synchronize child with group settings;
  1114. [interruptibleTarget setMute:mute_];
  1115. [interruptibleTarget setEnabled:enabled_];
  1116. [children_ addObject:interruptibleTarget];
  1117. }
  1118. -(void) removeAudioInterruptTarget:(NSObject<CDAudioInterruptProtocol>*) interruptibleTarget {
  1119. [children_ removeObjectIdenticalTo:interruptibleTarget];
  1120. }
  1121. - (BOOL) mute {
  1122. return mute_;
  1123. }
  1124. /**
  1125. * Setting mute silences all sounds but playing sounds continue to advance playback
  1126. */
  1127. - (void) setMute:(BOOL) newMuteValue {
  1128. if (newMuteValue == mute_) {
  1129. return;
  1130. }
  1131. for (NSObject<CDAudioInterruptProtocol>* target in children_) {
  1132. [target setMute:newMuteValue];
  1133. }
  1134. }
  1135. - (BOOL) enabled {
  1136. return enabled_;
  1137. }
  1138. - (void) setEnabled:(BOOL)enabledValue
  1139. {
  1140. if (enabledValue == enabled_) {
  1141. return;
  1142. }
  1143. for (NSObject<CDAudioInterruptProtocol>* target in children_) {
  1144. [target setEnabled:enabledValue];
  1145. }
  1146. }
  1147. @end
  1148. ////////////////////////////////////////////////////////////////////////////
  1149. #pragma mark -
  1150. #pragma mark CDAsynchBufferLoader
  1151. @implementation CDAsynchBufferLoader
  1152. -(id) init:(NSArray *)loadRequests soundEngine:(CDSoundEngine *) theSoundEngine {
  1153. if ((self = [super init])) {
  1154. _loadRequests = loadRequests;
  1155. [_loadRequests retain];
  1156. _soundEngine = theSoundEngine;
  1157. [_soundEngine retain];
  1158. }
  1159. return self;
  1160. }
  1161. -(void) main {
  1162. CDLOGINFO(@"Denshion::CDAsynchBufferLoader - loading buffers");
  1163. [super main];
  1164. _soundEngine.asynchLoadProgress = 0.0f;
  1165. if ([_loadRequests count] > 0) {
  1166. float increment = 1.0f / [_loadRequests count];
  1167. //Iterate over load request and load
  1168. for (CDBufferLoadRequest *loadRequest in _loadRequests) {
  1169. [_soundEngine loadBuffer:loadRequest.soundId filePath:loadRequest.filePath];
  1170. _soundEngine.asynchLoadProgress += increment;
  1171. }
  1172. }
  1173. //Completed
  1174. _soundEngine.asynchLoadProgress = 1.0f;
  1175. [[NSNotificationCenter defaultCenter] postNotificationName:kCDN_AsynchLoadComplete object:nil];
  1176. }
  1177. -(void) dealloc {
  1178. [_loadRequests release];
  1179. [_soundEngine release];
  1180. [super dealloc];
  1181. }
  1182. @end
  1183. ///////////////////////////////////////////////////////////////////////////////////////
  1184. #pragma mark -
  1185. #pragma mark CDBufferLoadRequest
  1186. @implementation CDBufferLoadRequest
  1187. @synthesize filePath, soundId;
  1188. -(id) init:(int) theSoundId filePath:(const NSString *) theFilePath {
  1189. if ((self = [super init])) {
  1190. soundId = theSoundId;
  1191. filePath = [theFilePath copy];
  1192. }
  1193. return self;
  1194. }
  1195. -(void) dealloc {
  1196. [filePath release];
  1197. [super dealloc];
  1198. }
  1199. @end
  1200. ///////////////////////////////////////////////////////////////////////////////////////
  1201. #pragma mark -
  1202. #pragma mark CDFloatInterpolator
  1203. @implementation CDFloatInterpolator
  1204. @synthesize start,end,interpolationType;
  1205. -(float) interpolate:(float) t {
  1206. if (t < 1.0f) {
  1207. switch (interpolationType) {
  1208. case kIT_Linear:
  1209. //Linear interpolation
  1210. return ((end - start) * t) + start;
  1211. case kIT_SCurve:
  1212. //Cubic s curve t^2 * (3 - 2t)
  1213. return ((float)(t * t * (3.0 - (2.0 * t))) * (end - start)) + start;
  1214. case kIT_Exponential:
  1215. //Formulas taken from EaseAction
  1216. if (end > start) {
  1217. //Fade in
  1218. float logDelta = (t==0) ? 0 : powf(2, 10 * (t/1 - 1)) - 1 * 0.001f;
  1219. return ((end - start) * logDelta) + start;
  1220. } else {
  1221. //Fade Out
  1222. float logDelta = (-powf(2, -10 * t/1) + 1);
  1223. return ((end - start) * logDelta) + start;
  1224. }
  1225. default:
  1226. return 0.0f;
  1227. }
  1228. } else {
  1229. return end;
  1230. }
  1231. }
  1232. -(id) init:(tCDInterpolationType) type startVal:(float) startVal endVal:(float) endVal {
  1233. if ((self = [super init])) {
  1234. start = startVal;
  1235. end = endVal;
  1236. interpolationType = type;
  1237. }
  1238. return self;
  1239. }
  1240. @end
  1241. ///////////////////////////////////////////////////////////////////////////////////////
  1242. #pragma mark -
  1243. #pragma mark CDPropertyModifier
  1244. @implementation CDPropertyModifier
  1245. @synthesize stopTargetWhenComplete;
  1246. -(id) init:(id) theTarget interpolationType:(tCDInterpolationType) type startVal:(float) startVal endVal:(float) endVal {
  1247. if ((self = [super init])) {
  1248. if (target) {
  1249. //Release the previous target if there is one
  1250. [target release];
  1251. }
  1252. target = theTarget;
  1253. #if CD_DEBUG
  1254. //Check target is of the required type
  1255. if (![theTarget isMemberOfClass:[self _allowableType]] ) {
  1256. CDLOG(@"Denshion::CDPropertyModifier target is not of type %@",[self _allowableType]);
  1257. NSAssert([theTarget isKindOfClass:[CDSoundEngine class]], @"CDPropertyModifier target not of required type");
  1258. }
  1259. #endif
  1260. [target retain];
  1261. startValue = startVal;
  1262. endValue = endVal;
  1263. if (interpolator) {
  1264. //Release previous interpolator if there is one
  1265. [interpolator release];
  1266. }
  1267. interpolator = [[CDFloatInterpolator alloc] init:type startVal:startVal endVal:endVal];
  1268. stopTargetWhenComplete = NO;
  1269. }
  1270. return self;
  1271. }
  1272. -(void) dealloc {
  1273. CDLOGINFO(@"Denshion::CDPropertyModifier deallocated %@",self);
  1274. [target release];
  1275. [interpolator release];
  1276. [super dealloc];
  1277. }
  1278. -(void) modify:(float) t {
  1279. if (t < 1.0) {
  1280. [self _s