textlayouter: added simple and incomplete text selection tools
[iv.d.git] / _obsolete_dont_use / simplealsa_mbeq.d
blob91eb1b61cf497f2605631ead270751a3d20cfb5e
1 /*
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
6 * are met:
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 */
28 module iv.simplealsa;
29 private:
31 //version = simplealsa_writefile;
33 import iv.alsa;
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;
64 snd_pcm_t* pcm;
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;
82 uint min, max;
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");
124 xxbufused = 0;
125 xxoutchans = chans;
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]);
133 } else {
134 auto bb = cast(const(short)*)buf;
135 auto fleft = bytes/(2*xxoutchans);
136 while (fleft > 0) {
137 auto frames = snd_pcm_writei(pcm, bb, fleft);
138 if (frames < 0) {
139 frames = snd_pcm_recover(pcm, cast(int)frames, 0);
140 if (frames < 0) {
141 //import core.stdc.stdio : printf;
142 //printf("snd_pcm_writei failed: %s\n", snd_strerror(cast(int)frames));
144 } else {
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;
161 xxbufused = 0;
162 //{ import core.stdc.stdio; printf("smpCount: %u\n", cast(uint)smpCount); }
164 bool didFloat = false;
165 short* b = cast(short*)xxbuffer.ptr;
166 // do gain
167 if (alsaGain) {
168 didFloat = true;
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]);
175 // equalizer
176 bool doeq = false;
177 if (alsaEnableEqualizer) foreach (int v; alsaEqBands[]) if (v != 0) { doeq = true; break; }
178 if (doeq && alsaEnableEqualizer) {
179 if (!didFloat) {
180 didFloat = true;
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]);
186 } else {
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); }
196 // need resampling?
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);
201 } else {
202 // oops, must resample
203 SpeexResampler.Data srbdata;
204 if (!didFloat) {
205 didFloat = true;
206 tflShort2Float(b[0..smpCount], rsfbufi[0..smpCount]);
208 uint inpos = 0;
209 for (;;) {
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);
218 } else {
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;
232 while (bytes > 0) {
233 while (bytes > 0 && xxbufused < XXBUF_SIZE) {
234 xxbuffer.ptr[xxbufused++] = *src++;
235 --bytes;
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 // ////////////////////////////////////////////////////////////////////////// //
253 /// shutdown player
254 public void alsaShutdown (bool immediate=false) {
255 if (pcm !is null) {
256 if (immediate) {
257 snd_pcm_drop(pcm);
258 } else {
259 snd_pcm_drain(pcm);
261 snd_pcm_close(pcm);
262 pcm = null;
264 srate = realsrate = 0;
265 xxoutchans = 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;
275 alsaShutdown(true);
276 fuck_alsa_messages();
278 if (asrate < 1024 || asrate > 96000) return false;
279 if (chans < 1 || chans > 2) return false;
281 srate = asrate;
282 if (asrate == 44100 || asrate == 48000) {
283 realsrate = alsaGetBestSampleRate(asrate);
284 } else {
285 realsrate = alsaGetBestSampleRate(48000);
287 if (realsrate == 0) return false; // alas
289 if (realsrate != srate) {
290 srb.setup(chans, srate, realsrate, alsaRQuality);
293 mbeql.setup(srate);
294 mbeqr.setup(srate);
296 outSoundInit(chans);
298 int err;
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);
304 return false;
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);
312 snd_pcm_close(pcm);
313 pcm = null;
314 return false;
317 version(simplealsa_writefile) fo = VFile("./zout.raw", "w");
319 return true;
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));