1 module awebview.sound; 2 3 4 import core.atomic; 5 import std.internal.scopebuffer; 6 import std.algorithm; 7 import std.exception; 8 import std.functional; 9 import std.string; 10 import std.typecons; 11 12 import derelict.sdl2.sdl; 13 import derelict.sdl2.mixer; 14 15 import awebview.gui.html; 16 17 18 private 19 struct SoundChunkImpl 20 { 21 void increment() 22 { 23 if(_hashId == 0) return; 24 SyncSoundChunk.incChunk(_hashId); 25 } 26 27 28 void decrement() 29 { 30 if(_hashId == 0) return; 31 32 if(SyncSoundChunk.decChunk(_hashId)){ 33 SDLMixer.callCLib!Mix_FreeChunk(_chunk); 34 _chunk = null; 35 _hashId = 0; 36 } 37 } 38 39 40 static 41 SoundChunkImpl fromFile(const(char)[] filename) 42 { 43 char[1024] tmpbuf; 44 auto buf = ScopeBuffer!char(tmpbuf); 45 scope(exit) buf.free(); 46 47 buf.put(filename); 48 buf.put('\0'); 49 50 auto chunk = SDLMixer.callCLib!Mix_LoadWAV(buf[].ptr); 51 return fromSDLMixChunk(chunk); 52 } 53 54 55 static 56 SoundChunkImpl fromMemory(const(void)[] buf) 57 { 58 auto rwobj = SDLMixer.callCLib!SDL_RWFromConstMem(buf.ptr, buf.length); 59 auto chunk = SDLMixer.callCLib!Mix_LoadWAV_RW(rwobj, 1); 60 return fromSDLMixChunk(chunk); 61 } 62 63 64 static 65 auto fromFileOnMemoryTask(const(char)[] filename) 66 { 67 import std.parallelism; 68 return task!(a => fromMemory(std.file.read(a)))(filename); 69 } 70 71 72 static private 73 SoundChunkImpl fromSDLMixChunk(Mix_Chunk* chunk) 74 { 75 auto id = SyncSoundChunk.getNextCount; 76 SyncSoundChunk.addChunk(chunk, id); 77 return SoundChunkImpl(id, chunk); 78 } 79 80 81 Mix_Chunk* handle() @property { return _chunk; } 82 83 84 private: 85 size_t _hashId; 86 Mix_Chunk* _chunk; 87 88 static: 89 static final synchronized class SyncSoundChunk 90 { 91 static: 92 struct ChunkField { Mix_Chunk* chunk; size_t refCount; } 93 94 size_t getNextCount() @property 95 { 96 ++_cnt; 97 return _cnt; 98 } 99 100 101 void addChunk(Mix_Chunk* chunk, size_t id) 102 { 103 removeNull(); 104 _hash[id] = ChunkField(chunk, 1); 105 } 106 107 108 void incChunk(size_t id) 109 { 110 ++_hash[id].refCount; 111 } 112 113 114 bool decChunk(size_t id) 115 { 116 auto p = id in _hash; 117 --p.refCount; 118 if(p.refCount == 0){ 119 p.chunk = null; 120 return true; 121 } 122 else 123 return false; 124 } 125 126 127 void removeNull() 128 { 129 size_t[] rmKeys; 130 foreach(k, ref e; _hash) 131 if(e.chunk is null) 132 rmKeys ~= k; 133 134 foreach(k; rmKeys) 135 _hash.remove(k); 136 } 137 138 139 __gshared size_t _cnt; 140 __gshared ChunkField[size_t] _hash; 141 } 142 } 143 144 145 private 146 struct SoundChunkShared 147 { 148 this(this) shared { (*cast(SoundChunkImpl*)&_impl).increment(); } 149 150 ~this() shared 151 { 152 (*cast(SoundChunkImpl*)&_impl).decrement(); 153 } 154 155 156 Mix_Chunk* handle() @property { return _impl._chunk; } 157 158 159 private: 160 SoundChunkImpl _impl; 161 } 162 163 164 struct SoundChunk 165 { 166 this(this) { _impl.increment(); } 167 ~this() { _impl.decrement(); } 168 169 Mix_Chunk* handle() @property { return _impl._chunk; } 170 171 172 static 173 SoundChunk fromFile(const(char)[] filename) 174 { 175 return SoundChunk(SoundChunkImpl.fromFile(filename)); 176 } 177 178 179 static 180 SoundChunk fromMemory(const(void)[] buf) 181 { 182 return SoundChunk(SoundChunkImpl.fromMemory(buf)); 183 } 184 185 186 static 187 auto fromFileOnMemoryTask(const(char)[] filename) 188 { 189 import std.parallelism; 190 return task!(a => fromMemory(std.file.read(a)))(filename); 191 } 192 193 194 static private 195 SoundChunk fromSDLMixChunk(Mix_Chunk* chunk) 196 { 197 return SoundChunk(SoundChunkImpl.fromSDLMixChunk(chunk)); 198 } 199 200 201 private: 202 SoundChunkImpl _impl; 203 } 204 205 206 final synchronized class SDLMixer 207 { 208 static: 209 auto ref callCLib(alias func, T...)(auto ref T args) 210 { 211 return func(forward!args); 212 } 213 } 214 215 216 private synchronized class SoundChannelImpl 217 { 218 this(shared(SoundManager) sm) 219 { 220 _sm = sm; 221 } 222 223 224 void onAttachToChannel(int id) 225 { 226 _id = id; 227 } 228 229 230 void playInf(SoundChunk chunk) 231 { 232 play(chunk, -1); 233 } 234 235 236 void play(SoundChunk chunk, int loop = 0) 237 { 238 *(cast(SoundChunkImpl*)&_chunk._impl) = chunk._impl; 239 enforce(SDLMixer.callCLib!Mix_PlayChannel(_id, chunk.handle, loop) == _id, "Error on SDL_Mix"); 240 } 241 242 243 void onFinished() 244 { 245 *(cast(SoundChunkImpl*)&_chunk._impl) = SoundChunk.init._impl; 246 } 247 248 249 void onDestroy() 250 { 251 _sm.detachChannel(_id); 252 } 253 254 255 private: 256 uint _id; 257 SoundChunkShared _chunk; 258 shared(SoundManager) _sm; 259 } 260 261 262 class SoundChannel : IDOnlyElement 263 { 264 this(string id, shared(SoundManager) sm) 265 { 266 this(id, sm, new shared SoundChannelImpl(sm)); 267 } 268 269 270 this(string id, shared(SoundManager) sm, shared(SoundChannelImpl) chImpl) 271 { 272 super(id); 273 impl = chImpl; 274 } 275 276 277 alias impl this; 278 shared(SoundChannelImpl) impl; 279 280 281 void onAttachToChannel(int id) 282 { 283 impl.onAttachToChannel(id); 284 } 285 286 287 override 288 void onDestroy() 289 { 290 impl.onDestroy(); 291 } 292 } 293 294 295 synchronized final class SoundManager 296 { 297 SoundChannel newChannel(string id) @property 298 { 299 return attachChannel(new SoundChannel(id, this)); 300 } 301 302 303 SoundChannel attachChannel(SoundChannel ch) 304 { 305 foreach(i, ref e; _chlist){ 306 if(e is null){ 307 e = ch.impl; 308 ch.onAttachToChannel(i); 309 return ch; 310 } 311 } 312 313 immutable len = _chlist.length; 314 _chlist.length = len + 1; 315 _chlist.length = Mix_AllocateChannels(_chlist.capacity.to!int); 316 317 _chlist[len] = ch.impl; 318 ch.onAttachToChannel(len); 319 return ch; 320 } 321 322 323 void detachChannel(uint chId) 324 { 325 _chlist[chId] = null; 326 } 327 328 329 void onChannelFinished(uint chId) 330 { 331 _chlist[chId].onFinished(); 332 } 333 334 335 private: 336 shared(SoundChannelImpl)[] _chlist; 337 }