CocosDenshion.m 52 KB

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