2 * Copyright (c) 2016, Ketmar // Invisible Vector
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
8 * - Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION
19 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 /** simple, yet useful blocking ALSA player. can do resampling and has 39-band equalizer */
31 //version = simplealsa_writefile;
34 import iv
.follin
.resampler
;
35 import iv
.follin
.utils
;
36 version(mbandeq_slow
) import iv
.mbandeq_slow
; else import iv
.mbandeq
;
37 version(simplealsa_writefile
) import iv
.vfs
;
40 // ////////////////////////////////////////////////////////////////////////// //
41 public __gshared string alsaDevice
= "default"; /// output device
42 public __gshared
ubyte alsaRQuality
= SpeexResampler
.Music
; /// resampling quality (if required); [0..10]; default is 8
43 public __gshared
int[MBandEq
.Bands
] alsaEqBands
= 0; /// 39-band equalizer options; [-70..30$(RPAREN)
44 public __gshared
int alsaGain
= 0; /// sound gain, in %
45 public __gshared
uint alsaLatencyms
= 100; /// output latency, in milliseconds
46 public __gshared
bool alsaEnableResampling
= true; /// set to `false` to disable resampling (sound can be distorted)
47 public __gshared
bool alsaEnableEqualizer
= false; /// set to `false` to disable equalizer
50 // ////////////////////////////////////////////////////////////////////////// //
51 public @property bool alsaIsOpen () nothrow @trusted @nogc { return (pcm
!is null); } ///
52 public @property uint alsaRate () nothrow @trusted @nogc { return srate
; } ///
53 public @property uint alsaRealRate () nothrow @trusted @nogc { return realsrate
; } ///
54 public @property ubyte alsaChannels () nothrow @trusted @nogc { return cast(ubyte)xxoutchans
; } ///
57 // ////////////////////////////////////////////////////////////////////////// //
58 /// find best (native, if output device is "default") supported sampling rate, or 0 on error
59 public uint alsaGetBestSampleRate (uint wantedRate
) {
60 import std
.internal
.cstring
: tempCString
;
62 if (wantedRate
== 0) wantedRate
= 44110;
65 snd_pcm_hw_params_t
* hwparams
;
67 auto err
= snd_pcm_open(&pcm
, alsaDevice
.tempCString
, SND_PCM_STREAM_PLAYBACK
, SND_PCM_NONBLOCK
);
68 if (err
< 0) return 0;
69 scope(exit
) snd_pcm_close(pcm
);
71 err
= snd_pcm_hw_params_malloc(&hwparams
);
72 if (err
< 0) return 0;
73 scope(exit
) snd_pcm_hw_params_free(hwparams
);
75 err
= snd_pcm_hw_params_any(pcm
, hwparams
);
76 if (err
< 0) return 0;
78 //printf("Device: %s (type: %s)\n", device_name, snd_pcm_type_name(snd_pcm_type(pcm)));
80 if (snd_pcm_hw_params_test_rate(pcm
, hwparams
, wantedRate
, 0) == 0) return wantedRate
;
84 err
= snd_pcm_hw_params_get_rate_min(hwparams
, &min
, null);
85 if (err
< 0) return 0;
87 err
= snd_pcm_hw_params_get_rate_max(hwparams
, &max
, null);
88 if (err
< 0) return 0;
90 if (wantedRate
< min
) return min
;
91 if (wantedRate
> max
) return max
;
93 for (int delta
= 1; delta
< wantedRate
; ++delta
) {
94 if (wantedRate
-delta
< min
&& wantedRate
+delta
> max
) break;
95 if (wantedRate
-delta
> min
) {
96 if (snd_pcm_hw_params_test_rate(pcm
, hwparams
, wantedRate
-delta
, 0) == 0) return wantedRate
-delta
;
98 if (wantedRate
+delta
< max
) {
99 if (snd_pcm_hw_params_test_rate(pcm
, hwparams
, wantedRate
+delta
, 0) == 0) return wantedRate
+delta
;
102 return (wantedRate
-min
< max
-wantedRate ? min
: max
);
106 // ////////////////////////////////////////////////////////////////////////// //
107 version(simplealsa_writefile
) VFile fo
;
108 __gshared snd_pcm_t
* pcm
;
110 __gshared SpeexResampler srb
;
112 __gshared
uint srate
, realsrate
;
113 __gshared MBandEq mbeql
, mbeqr
;
115 enum XXBUF_SIZE
= 4096;
116 __gshared
ubyte[XXBUF_SIZE
*4] xxbuffer
; // just in case
117 __gshared
uint xxbufused
;
118 __gshared
uint xxoutchans
;
121 // ////////////////////////////////////////////////////////////////////////// //
122 void outSoundInit (uint chans
) {
123 if (chans
< 1 || chans
> 2) assert(0, "invalid number of channels");
129 void outSoundFlushX (const(void)* buf
, uint bytes
) {
130 version(simplealsa_writefile
) {
131 auto bb
= cast(const(ubyte)*)buf
;
132 fo
.rawWriteExact(bb
[0..bytes
]);
134 auto bb
= cast(const(short)*)buf
;
135 auto fleft
= bytes
/(2*xxoutchans
);
137 auto frames
= snd_pcm_writei(pcm
, bb
, fleft
);
139 frames
= snd_pcm_recover(pcm
, cast(int)frames
, 0);
141 //import core.stdc.stdio : printf;
142 //printf("snd_pcm_writei failed: %s\n", snd_strerror(cast(int)frames));
145 bb
+= cast(uint)frames
*xxoutchans
;
146 fleft
-= cast(uint)frames
;
153 //TODO: optimize code to avoid multiple float<->short conversions
154 void outSoundFlush () {
155 __gshared
float[XXBUF_SIZE
] rsfbufi
= 0;
156 __gshared
float[XXBUF_SIZE
] rsfbufo
= 0;
158 if (xxbufused
== 0) return;
159 assert(xxbufused
%(2*xxoutchans
) == 0);
160 auto smpCount
= xxbufused
/2;
162 //{ import core.stdc.stdio; printf("smpCount: %u\n", cast(uint)smpCount); }
164 bool didFloat
= false;
165 short* b
= cast(short*)xxbuffer
.ptr
;
169 tflShort2Float(b
[0..smpCount
], rsfbufi
[0..smpCount
]);
170 immutable float gg
= alsaGain
/100.0f;
171 foreach (ref float v
; rsfbufi
[0..smpCount
]) v
+= v
*gg
;
172 //tflFloat2Short(rsfbufi[0..smpCount], b[0..smpCount]);
177 if (alsaEnableEqualizer
) foreach (int v
; alsaEqBands
[]) if (v
!= 0) { doeq
= true; break; }
178 if (doeq
&& alsaEnableEqualizer
) {
181 tflShort2Float(b
[0..smpCount
], rsfbufi
[0..smpCount
]);
183 mbeql
.bands
[] = alsaEqBands
[];
184 if (xxoutchans
== 1) {
185 mbeql
.run(rsfbufo
[0..smpCount
], rsfbufi
[0..smpCount
]);
187 mbeqr
.bands
[] = alsaEqBands
[];
188 mbeql
.run(rsfbufo
[0..smpCount
], rsfbufi
[0..smpCount
], 2, 0);
189 mbeqr
.run(rsfbufo
[0..smpCount
], rsfbufi
[0..smpCount
], 2, 1);
191 rsfbufi
[0..smpCount
] = rsfbufo
[0..smpCount
];
192 //tflFloat2Short(rsfbufo[0..smpCount], b[0..smpCount]);
195 //{ import core.stdc.stdio; printf("smpCount: %u\n", cast(uint)smpCount); }
197 if (srate
== realsrate ||
!alsaEnableResampling
) {
198 // easy deal, no resampling required
199 if (didFloat
) tflFloat2Short(rsfbufi
[0..smpCount
], b
[0..smpCount
]);
200 outSoundFlushX(b
, smpCount
*2);
202 // oops, must resample
203 SpeexResampler
.Data srbdata
;
206 tflShort2Float(b
[0..smpCount
], rsfbufi
[0..smpCount
]);
210 srbdata
= srbdata
.init
; // just in case
211 srbdata
.dataIn
= rsfbufi
[inpos
..smpCount
];
212 srbdata
.dataOut
= rsfbufo
[];
213 if (srb
.process(srbdata
) != 0) assert(0, "resampling error");
214 //{ import core.stdc.stdio; printf("inpos=%u; smpCount=%u; iu=%u; ou=%u\n", cast(uint)inpos, cast(uint)smpCount, cast(uint)srbdata.inputSamplesUsed, cast(uint)srbdata.outputSamplesUsed); }
215 if (srbdata
.outputSamplesUsed
) {
216 tflFloat2Short(rsfbufo
[0..srbdata
.outputSamplesUsed
], b
[0..srbdata
.outputSamplesUsed
]);
217 outSoundFlushX(b
, srbdata
.outputSamplesUsed
*2);
219 // no data consumed, no data produced, so we're done
220 if (inpos
>= smpCount
) break;
222 inpos
+= cast(uint)srbdata
.inputSamplesUsed
;
225 //{ import core.stdc.stdio; printf("OK (%u)\n", cast(uint)xxbufused); }
229 void outSoundS (const(void)* buf
, uint bytes
) {
230 //{ import core.stdc.stdio; printf("outSoundS: %u\n", bytes); }
231 auto src
= cast(const(ubyte)*)buf
;
233 while (bytes
> 0 && xxbufused
< XXBUF_SIZE
) {
234 xxbuffer
.ptr
[xxbufused
++] = *src
++;
237 if (xxbufused
== XXBUF_SIZE
) outSoundFlush();
239 //{ import core.stdc.stdio; printf("outSoundS: DONE\n"); }
243 void outSoundF (const(void)* buf
, uint bytes
) {
244 __gshared
short[XXBUF_SIZE
] cvtbuffer
;
245 auto len
= bytes
/float.sizeof
;
246 assert(len
<= cvtbuffer
.length
);
247 tflFloat2Short((cast(const(float)*)buf
)[0..len
], cvtbuffer
[0..len
]);
248 outSoundS(cvtbuffer
.ptr
, cast(uint)(len
*2));
252 // ////////////////////////////////////////////////////////////////////////// //
254 public void alsaShutdown (bool immediate
=false) {
264 srate
= realsrate
= 0;
266 version(simplealsa_writefile
) fo
.close();
270 // ////////////////////////////////////////////////////////////////////////// //
271 /// (re)initialize player; return success flag
272 public bool alsaInit (uint asrate
, ubyte chans
) {
273 import std
.internal
.cstring
: tempCString
;
276 fuck_alsa_messages();
278 if (asrate
< 1024 || asrate
> 96000) return false;
279 if (chans
< 1 || chans
> 2) return false;
282 if (asrate
== 44100 || asrate
== 48000) {
283 realsrate
= alsaGetBestSampleRate(asrate
);
285 realsrate
= alsaGetBestSampleRate(48000);
287 if (realsrate
== 0) return false; // alas
289 if (realsrate
!= srate
) {
290 srb
.setup(chans
, srate
, realsrate
, alsaRQuality
);
300 if ((err
= snd_pcm_open(&pcm
, alsaDevice
.tempCString
, SND_PCM_STREAM_PLAYBACK
, 0)) < 0) {
301 //import core.stdc.stdlib : exit, EXIT_FAILURE;
302 //conwriteln("Playback open error for device '%s': %s", device, snd_strerror(err));
303 //exit(EXIT_FAILURE);
306 //scope(exit) snd_pcm_close(pcm);
308 if ((err
= snd_pcm_set_params(pcm
, SND_PCM_FORMAT_S16_LE
, SND_PCM_ACCESS_RW_INTERLEAVED
, chans
, /*sio.rate*/realsrate
, 1, /*500000*//*20000*/alsaLatencyms
*1000)) < 0) {
309 //import core.stdc.stdlib : exit, EXIT_FAILURE;
310 //conwriteln("Playback open error: %s", snd_strerror(err));
311 //exit(EXIT_FAILURE);
317 version(simplealsa_writefile
) fo
= VFile("./zout.raw", "w");
323 // ////////////////////////////////////////////////////////////////////////// //
324 /// write (interleaved) buffer
325 public void alsaWriteShort (const(short)[] buf
) {
326 if (pcm
is null || buf
.length
== 0) return;
327 if (buf
.length
>= 1024*1024) assert(0, "too much");
328 outSoundS(buf
.ptr
, cast(uint)(buf
.length
*buf
[0].sizeof
));
332 /// write (interleaved) buffer
333 public void alsaWriteFloat (const(float)[] buf
) {
334 if (pcm
is null || buf
.length
== 0) return;
335 if (buf
.length
>= 1024*1024) assert(0, "too much");
336 outSoundF(buf
.ptr
, cast(uint)(buf
.length
*buf
[0].sizeof
));