CocosDenshion.m 52 KB

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