1 module awebview.sound; 2 3 4 import core.atomic; 5 import std.internal.scopebuffer; 6 import std.algorithm; 7 import std.conv; 8 import std.exception; 9 import std.functional; 10 import std..string; 11 import std.typecons; 12 13 import derelict.sdl2.sdl; 14 import derelict.sdl2.mixer; 15 16 //import phobosx.signal; 17 18 import awebview.gui.html; 19 20 21 enum bool isEffector(E) = is(typeof((E e){ 22 uint ch; 23 void[] stream; 24 e.applyEffect(ch, stream); 25 e.done(ch); 26 })); 27 28 29 private 30 struct SoundChunkImpl 31 { 32 void increment() 33 { 34 if(_hashId == 0) return; 35 SyncSoundChunk.incChunk(_hashId); 36 } 37 38 39 void decrement() 40 { 41 if(_hashId == 0) return; 42 43 if(SyncSoundChunk.decChunk(_hashId)){ 44 SDLMixer.callCLib!Mix_FreeChunk(_chunk); 45 _chunk = null; 46 _hashId = 0; 47 } 48 } 49 50 51 static 52 SoundChunkImpl fromFile(const(char)[] filename) 53 { 54 char[1024] tmpbuf; 55 auto buf = ScopeBuffer!char(tmpbuf); 56 scope(exit) buf.free(); 57 58 buf.put(filename); 59 buf.put('\0'); 60 61 auto chunk = SDLMixer.callCLib!Mix_LoadWAV(buf[].ptr); 62 return fromSDLMixChunk(chunk); 63 } 64 65 66 static 67 SoundChunkImpl fromMemory(const(void)[] buf) 68 { 69 auto rwobj = SDLMixer.callCLib!SDL_RWFromConstMem(buf.ptr, buf.length.to!uint); 70 auto chunk = SDLMixer.callCLib!Mix_LoadWAV_RW(rwobj, 1); 71 return fromSDLMixChunk(chunk); 72 } 73 74 75 static 76 auto fromFileOnMemoryTask(const(char)[] filename) 77 { 78 import std.parallelism; 79 return task!(a => fromMemory(std.file.read(a)))(filename); 80 } 81 82 83 static private 84 SoundChunkImpl fromSDLMixChunk(Mix_Chunk* chunk) 85 { 86 auto id = SyncSoundChunk.getNextCount; 87 SyncSoundChunk.addChunk(chunk, id); 88 return SoundChunkImpl(id, chunk); 89 } 90 91 92 Mix_Chunk* handle() @property { return _chunk; } 93 94 95 private: 96 size_t _hashId; 97 Mix_Chunk* _chunk; 98 99 static: 100 static final synchronized class SyncSoundChunk 101 { 102 static: 103 struct ChunkField { Mix_Chunk* chunk; size_t refCount; } 104 105 size_t getNextCount() @property 106 { 107 ++_cnt; 108 return _cnt; 109 } 110 111 112 void addChunk(Mix_Chunk* chunk, size_t id) 113 { 114 removeNull(); 115 _hash[id] = ChunkField(chunk, 1); 116 } 117 118 119 void incChunk(size_t id) 120 { 121 ++_hash[id].refCount; 122 } 123 124 125 bool decChunk(size_t id) 126 { 127 auto p = id in _hash; 128 --p.refCount; 129 if(p.refCount == 0){ 130 p.chunk = null; 131 return true; 132 } 133 else 134 return false; 135 } 136 137 138 void removeNull() 139 { 140 size_t[] rmKeys; 141 foreach(k, ref e; _hash) 142 if(e.chunk is null) 143 rmKeys ~= k; 144 145 foreach(k; rmKeys) 146 _hash.remove(k); 147 } 148 149 150 __gshared size_t _cnt; 151 __gshared ChunkField[size_t] _hash; 152 } 153 } 154 155 156 private 157 struct SoundChunkShared 158 { 159 this(this) { (*cast(SoundChunkImpl*)&_impl).increment(); } 160 161 ~this() 162 { 163 (*cast(SoundChunkImpl*)&_impl).decrement(); 164 } 165 166 167 Mix_Chunk* handle() @property { return _impl._chunk; } 168 169 170 private: 171 SoundChunkImpl _impl; 172 } 173 174 175 struct SoundChunk 176 { 177 this(this) { _impl.increment(); } 178 ~this() { _impl.decrement(); } 179 180 Mix_Chunk* handle() @property { return _impl._chunk; } 181 182 183 static 184 SoundChunk fromFile(const(char)[] filename) 185 { 186 return SoundChunk(SoundChunkImpl.fromFile(filename)); 187 } 188 189 190 static 191 SoundChunk fromMemory(const(void)[] buf) 192 { 193 return SoundChunk(SoundChunkImpl.fromMemory(buf)); 194 } 195 196 197 static 198 auto fromFileOnMemoryTask(const(char)[] filename) 199 { 200 import std.parallelism; 201 return task!(a => fromMemory(std.file.read(a)))(filename); 202 } 203 204 205 static private 206 SoundChunk fromSDLMixChunk(Mix_Chunk* chunk) 207 { 208 return SoundChunk(SoundChunkImpl.fromSDLMixChunk(chunk)); 209 } 210 211 212 private: 213 SoundChunkImpl _impl; 214 } 215 216 217 final synchronized class SDLMixer 218 { 219 static: 220 auto ref callCLib(alias func, T...)(auto ref T args) 221 { 222 return func(forward!args); 223 } 224 } 225 226 227 private synchronized final class SoundChannelImpl 228 { 229 this(shared(SoundManager) sm) 230 { 231 _sm = sm; 232 } 233 234 235 uint chId() @property 236 { 237 return _id; 238 } 239 240 241 void onAttachToChannel(int id) 242 { 243 _id = id; 244 SDLMixer.callCLib!Mix_ChannelFinished(&channel_finished); 245 } 246 247 248 void playInf(SoundChunk chunk) 249 { 250 play(chunk, -1); 251 } 252 253 254 void play(SoundChunk chunk, int loop = 0) 255 { 256 *(cast(SoundChunkImpl*)&_chunk._impl) = chunk._impl; 257 enforce(SDLMixer.callCLib!Mix_PlayChannel(_id, chunk.handle, loop) == _id, "Error on SDL_Mix"); 258 } 259 260 261 void playTimeout(SoundChunk chunk, int loop, Duration dur) 262 { 263 uint msecs = dur.total!"msecs".to!uint; 264 enforce(SDLMixer.callCLib!Mix_PlayChannelTimed(_id, chunk.handle, loop, msecs) == _id); 265 } 266 267 268 void playFadeIn(SoundChunk chunk, int loop, Duration dur) 269 { 270 uint msecs = dur.total!"msecs".to!uint; 271 enforce(SDLMixer.callCLib!Mix_FadeInChannel(_id, chunk.handle, loop, msecs) == _id); 272 } 273 274 275 void playFadeInChannelTimed(SoundChunk chunk, int loop, Duration fadeInDur, Duration loopDur) 276 { 277 uint msFadeIn = fadeInDur.total!"msecs".to!uint; 278 uint msLoop = loopDur.total!"msecs".to!uint; 279 enforce(SDLMixer.callCLib!Mix_FadeInChannelTimed(_id, chunk.handle, loop, msFadeIn, msLoop)); 280 } 281 282 283 //mixin(signal!()("onFinishedCB")); 284 285 286 void onFinished() 287 { 288 *(cast(SoundChunkImpl*)&_chunk._impl) = SoundChunk.init._impl; 289 //_onFinishedCB.emit(); 290 } 291 292 293 void detachFromSM() 294 { 295 _sm.detachChannel(_id); 296 _id = 0; 297 } 298 299 300 void onDestroy() 301 { 302 _sm.detachChannel(_id); 303 _id = 0; 304 *(cast(SoundChunkImpl*)&_chunk._impl) = SoundChunk.init._impl; 305 _effector = null; 306 } 307 308 309 void effector(E)(E eff) 310 if(isEffector!E) 311 { 312 static if(is(E == class) || is(E == interface)) 313 { 314 static struct Wrapper 315 { 316 alias instance this; 317 E instance; 318 } 319 320 this.effector = Wrapper(eff); 321 } 322 else 323 { 324 E* p = new E; 325 *p = eff; 326 _effector = cast(shared(E)*)p; 327 328 SDLMixer.callCLib!Mix_RegisterEffect(_id, &(effectFunc!E), &(effectDone!E), p); 329 } 330 } 331 332 333 private: 334 uint _id; 335 SoundChunkShared _chunk; 336 shared(SoundManager) _sm; 337 shared(void*) _effector; 338 339 340 static 341 extern(C) void effectFunc(E)(int chan, void* stream, int len, void* udata) 342 { 343 try{ 344 E* p = cast(E*)udata; 345 p.applyEffect(chan, stream[0 .. len]); 346 }catch(Exception ex){} 347 } 348 349 350 static 351 extern(C) void effectDone(E)(int chan, void* udata) 352 { 353 try{ 354 E* p = cast(E*)udata; 355 p.done(chan); 356 }catch(Exception ex){} 357 } 358 } 359 360 361 final class SoundChannel : IDOnlyElement 362 { 363 this(string id, shared(SoundManager) sm) 364 { 365 this(id, sm, new shared SoundChannelImpl(sm)); 366 } 367 368 369 this(string id, shared(SoundManager) sm, shared(SoundChannelImpl) chImpl) 370 { 371 super(id); 372 impl = chImpl; 373 } 374 375 376 alias impl this; 377 shared(SoundChannelImpl) impl; 378 379 380 void onAttachToChannel(int id) 381 { 382 impl.onAttachToChannel(id); 383 } 384 385 386 void detachFromSM() 387 { 388 impl.detachFromSM(); 389 } 390 391 392 override 393 void onDetach() 394 { 395 impl.detachFromSM(); 396 super.onDetach(); 397 } 398 399 400 override 401 void onDestroy() 402 { 403 impl.onDestroy(); 404 super.onDestroy(); 405 } 406 407 408 void effector(E)(E effector) 409 if(isEffector!E) 410 { 411 impl.effector(effector); 412 } 413 } 414 415 416 417 synchronized final class SoundManager 418 { 419 SoundChannel newChannel(string id) @property 420 { 421 return attachChannel(new SoundChannel(id, this)); 422 } 423 424 425 SoundChannel attachChannel(SoundChannel ch) 426 { 427 foreach(uint i, ref e; _chlist){ 428 if(e is null){ 429 e = ch.impl; 430 ch.onAttachToChannel(i); 431 return ch; 432 } 433 } 434 435 immutable len = _chlist.length.to!uint; 436 _chlist.length = len + 1; 437 _chlist.length = Mix_AllocateChannels(_chlist.capacity.to!int); 438 439 _chlist[len] = ch.impl; 440 ch.onAttachToChannel(len); 441 return ch; 442 } 443 444 445 private void detachChannel(uint chId) 446 { 447 _chlist[chId] = null; 448 } 449 450 451 void detachChannel(SoundChannel ch) 452 { 453 detachChannel(ch.chId); 454 ch.onDetach(); 455 } 456 457 458 void onChannelFinished(uint chId) 459 { 460 if(_chlist.length < chId && _chlist[chId] !is null) 461 _chlist[chId].onFinished(); 462 } 463 464 465 static 466 shared(SoundManager) instance() @property 467 { 468 if(_instance) 469 return _instance; 470 else{ 471 _instance = new shared SoundManager(); 472 return _instance; 473 } 474 } 475 476 477 private: 478 shared(SoundChannelImpl)[] _chlist; 479 480 static: 481 shared SoundManager _instance; 482 } 483 484 485 extern(C) void channel_finished(int ch) 486 { 487 if(SoundManager.instance !is null) 488 SoundManager.instance.onChannelFinished(ch); 489 }