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
.synth
.sfxr
/*is aliced*/;
20 import iv
.follin
.engine
: TflChannel
;
22 static if (__traits(compiles
, () { import iv
.stream
; })) {
26 // ////////////////////////////////////////////////////////////////////////// //
30 static ulong nextrand64 (ref ulong seed) nothrow @trusted @nogc {
31 static if (__VERSION__ > 2067) pragma(inline, true);
32 if (seed == 0) seed = 0x29a; // arbitrary number
33 seed ^= seed>>12; // a
34 seed ^= seed<<25; // b
35 seed ^= seed>>27; // c
36 return seed*0x2545f4914f6cdd1dUL;
39 static uint nextrand32 (ref ulong seed) nothrow @trusted @nogc {
40 static if (__VERSION__ > 2067) pragma(inline, true);
41 if (seed == 0) seed = 0x29a; // arbitrary number
42 seed ^= seed>>12; // a
43 seed ^= seed<<25; // b
44 seed ^= seed>>27; // c
45 return (seed*0x2545f4914f6cdd1dUL)&0xffff_ffffu;
49 static uint nextrand32 (ref int seed
) nothrow @trusted @nogc {
50 static if (__VERSION__
> 2067) pragma(inline
, true);
51 if (!seed
) seed
= 0x29a; // arbitrary number
53 return cast(uint)seed
;
56 // fast floating point rand, suitable for noise
57 align(1) static union FITrick
{
63 // gives [0..1] result (wow!)
64 static float nextfrand (ref int seed
) nothrow @trusted @nogc {
65 static if (__VERSION__
> 2067) pragma(inline
, true);
66 if (!seed
) seed
= 0x29a; // arbitrary number
69 fi
.ires
= (((cast(uint)seed
)>>9)|
0x3f800000);
73 // gives [-1..1] result (wow!)
74 static float nextfrandneg (ref int seed
) nothrow @trusted @nogc {
75 static if (__VERSION__
> 2067) pragma(inline
, true);
76 if (!seed
) seed
= 0x29a; // arbitrary number
79 fi
.ires
= (((cast(uint)seed
)>>9)|
0x3f800000);
81 fi
.ires ^
= cast(uint)seed
&0x8000_0000u;
92 int wave_type
= Type
.Square
;
94 float p_base_freq
= 0.3f;
95 float p_freq_limit
= 0.0f;
96 float p_freq_ramp
= 0.0f;
97 float p_freq_dramp
= 0.0f;
99 float p_duty_ramp
= 0.0f;
101 float p_vib_strength
= 0.0f;
102 float p_vib_speed
= 0.0f;
103 float p_vib_delay
= 0.0f;
105 float p_env_attack
= 0.0f;
106 float p_env_sustain
= 0.3f;
107 float p_env_decay
= 0.4f;
108 float p_env_punch
= 0.0f;
110 ubyte filter_on
= false; // bool
111 float p_lpf_resonance
= 0.0f;
112 float p_lpf_freq
= 1.0f;
113 float p_lpf_ramp
= 0.0f;
114 float p_hpf_freq
= 0.0f;
115 float p_hpf_ramp
= 0.0f;
117 float p_pha_offset
= 0.0f;
118 float p_pha_ramp
= 0.0f;
120 float p_repeat_speed
= 0.0f;
122 float p_arp_speed
= 0.0f;
123 float p_arp_mod
= 0.0f;
125 float sound_vol
= 0.5f;
127 int origSeed
= 0x29a, curseed
= 0x29a;
130 void setSeed (int seed
) nothrow @trusted @nogc { origSeed
= curseed
= seed
; }
132 void reset () nothrow @safe @nogc {
135 origSeed
= curseed
= sd
;
138 void load (const(void)[] buf
) {
139 if (buf
.length
!= 105) throw new NamedException
!"SFXR"("invalid sfxr init buffer");
140 auto ms
= MemoryStreamRO(buf
);
144 void load(ST
) (auto ref ST st
) if (isReadableStream
!ST
) {
145 scope(failure
) reset();
147 auto ver
= st
.readNum
!int();
148 if (ver
!= 100 && ver
!= 101 && ver
!= 102) throw new NamedException
!"SFXR"("invalid stream version");
150 wave_type
= st
.readNum
!int();
151 if (wave_type
< 0 || wave_type
> Type
.max
) throw new NamedException
!"SFXR"("invalid stream wave type");
153 sound_vol
= (ver
>= 102 ? st
.readNum
!float() : 0.5f);
155 p_base_freq
= st
.readNum
!float();
156 p_freq_limit
= st
.readNum
!float();
157 p_freq_ramp
= st
.readNum
!float();
158 p_freq_dramp
= (ver
>= 101 ? st
.readNum
!float() : 0.0f);
159 p_duty
= st
.readNum
!float();
160 p_duty_ramp
= st
.readNum
!float();
162 p_vib_strength
= st
.readNum
!float();
163 p_vib_speed
= st
.readNum
!float();
164 p_vib_delay
= st
.readNum
!float();
166 p_env_attack
= st
.readNum
!float();
167 p_env_sustain
= st
.readNum
!float();
168 p_env_decay
= st
.readNum
!float();
169 p_env_punch
= st
.readNum
!float();
171 filter_on
= st
.readNum
!ubyte();
172 p_lpf_resonance
= st
.readNum
!float();
173 p_lpf_freq
= st
.readNum
!float();
174 p_lpf_ramp
= st
.readNum
!float();
175 p_hpf_freq
= st
.readNum
!float();
176 p_hpf_ramp
= st
.readNum
!float();
178 p_pha_offset
= st
.readNum
!float();
179 p_pha_ramp
= st
.readNum
!float();
181 p_repeat_speed
= st
.readNum
!float();
184 p_arp_speed
= st
.readNum
!float();
185 p_arp_mod
= st
.readNum
!float();
192 void save(ST
) (auto ref ST st
) if (isWriteableStream
!ST
) {
193 st
.writeNum
!int(102); // version
195 st
.writeNum
!int(wave_type
);
197 st
.writeNum
!float(sound_vol
);
199 st
.writeNum
!float(p_base_freq
);
200 st
.writeNum
!float(p_freq_limit
);
201 st
.writeNum
!float(p_freq_ramp
);
202 st
.writeNum
!float(p_freq_dramp
);
203 st
.writeNum
!float(p_duty
);
204 st
.writeNum
!float(p_duty_ramp
);
206 st
.writeNum
!float(p_vib_strength
);
207 st
.writeNum
!float(p_vib_speed
);
208 st
.writeNum
!float(p_vib_delay
);
210 st
.writeNum
!float(p_env_attack
);
211 st
.writeNum
!float(p_env_sustain
);
212 st
.writeNum
!float(p_env_decay
);
213 st
.writeNum
!float(p_env_punch
);
215 st
.writeNum
!ubyte(filter_on
);
216 st
.writeNum
!float(p_lpf_resonance
);
217 st
.writeNum
!float(p_lpf_freq
);
218 st
.writeNum
!float(p_lpf_ramp
);
219 st
.writeNum
!float(p_hpf_freq
);
220 st
.writeNum
!float(p_hpf_ramp
);
222 st
.writeNum
!float(p_pha_offset
);
223 st
.writeNum
!float(p_pha_ramp
);
225 st
.writeNum
!float(p_repeat_speed
);
227 st
.writeNum
!float(p_arp_speed
);
228 st
.writeNum
!float(p_arp_mod
);
231 void exportWAV(string mode
, BT
, ST
) (auto ref ST st
)
232 if ((is(BT
== byte) ||
is(BT
== short)) && (mode
== "mono" || mode
== "stereo") && isWriteableStream
!ST
&& isSeekableStream
!ST
)
234 static if (is(BT
== byte)) {
235 enum bitsPerSample
= 8;
237 enum bitsPerSample
= 16;
239 static if (mode
== "mono") {
244 enum sampleRate
= 44100;
247 auto fszpos
= st
.tell
;
248 st
.writeNum
!uint(0); // remaining file size
252 st
.writeNum
!uint(16); // chunk size
253 st
.writeNum
!ushort(1); // compression code
254 st
.writeNum
!ushort(numChans
); // channels
255 st
.writeNum
!uint(sampleRate
); // sample rate
256 st
.writeNum
!uint(sampleRate
*(bitsPerSample
*numChans
)/8); // bytes per second
257 st
.writeNum
!ushort((bitsPerSample
*numChans
)/8); // block align
258 st
.writeNum
!ushort(bitsPerSample
); // bits per sample
261 auto dszpos
= st
.tell
;
262 st
.writeNum
!uint(0); // chunk size
264 BT
[256*numChans
] asmp
;
265 auto smp
= SfxrSample(this);
268 smp
.fillBuffer
!(mode
, BT
)(asmp
[]);
270 dataSize
+= cast(uint)(asmp
.length
*asmp
[0].sizeof
);
271 } while (smp
.playing
);
275 st
.writeNum
!uint(dataSize
);
277 st
.writeNum
!uint(epos
-8);
282 void exportRaw(string mode
, BT
, ST
) (auto ref ST st
)
283 if ((is(BT
== byte) ||
is(BT
== short)) && (mode
== "mono" || mode
== "stereo") && isWriteableStream
!ST
)
285 static if (is(BT
== byte)) {
286 enum bitsPerSample
= 8;
288 enum bitsPerSample
= 16;
290 static if (mode
== "mono") {
295 enum sampleRate
= 44100;
297 BT
[256*numChans
] asmp
;
298 auto smp
= SfxrSample(this);
301 smp
.fillBuffer
!(mode
, BT
)(asmp
[]);
303 dataSize
+= cast(uint)(asmp
.length
*asmp
[0].sizeof
);
304 } while (smp
.playing
);
307 uint rnd () nothrow @trusted @nogc { static if (__VERSION__
> 2067) pragma(inline
, true); return nextrand32(curseed
); }
308 float frnd (float range
) nothrow @trusted @nogc { static if (__VERSION__
> 2067) pragma(inline
, true); return nextfrand(curseed
)*range
; }
310 void rndPickup () nothrow @safe @nogc {
312 p_base_freq
= 0.4f+frnd(0.5f);
314 p_env_sustain
= frnd(0.1f);
315 p_env_decay
= 0.1f+frnd(0.4f);
316 p_env_punch
= 0.3f+frnd(0.3f);
318 p_arp_speed
= 0.5f+frnd(0.2f);
319 p_arp_mod
= 0.2f+frnd(0.4f);
323 void rndLaser () nothrow @safe @nogc {
326 if (wave_type
==2 && rnd()%2) wave_type
= rnd()%2;
327 p_base_freq
= 0.5f+frnd(0.5f);
328 p_freq_limit
= p_base_freq
-0.2f-frnd(0.6f);
329 if (p_freq_limit
< 0.2f) p_freq_limit
= 0.2f;
330 p_freq_ramp
= -0.15f-frnd(0.2f);
332 p_base_freq
= 0.3f+frnd(0.6f);
333 p_freq_limit
= frnd(0.1f);
334 p_freq_ramp
= -0.35f-frnd(0.3f);
338 p_duty_ramp
= frnd(0.2f);
340 p_duty
= 0.4f+frnd(0.5f);
341 p_duty_ramp
= -frnd(0.7f);
344 p_env_sustain
= 0.1f+frnd(0.2f);
345 p_env_decay
= frnd(0.4f);
346 if (rnd()%2) p_env_punch
= frnd(0.3f);
348 p_pha_offset
= frnd(0.2f);
349 p_pha_ramp
= -frnd(0.2f);
351 if (rnd()%2) p_hpf_freq
= frnd(0.3f);
354 void rndExplosion () nothrow @safe @nogc {
356 wave_type
= Type
.Noise
;
358 p_base_freq
= 0.1f+frnd(0.4f);
359 p_freq_ramp
= -0.1f+frnd(0.4f);
361 p_base_freq
= 0.2f+frnd(0.7f);
362 p_freq_ramp
= -0.2f-frnd(0.2f);
364 p_base_freq
*= p_base_freq
;
365 if (rnd()%5 == 0) p_freq_ramp
= 0.0f;
366 if (rnd()%3 == 0) p_repeat_speed
= 0.3f+frnd(0.5f);
368 p_env_sustain
= 0.1f+frnd(0.3f);
369 p_env_decay
= frnd(0.5f);
371 p_pha_offset
= -0.3f+frnd(0.9f);
372 p_pha_ramp
= -frnd(0.3f);
374 p_env_punch
= 0.2f+frnd(0.6f);
376 p_vib_strength
= frnd(0.7f);
377 p_vib_speed
= frnd(0.6f);
380 p_arp_speed
= 0.6f+frnd(0.3f);
381 p_arp_mod
= 0.8f-frnd(1.6f);
385 void rndPowerup () nothrow @safe @nogc {
387 if (rnd()%2) wave_type
= Type
.Sawtooth
; else p_duty
= frnd(0.6f);
389 p_base_freq
= 0.2f+frnd(0.3f);
390 p_freq_ramp
= 0.1f+frnd(0.4f);
391 p_repeat_speed
= 0.4f+frnd(0.4f);
393 p_base_freq
= 0.2f+frnd(0.3f);
394 p_freq_ramp
= 0.05f+frnd(0.2f);
396 p_vib_strength
= frnd(0.7f);
397 p_vib_speed
= frnd(0.6f);
401 p_env_sustain
= frnd(0.4f);
402 p_env_decay
= 0.1f+frnd(0.4f);
405 void rndHurt () nothrow @safe @nogc {
408 if (wave_type
== Type
.Sine
) wave_type
= Type
.Noise
;
409 if (wave_type
== Type
.Square
) p_duty
= frnd(0.6f);
410 p_base_freq
= 0.2f+frnd(0.6f);
411 p_freq_ramp
= -0.3f-frnd(0.4f);
413 p_env_sustain
= frnd(0.1f);
414 p_env_decay
= 0.1f+frnd(0.2f);
415 if (rnd()%2) p_hpf_freq
= frnd(0.3f);
418 void rndJump () nothrow @safe @nogc {
420 wave_type
= Type
.Square
;
422 p_base_freq
= 0.3f+frnd(0.3f);
423 p_freq_ramp
= 0.1f+frnd(0.2f);
425 p_env_sustain
= 0.1f+frnd(0.3f);
426 p_env_decay
= 0.1f+frnd(0.2f);
427 if (rnd()%2) p_hpf_freq
= frnd(0.3f);
428 if (rnd()%2) p_lpf_freq
= 1.0f-frnd(0.6f);
431 void rndBlip () nothrow @safe @nogc {
434 if (wave_type
== Type
.Square
) p_duty
= frnd(0.6f);
435 p_base_freq
= 0.2f+frnd(0.4f);
437 p_env_sustain
= 0.1f+frnd(0.1f);
438 p_env_decay
= frnd(0.2f);
444 // ////////////////////////////////////////////////////////////////////////// //
445 public struct SfxrSample
{
446 bool playing_sample
= false;
448 float master_vol
= 0.5f;
466 float[1024] phaser_buffer
;
468 float[32] noise_buffer
;
487 this() (in auto ref Sfxr sfx
) { reset(sfx
); curseed
= sfx
.origSeed
; }
489 void setSeed (int seed
) nothrow @trusted @nogc { curseed
= seed
; }
491 //float frnd (float range) nothrow @trusted @nogc { static if (__VERSION__ > 2067) pragma(inline, true); return (1.0f/16384.0f)*range*(Sfxr.nextrand32(curseed)%16384); }
493 @property bool playing () const pure nothrow @safe @nogc { return playing_sample
; }
495 void resetLoop () nothrow @safe @nogc {
496 import std
.math
: abs
, pow
;
498 fperiod
= 100.0/(fx
.p_base_freq
*fx
.p_base_freq
+0.001);
499 period
= cast(int)fperiod
;
500 fmaxperiod
= 100.0/(fx
.p_freq_limit
*fx
.p_freq_limit
+0.001);
501 fslide
= 1.0-pow(cast(double)fx
.p_freq_ramp
, 3.0)*0.01;
502 fdslide
= -pow(cast(double)fx
.p_freq_dramp
, 3.0)*0.000001;
503 square_duty
= 0.5f-fx
.p_duty
*0.5f;
504 square_slide
= -fx
.p_duty_ramp
*0.00005f;
505 if (fx
.p_arp_mod
>= 0.0f) {
506 arp_mod
= 1.0-pow(cast(double)fx
.p_arp_mod
, 2.0)*0.9;
508 arp_mod
= 1.0+pow(cast(double)fx
.p_arp_mod
, 2.0)*10.0;
511 arp_limit
= cast(int)(pow(1.0f-fx
.p_arp_speed
, 2.0f)*20000+32);
512 if (fx
.p_arp_speed
== 1.0f) arp_limit
= 0;
515 void reset() (in auto ref Sfxr sfx
) nothrow @trusted @nogc {
516 import std
.math
: abs
, pow
;
524 fltw
= pow(fx
.p_lpf_freq
, 3.0f)*0.1f;
525 fltw_d
= 1.0f+fx
.p_lpf_ramp
*0.0001f;
526 fltdmp
= 5.0f/(1.0f+pow(fx
.p_lpf_resonance
, 2.0f)*20.0f)*(0.01f+fltw
);
527 if (fltdmp
> 0.8f) fltdmp
= 0.8f;
529 flthp
= pow(fx
.p_hpf_freq
, 2.0f)*0.1f;
530 flthp_d
= 1.0+fx
.p_hpf_ramp
*0.0003f;
533 vib_speed
= pow(fx
.p_vib_speed
, 2.0f)*0.01f;
534 vib_amp
= fx
.p_vib_strength
*0.5f;
539 env_length
[0] = cast(int)(fx
.p_env_attack
*fx
.p_env_attack
*100000.0f);
540 env_length
[1] = cast(int)(fx
.p_env_sustain
*fx
.p_env_sustain
*100000.0f);
541 env_length
[2] = cast(int)(fx
.p_env_decay
*fx
.p_env_decay
*100000.0f);
543 fphase
= pow(fx
.p_pha_offset
, 2.0f)*1020.0f;
544 if (fx
.p_pha_offset
< 0.0f) fphase
= -fphase
;
545 fdphase
= pow(fx
.p_pha_ramp
, 2.0f)*1.0f;
546 if (fx
.p_pha_ramp
< 0.0f) fdphase
= -fdphase
;
547 iphase
= abs(cast(int)fphase
);
549 phaser_buffer
[] = 0.0f;
551 auto nb
= noise_buffer
.ptr
;
552 foreach (immutable _
; 0..noise_buffer
.length
) *nb
++ = /*frnd(2.0f)-1.0f*/Sfxr
.nextfrandneg(curseed
);
556 rep_limit
= cast(int)(pow(1.0f-fx
.p_repeat_speed
, 2.0f)*20000+32);
557 if (fx
.p_repeat_speed
== 0.0f) rep_limit
= 0;
559 playing_sample
= true;
562 // return `true` if the whole frame was silent
563 void fillBuffer(string mode
, BT
) (BT
[] buffer
) nothrow @trusted @nogc
564 if ((is(BT
== byte) ||
is(BT
== short) ||
is(BT
== float)) && (mode
== "mono" || mode
== "stereo"))
566 import std
.math
: abs
, pow
, sin
, PI
;
567 //static assert(is(BT == float));
568 //static assert(mode == "mono");
570 static if (mode
== "stereo") {
575 foreach (immutable bidx
; 0..buffer
.length
) {
576 auto bufel
= buffer
.ptr
+bidx
;
577 static if (mode
== "stereo") {
580 static if (is(BT
== float)) *bufel
= smp
;
581 else static if (is(BT
== byte)) *bufel
= cast(byte)(smp
*127);
582 else static if (is(BT
== short)) *bufel
= cast(short)(smp
*32767);
583 else static assert(0, "wtf?!");
587 //if (!playing_sample) break; //FIXME: silence buffer?
588 if (!playing_sample
) {
589 //static if (is(BT == ubyte)) bufel = 0.0f;
595 if (rep_limit
!= 0 && rep_time
>= rep_limit
) {
600 // frequency envelopes/arpeggios
602 if (arp_limit
!= 0 && arp_time
>= arp_limit
) {
608 if (fperiod
> fmaxperiod
) {
609 fperiod
= fmaxperiod
;
610 if (fx
.p_freq_limit
> 0.0f) playing_sample
= false;
612 float rfperiod
= fperiod
;
613 if (vib_amp
> 0.0f) {
614 vib_phase
+= vib_speed
;
615 rfperiod
= fperiod
*(1.0+sin(vib_phase
)*vib_amp
);
617 period
= cast(int)rfperiod
;
618 if (period
< 8) period
= 8;
619 square_duty
+= square_slide
;
620 if (square_duty
< 0.0f) square_duty
= 0.0f;
621 if (square_duty
> 0.5f) square_duty
= 0.5f;
624 if (env_time
> env_length
[env_stage
]) {
627 if (env_stage
== 3) playing_sample
= false;
629 if (env_stage
== 0) env_vol
= cast(float)env_time
/env_length
[0];
630 if (env_stage
== 1) env_vol
= 1.0f+pow(1.0f-cast(float)env_time
/env_length
[1], 1.0f)*2.0f*fx
.p_env_punch
;
631 if (env_stage
== 2) env_vol
= 1.0f-cast(float)env_time
/env_length
[2];
635 iphase
= abs(cast(int)fphase
);
636 if (iphase
> 1023) iphase
= 1023;
638 if (flthp_d
!= 0.0f) {
640 if (flthp
< 0.00001f) flthp
= 0.00001f;
641 if (flthp
> 0.1f) flthp
= 0.1f;
644 float ssample
= 0.0f;
646 foreach (immutable _
; 0..8) {
649 if (phase
>= period
) {
652 if (fx
.wave_type
== Sfxr
.Type
.Noise
) {
653 auto nb
= noise_buffer
.ptr
;
654 foreach (immutable _0
; 0..noise_buffer
.length
) *nb
++ = /*frnd(2.0f)-1.0f*/Sfxr
.nextfrandneg(curseed
);
658 float fp
= cast(float)phase
/period
;
659 final switch (fx
.wave_type
) with (Sfxr
.Type
) {
660 case Square
: sample
= (fp
< square_duty ?
0.5f : -0.5f); break;
661 case Sawtooth
: sample
= 1.0f-fp
*2; break;
662 case Sine
: sample
= cast(float)sin(fp
*2*PI
); break;
663 case Noise
: sample
= noise_buffer
[phase
*32/period
]; break;
668 if (fltw
< 0.0f) fltw
= 0.0f;
669 if (fltw
> 0.1f) fltw
= 0.1f;
670 if (fx
.p_lpf_freq
!= 1.0f) {
671 fltdp
+= (sample
-fltp
)*fltw
;
672 fltdp
-= fltdp
*fltdmp
;
680 fltphp
-= fltphp
*flthp
;
683 phaser_buffer
[ipp
&1023] = sample
;
684 sample
+= phaser_buffer
[(ipp
-iphase
+1024)&1023];
686 // final accumulation and envelope application
687 ssample
+= sample
*env_vol
;
689 ssample
= ssample
/8*master_vol
;
690 ssample
*= 2.0f*fx
.sound_vol
;
693 static if (mode
== "mono") float smp
= ssample
; else smp
= ssample
;
694 if (smp
> 1.0f) smp
= 1.0f;
695 if (smp
< -1.0f) smp
= -1.0f;
696 static if (is(BT
== float)) *bufel
= smp
;
697 else static if (is(BT
== byte)) *bufel
= cast(byte)(smp
*127);
698 else static if (is(BT
== short)) *bufel
= cast(short)(smp
*32767);
699 else static assert(0, "wtf?!");
700 static if (mode
== "stereo") smpdup
= true;
707 // ////////////////////////////////////////////////////////////////////////// //
708 public class SfxChannel
: TflChannel
{
710 //shared bool sfxdone = false;
712 this() (in auto ref Sfxr sfxr
) { smp
.reset(sfxr
); }
714 override uint fillFrames (float[] buf
) nothrow {
715 if (!smp
.playing
) return 0; // no more
716 //{ import core.stdc.stdio; printf("filling sfx buffer... (%u)\n", (smp.playing ? 1 : 0)); }
717 smp
.fillBuffer
!"stereo"(buf
[]);
718 return buf
.length
/2; // return number of frames
719 //{ import core.stdc.stdio; printf("filled sfx buffer... (%u)\n", (smp.playing ? 1 : 0)); }
723 override void discarded () nothrow {
724 import core.stdc.stdio;
725 printf("sfx complete\n");
726 atomicStore(sfxdone, true);