Line data Source code
1 0 : /* 2 : 3 : OOSoundSourcePool.m 4 : 5 : 6 : Copyright (C) 2008-2013 Jens Ayton 7 : 8 : Permission is hereby granted, free of charge, to any person obtaining a copy 9 : of this software and associated documentation files (the "Software"), to deal 10 : in the Software without restriction, including without limitation the rights 11 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 : copies of the Software, and to permit persons to whom the Software is 13 : furnished to do so, subject to the following conditions: 14 : 15 : The above copyright notice and this permission notice shall be included in all 16 : copies or substantial portions of the Software. 17 : 18 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 : SOFTWARE. 25 : 26 : */ 27 : 28 : #import "OOSoundSourcePool.h" 29 : #import "OOSound.h" 30 : #import "Universe.h" 31 : 32 : 33 0 : enum 34 : { 35 : kNoSlot = UINT8_MAX 36 : }; 37 : 38 : 39 0 : typedef struct OOSoundSourcePoolElement 40 : { 41 0 : OOSoundSource *source; 42 0 : OOTimeAbsolute expiryTime; 43 0 : float priority; 44 0 : } PoolElement; 45 : 46 : 47 : @interface OOSoundSourcePool (Private) 48 : 49 0 : - (uint8_t) selectSlotForPriority:(float)priority; 50 : 51 : @end 52 : 53 : 54 : @implementation OOSoundSourcePool 55 : 56 : + (instancetype) poolWithCount:(uint8_t)count minRepeatTime:(OOTimeDelta)minRepeat 57 : { 58 : return [[[self alloc] initWithCount:count minRepeatTime:minRepeat] autorelease]; 59 : } 60 : 61 : 62 : - (id) initWithCount:(uint8_t)count minRepeatTime:(OOTimeDelta)minRepeat 63 : { 64 : if ((self = [super init])) 65 : { 66 : // Sanity-check count 67 : if (count == 0) count = 1; 68 : if (count == kNoSlot) --count; 69 : _count = count; 70 : _reserved = kNoSlot; 71 : 72 : if (minRepeat < 0.0) minRepeat = 0.0; 73 : _minRepeat = minRepeat; 74 : 75 : // Create source pool 76 : _sources = calloc(sizeof(PoolElement), count); 77 : if (_sources == NULL) 78 : { 79 : [self release]; 80 : self = nil; 81 : } 82 : } 83 : return self; 84 : } 85 : 86 : 87 0 : - (void) dealloc 88 : { 89 : uint8_t i; 90 : 91 : for (i = 0; i != _count; i++) 92 : { 93 : [_sources[i].source release]; 94 : } 95 : free(_sources); 96 : 97 : [_lastKey release]; 98 : 99 : [super dealloc]; 100 : } 101 : 102 : 103 : - (void) playSoundWithKey:(NSString *)key 104 : priority:(float)priority 105 : expiryTime:(OOTimeDelta)expiryTime 106 : overlap:(BOOL)overlap 107 : position:(Vector)position 108 : { 109 : uint8_t slot; 110 : OOTimeAbsolute now, absExpiryTime; 111 : PoolElement *element = NULL; 112 : OOSound *sound = NULL; 113 : 114 : // Convert expiry time to absolute 115 : now = [UNIVERSE getTime]; 116 : absExpiryTime = expiryTime + now; 117 : 118 : // Avoid repeats if required 119 : if (now < _nextRepeat && [key isEqualToString:_lastKey]) return; 120 : if (!overlap && _reserved != kNoSlot && [_sources[_reserved].source isPlaying]) return; 121 : 122 : // Look for a slot in the source list to use 123 : slot = [self selectSlotForPriority:priority]; 124 : if (slot == kNoSlot) return; 125 : element = &_sources[slot]; 126 : 127 : // Load sound 128 : sound = [OOSound soundWithCustomSoundKey:key]; 129 : if (sound == nil) return; 130 : 131 : // Stop playing sound or set up sound source as appropriate 132 : if (element->source != nil) [element->source stop]; 133 : else 134 : { 135 : element->source = [[OOSoundSource alloc] init]; 136 : if (element->source == nil) return; 137 : } 138 : if (slot == _reserved) _reserved = kNoSlot; // _reserved has finished playing! 139 : if (!overlap) _reserved = slot; 140 : 141 : // Play and store metadata 142 : [element->source setPosition:position]; 143 : [element->source playSound:sound]; 144 : element->expiryTime = absExpiryTime; 145 : element->priority = priority; 146 : if (_minRepeat > 0.0) 147 : { 148 : _nextRepeat = now + _minRepeat; 149 : [_lastKey release]; 150 : _lastKey = [key copy]; 151 : } 152 : 153 : // Set staring search location for next slot lookup 154 : _latest = slot; 155 : } 156 : 157 : 158 : - (void) playSoundWithKey:(NSString *)key 159 : priority:(float)priority 160 : expiryTime:(OOTimeDelta)expiryTime 161 : { 162 : [self playSoundWithKey:key 163 : priority:priority 164 : expiryTime:expiryTime 165 : overlap:YES 166 : position:kZeroVector]; 167 : } 168 : 169 : 170 : - (void) playSoundWithKey:(NSString *)key 171 : priority:(float)priority 172 : position:(Vector)position 173 : { 174 : [self playSoundWithKey:key 175 : priority:priority 176 : expiryTime:0.5 + randf() * 0.1 177 : overlap:YES 178 : position:position]; 179 : } 180 : 181 : 182 : - (void) playSoundWithKey:(NSString *)key 183 : priority:(float)priority 184 : { 185 : [self playSoundWithKey:key 186 : priority:priority 187 : expiryTime:0.5 + randf() * 0.1]; 188 : } 189 : 190 : 191 : - (void) playSoundWithKey:(NSString *)key 192 : { 193 : [self playSoundWithKey:key priority:1.0]; 194 : } 195 : 196 : 197 : - (void) playSoundWithKey:(NSString *)key position:(Vector)position 198 : { 199 : [self playSoundWithKey:key priority:1.0 position:position]; 200 : } 201 : 202 : 203 : - (void) playSoundWithKey:(NSString *)key overlap:(BOOL)overlap 204 : { 205 : [self playSoundWithKey:key 206 : priority:1.0 207 : expiryTime:0.5 208 : overlap:overlap 209 : position:kZeroVector]; 210 : } 211 : 212 : 213 : - (void) playSoundWithKey:(NSString *)key overlap:(BOOL)overlap position:(Vector)position 214 : { 215 : [self playSoundWithKey:key 216 : priority:1.0 217 : expiryTime:0.5 218 : overlap:overlap 219 : position:position]; 220 : } 221 : 222 : 223 : @end 224 : 225 : 226 : @implementation OOSoundSourcePool (Private) 227 : 228 : - (uint8_t) selectSlotForPriority:(float)priority 229 : { 230 : uint8_t curr, count, expiredLower = kNoSlot, unexpiredLower = kNoSlot, expiredEqual = kNoSlot; 231 : PoolElement *element = NULL; 232 : OOTimeAbsolute now = [UNIVERSE getTime]; 233 : 234 0 : #define NEXT(x) (((x) + 1) % _count) 235 : 236 : curr = _latest; 237 : count = _count; 238 : do 239 : { 240 : curr = NEXT(curr); 241 : element = &_sources[curr]; 242 : 243 : if (element->source == nil || ![element->source isPlaying]) return curr; // Best type of slot: empty 244 : else if (element->priority < priority) 245 : { 246 : if (element->expiryTime <= now) expiredLower = curr; // Second-best type: expired lower-priority 247 : else if (curr != _reserved) unexpiredLower = curr; // Third-best type: unexpired lower-priority 248 : } 249 : else if (element->priority == priority && element->expiryTime <= now) 250 : { 251 : expiredEqual = curr; // Fourth-best type: expired equal-priority. 252 : } 253 : } while (--count); 254 : 255 : if (expiredLower != kNoSlot) return expiredLower; 256 : if (unexpiredLower != kNoSlot) return unexpiredLower; 257 : return expiredEqual; // Will be kNoSlot if none found 258 : } 259 : 260 : @end