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 }