more scanline shader parameters
[zxemut.git] / soundalsa.d
blob0cb4089de088b0c971ef14365496be9c205967ef
1 /* Sound support
2 * Copyright (c) 2017 Ketmar // Invisible Vector
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
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 along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 * modified by Ketmar // Invisible Vector
20 module soundalsa;
21 private:
23 import iv.alsa;
24 import iv.cmdcon;
25 import iv.pxclock;
27 //version = alsa_simple;
28 //version = alsa_debug;
29 //version = alsa_time_debug;
31 __gshared snd_pcm_t* pcm;
32 __gshared uint pcmChans = 2;
33 __gshared uint pcmRate;
34 version(alsa_simple) {} else {
35 __gshared bool sndJustStarted = true;
36 __gshared snd_pcm_uframes_t realBufSize;
40 ///
41 public bool soundrvInit (bool stereo, ref uint sampleRate) {
42 soundrvDeinit();
43 pcmChans = (stereo ? 2 : 1);
44 version(alsa_simple) {
45 int err;
46 if ((err = snd_pcm_open(&pcm, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
47 conwriteln("ALSA error: ", snd_strerror(err));
48 pcm = null; // just in case
49 return false;
51 if ((err = snd_pcm_set_params(pcm, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, pcmChans, sampleRate, 0, /*500000*/1000000/50)) < 0) {
52 conwriteln("ALSA error: ", snd_strerror(err));
53 snd_pcm_close(pcm);
54 pcm = null;
55 return false;
57 pcmRate = sampleRate;
58 } else {
59 sndJustStarted = true;
60 uint sr = sampleRate;
61 try {
62 pcm = alsaInit(null, sr, cast(ubyte)pcmChans);
63 } catch (Exception e) {
64 conwriteln("ALSA error: ", e.msg);
65 pcm = null;
66 return false;
68 sampleRate = sr;
70 return true;
74 ///
75 public void soundrvDeinit () {
76 if (pcm !is null) {
77 snd_pcm_close(pcm);
78 pcm = null;
83 ///
84 public void soundrvFrame (const(short)* smpbuf, int frmcount) nothrow @trusted {
85 auto frms = frmcount; // now in frames
86 version(alsa_simple) {
87 version(alsa_time_debug) auto stc = clockMilli;
88 while (frms > 0) {
89 snd_pcm_sframes_t frames = snd_pcm_writei(pcm, smpbuf, frms);
90 if (frames < 0) {
91 frames = snd_pcm_recover(pcm, cast(int)frames, 0);
92 if (frames < 0) {
93 import core.stdc.stdio : printf;
94 import core.stdc.stdlib : exit, EXIT_FAILURE;
95 printf("snd_pcm_writei failed: %s\n", snd_strerror(cast(int)frames));
96 //exit(EXIT_FAILURE);
97 assert(0);
99 } else {
100 frms -= frames;
101 smpbuf += frames*pcmChans;
104 } else {
105 int averrcount = 0;
106 if (sndJustStarted) {
107 sndJustStarted = false;
108 __gshared short[1024] sbuf;
109 if (snd_pcm_prepare(pcm) != 0) assert(0, "oops"); // alas
110 if (snd_pcm_start(pcm) != 0) assert(0, "oops"); // alas
111 for (;;) {
112 auto avail = snd_pcm_avail/*_update*/(pcm); // "_update" for mmaped i/o
113 if (avail < 0) {
114 import core.stdc.errno : EPIPE, EINTR, ESTRPIPE;
115 import core.stdc.stdio;
116 if (avail != -EPIPE && avail != -EINTR && avail != -ESTRPIPE) {
117 //fprintf(stderr, "ALSA ERROR: %s\n", snd_strerror(cast(int)avail));
118 assert(0);
120 snd_pcm_recover(pcm, cast(int)avail, 1);
121 if (++averrcount > 16) assert(0);
122 continue;
124 averrcount = 0;
125 version(alsa_time_debug) { import core.stdc.stdio; stderr.fprintf("first fill: %u\n", cast(uint)avail); }
126 //sbuf.length = avail*pcmChans;
127 auto xleft = avail;
128 while (xleft > 0) {
129 auto wrx = cast(uint)(avail > sbuf.length/2 ? sbuf.length/2 : avail);
130 auto xerr = snd_pcm_writei(pcm, sbuf.ptr, wrx);
131 if (xerr < 0) {
132 snd_pcm_recover(pcm, cast(int)xerr, 1);
133 } else {
134 version(alsa_time_debug) { import core.stdc.stdio; stderr.fprintf("first wrote: %u\n", cast(uint)xerr); }
136 xleft -= wrx;
138 break;
141 version(alsa_time_debug) auto stc = clockMilli;
142 auto fleft = frmcount;
143 averrcount = 0;
144 int awaitcount = 0;
145 while (fleft > 0) {
146 auto avail = snd_pcm_avail/*_update*/(pcm); // "_update" for mmaped i/o
147 //{ import core.stdc.stdio : stderr, fprintf; stderr.fprintf("AVAIL: %d\n", cast(int)avail); }
148 if (avail < 0) {
149 /*{ import core.stdc.stdio : stderr, fprintf; stderr.fprintf("AVAILBAD: %d\n", cast(int)avail); }*/
150 import core.stdc.errno : EPIPE, EINTR, ESTRPIPE;
151 import core.stdc.stdio;
152 if (avail != -EPIPE && avail != -EINTR && avail != -ESTRPIPE) {
153 //fprintf(stderr, "ALSA ERROR: %s\n", snd_strerror(cast(int)avail));
154 assert(0);
156 snd_pcm_recover(pcm, cast(int)avail, 1);
157 if (++averrcount > 16) assert(0);
158 continue;
160 averrcount = 0;
161 // now wait or write
162 auto used = realBufSize-avail;
163 if (used <= fleft || awaitcount >= 2) {
164 awaitcount = 0;
165 version(alsa_debug) { import core.stdc.stdio; stderr.fprintf("avail: %u; used: %u\n", cast(uint)avail, cast(uint)used); }
166 auto err = snd_pcm_writei(pcm, smpbuf, fleft);
167 if (err < 0) {
168 import core.stdc.stdio : fprintf, stderr;
169 import core.stdc.errno : EPIPE, EINTR, ESTRPIPE;
170 if (err == -EPIPE /*|| err == -EINTR || err == -ESTRPIPE*/) {
171 } else if (err == -11) {
172 // we can't write, that's wrong
173 fprintf(stderr, "ALSA: write failed (%s)\n", snd_strerror(cast(int)err));
174 } else {
175 fprintf(stderr, "ALSA: write failed %d (%s)\n", cast(int)err, snd_strerror(cast(int)err));
177 snd_pcm_recover(pcm, cast(int)err, 1);
178 //fleft = sndSamplesSize/2/*numchans*/;
179 //bpos = sndsilence; // write silence instead
180 //res = false;
181 //continue waitnwrite;
182 break;
184 //version(follin_write_debug) { import core.stdc.stdio; printf("Follin: written %u of %u frames\n", cast(uint)err, cast(uint)fleft); }
185 smpbuf += cast(uint)(err*pcmChans);
186 fleft -= err;
187 } else {
188 ++awaitcount;
189 // no room, wait
190 if (used > frmcount) {
191 uint over = used-frmcount;
192 uint mcswait = 1000_0*over/(pcmRate/100)+100; //441; // for 48000 it will just wait a little less
193 version(alsa_debug) { import core.stdc.stdio; stderr.fprintf("avail: %u; need: %u; used: %u; over: %u; mcswait=%u; avc=%u\n", cast(uint)avail, cast(uint)fleft, cast(uint)used, over, mcswait, cast(uint)awaitcount); }
194 clockSleepMicro(mcswait);
195 } else {
196 conwriteln("!!!ALSA: used=", used, "; frmcount=", frmcount);
201 version(alsa_time_debug) {
202 auto ste = clockMilli;
203 { import core.stdc.stdio; stderr.fprintf("frame time: %u msecs for %u frames\n", cast(uint)(ste-stc), frmcount); }
208 version(alsa_simple) {} else
209 snd_pcm_t* alsaInit (const(char)* alsaDev, ref uint srate, ubyte chans) {
210 snd_pcm_t* apcm = null;
211 snd_pcm_hw_params_t* hw_params = null;
212 snd_pcm_sw_params_t* sw_params = null;
213 snd_pcm_uframes_t rlbufsize;
214 uint sr = srate;
216 if (chans < 1 || chans > 2) throw new Exception("invalid number of channels");
217 if (alsaDev is null || !alsaDev[0]) alsaDev = "default";
219 static void alsaCall (int err, string msg) @trusted {
220 if (err < 0) {
221 import std.string : fromStringz;
222 throw new Exception((msg~" ("~snd_strerror(err).fromStringz~")").idup);
226 fuck_alsa_messages();
228 alsaCall(snd_pcm_open(&apcm, alsaDev, SND_PCM_STREAM_PLAYBACK, 0), "cannot open audio device");
229 scope(failure) snd_pcm_close(apcm);
231 alsaCall(snd_pcm_hw_params_malloc(&hw_params), "cannot allocate hardware parameter structure");
232 scope(exit) snd_pcm_hw_params_free(hw_params);
234 alsaCall(snd_pcm_hw_params_any(apcm, hw_params), "cannot initialize hardware parameter structure");
235 alsaCall(snd_pcm_hw_params_set_access(apcm, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED), "cannot set access type");
236 alsaCall(snd_pcm_hw_params_set_format(apcm, hw_params, SND_PCM_FORMAT_S16_LE), "cannot set sample format");
237 if (alsaDev[0] == 'p' && alsaDev[1] == 'l' && alsaDev[2] == 'u' && alsaDev[3] == 'g' && alsaDev[4] == ':') {
238 alsaCall(snd_pcm_hw_params_set_rate_resample(apcm, hw_params, 1), "cannot turn on resampling");
239 alsaCall(snd_pcm_hw_params_set_rate(apcm, hw_params, sr, 0), "cannot set sample rate");
240 } else {
241 alsaCall(snd_pcm_hw_params_set_rate_resample(apcm, hw_params, 0), "cannot turn off resampling");
242 alsaCall(snd_pcm_hw_params_set_rate_near(apcm, hw_params, &sr, null), "cannot set sample rate");
244 alsaCall(snd_pcm_hw_params_set_channels(apcm, hw_params, chans), "cannot set channel count");
246 //{ import core.stdc.stdio : fprintf, stderr; stderr.fprintf("sampling rate: %u (%u)\n", cast(uint)sr, cast(uint)srate); }
248 //alsaCall(snd_pcm_hw_params_set_buffer_size_near(apcm, hw_params, &rlbufsize), "cannot set buffer size");
249 rlbufsize = sr/50; // in frames
250 //rlbufsize *= 100;
251 if (rlbufsize < 1) assert(0, "wtf?!");
252 auto wantbs = rlbufsize;
253 alsaCall(snd_pcm_hw_params_set_buffer_size_near(apcm, hw_params, &rlbufsize), "cannot set buffer size");
254 if (rlbufsize < wantbs) throw new Exception("ALSA: alas");
255 if (rlbufsize > wantbs*3) throw new Exception("ALSA: alas");
256 realBufSize = rlbufsize;
257 //alsaCall(snd_pcm_hw_params_set_buffer_size(apcm, hw_params, rlbufsize), "cannot set buffer size");
258 auto latency = 1000*rlbufsize/sr;
261 import core.stdc.stdio : fprintf, stderr;
262 stderr.fprintf("ALSA: sample rate: %u (%u); frames in buffer: %u (%u); latency: %u\n", cast(uint)sr, cast(uint)srate, cast(uint)rlbufsize, cast(uint)wantbs, cast(uint)latency);
265 alsaCall(snd_pcm_hw_params(apcm, hw_params), "cannot set parameters");
267 alsaCall(snd_pcm_sw_params_malloc(&sw_params), "cannot allocate software parameters structure");
268 scope(exit) snd_pcm_sw_params_free(sw_params);
270 alsaCall(snd_pcm_sw_params_current(apcm, sw_params), "cannot initialize software parameters structure");
271 alsaCall(snd_pcm_sw_params_set_avail_min(apcm, sw_params, rlbufsize), "cannot set minimum available count");
272 alsaCall(snd_pcm_sw_params_set_start_threshold(apcm, sw_params, rlbufsize), "cannot set start mode");
273 alsaCall(snd_pcm_sw_params(apcm, sw_params), "cannot set software parameters");
274 alsaCall(snd_pcm_nonblock(apcm, 0), "cannot set blocking mode");
275 //alsaCall(snd_pcm_nonblock(apcm, 1), "cannot set non-blocking mode");
277 srate = sr;
278 pcmRate = sr;
280 return apcm;