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 }