egedit: do not save cursor movement in undo -- this is my stupid habit, and it comple...
[iv.d.git] / follin / engine.d
blobf5942dafa8645f3044615e704b11ca0c359c4bcf
1 /* Invisible Vector Library
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module iv.follin.engine /*is aliced*/;
19 version = follin_use_spinrw;
20 //version = follin_prefer_alsa_plug;
21 //version = follin_threads_debug;
22 //version = follin_wait_debug;
23 //version = follin_debug_resampler_type;
25 import iv.alice;
26 import iv.follin.exception;
29 // ////////////////////////////////////////////////////////////////////////// //
30 // be evil!
32 import core.sys.posix.signal : siginfo_t, sigaction_t;
33 private extern(C) void handleSignalZZ (int signum, siginfo_t* info, void* contextPtr) nothrow {
34 import core.stdc.stdio : fprintf, stderr;
35 import core.stdc.stdlib : abort;
36 fprintf(stderr, "\bFUUUUUUU....\n");
37 abort();
39 private __gshared sigaction_t old_sigaction;
41 shared static this () {
42 //import etc.linux.memoryerror;
43 //registerMemoryErrorHandler();
44 import core.sys.posix.signal;
45 sigaction_t action;
46 action.sa_sigaction = &handleSignalZZ;
47 action.sa_flags = SA_SIGINFO;
48 auto oldptr = &old_sigaction;
49 sigaction(SIGSEGV, &action, oldptr);
54 // ////////////////////////////////////////////////////////////////////////// //
55 version(X86) {
56 version(D_PIC) {} else {
57 version = follin_use_sse;
58 version = follin_use_sse2;
62 import iv.follin.utils;
65 // ////////////////////////////////////////////////////////////////////////// //
66 // throws FollinException on error
67 // starts paused
68 void tflInit () {
69 version(follin_prefer_alsa_plug) {
70 static immutable string[2] devnames = ["plug:default", "default"];
71 } else {
72 static immutable string[2] devnames = ["default", "plug:default"];
74 if (!cas(&initialized, false, true)) throw new FollinException("double initialization");
75 foreach (string name; devnames) {
76 try {
77 sndInit(name.ptr, samplerate);
78 sndEngineInit();
79 atomicStore(sndPaused, false);
80 atomicStore(sndWantShutdown, false);
81 atomicStore(sndSafeToShutdown0, false);
82 atomicStore(sndSafeToShutdown1, false);
83 //{ import core.stdc.stdio; printf("pmin=%u; pmax=%u; pdef=%u\n", Thread.PRIORITY_MIN, Thread.PRIORITY_MAX, Thread.PRIORITY_DEFAULT); }
84 // 2.066 compatibility
85 sndThread = new Thread(&sndPlayTreadFunc);
86 sndThread.start();
87 sndMixThread = new Thread(&sndMixTreadFunc);
88 sndMixThread.start();
89 return;
90 } catch (FollinException e) {
91 import core.stdc.stdio : fprintf, stderr;
92 fprintf(stderr, "Follin: '%s' error: %.*s", name.ptr, cast(int)e.msg.length, cast(const(char)*)e.msg.ptr);
95 atomicStore(initialized, false);
96 throw new FollinException("can't initialize audio");
99 void tflDeinit () nothrow @trusted /*@nogc*/ { sndEngineDeinit(); }
102 @property nothrow @trusted @nogc bool tflInitialized () { static if (__VERSION__ > 2067) pragma(inline, true); return atomicLoad(initialized); }
104 @property pure nothrow @safe @nogc {
105 bool tflStereo () { static if (__VERSION__ > 2067) pragma(inline, true); return (numchans == 2); }
106 uint tflBytesPerSample () { static if (__VERSION__ > 2067) pragma(inline, true); return 2; }
109 @property nothrow @trusted @nogc {
110 uint tflSampleRate () { static if (__VERSION__ > 2067) pragma(inline, true); return realSampleRate; }
111 uint tflLatency () { static if (__VERSION__ > 2067) pragma(inline, true); return latency*bufcount; } // in milliseconds
112 uint tflActiveChannels () { static if (__VERSION__ > 2067) pragma(inline, true); return atomicLoad(/*sndActiveChanCount*/firstFreeChan); }
114 bool tflPaused () { static if (__VERSION__ > 2067) pragma(inline, true); return atomicLoad(sndPaused); }
115 void tflPaused (bool v) { static if (__VERSION__ > 2067) pragma(inline, true); atomicStore(sndPaused, v); }
117 ubyte tflMasterVolume () {
118 static if (__VERSION__ > 2067) pragma(inline, true);
119 auto l = atomicLoad(sndMasterVolume);
120 auto r = (l>>8)&0xff;
121 return cast(ubyte)(l > r ? l : r);
123 void tflMasterVolume (ubyte v) { static if (__VERSION__ > 2067) pragma(inline, true); atomicStore(sndMasterVolume, cast(ushort)(v<<8|v)); }
125 ubyte tflMasterVolumeL () { static if (__VERSION__ > 2067) pragma(inline, true); return atomicLoad(sndMasterVolume)&0xff; }
126 ubyte tflMasterVolumeR () { static if (__VERSION__ > 2067) pragma(inline, true); return (atomicLoad(sndMasterVolume)>>8)&0xff; }
127 void tflMasterVolumeL (ubyte v) { static if (__VERSION__ > 2067) pragma(inline, true); atomicStore(sndMasterVolume, cast(ushort)((atomicLoad(sndMasterVolume)&0xff00)|v)); }
128 void tflMasterVolumeR (ubyte v) { static if (__VERSION__ > 2067) pragma(inline, true); atomicStore(sndMasterVolume, cast(ushort)((atomicLoad(sndMasterVolume)&0x00ff)|(v<<8))); }
130 // DON'T USE YET!
131 // callback is called inside mixing thread and lock
132 auto tflS16Callback () { return sndS16CB; }
133 void tflS16Callback (void delegate (short[] buf) nothrow @nogc dg) { sndS16CB = dg; }
137 // ////////////////////////////////////////////////////////////////////////// //
138 // channel management
139 // note that any method can be called while sound engine is locked, and from arbitrary thread
140 class TflChannel {
141 // resampling quality
142 alias Quality = int;
143 enum QualityMin = -1; // try cubic upsampler
144 enum QualityMax = 10;
145 enum QualityMusic = 8;
146 enum QualitySfx = -1; // default
148 // volumes for each channel
149 ubyte volL = 255;
150 ubyte volR = 255;
151 uint sampleRate = 44100;
152 bool stereo = true;
153 bool paused = false;
155 final @property ubyte volume () pure const nothrow @safe @nogc { return (volL > volR ? volL : volR); }
156 final @property void volume (ubyte v) nothrow @safe @nogc { volL = volR = v; }
158 // all of the above will be read before `fillFrames`
159 // if channel is paused, `fillFrames` will not be called
161 // override this if you can provide floating buffer
162 // Follin will provide `tmpbuf` of the same size as `buf`
163 // return number of *frames* (not samples!) written
164 // 0 means "this channel is no more"
165 // note that this function SHOULD NOT ALLOCATE, despite commented out @nogc
166 uint fillFrames (float[] buf) nothrow /*@nogc*/ { return 0; }
168 // called when the channel is discarded (due to being `done` or by replacing with another channel)
169 // note that sound engine is locked, so you can't call `tflAddChan()` here
170 // also, this can (and will) be called from different threads
171 void discarded () nothrow @nogc {}
173 // can be overriden by channel if it knows it's length
174 // this is purely for infomation purposes, Follin will not use this for anything
175 // return -1 for "unknown", or total channel duration in milliseconds
176 long totalMsecs () { return -1; }
180 // default priority; channel with lesser priority wins
181 enum TFLdefault = 1000;
182 enum TFLplayer = 500;
183 enum TFLmusic = 100; // music should always play
185 // returns `false` if there's no room for new channel (and it's not added)
186 bool tflAddChannel (const(char)[] name, TflChannel chan, uint prio=TFLdefault, TflChannel.Quality q=TflChannel.QualitySfx) { static if (__VERSION__ > 2067) pragma(inline, true); return sndAddChan(name, chan, prio, q); }
187 bool tflKillChannel (const(char)[] name) { /*static if (__VERSION__ > 2067) pragma(inline, true);*/ return sndKillChan(name); }
188 bool tflIsChannelAlive (const(char)[] name) nothrow @trusted /*@nogc*/ { static if (__VERSION__ > 2067) pragma(inline, true); return sndIsChanAlive(name); }
189 TflChannel tflChannelObject (const(char)[] name) nothrow @trusted /*@nogc*/ { static if (__VERSION__ > 2067) pragma(inline, true); return sndGetChanObj(name); }
190 uint tflChannelPlayTimeMsec (const(char)[] name) nothrow @trusted /*@nogc*/ { static if (__VERSION__ > 2067) pragma(inline, true); return sndGetPlayTimeMsec(name); }
192 // ////////////////////////////////////////////////////////////////////////// //
193 private:
194 import core.atomic;
195 import core.sync.condition;
196 import core.sync.rwmutex;
197 import core.sync.mutex;
198 import core.thread;
200 import iv.follin.drivers;
201 import iv.follin.ftrick;
202 import iv.follin.hash;
203 import iv.follin.resampler;
204 import iv.follin.sdata;
205 version(follin_use_spinrw) import iv.follin.rwlock;
208 enum samplerate = 44100;
209 enum numchans = 2;
210 enum bufcount = 2;
211 enum maxchans = 32;
212 //enum latency = 200; // milliseconds
213 //static assert(1000%latency == 0);
214 shared bool initialized = false;
215 shared bool sndWantShutdown = false;
216 shared bool sndSafeToShutdown0 = false, sndSafeToShutdown1 = false;
217 //shared uint sndActiveChanCount;
218 __gshared void delegate (short[] buf) nothrow @nogc sndS16CB;
219 __gshared short* sndbufMem = null; // chunk of memory for `sndbufptr`
220 __gshared float* sndrsbufMem = null; // chunk of memory for `sndrsbufptr`
221 __gshared float* sndrsbufptr = null; // aligned on 16 bytes for sse
222 __gshared float* tmprsbufMem = null; // chunk of memory for `tmprsbufptr`
223 __gshared float* tmprsbufptr = null; // here we'll collect floating point samples; aligned on 16 bytes for sse
224 __gshared float* tmpmonobufMem = null; // chunk of memory for `tmpmonobufptr`
225 __gshared float* tmpmonobufptr = null; // here we'll collect mono samples; aligned on 16 bytes for sse
226 __gshared Thread sndThread = null, sndMixThread = null;
227 shared ushort sndMasterVolume = 0xffff;
228 version(follin_use_spinrw) {
229 shared TflRWLock sndMutexChanRW;
230 } else {
231 __gshared ReadWriteMutex sndMutexChanRW;
233 __gshared Mutex mutexCondMixer;
234 __gshared Condition condMixerWait;
236 shared static this () {
237 mutexCondMixer = new Mutex();
238 condMixerWait = new Condition(mutexCondMixer);
239 version(follin_use_spinrw) {} else sndMutexChanRW = new ReadWriteMutex(ReadWriteMutex.Policy.PREFER_WRITERS);
243 // ////////////////////////////////////////////////////////////////////////// //
244 private T* alignTo16(T) (T* ptr) {
245 pragma(inline, true);
246 if ((cast(usize)ptr)&0x0f) ptr = cast(T*)(((cast(usize)ptr)|0x0f)+1);
247 return ptr;
251 void sndEngineInit () {
252 static T* realloc(T) (ref T* ptr, uint len) {
253 import core.stdc.stdlib: realloc;
254 ptr = cast(T*)realloc(ptr, len*T.sizeof);
255 if (ptr is null) assert(0, "Follin: out of memory");
256 return alignTo16(ptr);
258 sndSamplesSize *= numchans; // frames to samples
259 // buffers for final mixed sound
260 sndbufptr = cast(short**)realloc(sndbufptr, (short*).sizeof*bufcount);
261 if (sndbufptr is null) assert(0, "Follin: out of memory");
262 sndbufptr[0] = realloc(sndbufMem, (sndSamplesSize+128)*bufcount);
263 //{ import std.stdio; writefln("0x%08x 0x%08x 0x%08x", cast(uint)sndbufMem, cast(uint)sndbufptr[0], cast(uint)sndbufptr[1]); }
264 foreach (immutable idx; 1..bufcount) sndbufptr[idx] = alignTo16(sndbufptr[idx-1]+sndSamplesSize+8);
265 // buffer to collect stereo samples (float)
266 tmprsbufptr = realloc(tmprsbufMem, sndSamplesSize+128);
267 // buffer to collect mono samples (float)
268 tmpmonobufptr = realloc(tmpmonobufMem, sndSamplesSize+128);
269 // buffer to mix samples (float)
270 sndrsbufptr = realloc(sndrsbufMem, sndSamplesSize+128);
271 // init channels
272 foreach (ref ch; chans) {
273 import core.stdc.stdlib : malloc;
274 ch.bufptr = realloc(ch.bufMem, sndSamplesSize+128);
275 ch.bufpos = 0;
276 if (!ch.srb.inited) {
277 ch.lastquality = 8;
278 ch.useCubic = false;
279 ch.srb.setup(numchans, 44100, 48000, ch.lastquality);
281 ch.cub.reset();
282 ch.prevsrate = ch.lastsrate = 44100;
285 scope(failure) {
286 import core.stdc.stdlib : free;
287 foreach (ref ch; chans) {
288 if (ch.bufMem !is null) { free(ch.bufMem); ch.bufMem = null; }
295 // ////////////////////////////////////////////////////////////////////////// //
296 struct Channel {
297 uint prio; // channel priority
298 usize namehash;
299 usize namelen; // 0: this channel is free
300 char[128] name; // no longer than this
301 TflChannel chan; // can be `null` if channel callback is done, but something is still playing
302 SpeexResampler srb;
303 CubicUpsampler cub;
304 // buffers are always of `sndSamplesSize` size
305 float* bufMem; // chunk of memory for `buf`
306 float* bufptr; // frame buffer, aligned to 16 bytes
307 uint bufpos; // current position to write in `tmpbuf`/`buf`
308 ubyte lastvolL, lastvolR; // latest volume
309 int lastquality = 666; // <0: try cubic
310 bool useCubic;
311 uint lastsrate, prevsrate; // last sampling rate
312 ulong genFrames; // generated, but not yet consumed frames
313 ulong playedFrames;
315 @disable this (this); // no copies
319 __gshared Channel[maxchans] chans;
320 shared ulong chanid = 0;
321 shared uint firstFreeChan = 0;
322 __gshared uint sndFrameId;
325 void packChannels () nothrow @trusted @nogc {
326 import core.stdc.string : memcpy;
328 __gshared ubyte[Channel.srb.sizeof > Channel.cub.sizeof ? Channel.srb.sizeof : Channel.cub.sizeof] srbMoveBuf = void;
330 int freeIdx = 0;
331 for (;;) {
332 // find free channel
333 while (freeIdx < firstFreeChan && chans.ptr[freeIdx].namelen != 0) ++freeIdx;
334 if (freeIdx >= firstFreeChan) return; // nothing to do
335 // find used channel
336 int idx = freeIdx;
337 while (idx < firstFreeChan && chans.ptr[idx].namelen == 0) ++idx;
338 if (idx >= firstFreeChan) { firstFreeChan = freeIdx; return; } // done
339 // now move used channel up
340 auto sc = &chans.ptr[idx];
341 auto dc = &chans.ptr[freeIdx];
342 sc.prio = dc.prio;
343 sc.namehash = dc.namehash;
344 sc.namelen = dc.namelen;
345 sc.name = dc.name;
346 sc.chan = dc.chan;
347 // now tricky part: swap SpeexResampler
348 memcpy(srbMoveBuf.ptr, &sc.srb, sc.srb.sizeof);
349 memcpy(&sc.srb, &dc.srb, sc.srb.sizeof);
350 memcpy(&dc.srb, srbMoveBuf.ptr, sc.srb.sizeof);
351 // now tricky part: swap CubicUpsampler
352 memcpy(srbMoveBuf.ptr, &sc.cub, sc.cub.sizeof);
353 memcpy(&sc.cub, &dc.cub, sc.cub.sizeof);
354 memcpy(&dc.cub, srbMoveBuf.ptr, sc.cub.sizeof);
355 // mark as free
356 sc.namelen = 0;
357 sc.chan = null;
362 Channel* sndFindChanByName (const(char)[] name) nothrow @trusted @nogc {
363 if (name.length == 0 || name.length > Channel.name.length) return null;
364 auto hash = hashBuffer(name.ptr, name.length);
365 foreach (immutable idx; 0..chans.length) {
366 auto ch = chans.ptr+idx;
367 if (ch.namelen == 0) break;
368 if (ch.namehash == hash && ch.namelen == name.length && ch.name[0..ch.namelen] == name) return ch;
370 return null;
374 bool sndIsChanAlive (const(char)[] name) nothrow @trusted /*@nogc*/ {
375 version(follin_use_spinrw) {
376 sndMutexChanRW.readLock();
377 auto ch = sndFindChanByName(name);
378 sndMutexChanRW.readUnlock();
379 return (ch !is null);
380 } else {
381 synchronized (sndMutexChanRW.reader) {
382 if (auto ch = sndFindChanByName(name)) return true;
384 return false;
389 TflChannel sndGetChanObj (const(char)[] name) nothrow @trusted /*@nogc*/ {
390 version(follin_use_spinrw) {
391 sndMutexChanRW.readLock();
392 auto ch = sndFindChanByName(name);
393 TflChannel res = (ch !is null ? ch.chan : null);
394 sndMutexChanRW.readUnlock();
395 return res;
396 } else {
397 synchronized (sndMutexChanRW.reader) {
398 if (auto ch = sndFindChanByName(name)) return ch.chan;
400 return null;
405 uint sndGetPlayTimeMsec (const(char)[] name) nothrow @trusted /*@nogc*/ {
406 version(follin_use_spinrw) {
407 sndMutexChanRW.readLock();
408 if (auto ch = sndFindChanByName(name)) {
409 auto res = cast(uint)(ch.playedFrames*1000/realSampleRate);
410 sndMutexChanRW.readUnlock();
411 return res;
413 sndMutexChanRW.readUnlock();
414 return 0;
415 } else {
416 synchronized (sndMutexChanRW.reader) {
417 if (auto ch = sndFindChanByName(name)) return cast(uint)(ch.playedFrames*1000/realSampleRate);
419 return 0;
424 bool sndKillChan (const(char)[] name) {
425 static bool doIt() (usize hash, const(char)[] name) {
426 auto ch = chans.ptr;
427 foreach (immutable idx; 0..chans.length) {
428 if (ch.namelen == 0) break;
429 if (ch.namehash == hash && ch.namelen == name.length && ch.name[0..ch.namelen] == name) {
430 // kill this channel
431 auto ochan = ch.chan;
432 ch.chan = null;
433 ch.bufpos = 0;
434 ch.namelen = 0;
435 ch.genFrames = 0;
436 if (ochan !is null) ochan.discarded();
437 packChannels();
438 return true;
440 ++ch;
442 return false;
445 if (name.length == 0 || name.length > Channel.name.length) return false;
446 auto hash = hashBuffer(name.ptr, name.length);
447 version(follin_use_spinrw) {
448 sndMutexChanRW.writeLock();
449 auto res = doIt(hash, name);
450 sndMutexChanRW.writeUnlock();
451 return res;
452 } else {
453 synchronized (sndMutexChanRW.writer) return doIt(hash, name);
458 bool sndAddChan (const(char)[] name, TflChannel chan, uint prio, TflChannel.Quality q) {
459 char[64] tmpname;
460 // build name if there's none
461 if (name.length == 0) {
462 if (chan is null) return false;
463 usize pos = tmpname.length;
464 ulong num = atomicOp!"+="(chanid, 1);
465 do { tmpname[--pos] = cast(char)('0'+num%10); } while ((num /= 10) != 0);
466 enum pfx = "uninspiring channel #";
467 pos -= pfx.length;
468 tmpname[pos..pos+pfx.length] = pfx[];
469 name = tmpname[pos..$];
470 } else if (name.length > Channel.name.length) {
471 //throw FollinException("channel name too long: '"~name.idup~"'");
472 return false;
474 auto hash = hashBuffer(name.ptr, name.length);
476 static struct VictimInfo {
477 int idx = -1;
478 ubyte vol = 255;
479 int state = int.max; // bufpos, or `int.max-1` for paused
481 static T max(T) (T a, T b) { return (a > b ? a : b); }
482 static T min(T) (T a, T b) { return (a < b ? a : b); }
484 void fixVictim(string mode) (ref Channel ch, uint prio, usize curidx) nothrow @trusted @nogc {
485 static assert(mode == "same" || mode == "lower");
486 static if (mode == "same") {
487 if (ch.prio != prio) return;
488 } else {
489 if (ch.prio <= prio) return;
490 if (idx >= 0 && chans.ptr[idx].prio > ch.prio) {
491 // we have a victim with better priority, but we'll prefer "going to die" to paused one
492 if (chans.ptr[idx].chan is null || ch.chan !is null) return;
495 if (ch.chan is null) {
496 // this channel is going to die
497 if (ch.bufpos < state || max(ch.lastvolL, ch.lastvolR) < vol) {
498 // this channel is better victim: has less data left, or has lower volume
499 idx = cast(int)curidx;
500 state = ch.bufpos;
501 vol = max(ch.lastvolL, ch.lastvolR);
503 } else if (ch.chan.paused) {
504 // this channel is paused
505 if (state == int.max || (state == int.max-1 && max(ch.lastvolL, ch.lastvolR) < vol)) {
506 // this channel is better victim: either there is no victim found yet, or has paused victim with lower volume
507 idx = cast(int)curidx;
508 state = int.max-1;
509 vol = max(ch.lastvolL, ch.lastvolR);
515 VictimInfo lower, same;
516 int replaceIdx = -1;
518 bool doIt() () {
519 // for lowest prio: prefer finished channel, then paused channel
520 foreach (immutable idx; 0..chans.length) {
521 auto ch = chans.ptr+idx;
522 if (ch.namelen == 0) break; // last channel
523 if (ch.namehash == hash && ch.namelen == name.length && ch.name[0..ch.namelen] == name) {
524 replaceIdx = cast(int)idx;
525 break;
527 lower.fixVictim!"lower"(*ch, prio, idx);
528 same.fixVictim!"same"(*ch, prio, idx);
531 //{ import core.stdc.stdio; printf("replaceIdx(0)=%d; firstFreeChan=%u\n", replaceIdx, firstFreeChan); }
533 if (replaceIdx < 0) {
534 if (chan is null) return false;
535 if (lower.idx >= 0 && lower.state < int.max-1) {
536 // lower and dying
537 replaceIdx = lower.idx;
538 } else if (same.idx >= 0 && same.state < int.max-1) {
539 // same and dying
540 replaceIdx = same.idx;
541 } else if (lower.idx >= 0) {
542 // lower
543 replaceIdx = lower.idx;
544 } else {
545 // same
546 replaceIdx = same.idx;
548 if (replaceIdx < 0) {
549 if (firstFreeChan >= chans.length) return false; // alas
550 replaceIdx = firstFreeChan;
554 //{ import core.stdc.stdio; printf("replaceIdx(1)=%d; firstFreeChan=%u\n", replaceIdx, firstFreeChan); }
556 // replace channel
557 auto ch = &chans.ptr[replaceIdx];
559 // fix resampling ratio
560 if (chan !is null && ch.chan !is chan) {
561 auto srate = chan.sampleRate;
562 ch.lastsrate = (srate < 1024 || srate > 96000 ? 0 : srate);
563 if (ch.lastsrate != 0) {
564 ch.srb.reset();
565 if (ch.prevsrate != ch.lastsrate && ch.lastsrate != realSampleRate) {
566 if (ch.srb.setRate(ch.lastsrate, realSampleRate)) ch.lastsrate = 0;
569 ch.prevsrate = ch.lastsrate;
570 if (ch.lastsrate == 0) chan = null;
573 if (ch.chan !is chan) {
574 auto ochan = ch.chan;
575 ch.chan = null;
576 ch.bufpos = 0;
577 ch.genFrames = 0;
578 ch.playedFrames = 0;
579 if (ochan !is null) ochan.discarded();
580 ch.cub.reset(); // it is fast
583 if (chan !is null) {
584 //if (ch.chan is null) ch.srb.reset();
585 if (q < -1) q = -1; else if (q > 10) q = 10;
586 if (ch.lastquality != q) {
587 //{ import core.stdc.stdio; printf("changing quality from %u to %u\n", ch.lastquality, q); }
588 ch.lastquality = q;
589 if (ch.srb.inited) {
590 ch.srb.setQuality(q);
591 } else {
592 //ch.srb.deinit();
593 ch.srb.setup(numchans, 44100, 48000, q);
596 if (ch.prevsrate != ch.lastsrate) {
597 if (ch.srb.setRate(ch.lastsrate, realSampleRate)) {
598 ch.prevsrate = ch.lastsrate = 0;
599 auto ochan = ch.chan;
600 ch.chan = null;
601 ch.bufpos = 0;
602 if (ochan !is null) ochan.discarded();
603 ch.namelen = 0; // "it's free" flag
604 packChannels();
605 return false;
608 if (ch.lastquality < 0) ch.useCubic = ch.cub.setup(cast(float)ch.lastsrate/cast(float)realSampleRate); // don't use cubic
609 version(follin_debug_resampler_type) if (ch.useCubic) { import core.stdc.stdio; printf("*** using cubic upsampler\n"); }
610 ch.prevsrate = ch.lastsrate;
611 if (ch.bufpos == 0) ch.lastvolL = ch.lastvolR = 0; // so if we won't even had a chance to play it, it can be replaced
612 ch.prio = prio;
613 ch.namehash = hash;
614 ch.namelen = name.length;
615 ch.name[0..name.length] = name;
616 ch.chan = chan;
617 if (replaceIdx >= firstFreeChan) {
618 firstFreeChan = replaceIdx+1;
619 packChannels();
621 } else {
622 ch.namelen = 0; // "it's free" flag
623 packChannels();
625 return true;
628 version(follin_use_spinrw) {
629 sndMutexChanRW.writeLock();
630 auto res = doIt();
631 sndMutexChanRW.writeUnlock();
632 return res;
633 } else {
634 synchronized (sndMutexChanRW.writer) return doIt();
639 // ////////////////////////////////////////////////////////////////////////// //
640 bool sndGenerateBuffer () {
641 //version(follin_use_sse) align(64) __gshared float[4] zeroes = 0; // dmd cannot into such aligns, alas
642 version(follin_use_sse) {
643 align(64) __gshared float[4+32] zeroesBuf = 0; // dmd cannot into such aligns, alas
644 __gshared uint zeroesptr = 0;
645 if (zeroesptr == 0) {
646 zeroesptr = cast(uint)zeroesBuf.ptr;
647 if (zeroesptr&0x3f) zeroesptr = (zeroesptr|0x3f)+1;
649 assert((zeroesptr&0x3f) == 0, "wtf?!");
651 //version(follin_use_sse) assert(((cast(usize)zeroes.ptr)&0x3f) == 0, "wtf?!");
653 static void killChan() (Channel* ch, ref bool channelsChanged) {
654 if (ch.namelen) {
655 channelsChanged = true;
656 auto chan = ch.chan;
657 ch.chan = null;
658 ch.namelen = 0;
659 ch.playedFrames = 0;
660 if (chan !is null) {
661 chan.discarded();
662 ch.srb.reset();
664 } else {
665 ch.chan = null;
667 ch.bufpos = 0;
670 SpeexResampler.Error err;
671 SpeexResampler.Data srbdata = void;
672 bool channelsChanged = false;
673 auto buf2fill = atomicLoad(sndbufToFill);
674 atomicStore(sndbufFillingNow, true);
675 bool wasAtLeastOne = false;
677 enum doer = q{
678 if (firstFreeChan > 0) {
679 immutable bufsz = sndSamplesSize;
680 // use SSE to clear buffer, if we can
681 version(follin_use_sse) {
682 asm nothrow @safe @nogc {
683 mov EAX,[sndrsbufptr];
684 // ECX = (sndSamplesSize+3)/4
685 mov ECX,[sndSamplesSize];
686 add ECX,3;
687 shr ECX,2;
688 //mov EBX,offsetof zeroes[0];
689 mov EBX,[zeroesptr];
690 //movntdqa XMM0,[EBX]; // non-temporal, don't bring zeroes to cache (sse4.1)
691 movaps XMM0,[EBX];
692 align 8;
693 loopsseclear_x0:
694 movaps [EAX],XMM0; // dest is always aligned
695 add EAX,16;
696 dec ECX;
697 jnz loopsseclear_x0;
699 } else {
700 sndrsbufptr[0..sndSamplesSize] = 0.0f;
702 //{ import core.stdc.stdio; printf("filling buffer %u\n", buf2fill); }
703 foreach (immutable cidx; 0..chans.length) {
704 auto ch = chans.ptr+cidx;
705 if (ch.namelen == 0) break; // last channel
706 uint rspos = 0; // current position in `sndrsbufptr`
707 chmixloop: while (rspos < bufsz) {
708 // try to get as much data from the channel as we can
709 if (ch.bufpos == 0 && (ch.chan !is null && !ch.chan.paused) && ch.lastsrate != ch.chan.sampleRate) {
710 auto srate = ch.chan.sampleRate;
711 ch.lastsrate = srate;
712 if (srate < 1024 || srate > 96000) {
713 ch.lastsrate = srate = 0;
714 } else if (srate != ch.prevsrate && srate != realSampleRate) {
715 if (ch.srb.setRate(srate, realSampleRate)) ch.lastsrate = srate = 0;
717 ch.prevsrate = ch.lastsrate;
718 if (!srate) { killChan(ch, channelsChanged); break chmixloop; } // something is wrong with this channel, kill it
719 if (ch.lastquality < 0) ch.useCubic = ch.cub.setup(cast(float)srate/cast(float)realSampleRate); // don't use cubic
720 version(follin_debug_resampler_type) { if (ch.useCubic) { import core.stdc.stdio; printf("*** using cubic upsampler\n"); } }
722 //{ import core.stdc.stdio; printf("wanted %u frames (has %u frames)\n", (bufsz-ch.bufpos)/2, ch.bufpos/2); }
723 while (ch.bufpos < bufsz && (ch.chan !is null && !ch.chan.paused) && ch.lastsrate == ch.chan.sampleRate) {
724 // fix last known volume
725 ch.lastvolL = ch.chan.volL;
726 ch.lastvolR = ch.chan.volR;
727 // fix last known sample rate
728 uint fblen;
729 auto len = (bufsz-ch.bufpos)/2; // frames
730 if (ch.chan.stereo) {
731 // stereo
732 fblen = ch.chan.fillFrames(ch.bufptr[ch.bufpos..bufsz]);
733 if (fblen > len) fblen = 0; // something is very wrong with this channel
734 } else {
735 //FIXME mono
736 fblen = ch.chan.fillFrames(tmpmonobufptr[0..len]);
737 if (fblen > len) fblen = 0; // something is very wrong with this channel
738 // expand
739 auto s = tmpmonobufptr;
740 auto d = ch.bufptr+ch.bufpos;
741 foreach (immutable _; 0..fblen) {
742 *d++ = *s;
743 *d++ = *s++;
746 //{ import core.stdc.stdio; printf("trying to get %u frames, got %u frames...\n", (bufsz-ch.bufpos)/2, fblen); }
747 if (fblen == 0) { killChan(ch, channelsChanged); break; }
748 // do volume
749 auto bptr = ch.bufptr+ch.bufpos;
750 ch.bufpos += fblen*2; // frames to samples
751 if ((ch.lastvolL|ch.lastvolR) == 0) {
752 // silent
753 version(follin_use_sse) {
754 asm nothrow @safe @nogc {
755 mov EAX,[bptr];
756 //mov EBX,offsetof zeroes[0];
757 mov EBX,[zeroesptr];
758 //movntdqa XMM0,[EBX]; // non-temporal (sse4.1)
759 movaps XMM0,[EBX];
760 mov ECX,[fblen];
761 mov EDX,8;
762 // is buffer aligned?
763 // process floats one-by-one if not
764 zerovol_unaligned:
765 test EAX,0x0f;
766 jz zerovol_aligned;
767 // using `movsd` brings some penalty here, but meh...
768 movsd [EAX],XMM0; // store two floats (single double)
769 add EAX,EDX;
770 dec ECX;
771 jnz zerovol_unaligned;
772 jmp zerovol_done;
773 zerovol_aligned:
774 mov EDX,16;
775 // ECX = (xlen+1)/2
776 inc ECX;
777 shr ECX,1;
778 align 8;
779 zerovol:
780 movaps [EAX],XMM0;
781 add EAX,EDX;
782 dec ECX;
783 jnz zerovol;
784 zerovol_done:;
786 } else {
787 //ch.bufptr[ch.bufpos..bufsz] = 0.0f;
788 bptr[0..fblen*2] = 0;
790 } else {
791 version(follin_use_sse) {
792 if (ch.lastvolL != 255 || ch.lastvolR != 255) {
793 align(64) __gshared float[4] mul = void;
794 mul[0] = mul[2] = (1.0f/255.0f)*cast(float)ch.lastvolL;
795 mul[1] = mul[3] = (1.0f/255.0f)*cast(float)ch.lastvolR;
796 asm nothrow @safe @nogc {
797 mov EAX,[bptr];
798 mov EBX,offsetof mul[0];
799 //movntdqa XMM1,[EBX]; // non-temporal, don't bring volumes to cache (sse4.1)
800 movaps XMM1,[EBX];
801 mov ECX,[fblen];
802 mov EDX,8;
803 // is buffer aligned?
804 // process floats one-by-one if not
805 addloopchvol_unaligned:
806 test EAX,0x0f;
807 jz addloopchvol_aligned;
808 // using `movsd` brings some penalty here, but meh...
809 movsd XMM3,[EAX]; // load two floats (single double), clear others
810 mulps XMM3,XMM1;
811 movsd [EAX],XMM3; // store two floats (single double)
812 add EAX,EDX;
813 dec ECX;
814 jnz addloopchvol_unaligned;
815 jmp addloopchvol_done;
816 addloopchvol_aligned:
817 mov EDX,16;
818 // ECX = (xlen+1)/2
819 inc ECX;
820 shr ECX,1;
821 align 8;
822 addloopchvol:
823 movaps XMM0,[EAX];
824 mulps XMM0,XMM1;
825 movaps [EAX],XMM0;
826 add EAX,EDX;
827 dec ECX;
828 jnz addloopchvol;
829 addloopchvol_done:;
832 } else {
833 // do volume
834 if (ch.lastvolL != 255 || ch.lastvolR != 255) {
835 immutable float mulL = (1.0f/255.0f)*cast(float)ch.lastvolL;
836 immutable float mulR = (1.0f/255.0f)*cast(float)ch.lastvolR;
837 foreach (immutable _; 0..fblen) {
838 *bptr++ *= mulL;
839 *bptr++ *= mulR;
842 } // version
844 } // while
846 //{ import core.stdc.stdio; printf("have %u frames out of %u\n", ch.bufpos/2, bufsz/2); }
847 // if we have any data in channel buffer, resample it
848 uint blen = void, bsused = void;
849 float* xss = void;
850 auto xdd = sndrsbufptr+rspos;
851 if (ch.lastsrate != realSampleRate) {
852 // resample
853 xss = tmprsbufptr+rspos;
854 bsused = 0;
855 blen = 0;
856 while (bsused < ch.bufpos && rspos < bufsz) {
857 srbdata.dataIn = ch.bufptr[bsused..ch.bufpos];
858 srbdata.dataOut = tmprsbufptr[rspos..bufsz];
859 //{ import core.stdc.stdio; printf("realSampleRate=%u; ch.lastsrate=%u\n", realSampleRate, ch.lastsrate); }
860 err = (ch.useCubic ? ch.cub.process(srbdata) : ch.srb.process(srbdata));
861 if (err) { killChan(ch, channelsChanged); break; }
862 //{ import core.stdc.stdio; printf("inused: %u of %u; outused: %u of %u; bsused=%u\n", srbdata.inputSamplesUsed, cast(uint)srbdata.dataIn.length, srbdata.outputSamplesUsed, cast(uint)srbdata.dataOut.length, bsused); }
863 bsused += srbdata.inputSamplesUsed;
864 rspos += srbdata.outputSamplesUsed;
865 blen += srbdata.outputSamplesUsed;
867 } else {
868 // no need to resample, can use data as-is
869 xss = ch.bufptr;
870 bsused = blen = ch.bufpos;
871 rspos += blen;
873 // if we have something to mix...
874 if (blen) {
875 // ...mix it; will clamp data later (this should theoretically improve mixing quality)
876 ch.genFrames += blen/2;
877 version(follin_use_sse) {
878 asm nothrow @safe @nogc {
879 mov ECX,[blen];
880 mov EAX,[xdd];
881 mov EBX,[xss];
882 // dest may be unaligned, check it
883 test EAX,0x0f;
884 jz diraddmix_daligned;
885 // here we should process some floats to become aligned again
886 mov EDX,EAX;
887 shr EDX,2;
888 and EDX,0x03;
889 // now DL is the number of unaligned floats
890 jz diraddmix_daligned;
891 sub ECX,EDX; // will check ECX later
892 // we can do it faster by unrolling here, but meh...
893 mov DH,3;
894 diraddmix_unaligned:
895 movss XMM2,[EBX]; // load single float, clear others
896 movss XMM3,[EAX]; // load single float, clear others
897 addss XMM3,XMM2;
898 movss [EAX],XMM3; // store single float
899 add EAX,4;
900 add EBX,4;
901 inc DL;
902 and DL,DH;
903 jnz diraddmix_unaligned;
904 // ECX is actually really small, so check the high bit to detect overflow
905 test ECX,0x8000_0000U;
906 jnz diraddmix_done;
907 // has something more to do
908 diraddmix_daligned:
909 // ECX=(blen+3)/4;
910 mov EDX,16; // memory increment
911 add ECX,3;
912 shr ECX,2;
913 // is source aligned too?
914 test EBX,0x0f;
915 jnz diraddmix_bad;
916 // good case: source is aligned
917 align 8;
918 diraddmix_good:
919 movaps XMM0,[EAX];
920 movaps XMM1,[EBX];
921 addps XMM0,XMM1;
922 movaps [EAX],XMM0;
923 add EAX,EDX;
924 add EBX,EDX;
925 dec ECX;
926 jnz diraddmix_good;
927 jmp diraddmix_done;
928 // bad case: source is not aligned
929 align 8;
930 diraddmix_bad:
931 movaps XMM0,[EAX];
932 movups XMM1,[EBX];
933 addps XMM0,XMM1;
934 movaps [EAX],XMM0;
935 add EAX,EDX;
936 add EBX,EDX;
937 dec ECX;
938 jnz diraddmix_bad;
939 diraddmix_done:;
941 } else {
942 // no sse
943 foreach (immutable _; 0..blen) *xdd++ += *xss++;
947 // remove data from input buffer
948 if (bsused >= ch.bufpos) {
949 ch.bufpos = 0;
950 } else if (bsused > 0) {
951 import core.stdc.string : memmove;
952 memmove(ch.bufptr, ch.bufptr+bsused, (ch.bufpos-bsused)*ch.bufptr[0].sizeof);
953 ch.bufpos -= bsused;
956 // if this channel is paused or dead, do not try to process it again
957 if (ch.chan is null || ch.chan.paused) {
958 // try to squeeze last resampled data bytes out of it
959 if (rspos < bufsz && ch.lastsrate != realSampleRate) {
960 // here we can shift tmprsbufptr to get both buffers aligned
961 blen = bufsz-rspos; // we don't need more than this
962 xss = tmprsbufptr+(rspos&0x03); // if rspos is not aligned, shift xss
963 srbdata.dataIn = null;
964 srbdata.dataOut = xss[0..blen];
965 err = (ch.useCubic ? ch.cub.process(srbdata) : ch.srb.process(srbdata));
966 if (err) { killChan(ch, channelsChanged); break chmixloop; }
967 if (ch.lastquality < 0 && ch.bufpos < bufsz) ch.cub.reset(); // it is fast
968 if (srbdata.outputSamplesUsed) {
969 // ...mix it; will clamp data later (this should theoretically improve mixing quality)
970 xdd = sndrsbufptr+rspos;
971 rspos += srbdata.outputSamplesUsed;
972 version(follin_use_sse) {
973 blen = srbdata.outputSamplesUsed;
974 asm nothrow @safe @nogc {
975 mov EAX,[xdd];
976 mov EBX,[xss];
977 mov ECX,[blen];
978 // dest may be unaligned, check it
979 test EAX,0x0f;
980 jz diraddmix2_daligned;
981 // here we should process some floats to become aligned again
982 mov EDX,EAX;
983 shr EDX,2;
984 and EDX,0x03;
985 // now DL is the number of unaligned floats
986 jz diraddmix2_daligned;
987 sub ECX,EDX; // will check ECX later
988 // we can do it faster by unrolling here, but meh...
989 mov DH,3;
990 diraddmix2_unaligned:
991 movss XMM2,[EBX]; // load single float, clear others
992 movss XMM3,[EAX]; // load single float, clear others
993 addss XMM3,XMM2;
994 movss [EAX],XMM3; // store single float
995 add EAX,4;
996 add EBX,4;
997 inc DL;
998 and DL,DH;
999 jnz diraddmix2_unaligned;
1000 // ECX is actually really small, so check the high bit to detect overflow
1001 test ECX,0x8000_0000U;
1002 jnz diraddmix2_done;
1003 // has something more to do
1004 diraddmix2_daligned:
1005 // ECX=(blen+3)/4;
1006 mov EDX,16; // memory increment
1007 add ECX,3;
1008 shr ECX,2;
1009 // source is aligned too
1010 align 8;
1011 diraddmix2_good:
1012 movaps XMM0,[EAX];
1013 movaps XMM1,[EBX];
1014 addps XMM0,XMM1;
1015 movaps [EAX],XMM0;
1016 add EAX,EDX;
1017 add EBX,EDX;
1018 dec ECX;
1019 jnz diraddmix2_good;
1020 diraddmix2_done:;
1022 } else {
1023 foreach (immutable _; 0..srbdata.outputSamplesUsed) *xdd++ += *xss++;
1027 break;
1029 } // rspos loop
1030 // what is left is silence, no need to mix it
1031 wasAtLeastOne = wasAtLeastOne || (rspos > 0);
1034 if (channelsChanged) packChannels();
1035 }; // doer
1037 version(follin_use_spinrw) {
1038 sndMutexChanRW.writeLock();
1039 mixin(doer);
1040 sndMutexChanRW.writeUnlock();
1041 } else {
1042 synchronized (sndMutexChanRW.writer) { mixin(doer); }
1045 // do final converting
1046 auto dp = sndbufptr[buf2fill];
1047 if (wasAtLeastOne) {
1048 // something is playing, mix it
1049 auto mvol = atomicLoad(sndMasterVolume);
1050 if (mvol == 0) {
1051 version(follin_use_sse) {
1052 asm nothrow @safe @nogc {
1053 mov ECX,[sndSamplesSize];
1054 // (sndSamplesSize+7)/8 -- destination is s16, not float
1055 add ECX,7;
1056 shr ECX,3;
1057 mov EAX,[dp]; // dest, aligned
1058 //mov EBX,offsetof zeroes[0]; // lucky me, floating zero is s16 zero too
1059 mov EBX,[zeroesptr]; // lucky me, floating zero is s16 zero too
1060 //movntdqa XMM0,[EBX]; // non-temporal, don't bring zeroes to cache (sse4.1)
1061 movaps XMM0,[EBX];
1062 mov EDX,16;
1063 align 8;
1064 lastzfill_loop:
1065 movaps [EAX],XMM0;
1066 add EAX,EDX;
1067 dec ECX;
1068 jnz lastzfill_loop;
1070 } else {
1071 dp[0..sndSamplesSize] = 0;
1073 } else {
1074 auto mvolL = mvol&0xff;
1075 auto mvolR = (mvol>>8)&0xff;
1076 auto src = sndrsbufptr;
1077 version(follin_use_sse) {
1078 //align(64) __gshared float[4] fmin4 = -32768.0;
1079 //align(64) __gshared float[4] fmax4 = 32767.0;
1080 align(64) __gshared float[4] mvolf = void;
1081 mvolf[0] = mvolf[2] = (32768.0/255.0f)*cast(float)mvolL;
1082 mvolf[1] = mvolf[3] = (32768.0/255.0f)*cast(float)mvolR;
1083 auto blen = (sndSamplesSize+3)/4;
1084 asm nothrow @safe @nogc {
1085 //mov EAX,offsetof fmin4[0];
1086 //movntdqa XMM2,[EAX]; // XMM2: min values
1087 //mov EAX,offsetof fmax4[0];
1088 //movntdqa XMM3,[EAX]; // XMM3: max values
1089 mov EAX,offsetof mvolf[0]; // source
1090 //movntdqa XMM4,[EAX]; // XMM4: multipliers (sse4.1)
1091 movaps XMM4,[EAX];
1092 // source and dest are aligned
1093 mov EAX,[src]; // source
1094 mov EBX,[dp]; // dest
1095 mov ECX,[blen];
1096 mov EDX,16;
1097 align 8;
1098 finalloopmix:
1099 movaps XMM0,[EAX];
1100 mulps XMM0,XMM4; // mul by volume and shift
1101 //maxps XMM0,XMM2; // clip lower
1102 //minps XMM0,XMM3; // clip upper
1104 version(follin_use_sse2) asm nothrow @safe @nogc {
1105 cvttps2dq XMM1,XMM0; // XMM1 now contains four int32 values
1106 packssdw XMM1,XMM1;
1107 movq [EBX],XMM1;
1108 } else asm nothrow @safe @nogc {
1109 cvtps2pi MM0,XMM0; // MM0 now contains two low int32 values
1110 movhlps XMM5,XMM0; // get high floats
1111 cvtps2pi MM1,XMM5; // MM1 now contains two high int32 values
1112 packssdw MM0,MM1; // MM0 now contains 4 int16 values
1113 movq [EBX],MM0;
1115 asm nothrow @safe @nogc {
1116 add EAX,EDX;
1117 add EBX,8;
1118 dec ECX;
1119 jnz finalloopmix;
1121 version(follin_use_sse2) {} else {
1122 asm nothrow @safe @nogc { emms; }
1124 } else {
1125 immutable float mull = (1.0f/255.0f)*mvolL;
1126 immutable float mulr = (1.0f/255.0f)*mvolR;
1127 mixin(declfcvar!"temp");
1128 foreach (immutable _; 0..sndSamplesSize/2) {
1129 // left
1130 float f = (*src++)*mull;
1132 mixin(FAST_SCALED_FLOAT_TO_INT!("f", "15"));
1133 if (cast(uint)(v+32768) > 65535) v = (v < 0 ? -32768 : 32767);
1134 *dp++ = cast(short)v;
1136 // right
1137 f = (*src++)*mulr;
1139 mixin(FAST_SCALED_FLOAT_TO_INT!("f", "15"));
1140 if (cast(uint)(v+32768) > 65535) v = (v < 0 ? -32768 : 32767);
1141 *dp++ = cast(short)v;
1146 } else {
1147 dp[0..sndSamplesSize] = 0;
1149 if (sndS16CB !is null) sndS16CB(dp[0..sndSamplesSize]);
1150 atomicStore(sndbufToFill, (buf2fill+1)%bufcount);
1151 atomicStore(sndbufFillingNow, false);
1152 return wasAtLeastOne;
1156 // ////////////////////////////////////////////////////////////////////////// //
1157 private extern (C) void thread_suspendAll() nothrow; // steal that func!
1160 // ////////////////////////////////////////////////////////////////////////// //
1161 // mixer thread: it mixes sound buffers
1162 // as modern systems had multicore CPUs, i believe that it worth having two worker threads
1163 void sndMixTreadFunc () {
1164 // detach ourself, so GC will not stop us
1165 thread_detachThis();
1166 // while documentation says that we have to call this, in reality
1167 // i see one more module tls dtor called with it. this is due to
1168 // the fact that `thread_entryPoint()` will call `rt_moduleTlsDtor()`
1169 // on exiting. let's hope that modules won't rely on GC suspending
1170 // all threads on collecting (they shouldn't)
1171 //rt_moduleTlsDtor();
1172 try {
1173 version(follin_threads_debug) { import core.stdc.stdio; printf("mixer thread started\n"); }
1174 for (;;) {
1175 if (atomicLoad(sndWantShutdown)) { atomicStore(sndSafeToShutdown0, true); return; }
1176 // do we have a room for new buffer?
1177 auto b2f = atomicLoad(sndbufToFill);
1178 auto b2p = atomicLoad(sndbufToPlay);
1179 // note that if playback is paused, we will eventually fill all buffers and wait
1180 if (b2f != b2p) {
1181 // yay!
1182 version(follin_threads_debug) { import core.stdc.stdio; printf("creating %u buffer; playing %u buffer\n", b2f, b2p); }
1183 sndGenerateBuffer();
1184 } else {
1185 version(follin_threads_debug) { import core.stdc.stdio; printf("mixer thread fell asleep; 2play=%u; 2fill=%u\n", atomicLoad(sndbufToPlay), atomicLoad(sndbufToFill)); }
1186 synchronized(mutexCondMixer) condMixerWait.wait();
1187 version(follin_threads_debug) { import core.stdc.stdio; printf("mixer thread awoken; 2play=%u; 2fill=%u\n", atomicLoad(sndbufToPlay), atomicLoad(sndbufToFill)); }
1190 } catch (Throwable e) {
1191 // here, we are dead and fucked (the exact order doesn't matter)
1192 import core.stdc.stdlib : abort;
1193 import core.stdc.stdio : fprintf, stderr;
1194 import core.memory : GC;
1195 GC.disable(); // yeah
1196 thread_suspendAll(); // stop right here, you criminal scum!
1197 auto s = e.toString();
1198 fprintf(stderr, "\n=== FATAL ===\n%.*s\n", cast(uint)s.length, s.ptr);
1199 abort(); // die, you bitch!
1204 // player thread: it plays mixed buffers
1205 void sndPlayTreadFunc () {
1206 // detach ourself, so GC will not stop us
1207 thread_detachThis(); // see comment in `sndMixTreadFunc()`
1208 try {
1209 // fill all buffers
1210 foreach (uint bufnum; 0..bufcount) {
1211 atomicStore(sndbufToPlay, bufnum+1); // hoax
1212 atomicStore(sndbufToFill, bufnum);
1213 // generate one buffer
1214 sndGenerateBuffer();
1216 // restore playing and filling order
1217 atomicStore(sndbufToFill, 0);
1218 atomicStore(sndbufToPlay, 0);
1219 // and go with the sound
1220 version(follin_threads_debug) { import core.stdc.stdio; printf("starting playback\n"); }
1221 bool playbackStarted = false;
1222 for (;;) {
1223 if (atomicLoad(sndWantShutdown)) { atomicStore(sndSafeToShutdown1, true); return; }
1224 auto b2p = atomicLoad(sndbufToPlay);
1225 auto consumed = sndWriteBuffer(playbackStarted);
1226 if (consumed) {
1227 // buffer consumed
1228 // fix channel playing time
1229 enum doer = q{
1230 if (firstFreeChan > 0) {
1231 foreach (immutable idx; 0..chans.length) {
1232 auto ch = chans.ptr+idx;
1233 if (ch.namelen == 0) break; // last channel
1234 //{ import core.stdc.stdio; printf("pf: %u; gf: %u\n", cast(uint)ch.playedFrames, cast(uint)ch.genFrames); }
1235 if (ch.playedFrames < ch.genFrames) {
1236 ch.playedFrames += sndSamplesSize/2;
1237 if (ch.playedFrames > ch.genFrames) ch.playedFrames = ch.genFrames;
1241 }; // doer
1242 version(follin_use_spinrw) {
1243 sndMutexChanRW.writeLock();
1244 mixin(doer);
1245 sndMutexChanRW.writeUnlock();
1246 } else {
1247 synchronized (sndMutexChanRW.writer) { mixin(doer); }
1249 b2p = (b2p+1)%bufcount;
1250 atomicStore(sndbufToPlay, b2p);
1251 //if (b2p != atomicLoad(sndbufToFill)) atomicStore(sndbufToPlay, (b2p+1)%bufcount);
1252 version(follin_threads_debug) { import core.stdc.stdio; printf("pinging mixer thread; b2p=%u; b2f=%u\n", atomicLoad(sndbufToPlay), atomicLoad(sndbufToFill)); }
1253 synchronized(mutexCondMixer) condMixerWait.notify();
1256 } catch (Throwable e) {
1257 // here, we are dead and fucked (the exact order doesn't matter)
1258 import core.stdc.stdlib : abort;
1259 import core.stdc.stdio : fprintf, stderr;
1260 import core.memory : GC;
1261 GC.disable(); // yeah
1262 thread_suspendAll(); // stop right here, you criminal scum!
1263 auto s = e.toString();
1264 fprintf(stderr, "\n=== FATAL ===\n%.*s\n", cast(uint)s.length, s.ptr);
1265 abort(); // die, you bitch!
1270 // ////////////////////////////////////////////////////////////////////////// //
1271 shared static ~this () nothrow @trusted /*@nogc*/ { sndEngineDeinit(); }
1274 void sndEngineDeinit () nothrow @trusted {
1275 //{ import core.stdc.stdio; printf("DEINIT...\n"); }
1276 if (cas(&sndWantShutdown, false, true)) {
1277 //{ import core.stdc.stdio; printf("DEINIT: 0\n"); }
1278 if (atomicLoad(initialized)) {
1279 import core.sys.posix.unistd : usleep;
1280 while (!atomicLoad(sndSafeToShutdown0)) { synchronized(mutexCondMixer) condMixerWait.notify(); usleep(1000); } // arbitrary number
1281 while (!atomicLoad(sndSafeToShutdown1)) usleep(1000); // arbitrary number
1282 //{ import core.stdc.stdio; printf("DEINIT: 1\n"); }
1283 sndThread = sndMixThread = null;
1284 sndDeinit();
1286 foreach (ref ch; chans) {
1287 import core.stdc.stdlib : free;
1288 ch.namelen = 0;
1289 ch.chan = null;
1290 if (ch.bufMem !is null) { free(ch.bufMem); ch.bufMem = null; }
1291 ch.srb.deinit();
1294 atomicStore(initialized, false);
1297 atomicStore(sndWantShutdown, false);
1298 atomicStore(sndSafeToShutdown0, false);
1299 atomicStore(sndSafeToShutdown1, false);