1 /// Translated from C to D
2 module soundio.alsa;
3 
4 extern(C): @nogc: nothrow: __gshared:
5 
6 import soundio.soundio_internal;
7 import soundio.soundio_private;
8 import soundio.os;
9 import soundio.list;
10 import soundio.atomics;
11 import soundio.headers.alsaheader;
12 
13 import core.sys.linux.sys.inotify;
14 import core.sys.posix.fcntl;
15 import core.sys.posix.unistd;
16 import core.sys.posix.poll; //pollfd, POLLERR etc.
17 import core.stdc.errno;
18 import core.stdc..string: strcmp, strncmp, strlen, strstr, strdup;
19 import core.stdc.stdlib: free;
20 
21 private:
22 
23 static if (__VERSION__ < 2094) {
24     // @nogc inotify headers
25     int inotify_init();
26     int inotify_init1(int flags);
27     int inotify_add_watch(int fd, const(char)* name, uint mask);
28     int inotify_rm_watch(int fd, uint wd);
29 }
30 // in unistd.h, missing from core.sys.posix.unistd
31 int pipe2(int* __pipedes, int __flags);
32 
33 package struct SoundIoDeviceAlsa { int make_the_struct_not_empty; }
34 
35 enum SOUNDIO_MAX_ALSA_SND_FILE_LEN = 16;
36 struct SoundIoAlsaPendingFile {
37     char[SOUNDIO_MAX_ALSA_SND_FILE_LEN] name;
38 }
39 
40 alias SoundIoListAlsaPendingFile = SOUNDIO_LIST!SoundIoAlsaPendingFile;
41 
42 package struct SoundIoAlsa {
43     SoundIoOsMutex* mutex;
44     SoundIoOsCond* cond;
45 
46     SoundIoOsThread* thread;
47     SoundIoAtomicFlag abort_flag;
48     int notify_fd;
49     int notify_wd;
50     bool have_devices_flag;
51     int[2] notify_pipe_fd;
52     SoundIoListAlsaPendingFile pending_files;
53 
54     // this one is ready to be read with flush_events. protected by mutex
55     SoundIoDevicesInfo* ready_devices_info;
56 
57     int shutdown_err;
58     bool emitted_shutdown_cb;
59 }
60 
61 package struct SoundIoOutStreamAlsa {
62     snd_pcm_t* handle;
63     snd_pcm_chmap_t* chmap;
64     int chmap_size;
65     snd_pcm_uframes_t offset;
66     snd_pcm_access_t access;
67     snd_pcm_uframes_t buffer_size_frames;
68     int sample_buffer_size;
69     char* sample_buffer;
70     int poll_fd_count;
71     int poll_fd_count_with_extra;
72     pollfd* poll_fds;
73     int[2] poll_exit_pipe_fd;
74     SoundIoOsThread* thread;
75     SoundIoAtomicFlag thread_exit_flag;
76     snd_pcm_uframes_t period_size;
77     int write_frame_count;
78     bool is_paused;
79     SoundIoAtomicFlag clear_buffer_flag;
80     SoundIoChannelArea[SOUNDIO_MAX_CHANNELS] areas;
81 }
82 
83 package struct SoundIoInStreamAlsa {
84     snd_pcm_t* handle;
85     snd_pcm_chmap_t* chmap;
86     int chmap_size;
87     snd_pcm_uframes_t offset;
88     snd_pcm_access_t access;
89     int sample_buffer_size;
90     char* sample_buffer;
91     int poll_fd_count;
92     pollfd* poll_fds;
93     SoundIoOsThread* thread;
94     SoundIoAtomicFlag thread_exit_flag;
95     int period_size;
96     int read_frame_count;
97     bool is_paused;
98     SoundIoChannelArea[SOUNDIO_MAX_CHANNELS] areas;
99 }
100 
101 immutable snd_pcm_stream_t[2] stream_types = [SND_PCM_STREAM_PLAYBACK, SND_PCM_STREAM_CAPTURE];
102 
103 static snd_pcm_access_t[5] prioritized_access_types = [
104     SND_PCM_ACCESS_MMAP_INTERLEAVED,
105     SND_PCM_ACCESS_MMAP_NONINTERLEAVED,
106     SND_PCM_ACCESS_MMAP_COMPLEX,
107     SND_PCM_ACCESS_RW_INTERLEAVED,
108     SND_PCM_ACCESS_RW_NONINTERLEAVED,
109 ];
110 
111 static void wakeup_device_poll(SoundIoAlsa* sia) {
112     ssize_t amt = write(sia.notify_pipe_fd[1], "a".ptr, 1);
113     if (amt == -1) {
114         assert(errno != EBADF);
115         assert(errno != EIO);
116         assert(errno != ENOSPC);
117         assert(errno != EPERM);
118         assert(errno != EPIPE);
119     }
120 }
121 
122 static void wakeup_outstream_poll(SoundIoOutStreamAlsa* osa) {
123     ssize_t amt = write(osa.poll_exit_pipe_fd[1], "a".ptr, 1);
124     if (amt == -1) {
125         assert(errno != EBADF);
126         assert(errno != EIO);
127         assert(errno != ENOSPC);
128         assert(errno != EPERM);
129         assert(errno != EPIPE);
130     }
131 }
132 
133 static void destroy_alsa(SoundIoPrivate* si) {
134     SoundIoAlsa* sia = &si.backend_data.alsa;
135 
136     if (sia.thread) {
137         SOUNDIO_ATOMIC_FLAG_CLEAR(sia.abort_flag);
138         wakeup_device_poll(sia);
139         soundio_os_thread_destroy(sia.thread);
140     }
141 
142     sia.pending_files.deinit();
143 
144     if (sia.cond)
145         soundio_os_cond_destroy(sia.cond);
146 
147     if (sia.mutex)
148         soundio_os_mutex_destroy(sia.mutex);
149 
150     soundio_destroy_devices_info(sia.ready_devices_info);
151 
152 
153 
154     close(sia.notify_pipe_fd[0]);
155     close(sia.notify_pipe_fd[1]);
156     close(sia.notify_fd);
157 }
158 
159 pragma(inline, true) static snd_pcm_uframes_t ceil_dbl_to_uframes(double x) {
160     const(double) truncation = cast(snd_pcm_uframes_t)x;
161     return cast(snd_pcm_uframes_t) (truncation + (truncation < x));
162 }
163 
164 static char* str_partition_on_char(char* str, char c) {
165     if (!str)
166         return null;
167     while (*str) {
168         if (*str == c) {
169             *str = 0;
170             return str + 1;
171         }
172         str += 1;
173     }
174     return null;
175 }
176 
177 static snd_pcm_stream_t aim_to_stream(SoundIoDeviceAim aim) {
178     final switch (aim) {
179         case SoundIoDeviceAim.Output: return SND_PCM_STREAM_PLAYBACK;
180         case SoundIoDeviceAim.Input: return SND_PCM_STREAM_CAPTURE;
181     }
182     //assert(0); // Invalid aim
183     //return SND_PCM_STREAM_PLAYBACK;
184 }
185 
186 static SoundIoChannelId from_alsa_chmap_pos(uint pos) {
187     switch (/*cast(snd_pcm_chmap_position)*/ pos) {
188         case SND_CHMAP_UNKNOWN: return SoundIoChannelId.Invalid;
189         case SND_CHMAP_NA:      return SoundIoChannelId.Invalid;
190         case SND_CHMAP_MONO:    return SoundIoChannelId.FrontCenter;
191         case SND_CHMAP_FL:      return SoundIoChannelId.FrontLeft; // front left
192         case SND_CHMAP_FR:      return SoundIoChannelId.FrontRight; // front right
193         case SND_CHMAP_RL:      return SoundIoChannelId.BackLeft; // rear left
194         case SND_CHMAP_RR:      return SoundIoChannelId.BackRight; // rear right
195         case SND_CHMAP_FC:      return SoundIoChannelId.FrontCenter; // front center
196         case SND_CHMAP_LFE:     return SoundIoChannelId.Lfe; // LFE
197         case SND_CHMAP_SL:      return SoundIoChannelId.SideLeft; // side left
198         case SND_CHMAP_SR:      return SoundIoChannelId.SideRight; // side right
199         case SND_CHMAP_RC:      return SoundIoChannelId.BackCenter; // rear center
200         case SND_CHMAP_FLC:     return SoundIoChannelId.FrontLeftCenter; // front left center
201         case SND_CHMAP_FRC:     return SoundIoChannelId.FrontRightCenter; // front right center
202         case SND_CHMAP_RLC:     return SoundIoChannelId.BackLeftCenter; // rear left center
203         case SND_CHMAP_RRC:     return SoundIoChannelId.BackRightCenter; // rear right center
204         case SND_CHMAP_FLW:     return SoundIoChannelId.FrontLeftWide; // front left wide
205         case SND_CHMAP_FRW:     return SoundIoChannelId.FrontRightWide; // front right wide
206         case SND_CHMAP_FLH:     return SoundIoChannelId.FrontLeftHigh; // front left high
207         case SND_CHMAP_FCH:     return SoundIoChannelId.FrontCenterHigh; // front center high
208         case SND_CHMAP_FRH:     return SoundIoChannelId.FrontRightHigh; // front right high
209         case SND_CHMAP_TC:      return SoundIoChannelId.TopCenter; // top center
210         case SND_CHMAP_TFL:     return SoundIoChannelId.TopFrontLeft; // top front left
211         case SND_CHMAP_TFR:     return SoundIoChannelId.TopFrontRight; // top front right
212         case SND_CHMAP_TFC:     return SoundIoChannelId.TopFrontCenter; // top front center
213         case SND_CHMAP_TRL:     return SoundIoChannelId.TopBackLeft; // top rear left
214         case SND_CHMAP_TRR:     return SoundIoChannelId.TopBackRight; // top rear right
215         case SND_CHMAP_TRC:     return SoundIoChannelId.TopBackCenter; // top rear center
216         case SND_CHMAP_TFLC:    return SoundIoChannelId.TopFrontLeftCenter; // top front left center
217         case SND_CHMAP_TFRC:    return SoundIoChannelId.TopFrontRightCenter; // top front right center
218         case SND_CHMAP_TSL:     return SoundIoChannelId.TopSideLeft; // top side left
219         case SND_CHMAP_TSR:     return SoundIoChannelId.TopSideRight; // top side right
220         case SND_CHMAP_LLFE:    return SoundIoChannelId.LeftLfe; // left LFE
221         case SND_CHMAP_RLFE:    return SoundIoChannelId.RightLfe; // right LFE
222         case SND_CHMAP_BC:      return SoundIoChannelId.BottomCenter; // bottom center
223         case SND_CHMAP_BLC:     return SoundIoChannelId.BottomLeftCenter; // bottom left center
224         case SND_CHMAP_BRC:     return SoundIoChannelId.BottomRightCenter; // bottom right center
225         default: break;
226     }
227     return SoundIoChannelId.Invalid;
228 }
229 
230 static int to_alsa_chmap_pos(SoundIoChannelId channel_id) {
231     switch (channel_id) {
232         case SoundIoChannelId.FrontLeft:             return SND_CHMAP_FL;
233         case SoundIoChannelId.FrontRight:            return SND_CHMAP_FR;
234         case SoundIoChannelId.BackLeft:              return SND_CHMAP_RL;
235         case SoundIoChannelId.BackRight:             return SND_CHMAP_RR;
236         case SoundIoChannelId.FrontCenter:           return SND_CHMAP_FC;
237         case SoundIoChannelId.Lfe:                   return SND_CHMAP_LFE;
238         case SoundIoChannelId.SideLeft:              return SND_CHMAP_SL;
239         case SoundIoChannelId.SideRight:             return SND_CHMAP_SR;
240         case SoundIoChannelId.BackCenter:            return SND_CHMAP_RC;
241         case SoundIoChannelId.FrontLeftCenter:       return SND_CHMAP_FLC;
242         case SoundIoChannelId.FrontRightCenter:      return SND_CHMAP_FRC;
243         case SoundIoChannelId.BackLeftCenter:        return SND_CHMAP_RLC;
244         case SoundIoChannelId.BackRightCenter:       return SND_CHMAP_RRC;
245         case SoundIoChannelId.FrontLeftWide:         return SND_CHMAP_FLW;
246         case SoundIoChannelId.FrontRightWide:        return SND_CHMAP_FRW;
247         case SoundIoChannelId.FrontLeftHigh:         return SND_CHMAP_FLH;
248         case SoundIoChannelId.FrontCenterHigh:       return SND_CHMAP_FCH;
249         case SoundIoChannelId.FrontRightHigh:        return SND_CHMAP_FRH;
250         case SoundIoChannelId.TopCenter:             return SND_CHMAP_TC;
251         case SoundIoChannelId.TopFrontLeft:          return SND_CHMAP_TFL;
252         case SoundIoChannelId.TopFrontRight:         return SND_CHMAP_TFR;
253         case SoundIoChannelId.TopFrontCenter:        return SND_CHMAP_TFC;
254         case SoundIoChannelId.TopBackLeft:           return SND_CHMAP_TRL;
255         case SoundIoChannelId.TopBackRight:          return SND_CHMAP_TRR;
256         case SoundIoChannelId.TopBackCenter:         return SND_CHMAP_TRC;
257         case SoundIoChannelId.TopFrontLeftCenter:    return SND_CHMAP_TFLC;
258         case SoundIoChannelId.TopFrontRightCenter:   return SND_CHMAP_TFRC;
259         case SoundIoChannelId.TopSideLeft:           return SND_CHMAP_TSL;
260         case SoundIoChannelId.TopSideRight:          return SND_CHMAP_TSR;
261         case SoundIoChannelId.LeftLfe:               return SND_CHMAP_LLFE;
262         case SoundIoChannelId.RightLfe:              return SND_CHMAP_RLFE;
263         case SoundIoChannelId.BottomCenter:          return SND_CHMAP_BC;
264         case SoundIoChannelId.BottomLeftCenter:      return SND_CHMAP_BLC;
265         case SoundIoChannelId.BottomRightCenter:     return SND_CHMAP_BRC;
266 
267         default:
268             return SND_CHMAP_UNKNOWN;
269     }
270 }
271 
272 static void get_channel_layout(SoundIoChannelLayout* dest, snd_pcm_chmap_t* chmap) {
273     int channel_count = soundio_int_min(SOUNDIO_MAX_CHANNELS, chmap.channels);
274     dest.channel_count = channel_count;
275     for (int i = 0; i < channel_count; i += 1) {
276         // chmap.pos is a variable length array, typed as a `uint[0]`.
277         // The `.ptr` is needed to avoid a range violation with array bounds checks
278         dest.channels[i] = from_alsa_chmap_pos(chmap.pos.ptr[i]);
279     }
280     soundio_channel_layout_detect_builtin(dest);
281 }
282 
283 static int handle_channel_maps(SoundIoDevice* device, snd_pcm_chmap_query_t** maps) {
284     if (!maps)
285         return 0;
286 
287     snd_pcm_chmap_query_t** p;
288     snd_pcm_chmap_query_t* v;
289 
290     // one iteration to count
291     int layout_count = 0;
292     for (p = maps; cast(bool) (v = *p) && layout_count < SOUNDIO_MAX_CHANNELS; p += 1, layout_count += 1) { }
293     device.layouts = ALLOCATE!SoundIoChannelLayout(layout_count);
294     if (!device.layouts) {
295         snd_pcm_free_chmaps(maps);
296         return SoundIoError.NoMem;
297     }
298     device.layout_count = layout_count;
299 
300     // iterate again to collect data
301     int layout_index;
302     for (p = maps, layout_index = 0;
303         cast(bool) (v = *p) && layout_index < layout_count;
304         p += 1, layout_index += 1)
305     {
306         get_channel_layout(&device.layouts[layout_index], &v.map);
307     }
308     snd_pcm_free_chmaps(maps);
309 
310     return 0;
311 }
312 
313 static snd_pcm_format_t to_alsa_fmt(SoundIoFormat fmt) {
314     switch (fmt) {
315     case SoundIoFormat.S8:           return SND_PCM_FORMAT_S8;
316     case SoundIoFormat.U8:           return SND_PCM_FORMAT_U8;
317     case SoundIoFormat.S16LE:        return SND_PCM_FORMAT_S16_LE;
318     case SoundIoFormat.S16BE:        return SND_PCM_FORMAT_S16_BE;
319     case SoundIoFormat.U16LE:        return SND_PCM_FORMAT_U16_LE;
320     case SoundIoFormat.U16BE:        return SND_PCM_FORMAT_U16_BE;
321     case SoundIoFormat.S24LE:        return SND_PCM_FORMAT_S24_LE;
322     case SoundIoFormat.S24BE:        return SND_PCM_FORMAT_S24_BE;
323     case SoundIoFormat.U24LE:        return SND_PCM_FORMAT_U24_LE;
324     case SoundIoFormat.U24BE:        return SND_PCM_FORMAT_U24_BE;
325     case SoundIoFormat.S32LE:        return SND_PCM_FORMAT_S32_LE;
326     case SoundIoFormat.S32BE:        return SND_PCM_FORMAT_S32_BE;
327     case SoundIoFormat.U32LE:        return SND_PCM_FORMAT_U32_LE;
328     case SoundIoFormat.U32BE:        return SND_PCM_FORMAT_U32_BE;
329     case SoundIoFormat.Float32LE:    return SND_PCM_FORMAT_FLOAT_LE;
330     case SoundIoFormat.Float32BE:    return SND_PCM_FORMAT_FLOAT_BE;
331     case SoundIoFormat.Float64LE:    return SND_PCM_FORMAT_FLOAT64_LE;
332     case SoundIoFormat.Float64BE:    return SND_PCM_FORMAT_FLOAT64_BE;
333     case SoundIoFormat.Invalid:
334     default: break;
335     }
336     return SND_PCM_FORMAT_UNKNOWN;
337 }
338 
339 static void test_fmt_mask(SoundIoDevice* device, const(snd_pcm_format_mask_t)* fmt_mask, SoundIoFormat fmt) {
340     if (snd_pcm_format_mask_test(fmt_mask, to_alsa_fmt(fmt))) {
341         device.formats[device.format_count] = fmt;
342         device.format_count += 1;
343     }
344 }
345 
346 static int set_access(snd_pcm_t* handle, snd_pcm_hw_params_t* hwparams, snd_pcm_access_t* out_access) {
347     for (int i = 0; i < prioritized_access_types.length; i += 1) {
348         snd_pcm_access_t access = prioritized_access_types[i];
349         int err = snd_pcm_hw_params_set_access(handle, hwparams, access);
350         if (err >= 0) {
351             if (out_access)
352                 *out_access = access;
353             return 0;
354         }
355     }
356     return SoundIoError.OpeningDevice;
357 }
358 
359 // this function does not override device->formats, so if you want it to, deallocate and set it to NULL
360 static int probe_open_device(SoundIoDevice* device, snd_pcm_t* handle, int resample, int* out_channels_min, int* out_channels_max) {
361     SoundIoDevicePrivate* dev = cast(SoundIoDevicePrivate*)device;
362     int err;
363 
364     snd_pcm_hw_params_t* hwparams;
365     snd_pcm_hw_params_malloc(&hwparams);
366     if (!hwparams)
367         return SoundIoError.NoMem;
368     scope(exit) snd_pcm_hw_params_free(hwparams);
369 
370     err = snd_pcm_hw_params_any(handle, hwparams);
371     if (err < 0)
372         return SoundIoError.OpeningDevice;
373 
374     err = snd_pcm_hw_params_set_rate_resample(handle, hwparams, resample);
375     if (err < 0)
376         return SoundIoError.OpeningDevice;
377 
378     if (auto err1 = set_access(handle, hwparams, null))
379         return err1;
380 
381     uint channels_min;
382     uint channels_max;
383 
384     err = snd_pcm_hw_params_get_channels_min(hwparams, &channels_min);
385     if (err < 0)
386         return SoundIoError.OpeningDevice;
387     err = snd_pcm_hw_params_set_channels_last(handle, hwparams, &channels_max);
388     if (err < 0)
389         return SoundIoError.OpeningDevice;
390 
391     *out_channels_min = channels_min;
392     *out_channels_max = channels_max;
393 
394     uint rate_min;
395     uint rate_max;
396 
397     err = snd_pcm_hw_params_get_rate_min(hwparams, &rate_min, null);
398     if (err < 0)
399         return SoundIoError.OpeningDevice;
400 
401     err = snd_pcm_hw_params_set_rate_last(handle, hwparams, &rate_max, null);
402     if (err < 0)
403         return SoundIoError.OpeningDevice;
404 
405     device.sample_rate_count = 1;
406     device.sample_rates = &dev.prealloc_sample_rate_range;
407     device.sample_rates[0].min = rate_min;
408     device.sample_rates[0].max = rate_max;
409 
410     double one_over_actual_rate = 1.0 / cast(double)rate_max;
411 
412     // Purposefully leave the parameters with the highest rate, highest channel count.
413 
414     snd_pcm_uframes_t min_frames;
415     snd_pcm_uframes_t max_frames;
416 
417 
418     err = snd_pcm_hw_params_get_buffer_size_min(hwparams, &min_frames);
419     if (err < 0)
420         return SoundIoError.OpeningDevice;
421     err = snd_pcm_hw_params_get_buffer_size_max(hwparams, &max_frames);
422     if (err < 0)
423         return SoundIoError.OpeningDevice;
424 
425     device.software_latency_min = min_frames * one_over_actual_rate;
426     device.software_latency_max = max_frames * one_over_actual_rate;
427 
428     err = snd_pcm_hw_params_set_buffer_size_first(handle, hwparams, &min_frames);
429     if (err < 0)
430         return SoundIoError.OpeningDevice;
431 
432 
433     snd_pcm_format_mask_t* fmt_mask;
434     snd_pcm_format_mask_malloc(&fmt_mask);
435     if (!fmt_mask)
436         return SoundIoError.NoMem;
437     scope(exit) snd_pcm_format_mask_free(fmt_mask);
438     snd_pcm_format_mask_none(fmt_mask);
439     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_S8);
440     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_U8);
441     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_S16_LE);
442     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_S16_BE);
443     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_U16_LE);
444     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_U16_BE);
445     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_S24_LE);
446     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_S24_BE);
447     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_U24_LE);
448     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_U24_BE);
449     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_S32_LE);
450     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_S32_BE);
451     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_U32_LE);
452     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_U32_BE);
453     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_FLOAT_LE);
454     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_FLOAT_BE);
455     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_FLOAT64_LE);
456     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_FLOAT64_BE);
457 
458     err = snd_pcm_hw_params_set_format_mask(handle, hwparams, fmt_mask);
459     if (err < 0)
460         return SoundIoError.OpeningDevice;
461 
462     if (!device.formats) {
463         snd_pcm_hw_params_get_format_mask(hwparams, fmt_mask);
464         device.formats = ALLOCATE!SoundIoFormat(18);
465         if (!device.formats)
466             return SoundIoError.NoMem;
467 
468         device.format_count = 0;
469         test_fmt_mask(device, fmt_mask, SoundIoFormat.S8);
470         test_fmt_mask(device, fmt_mask, SoundIoFormat.U8);
471         test_fmt_mask(device, fmt_mask, SoundIoFormat.S16LE);
472         test_fmt_mask(device, fmt_mask, SoundIoFormat.S16BE);
473         test_fmt_mask(device, fmt_mask, SoundIoFormat.U16LE);
474         test_fmt_mask(device, fmt_mask, SoundIoFormat.U16BE);
475         test_fmt_mask(device, fmt_mask, SoundIoFormat.S24LE);
476         test_fmt_mask(device, fmt_mask, SoundIoFormat.S24BE);
477         test_fmt_mask(device, fmt_mask, SoundIoFormat.U24LE);
478         test_fmt_mask(device, fmt_mask, SoundIoFormat.U24BE);
479         test_fmt_mask(device, fmt_mask, SoundIoFormat.S32LE);
480         test_fmt_mask(device, fmt_mask, SoundIoFormat.S32BE);
481         test_fmt_mask(device, fmt_mask, SoundIoFormat.U32LE);
482         test_fmt_mask(device, fmt_mask, SoundIoFormat.U32BE);
483         test_fmt_mask(device, fmt_mask, SoundIoFormat.Float32LE);
484         test_fmt_mask(device, fmt_mask, SoundIoFormat.Float32BE);
485         test_fmt_mask(device, fmt_mask, SoundIoFormat.Float64LE);
486         test_fmt_mask(device, fmt_mask, SoundIoFormat.Float64BE);
487     }
488 
489     return 0;
490 }
491 
492 extern(D) int probe_device(SoundIoDevice* device, snd_pcm_chmap_query_t** maps) {
493     int err;
494     snd_pcm_t* handle;
495 
496     snd_pcm_stream_t stream = aim_to_stream(device.aim);
497 
498     err = snd_pcm_open(&handle, device.id, stream, 0);
499     if (err < 0) {
500         handle_channel_maps(device, maps);
501         return SoundIoError.OpeningDevice;
502     }
503 
504     int channels_min;
505     int channels_max;
506     err = probe_open_device(device, handle, 0, &channels_min, &channels_max);
507     if (err) {
508         handle_channel_maps(device, maps);
509         snd_pcm_close(handle);
510         return err;
511     }
512 
513     if (!maps) {
514         maps = snd_pcm_query_chmaps(handle);
515         if (!maps) {
516             // device gave us no channel maps. we're forced to conclude that
517             // the min and max channel counts are correct.
518             int layout_count = 0;
519             for (int i = 0; i < soundio_channel_layout_builtin_count(); i += 1) {
520                 const(SoundIoChannelLayout)* layout = soundio_channel_layout_get_builtin(i);
521                 if (layout.channel_count >= channels_min && layout.channel_count <= channels_max) {
522                     layout_count += 1;
523                 }
524             }
525             device.layout_count = layout_count;
526             device.layouts = ALLOCATE!SoundIoChannelLayout( device.layout_count);
527             if (!device.layouts) {
528                 snd_pcm_close(handle);
529                 return SoundIoError.NoMem;
530             }
531             int layout_index = 0;
532             for (int i = 0; i < soundio_channel_layout_builtin_count(); i += 1) {
533                 const(SoundIoChannelLayout)* layout = soundio_channel_layout_get_builtin(i);
534                 if (layout.channel_count >= channels_min && layout.channel_count <= channels_max) {
535                     device.layouts[layout_index++] = *soundio_channel_layout_get_builtin(i);
536                 }
537             }
538         }
539     }
540 
541     snd_pcm_chmap_t* chmap = snd_pcm_get_chmap(handle);
542     if (chmap) {
543         get_channel_layout(&device.current_layout, chmap);
544         free(chmap);
545     }
546     err = handle_channel_maps(device, maps);
547     if (err) {
548         snd_pcm_close(handle);
549         return err;
550     }
551     maps = null;
552 
553     if (!device.is_raw) {
554         if (device.sample_rates[0].min == device.sample_rates[0].max)
555             device.sample_rate_current = device.sample_rates[0].min;
556 
557         if (device.software_latency_min == device.software_latency_max)
558             device.software_latency_current = device.software_latency_min;
559 
560         // now say that resampling is OK and see what the real min and max is.
561         err = probe_open_device(device, handle, 1, &channels_min, &channels_max);
562         if (err < 0) {
563             snd_pcm_close(handle);
564             return SoundIoError.OpeningDevice;
565         }
566     }
567 
568     snd_pcm_close(handle);
569     return 0;
570 }
571 
572 pragma(inline, true) static bool str_has_prefix(const(char)* big_str, const(char)* prefix) {
573     return strncmp(big_str, prefix, strlen(prefix)) == 0;
574 }
575 
576 extern(D) int refresh_devices(SoundIoPrivate* si) {
577     SoundIo* soundio = &si.pub;
578     SoundIoAlsa* sia = &si.backend_data.alsa;
579 
580     int err;
581     err = snd_config_update_free_global();
582     if (err < 0)
583         return SoundIoError.SystemResources;
584     err = snd_config_update();
585     if (err < 0)
586         return SoundIoError.SystemResources;
587 
588     SoundIoDevicesInfo* devices_info = ALLOCATE!SoundIoDevicesInfo(1);
589     if (!devices_info)
590         return SoundIoError.NoMem;
591     devices_info.default_output_index = -1;
592     devices_info.default_input_index = -1;
593 
594     void** hints;
595     if (snd_device_name_hint(-1, "pcm", &hints) < 0) {
596         soundio_destroy_devices_info(devices_info);
597         return SoundIoError.NoMem;
598     }
599 
600     int default_output_index = -1;
601     int sysdefault_output_index = -1;
602     int default_input_index = -1;
603     int sysdefault_input_index = -1;
604 
605     for (void** hint_ptr = hints; *hint_ptr; hint_ptr += 1) {
606         char* name = snd_device_name_get_hint(*hint_ptr, "NAME");
607         // null - libsoundio has its own dummy backend. API clients should use
608         // that instead of alsa null device.
609         if (strcmp(name, "null") == 0 ||
610             // all these surround devices are clutter
611             str_has_prefix(name, "front:") ||
612             str_has_prefix(name, "surround21:") ||
613             str_has_prefix(name, "surround40:") ||
614             str_has_prefix(name, "surround41:") ||
615             str_has_prefix(name, "surround50:") ||
616             str_has_prefix(name, "surround51:") ||
617             str_has_prefix(name, "surround71:"))
618         {
619             free(name);
620             continue;
621         }
622 
623         // One or both of descr and descr1 can be NULL.
624         char* descr = snd_device_name_get_hint(*hint_ptr, "DESC");
625         char* descr1 = str_partition_on_char(descr, '\n');
626 
627         char* io = snd_device_name_get_hint(*hint_ptr, "IOID");
628         bool is_playback;
629         bool is_capture;
630 
631         // Workaround for Raspberry Pi driver bug, reporting itself as output
632         // when really it is input.
633         if (descr && strcmp(descr, "bcm2835 ALSA, bcm2835 ALSA") == 0 &&
634             descr1 && strcmp(descr1, "Direct sample snooping device") == 0)
635         {
636             is_playback = false;
637             is_capture = true;
638         } else if (descr && strcmp(descr, "bcm2835 ALSA, bcm2835 IEC958/HDMI") == 0 &&
639                    descr1 && strcmp(descr1, "Direct sample snooping device") == 0)
640         {
641             is_playback = false;
642             is_capture = true;
643         } else if (io) {
644             if (strcmp(io, "Input") == 0) {
645                 is_playback = false;
646                 is_capture = true;
647             } else {
648                 assert(strcmp(io, "Output") == 0);
649                 is_playback = true;
650                 is_capture = false;
651             }
652             free(io);
653         } else {
654             is_playback = true;
655             is_capture = true;
656         }
657 
658         for (int stream_type_i = 0; stream_type_i < stream_types.length; stream_type_i += 1) {
659             snd_pcm_stream_t stream = stream_types[stream_type_i];
660             if (stream == SND_PCM_STREAM_PLAYBACK && !is_playback) continue;
661             if (stream == SND_PCM_STREAM_CAPTURE && !is_capture) continue;
662             if (stream == SND_PCM_STREAM_CAPTURE && descr1 &&
663                 (strstr(descr1, "Output") || strstr(descr1, "output")))
664             {
665                 continue;
666             }
667 
668 
669             SoundIoDevicePrivate* dev = ALLOCATE!SoundIoDevicePrivate(1);
670             if (!dev) {
671                 free(name);
672                 free(descr);
673                 soundio_destroy_devices_info(devices_info);
674                 snd_device_name_free_hint(hints);
675                 return SoundIoError.NoMem;
676             }
677             SoundIoDevice* device = &dev.pub;
678             device.ref_count = 1;
679             device.soundio = soundio;
680             device.is_raw = false;
681             device.id = strdup(name);
682             if (descr1) {
683                 device.name = soundio_alloc_sprintf(null, "%s: %s", descr, descr1);
684             } else if (descr) {
685                 device.name = strdup(descr);
686             } else {
687                 device.name = strdup(name);
688             }
689 
690             if (!device.id || !device.name) {
691                 soundio_device_unref(device);
692                 free(name);
693                 free(descr);
694                 soundio_destroy_devices_info(devices_info);
695                 snd_device_name_free_hint(hints);
696                 return SoundIoError.NoMem;
697             }
698 
699             SoundIoListDevicePtr* device_list;
700             bool is_default = str_has_prefix(name, "default:") || strcmp(name, "default") == 0;
701             bool is_sysdefault = str_has_prefix(name, "sysdefault:") || strcmp(name, "sysdefault") == 0;
702 
703             if (stream == SND_PCM_STREAM_PLAYBACK) {
704                 device.aim = SoundIoDeviceAim.Output;
705                 device_list = &devices_info.output_devices;
706                 if (is_default)
707                     default_output_index = device_list.length;
708                 if (is_sysdefault)
709                     sysdefault_output_index = device_list.length;
710                 if (devices_info.default_output_index == -1)
711                     devices_info.default_output_index = device_list.length;
712             } else {
713                 assert(stream == SND_PCM_STREAM_CAPTURE);
714                 device.aim = SoundIoDeviceAim.Input;
715                 device_list = &devices_info.input_devices;
716                 if (is_default)
717                     default_input_index = device_list.length;
718                 if (is_sysdefault)
719                     sysdefault_input_index = device_list.length;
720                 if (devices_info.default_input_index == -1)
721                     devices_info.default_input_index = device_list.length;
722             }
723 
724             device.probe_error = probe_device(device, null);
725 
726             if (device_list.append(device)) {
727                 soundio_device_unref(device);
728                 free(name);
729                 free(descr);
730                 soundio_destroy_devices_info(devices_info);
731                 snd_device_name_free_hint(hints);
732                 return SoundIoError.NoMem;
733             }
734         }
735 
736         free(name);
737         free(descr);
738     }
739 
740     if (default_input_index >= 0) {
741         devices_info.default_input_index = default_input_index;
742     } else if (sysdefault_input_index >= 0) {
743         devices_info.default_input_index = sysdefault_input_index;
744     }
745 
746     if (default_output_index >= 0) {
747         devices_info.default_output_index = default_output_index;
748     } else if (sysdefault_output_index >= 0) {
749         devices_info.default_output_index = sysdefault_output_index;
750     }
751 
752     snd_device_name_free_hint(hints);
753 
754     int card_index = -1;
755 
756     if (snd_card_next(&card_index) < 0)
757         return SoundIoError.SystemResources;
758 
759     snd_ctl_card_info_t* card_info;
760     snd_ctl_card_info_malloc(&card_info);
761     if (!card_info)
762         return SoundIoError.NoMem;
763     scope(exit) snd_ctl_card_info_free(card_info);
764 
765     snd_pcm_info_t* pcm_info;
766     snd_pcm_info_malloc(&pcm_info);
767     if (!pcm_info)
768         return SoundIoError.NoMem;
769     scope(exit) snd_pcm_info_free(pcm_info);
770 
771     while (card_index >= 0) {
772         snd_ctl_t* handle;
773         char[32] name;
774         import core.stdc.stdio: sprintf;
775         sprintf(name.ptr, "hw:%d", card_index);
776         err = snd_ctl_open(&handle, name.ptr, 0);
777         if (err < 0) {
778             if (err == -ENOENT) {
779                 break;
780             } else {
781                 soundio_destroy_devices_info(devices_info);
782                 return SoundIoError.OpeningDevice;
783             }
784         }
785 
786         err = snd_ctl_card_info(handle, card_info);
787         if (err < 0) {
788             snd_ctl_close(handle);
789             soundio_destroy_devices_info(devices_info);
790             return SoundIoError.SystemResources;
791         }
792         const(char)* card_name = snd_ctl_card_info_get_name(card_info);
793 
794         int device_index = -1;
795         for (;;) {
796             if (snd_ctl_pcm_next_device(handle, &device_index) < 0) {
797                 snd_ctl_close(handle);
798                 soundio_destroy_devices_info(devices_info);
799                 return SoundIoError.SystemResources;
800             }
801             if (device_index < 0)
802                 break;
803 
804             snd_pcm_info_set_device(pcm_info, device_index);
805             snd_pcm_info_set_subdevice(pcm_info, 0);
806 
807             for (int stream_type_i = 0; stream_type_i < stream_types.length; stream_type_i += 1) {
808                 snd_pcm_stream_t stream = stream_types[stream_type_i];
809                 snd_pcm_info_set_stream(pcm_info, stream);
810 
811                 err = snd_ctl_pcm_info(handle, pcm_info);
812                 if (err < 0) {
813                     if (err == -ENOENT) {
814                         continue;
815                     } else {
816                         snd_ctl_close(handle);
817                         soundio_destroy_devices_info(devices_info);
818                         return SoundIoError.SystemResources;
819                     }
820                 }
821 
822                 const(char)* device_name = snd_pcm_info_get_name(pcm_info);
823 
824                 SoundIoDevicePrivate* dev = ALLOCATE!SoundIoDevicePrivate(1);
825                 if (!dev) {
826                     snd_ctl_close(handle);
827                     soundio_destroy_devices_info(devices_info);
828                     return SoundIoError.NoMem;
829                 }
830                 SoundIoDevice* device = &dev.pub;
831                 device.ref_count = 1;
832                 device.soundio = soundio;
833                 device.id = soundio_alloc_sprintf(null, "hw:%d,%d", card_index, device_index);
834                 device.name = soundio_alloc_sprintf(null, "%s %s", card_name, device_name);
835                 device.is_raw = true;
836 
837                 if (!device.id || !device.name) {
838                     soundio_device_unref(device);
839                     snd_ctl_close(handle);
840                     soundio_destroy_devices_info(devices_info);
841                     return SoundIoError.NoMem;
842                 }
843 
844                 SoundIoListDevicePtr* device_list;
845                 if (stream == SND_PCM_STREAM_PLAYBACK) {
846                     device.aim = SoundIoDeviceAim.Output;
847                     device_list = &devices_info.output_devices;
848                 } else {
849                     assert(stream == SND_PCM_STREAM_CAPTURE);
850                     device.aim = SoundIoDeviceAim.Input;
851                     device_list = &devices_info.input_devices;
852                 }
853 
854                 snd_pcm_chmap_query_t** maps = snd_pcm_query_chmaps_from_hw(card_index, device_index, -1, stream);
855                 device.probe_error = probe_device(device, maps);
856 
857                 if (device_list.append(device)) {
858                     soundio_device_unref(device);
859                     soundio_destroy_devices_info(devices_info);
860                     return SoundIoError.NoMem;
861                 }
862             }
863         }
864         snd_ctl_close(handle);
865         if (snd_card_next(&card_index) < 0) {
866             soundio_destroy_devices_info(devices_info);
867             return SoundIoError.SystemResources;
868         }
869     }
870 
871     soundio_os_mutex_lock(sia.mutex);
872     soundio_destroy_devices_info(sia.ready_devices_info);
873     sia.ready_devices_info = devices_info;
874     sia.have_devices_flag = true;
875     soundio_os_cond_signal(sia.cond, sia.mutex);
876     soundio.on_events_signal(soundio);
877     soundio_os_mutex_unlock(sia.mutex);
878     return 0;
879 }
880 
881 static void shutdown_backend(SoundIoPrivate* si, int err) {
882     SoundIo* soundio = &si.pub;
883     SoundIoAlsa* sia = &si.backend_data.alsa;
884     soundio_os_mutex_lock(sia.mutex);
885     sia.shutdown_err = err;
886     soundio_os_cond_signal(sia.cond, sia.mutex);
887     soundio.on_events_signal(soundio);
888     soundio_os_mutex_unlock(sia.mutex);
889 }
890 
891 static bool copy_str(char* dest, const(char)* src, int buf_len) {
892     for (;;) {
893         buf_len -= 1;
894         if (buf_len <= 0)
895             return false;
896         *dest = *src;
897         dest += 1;
898         src += 1;
899         if (!*src)
900             break;
901     }
902     *dest = '\0';
903     return true;
904 }
905 
906 static void device_thread_run(void* arg) {
907     SoundIoPrivate* si = cast(SoundIoPrivate*)arg;
908     SoundIoAlsa* sia = &si.backend_data.alsa;
909 
910     // Some systems cannot read integer variables if they are not
911     // properly aligned. On other systems, incorrect alignment may
912     // decrease performance. Hence, the buffer used for reading from
913     // the inotify file descriptor should have the same alignment as
914     // struct inotify_event.
915     char[4096] buf; const(inotify_event)* event;
916 
917     pollfd[2] fds;
918     fds[0].fd = sia.notify_fd;
919     fds[0].events = POLLIN;
920 
921     fds[1].fd = sia.notify_pipe_fd[0];
922     fds[1].events = POLLIN;
923 
924     int err;
925     for (;;) {
926         int poll_num = poll(fds.ptr, 2, -1);
927         if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(sia.abort_flag))
928             break;
929         if (poll_num == -1) {
930             if (errno == EINTR)
931                 continue;
932             assert(errno != EFAULT);
933             assert(errno != EINVAL);
934             assert(errno == ENOMEM);
935             // Kernel ran out of polling memory.
936             shutdown_backend(si, SoundIoError.SystemResources);
937             return;
938         }
939         if (poll_num <= 0)
940             continue;
941         bool got_rescan_event = false;
942         if (fds[0].revents & POLLIN) {
943             for (;;) {
944                 ssize_t len = read(sia.notify_fd, buf.ptr, buf.sizeof);
945                 if (len == -1) {
946                     assert(errno != EBADF);
947                     assert(errno != EFAULT);
948                     assert(errno != EINVAL);
949                     assert(errno != EIO);
950                     assert(errno != EISDIR);
951                     if (errno == EBADF || errno == EFAULT || errno == EINVAL ||
952                         errno == EIO || errno == EISDIR)
953                     {
954                         shutdown_backend(si, SoundIoError.SystemResources);
955                         return;
956                     }
957                 }
958 
959                 // catches EINTR and EAGAIN
960                 if (len <= 0)
961                     break;
962 
963                 // loop over all events in the buffer
964                 for (char* ptr = buf.ptr; ptr < buf.ptr + len; ptr += inotify_event.sizeof + event.len) {
965                     event = cast(const(inotify_event)*) ptr;
966 
967                     if (!((event.mask & IN_CLOSE_WRITE) || (event.mask & IN_DELETE) || (event.mask & IN_CREATE)))
968                         continue;
969                     if (event.mask & IN_ISDIR)
970                         continue;
971                     if (!event.len || event.len < 8)
972                         continue;
973                     if (strncmp(event.name.ptr, "controlC", 8) != 0) {
974                         continue;
975                     }
976                     if (event.mask & IN_CREATE) {
977                         err = sia.pending_files.add_one();
978                         if (err) {
979                             shutdown_backend(si, SoundIoError.NoMem);
980                             return;
981                         }
982                         SoundIoAlsaPendingFile* pending_file = sia.pending_files.last_ptr();
983                         if (!copy_str(pending_file.name.ptr, event.name.ptr, SOUNDIO_MAX_ALSA_SND_FILE_LEN)) {
984                             sia.pending_files.pop();
985                         }
986                         continue;
987                     }
988                     if (sia.pending_files.length > 0) {
989                         // At this point ignore IN_DELETE in favor of waiting until the files
990                         // opened with IN_CREATE have their IN_CLOSE_WRITE event.
991                         if (!(event.mask & IN_CLOSE_WRITE))
992                             continue;
993                         for (int i = 0; i < sia.pending_files.length; i += 1) {
994                             SoundIoAlsaPendingFile* pending_file = sia.pending_files.ptr_at(i);
995                             if (strcmp(pending_file.name.ptr, event.name.ptr) == 0) {
996                                 sia.pending_files.swap_remove(i);
997                                 if (sia.pending_files.length == 0) {
998                                     got_rescan_event = true;
999                                 }
1000                                 break;
1001                             }
1002                         }
1003                     } else if (event.mask & IN_DELETE) {
1004                         // We are not waiting on created files to be closed, so when
1005                         // a delete happens we act on it.
1006                         got_rescan_event = true;
1007                     }
1008                 }
1009             }
1010         }
1011         if (fds[1].revents & POLLIN) {
1012             got_rescan_event = true;
1013             for (;;) {
1014                 ssize_t len = read(sia.notify_pipe_fd[0], buf.ptr, buf.sizeof);
1015                 if (len == -1) {
1016                     assert(errno != EBADF);
1017                     assert(errno != EFAULT);
1018                     assert(errno != EINVAL);
1019                     assert(errno != EIO);
1020                     assert(errno != EISDIR);
1021                     if (errno == EBADF || errno == EFAULT || errno == EINVAL ||
1022                         errno == EIO || errno == EISDIR)
1023                     {
1024                         shutdown_backend(si, SoundIoError.SystemResources);
1025                         return;
1026                     }
1027                 }
1028                 if (len <= 0)
1029                     break;
1030             }
1031         }
1032         if (got_rescan_event) {
1033             err = refresh_devices(si);
1034             if (err) {
1035                 shutdown_backend(si, err);
1036                 return;
1037             }
1038         }
1039     }
1040 }
1041 
1042 extern(D) void my_flush_events(SoundIoPrivate* si, bool wait) {
1043     SoundIo* soundio = &si.pub;
1044     SoundIoAlsa* sia = &si.backend_data.alsa;
1045 
1046     bool change = false;
1047     bool cb_shutdown = false;
1048     SoundIoDevicesInfo* old_devices_info = null;
1049 
1050     soundio_os_mutex_lock(sia.mutex);
1051 
1052     // block until have devices
1053     while (wait || (!sia.have_devices_flag && !sia.shutdown_err)) {
1054         soundio_os_cond_wait(sia.cond, sia.mutex);
1055         wait = false;
1056     }
1057 
1058     if (sia.shutdown_err && !sia.emitted_shutdown_cb) {
1059         sia.emitted_shutdown_cb = true;
1060         cb_shutdown = true;
1061     } else if (sia.ready_devices_info) {
1062         old_devices_info = si.safe_devices_info;
1063         si.safe_devices_info = sia.ready_devices_info;
1064         sia.ready_devices_info = null;
1065         change = true;
1066     }
1067 
1068     soundio_os_mutex_unlock(sia.mutex);
1069 
1070     if (cb_shutdown)
1071         soundio.on_backend_disconnect(soundio, sia.shutdown_err);
1072     else if (change)
1073         soundio.on_devices_change(soundio);
1074 
1075     soundio_destroy_devices_info(old_devices_info);
1076 }
1077 
1078 void flush_events_alsa(SoundIoPrivate* si) {
1079     my_flush_events(si, false);
1080 }
1081 
1082 void wait_events_alsa(SoundIoPrivate* si) {
1083     my_flush_events(si, false);
1084     my_flush_events(si, true);
1085 }
1086 
1087 void wakeup_alsa(SoundIoPrivate* si) {
1088     SoundIoAlsa* sia = &si.backend_data.alsa;
1089     soundio_os_mutex_lock(sia.mutex);
1090     soundio_os_cond_signal(sia.cond, sia.mutex);
1091     soundio_os_mutex_unlock(sia.mutex);
1092 }
1093 
1094 void force_device_scan_alsa(SoundIoPrivate* si) {
1095     SoundIoAlsa* sia = &si.backend_data.alsa;
1096     wakeup_device_poll(sia);
1097 }
1098 
1099 void outstream_destroy_alsa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) {
1100     SoundIoOutStreamAlsa* osa = &os.backend_data.alsa;
1101 
1102     if (osa.thread) {
1103         SOUNDIO_ATOMIC_FLAG_CLEAR(osa.thread_exit_flag);
1104         wakeup_outstream_poll(osa);
1105         soundio_os_thread_destroy(osa.thread);
1106         osa.thread = null;
1107     }
1108 
1109     if (osa.handle) {
1110         snd_pcm_close(osa.handle);
1111         osa.handle = null;
1112     }
1113 
1114     free(osa.poll_fds);
1115     osa.poll_fds = null;
1116 
1117     free(osa.chmap);
1118     osa.chmap = null;
1119 
1120     free(osa.sample_buffer);
1121     osa.sample_buffer = null;
1122 }
1123 
1124 int outstream_xrun_recovery(SoundIoOutStreamPrivate* os, int err) {
1125     SoundIoOutStream* outstream = &os.pub;
1126     SoundIoOutStreamAlsa* osa = &os.backend_data.alsa;
1127     if (err == -EPIPE) {
1128         err = snd_pcm_prepare(osa.handle);
1129         if (err >= 0)
1130             outstream.underflow_callback(outstream);
1131     } else if (err == -ESTRPIPE) {
1132         while ((err = snd_pcm_resume(osa.handle)) == -EAGAIN) {
1133             // wait until suspend flag is released
1134             poll(null, 0, 1);
1135         }
1136         if (err < 0)
1137             err = snd_pcm_prepare(osa.handle);
1138         if (err >= 0)
1139             outstream.underflow_callback(outstream);
1140     }
1141     return err;
1142 }
1143 
1144 int instream_xrun_recovery(SoundIoInStreamPrivate* is_, int err) {
1145     SoundIoInStream* instream = &is_.pub;
1146     SoundIoInStreamAlsa* isa = &is_.backend_data.alsa;
1147     if (err == -EPIPE) {
1148         err = snd_pcm_prepare(isa.handle);
1149         if (err >= 0)
1150             instream.overflow_callback(instream);
1151     } else if (err == -ESTRPIPE) {
1152         while ((err = snd_pcm_resume(isa.handle)) == -EAGAIN) {
1153             // wait until suspend flag is released
1154             poll(null, 0, 1);
1155         }
1156         if (err < 0)
1157             err = snd_pcm_prepare(isa.handle);
1158         if (err >= 0)
1159             instream.overflow_callback(instream);
1160     }
1161     return err;
1162 }
1163 
1164 int outstream_wait_for_poll(SoundIoOutStreamPrivate* os) {
1165     SoundIoOutStreamAlsa* osa = &os.backend_data.alsa;
1166     int err;
1167     ushort revents;
1168     for (;;) {
1169         err = poll(osa.poll_fds, osa.poll_fd_count_with_extra, -1);
1170         if (err < 0) {
1171             return SoundIoError.Streaming;
1172         }
1173         if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osa.thread_exit_flag))
1174             return SoundIoError.Interrupted;
1175         if ((err = snd_pcm_poll_descriptors_revents(osa.handle,
1176                         osa.poll_fds, osa.poll_fd_count, &revents)) < 0)
1177         {
1178             return SoundIoError.Streaming;
1179         }
1180         if (revents & (POLLERR|POLLNVAL|POLLHUP)) {
1181             return 0;
1182         }
1183         if (revents & POLLOUT)
1184             return 0;
1185     }
1186 }
1187 
1188 int instream_wait_for_poll(SoundIoInStreamPrivate* is_) {
1189     SoundIoInStreamAlsa* isa = &is_.backend_data.alsa;
1190     int err;
1191     ushort revents;
1192     for (;;) {
1193         err = poll(isa.poll_fds, isa.poll_fd_count, -1);
1194         if (err < 0) {
1195             return err;
1196         }
1197         if ((err = snd_pcm_poll_descriptors_revents(isa.handle,
1198                         isa.poll_fds, isa.poll_fd_count, &revents)) < 0)
1199         {
1200             return err;
1201         }
1202         if (revents & (POLLERR|POLLNVAL|POLLHUP)) {
1203             return 0;
1204         }
1205         if (revents & POLLIN)
1206             return 0;
1207     }
1208 }
1209 
1210 void outstream_thread_run(void* arg) {
1211     SoundIoOutStreamPrivate* os = cast(SoundIoOutStreamPrivate*) arg;
1212     SoundIoOutStream* outstream = &os.pub;
1213     SoundIoOutStreamAlsa* osa = &os.backend_data.alsa;
1214 
1215     int err;
1216 
1217     for (;;) {
1218         snd_pcm_state_t state = snd_pcm_state(osa.handle);
1219         switch (state) {
1220             case SND_PCM_STATE_SETUP:
1221             {
1222                 err = snd_pcm_prepare(osa.handle);
1223                 if (err < 0) {
1224                     outstream.error_callback(outstream, SoundIoError.Streaming);
1225                     return;
1226                 }
1227                 continue;
1228             }
1229             case SND_PCM_STATE_PREPARED:
1230             {
1231                 snd_pcm_sframes_t avail = snd_pcm_avail(osa.handle);
1232                 if (avail < 0) {
1233                     outstream.error_callback(outstream, SoundIoError.Streaming);
1234                     return;
1235                 }
1236 
1237                 if (cast(snd_pcm_uframes_t)avail == osa.buffer_size_frames) {
1238                     outstream.write_callback(outstream, 0, cast(int) avail);
1239                     if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osa.thread_exit_flag))
1240                         return;
1241                     continue;
1242                 }
1243 
1244                 err = snd_pcm_start(osa.handle);
1245                 if (err < 0) {
1246                     outstream.error_callback(outstream, SoundIoError.Streaming);
1247                     return;
1248                 }
1249                 continue;
1250             }
1251             case SND_PCM_STATE_RUNNING:
1252             case SND_PCM_STATE_PAUSED:
1253             {
1254                 err = outstream_wait_for_poll(os);
1255                 if (err) {
1256                     if (err == SoundIoError.Interrupted)
1257                         return;
1258                     outstream.error_callback(outstream, err);
1259                     return;
1260                 }
1261                 if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osa.thread_exit_flag))
1262                     return;
1263                 if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osa.clear_buffer_flag)) {
1264                     err = snd_pcm_drop(osa.handle);
1265                     if (err < 0) {
1266                         outstream.error_callback(outstream, SoundIoError.Streaming);
1267                         return;
1268                     }
1269                     err = snd_pcm_reset(osa.handle);
1270                     if (err < 0) {
1271                         if (err == -EBADFD) {
1272                             // If this happens the snd_pcm_drop will have done
1273                             // the function of the reset so it's ok that this
1274                             // did not work.
1275                         } else {
1276                             outstream.error_callback(outstream, SoundIoError.Streaming);
1277                             return;
1278                         }
1279                     }
1280                     continue;
1281                 }
1282 
1283                 snd_pcm_sframes_t avail = snd_pcm_avail_update(osa.handle);
1284                 if (avail < 0) {
1285                     err = outstream_xrun_recovery(os, cast(int) avail);
1286                     if (err < 0) {
1287                         outstream.error_callback(outstream, SoundIoError.Streaming);
1288                         return;
1289                     }
1290                     continue;
1291                 }
1292 
1293                 if (avail > 0)
1294                     outstream.write_callback(outstream, 0, cast(int) avail);
1295                 continue;
1296             }
1297             case SND_PCM_STATE_XRUN:
1298                 err = outstream_xrun_recovery(os, -EPIPE);
1299                 if (err < 0) {
1300                     outstream.error_callback(outstream, SoundIoError.Streaming);
1301                     return;
1302                 }
1303                 continue;
1304             case SND_PCM_STATE_SUSPENDED:
1305                 err = outstream_xrun_recovery(os, -ESTRPIPE);
1306                 if (err < 0) {
1307                     outstream.error_callback(outstream, SoundIoError.Streaming);
1308                     return;
1309                 }
1310                 continue;
1311             case SND_PCM_STATE_OPEN:
1312             case SND_PCM_STATE_DRAINING:
1313             case SND_PCM_STATE_DISCONNECTED:
1314                 outstream.error_callback(outstream, SoundIoError.Streaming);
1315                 return;
1316             default:
1317                 continue;
1318         }
1319     }
1320 }
1321 
1322 static void instream_thread_run(void* arg) {
1323     SoundIoInStreamPrivate* is_ = cast(SoundIoInStreamPrivate*) arg;
1324     SoundIoInStream* instream = &is_.pub;
1325     SoundIoInStreamAlsa* isa = &is_.backend_data.alsa;
1326 
1327     int err;
1328 
1329     for (;;) {
1330         snd_pcm_state_t state = snd_pcm_state(isa.handle);
1331         switch (state) {
1332             case SND_PCM_STATE_SETUP:
1333                 err = snd_pcm_prepare(isa.handle);
1334                 if (err < 0) {
1335                     instream.error_callback(instream, SoundIoError.Streaming);
1336                     return;
1337                 }
1338                 continue;
1339             case SND_PCM_STATE_PREPARED:
1340                 err = snd_pcm_start(isa.handle);
1341                 if (err < 0) {
1342                     instream.error_callback(instream, SoundIoError.Streaming);
1343                     return;
1344                 }
1345                 continue;
1346             case SND_PCM_STATE_RUNNING:
1347             case SND_PCM_STATE_PAUSED:
1348             {
1349                 err = instream_wait_for_poll(is_);
1350                 if (err < 0) {
1351                     if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(isa.thread_exit_flag))
1352                         return;
1353                     instream.error_callback(instream, SoundIoError.Streaming);
1354                     return;
1355                 }
1356                 if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(isa.thread_exit_flag))
1357                     return;
1358 
1359                 snd_pcm_sframes_t avail = snd_pcm_avail_update(isa.handle);
1360 
1361                 if (avail < 0) {
1362                     err = instream_xrun_recovery(is_, cast(int) avail);
1363                     if (err < 0) {
1364                         instream.error_callback(instream, SoundIoError.Streaming);
1365                         return;
1366                     }
1367                     continue;
1368                 }
1369 
1370                 if (avail > 0)
1371                     instream.read_callback(instream, 0, cast(int) avail);
1372                 continue;
1373             }
1374             case SND_PCM_STATE_XRUN:
1375                 err = instream_xrun_recovery(is_, -EPIPE);
1376                 if (err < 0) {
1377                     instream.error_callback(instream, SoundIoError.Streaming);
1378                     return;
1379                 }
1380                 continue;
1381             case SND_PCM_STATE_SUSPENDED:
1382                 err = instream_xrun_recovery(is_, -ESTRPIPE);
1383                 if (err < 0) {
1384                     instream.error_callback(instream, SoundIoError.Streaming);
1385                     return;
1386                 }
1387                 continue;
1388             case SND_PCM_STATE_OPEN:
1389             case SND_PCM_STATE_DRAINING:
1390             case SND_PCM_STATE_DISCONNECTED:
1391                 instream.error_callback(instream, SoundIoError.Streaming);
1392                 return;
1393             default:
1394                 continue;
1395         }
1396     }
1397 }
1398 
1399 static int outstream_open_alsa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) {
1400     SoundIoOutStreamAlsa* osa = &os.backend_data.alsa;
1401     SoundIoOutStream* outstream = &os.pub;
1402     SoundIoDevice* device = outstream.device;
1403 
1404     SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osa.clear_buffer_flag);
1405 
1406     if (outstream.software_latency == 0.0)
1407         outstream.software_latency = 1.0;
1408     outstream.software_latency = soundio_double_clamp(device.software_latency_min, outstream.software_latency, device.software_latency_max);
1409 
1410     int ch_count = outstream.layout.channel_count;
1411 
1412     osa.chmap_size = cast(int) (int.sizeof + int.sizeof * ch_count);
1413     osa.chmap = cast(snd_pcm_chmap_t*)ALLOCATE!char(osa.chmap_size);
1414     if (!osa.chmap) {
1415         outstream_destroy_alsa(si, os);
1416         return SoundIoError.NoMem;
1417     }
1418 
1419     int err;
1420 
1421     snd_pcm_hw_params_t* hwparams;
1422     snd_pcm_hw_params_malloc(&hwparams);
1423     if (!hwparams)
1424         return SoundIoError.NoMem;
1425     scope(exit) snd_pcm_hw_params_free(hwparams);
1426 
1427     snd_pcm_stream_t stream = aim_to_stream(outstream.device.aim);
1428 
1429     err = snd_pcm_open(&osa.handle, outstream.device.id, stream, 0);
1430     if (err < 0) {
1431         outstream_destroy_alsa(si, os);
1432         return SoundIoError.OpeningDevice;
1433     }
1434 
1435     err = snd_pcm_hw_params_any(osa.handle, hwparams);
1436     if (err < 0) {
1437         outstream_destroy_alsa(si, os);
1438         return SoundIoError.OpeningDevice;
1439     }
1440 
1441     int want_resample = !outstream.device.is_raw;
1442     err = snd_pcm_hw_params_set_rate_resample(osa.handle, hwparams, want_resample);
1443     if (err < 0) {
1444         outstream_destroy_alsa(si, os);
1445         return SoundIoError.OpeningDevice;
1446     }
1447 
1448     err = set_access(osa.handle, hwparams, &osa.access);
1449     if (err) {
1450         outstream_destroy_alsa(si, os);
1451         return err;
1452     }
1453 
1454     err = snd_pcm_hw_params_set_channels(osa.handle, hwparams, ch_count);
1455     if (err < 0) {
1456         outstream_destroy_alsa(si, os);
1457         return SoundIoError.OpeningDevice;
1458     }
1459 
1460     err = snd_pcm_hw_params_set_rate(osa.handle, hwparams, outstream.sample_rate, 0);
1461     if (err < 0) {
1462         outstream_destroy_alsa(si, os);
1463         return SoundIoError.OpeningDevice;
1464     }
1465 
1466     snd_pcm_format_t format = to_alsa_fmt(outstream.format);
1467     int phys_bits_per_sample = snd_pcm_format_physical_width(format);
1468     if (phys_bits_per_sample % 8 != 0) {
1469         outstream_destroy_alsa(si, os);
1470         return SoundIoError.IncompatibleDevice;
1471     }
1472     int phys_bytes_per_sample = phys_bits_per_sample / 8;
1473     err = snd_pcm_hw_params_set_format(osa.handle, hwparams, format);
1474     if (err < 0) {
1475         outstream_destroy_alsa(si, os);
1476         return SoundIoError.OpeningDevice;
1477     }
1478 
1479     osa.buffer_size_frames = cast(ulong) (outstream.software_latency * outstream.sample_rate);
1480     err = snd_pcm_hw_params_set_buffer_size_near(osa.handle, hwparams, &osa.buffer_size_frames);
1481     if (err < 0) {
1482         outstream_destroy_alsa(si, os);
1483         return SoundIoError.OpeningDevice;
1484     }
1485     outstream.software_latency = (cast(double)osa.buffer_size_frames) / cast(double)outstream.sample_rate;
1486 
1487     // write the hardware parameters to device
1488     err = snd_pcm_hw_params(osa.handle, hwparams);
1489     if (err < 0) {
1490         outstream_destroy_alsa(si, os);
1491         return (err == -EINVAL) ? SoundIoError.IncompatibleDevice : SoundIoError.OpeningDevice;
1492     }
1493 
1494     if ((snd_pcm_hw_params_get_period_size(hwparams, &osa.period_size, null)) < 0) {
1495         outstream_destroy_alsa(si, os);
1496         return SoundIoError.OpeningDevice;
1497     }
1498 
1499 
1500     // set channel map
1501     osa.chmap.channels = ch_count;
1502     for (int i = 0; i < ch_count; i += 1) {
1503         // `pos` is variable length array typed `uint[0]`, .ptr to avoid range violation
1504         osa.chmap.pos.ptr[i] = to_alsa_chmap_pos(outstream.layout.channels[i]);
1505     }
1506     err = snd_pcm_set_chmap(osa.handle, osa.chmap);
1507     if (err < 0)
1508         outstream.layout_error = SoundIoError.IncompatibleDevice;
1509 
1510     // get current swparams
1511     snd_pcm_sw_params_t* swparams;
1512     snd_pcm_sw_params_malloc(&swparams);
1513     if (!swparams)
1514         return SoundIoError.NoMem;
1515     scope(exit) snd_pcm_sw_params_free(swparams);
1516 
1517     err = snd_pcm_sw_params_current(osa.handle, swparams);
1518     if (err < 0) {
1519         outstream_destroy_alsa(si, os);
1520         return SoundIoError.OpeningDevice;
1521     }
1522 
1523     err = snd_pcm_sw_params_set_start_threshold(osa.handle, swparams, 0);
1524     if (err < 0) {
1525         outstream_destroy_alsa(si, os);
1526         return SoundIoError.OpeningDevice;
1527     }
1528 
1529     err = snd_pcm_sw_params_set_avail_min(osa.handle, swparams, osa.period_size);
1530     if (err < 0) {
1531         outstream_destroy_alsa(si, os);
1532         return SoundIoError.OpeningDevice;
1533     }
1534 
1535     // write the software parameters to device
1536     err = snd_pcm_sw_params(osa.handle, swparams);
1537     if (err < 0) {
1538         outstream_destroy_alsa(si, os);
1539         return (err == -EINVAL) ? SoundIoError.IncompatibleDevice : SoundIoError.OpeningDevice;
1540     }
1541 
1542     if (osa.access == SND_PCM_ACCESS_RW_INTERLEAVED || osa.access == SND_PCM_ACCESS_RW_NONINTERLEAVED) {
1543         osa.sample_buffer_size = cast(int) (ch_count * osa.period_size * phys_bytes_per_sample);
1544         osa.sample_buffer = ALLOCATE_NONZERO!(char)(osa.sample_buffer_size);
1545         if (!osa.sample_buffer) {
1546             outstream_destroy_alsa(si, os);
1547             return SoundIoError.NoMem;
1548         }
1549     }
1550 
1551     osa.poll_fd_count = snd_pcm_poll_descriptors_count(osa.handle);
1552     if (osa.poll_fd_count <= 0) {
1553         outstream_destroy_alsa(si, os);
1554         return SoundIoError.OpeningDevice;
1555     }
1556 
1557     osa.poll_fd_count_with_extra = osa.poll_fd_count + 1;
1558     osa.poll_fds = ALLOCATE!pollfd( osa.poll_fd_count_with_extra);
1559     if (!osa.poll_fds) {
1560         outstream_destroy_alsa(si, os);
1561         return SoundIoError.NoMem;
1562     }
1563 
1564     err = snd_pcm_poll_descriptors(osa.handle, osa.poll_fds, osa.poll_fd_count);
1565     if (err < 0) {
1566         outstream_destroy_alsa(si, os);
1567         return SoundIoError.OpeningDevice;
1568     }
1569 
1570     pollfd* extra_fd = &osa.poll_fds[osa.poll_fd_count];
1571     if (pipe2(osa.poll_exit_pipe_fd.ptr, O_NONBLOCK)) {
1572         assert(errno != EFAULT);
1573         assert(errno != EINVAL);
1574         assert(errno == EMFILE || errno == ENFILE);
1575         outstream_destroy_alsa(si, os);
1576         return SoundIoError.SystemResources;
1577     }
1578     extra_fd.fd = osa.poll_exit_pipe_fd[0];
1579     extra_fd.events = POLLIN;
1580 
1581     return 0;
1582 }
1583 
1584 static int outstream_start_alsa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) {
1585     SoundIoOutStreamAlsa* osa = &os.backend_data.alsa;
1586     SoundIo* soundio = &si.pub;
1587 
1588     assert(!osa.thread);
1589 
1590     SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osa.thread_exit_flag);
1591     if (auto err = soundio_os_thread_create(&outstream_thread_run, os, soundio.emit_rtprio_warning, &osa.thread))
1592         return err;
1593 
1594     return 0;
1595 }
1596 
1597 static int outstream_begin_write_alsa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os, SoundIoChannelArea** out_areas, int* frame_count) {
1598     *out_areas = null;
1599     SoundIoOutStreamAlsa* osa = &os.backend_data.alsa;
1600     SoundIoOutStream* outstream = &os.pub;
1601 
1602     if (osa.access == SND_PCM_ACCESS_RW_INTERLEAVED) {
1603         for (int ch = 0; ch < outstream.layout.channel_count; ch += 1) {
1604             osa.areas[ch].ptr = osa.sample_buffer + ch * outstream.bytes_per_sample;
1605             osa.areas[ch].step = outstream.bytes_per_frame;
1606         }
1607 
1608         osa.write_frame_count = soundio_int_min(*frame_count, cast(int) osa.period_size);
1609         *frame_count = osa.write_frame_count;
1610     } else if (osa.access == SND_PCM_ACCESS_RW_NONINTERLEAVED) {
1611         for (int ch = 0; ch < outstream.layout.channel_count; ch += 1) {
1612             osa.areas[ch].ptr = osa.sample_buffer + ch * outstream.bytes_per_sample * osa.period_size;
1613             osa.areas[ch].step = outstream.bytes_per_sample;
1614         }
1615 
1616         osa.write_frame_count = soundio_int_min(*frame_count, cast(int) osa.period_size);
1617         *frame_count = osa.write_frame_count;
1618     } else {
1619         const(snd_pcm_channel_area_t)* areas;
1620         snd_pcm_uframes_t frames = *frame_count;
1621         int err;
1622 
1623         err = snd_pcm_mmap_begin(osa.handle, &areas, &osa.offset, &frames);
1624         if (err < 0) {
1625             if (err == -EPIPE || err == -ESTRPIPE)
1626                 return SoundIoError.Underflow;
1627             else
1628                 return SoundIoError.Streaming;
1629         }
1630 
1631         for (int ch = 0; ch < outstream.layout.channel_count; ch += 1) {
1632             if ((areas[ch].first % 8 != 0) || (areas[ch].step % 8 != 0))
1633                 return SoundIoError.IncompatibleDevice;
1634             osa.areas[ch].step = areas[ch].step / 8;
1635             osa.areas[ch].ptr = (cast(char*)areas[ch].addr) + (areas[ch].first / 8) +
1636                 (osa.areas[ch].step * osa.offset);
1637         }
1638 
1639         osa.write_frame_count = cast(int) frames;
1640         *frame_count = osa.write_frame_count;
1641     }
1642 
1643     *out_areas = osa.areas.ptr;
1644     return 0;
1645 }
1646 
1647 static int outstream_end_write_alsa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) {
1648     SoundIoOutStreamAlsa* osa = &os.backend_data.alsa;
1649     SoundIoOutStream* outstream = &os.pub;
1650 
1651     snd_pcm_sframes_t commitres;
1652     if (osa.access == SND_PCM_ACCESS_RW_INTERLEAVED) {
1653         commitres = snd_pcm_writei(osa.handle, osa.sample_buffer, osa.write_frame_count);
1654     } else if (osa.access == SND_PCM_ACCESS_RW_NONINTERLEAVED) {
1655         char*[SOUNDIO_MAX_CHANNELS] ptrs;
1656         for (int ch = 0; ch < outstream.layout.channel_count; ch += 1) {
1657             ptrs[ch] = osa.sample_buffer + ch * outstream.bytes_per_sample * osa.period_size;
1658         }
1659         commitres = snd_pcm_writen(osa.handle, cast(void**)ptrs, osa.write_frame_count);
1660     } else {
1661         commitres = snd_pcm_mmap_commit(osa.handle, osa.offset, osa.write_frame_count);
1662     }
1663 
1664     if (commitres < 0 || commitres != osa.write_frame_count) {
1665         int err = cast(int) ((commitres >= 0) ? -EPIPE : commitres);
1666         if (err == -EPIPE || err == -ESTRPIPE)
1667             return SoundIoError.Underflow;
1668         else
1669             return SoundIoError.Streaming;
1670     }
1671     return 0;
1672 }
1673 
1674 static int outstream_clear_buffer_alsa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) {
1675     SoundIoOutStreamAlsa* osa = &os.backend_data.alsa;
1676     SOUNDIO_ATOMIC_FLAG_CLEAR(osa.clear_buffer_flag);
1677     return 0;
1678 }
1679 
1680 static int outstream_pause_alsa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os, bool pause) {
1681     if (!si)
1682         return SoundIoError.Invalid;
1683 
1684     SoundIoOutStreamAlsa* osa = &os.backend_data.alsa;
1685 
1686     if (!osa.handle)
1687         return SoundIoError.Invalid;
1688 
1689     if (osa.is_paused == pause)
1690         return 0;
1691 
1692     int err;
1693     err = snd_pcm_pause(osa.handle, pause);
1694     if (err < 0) {
1695         return SoundIoError.IncompatibleDevice;
1696     }
1697 
1698     osa.is_paused = pause;
1699     return 0;
1700 }
1701 
1702 static int outstream_get_latency_alsa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os, double* out_latency) {
1703     SoundIoOutStream* outstream = &os.pub;
1704     SoundIoOutStreamAlsa* osa = &os.backend_data.alsa;
1705     int err;
1706 
1707     snd_pcm_sframes_t delay;
1708     err = snd_pcm_delay(osa.handle, &delay);
1709     if (err < 0) {
1710         return SoundIoError.Streaming;
1711     }
1712 
1713     *out_latency = delay / cast(double)outstream.sample_rate;
1714     return 0;
1715 }
1716 
1717 static void instream_destroy_alsa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) {
1718     SoundIoInStreamAlsa* isa = &is_.backend_data.alsa;
1719 
1720     if (isa.thread) {
1721         SOUNDIO_ATOMIC_FLAG_CLEAR(isa.thread_exit_flag);
1722         soundio_os_thread_destroy(isa.thread);
1723         isa.thread = null;
1724     }
1725 
1726     if (isa.handle) {
1727         snd_pcm_close(isa.handle);
1728         isa.handle = null;
1729     }
1730 
1731     free(isa.poll_fds);
1732     isa.poll_fds = null;
1733 
1734     free(isa.chmap);
1735     isa.chmap = null;
1736 
1737     free(isa.sample_buffer);
1738     isa.sample_buffer = null;
1739 }
1740 
1741 static int instream_open_alsa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) {
1742     SoundIoInStreamAlsa* isa = &is_.backend_data.alsa;
1743     SoundIoInStream* instream = &is_.pub;
1744     SoundIoDevice* device = instream.device;
1745 
1746     if (instream.software_latency == 0.0)
1747         instream.software_latency = 1.0;
1748     instream.software_latency = soundio_double_clamp(device.software_latency_min, instream.software_latency, device.software_latency_max);
1749 
1750     int ch_count = instream.layout.channel_count;
1751 
1752     isa.chmap_size = cast(int) (int.sizeof + int.sizeof * ch_count);
1753     isa.chmap = cast(snd_pcm_chmap_t*) ALLOCATE!char(isa.chmap_size);
1754     if (!isa.chmap) {
1755         instream_destroy_alsa(si, is_);
1756         return SoundIoError.NoMem;
1757     }
1758 
1759     int err;
1760 
1761     snd_pcm_hw_params_t* hwparams;
1762     snd_pcm_hw_params_malloc(&hwparams);
1763     if (!hwparams)
1764         return SoundIoError.NoMem;
1765     scope(exit) snd_pcm_hw_params_free(hwparams);
1766 
1767     snd_pcm_stream_t stream = aim_to_stream(instream.device.aim);
1768 
1769     err = snd_pcm_open(&isa.handle, instream.device.id, stream, 0);
1770     if (err < 0) {
1771         instream_destroy_alsa(si, is_);
1772         return SoundIoError.OpeningDevice;
1773     }
1774 
1775     err = snd_pcm_hw_params_any(isa.handle, hwparams);
1776     if (err < 0) {
1777         instream_destroy_alsa(si, is_);
1778         return SoundIoError.OpeningDevice;
1779     }
1780 
1781     int want_resample = !instream.device.is_raw;
1782     err = snd_pcm_hw_params_set_rate_resample(isa.handle, hwparams, want_resample);
1783     if (err < 0) {
1784         instream_destroy_alsa(si, is_);
1785         return SoundIoError.OpeningDevice;
1786     }
1787 
1788     err = set_access(isa.handle, hwparams, &isa.access);
1789     if (err) {
1790         instream_destroy_alsa(si, is_);
1791         return err;
1792     }
1793 
1794     err = snd_pcm_hw_params_set_channels(isa.handle, hwparams, ch_count);
1795     if (err < 0) {
1796         instream_destroy_alsa(si, is_);
1797         return SoundIoError.OpeningDevice;
1798     }
1799 
1800     err = snd_pcm_hw_params_set_rate(isa.handle, hwparams, instream.sample_rate, 0);
1801     if (err < 0) {
1802         instream_destroy_alsa(si, is_);
1803         return SoundIoError.OpeningDevice;
1804     }
1805 
1806     snd_pcm_format_t format = to_alsa_fmt(instream.format);
1807     int phys_bits_per_sample = snd_pcm_format_physical_width(format);
1808     if (phys_bits_per_sample % 8 != 0) {
1809         instream_destroy_alsa(si, is_);
1810         return SoundIoError.IncompatibleDevice;
1811     }
1812     int phys_bytes_per_sample = phys_bits_per_sample / 8;
1813     err = snd_pcm_hw_params_set_format(isa.handle, hwparams, format);
1814     if (err < 0) {
1815         instream_destroy_alsa(si, is_);
1816         return SoundIoError.OpeningDevice;
1817     }
1818 
1819     snd_pcm_uframes_t period_frames = ceil_dbl_to_uframes(0.5 * instream.software_latency * cast(double)instream.sample_rate);
1820     err = snd_pcm_hw_params_set_period_size_near(isa.handle, hwparams, &period_frames, null);
1821     if (err < 0) {
1822         instream_destroy_alsa(si, is_);
1823         return SoundIoError.OpeningDevice;
1824     }
1825     instream.software_latency = (cast(double)period_frames) / cast(double)instream.sample_rate;
1826     isa.period_size = cast(int) period_frames;
1827 
1828 
1829     snd_pcm_uframes_t buffer_size_frames;
1830     err = snd_pcm_hw_params_set_buffer_size_last(isa.handle, hwparams, &buffer_size_frames);
1831     if (err < 0) {
1832         instream_destroy_alsa(si, is_);
1833         return SoundIoError.OpeningDevice;
1834     }
1835 
1836     // write the hardware parameters to device
1837     err = snd_pcm_hw_params(isa.handle, hwparams);
1838     if (err < 0) {
1839         instream_destroy_alsa(si, is_);
1840         return (err == -EINVAL) ? SoundIoError.IncompatibleDevice : SoundIoError.OpeningDevice;
1841     }
1842 
1843     // set channel map
1844     isa.chmap.channels = ch_count;
1845     for (int i = 0; i < ch_count; i += 1) {
1846         // `pos` is variable length array typed `uint[0]`, .ptr to avoid range violation
1847         isa.chmap.pos.ptr[i] = to_alsa_chmap_pos(instream.layout.channels[i]);
1848     }
1849     err = snd_pcm_set_chmap(isa.handle, isa.chmap);
1850     if (err < 0)
1851         instream.layout_error = SoundIoError.IncompatibleDevice;
1852 
1853     // get current swparams
1854     snd_pcm_sw_params_t* swparams;
1855     snd_pcm_sw_params_malloc(&swparams);
1856     if (!swparams)
1857         return SoundIoError.NoMem;
1858     scope(exit) snd_pcm_sw_params_free(swparams);
1859 
1860     err = snd_pcm_sw_params_current(isa.handle, swparams);
1861     if (err < 0) {
1862         instream_destroy_alsa(si, is_);
1863         return SoundIoError.OpeningDevice;
1864     }
1865 
1866     // write the software parameters to device
1867     err = snd_pcm_sw_params(isa.handle, swparams);
1868     if (err < 0) {
1869         instream_destroy_alsa(si, is_);
1870         return (err == -EINVAL) ? SoundIoError.IncompatibleDevice : SoundIoError.OpeningDevice;
1871     }
1872 
1873     if (isa.access == SND_PCM_ACCESS_RW_INTERLEAVED || isa.access == SND_PCM_ACCESS_RW_NONINTERLEAVED) {
1874         isa.sample_buffer_size = ch_count * isa.period_size * phys_bytes_per_sample;
1875         isa.sample_buffer = ALLOCATE_NONZERO!char(isa.sample_buffer_size);
1876         if (!isa.sample_buffer) {
1877             instream_destroy_alsa(si, is_);
1878             return SoundIoError.NoMem;
1879         }
1880     }
1881 
1882     isa.poll_fd_count = snd_pcm_poll_descriptors_count(isa.handle);
1883     if (isa.poll_fd_count <= 0) {
1884         instream_destroy_alsa(si, is_);
1885         return SoundIoError.OpeningDevice;
1886     }
1887 
1888     isa.poll_fds = ALLOCATE!pollfd(isa.poll_fd_count);
1889     if (!isa.poll_fds) {
1890         instream_destroy_alsa(si, is_);
1891         return SoundIoError.NoMem;
1892     }
1893 
1894     err = snd_pcm_poll_descriptors(isa.handle, isa.poll_fds, isa.poll_fd_count);
1895     if (err < 0) {
1896         instream_destroy_alsa(si, is_);
1897         return SoundIoError.OpeningDevice;
1898     }
1899 
1900     return 0;
1901 }
1902 
1903 static int instream_start_alsa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) {
1904     SoundIoInStreamAlsa* isa = &is_.backend_data.alsa;
1905     SoundIo* soundio = &si.pub;
1906 
1907     assert(!isa.thread);
1908 
1909     SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(isa.thread_exit_flag);
1910     if (auto err = soundio_os_thread_create(&instream_thread_run, is_, soundio.emit_rtprio_warning, &isa.thread)) {
1911         instream_destroy_alsa(si, is_);
1912         return err;
1913     }
1914 
1915     return 0;
1916 }
1917 
1918 static int instream_begin_read_alsa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_, SoundIoChannelArea** out_areas, int* frame_count) {
1919     *out_areas = null;
1920     SoundIoInStreamAlsa* isa = &is_.backend_data.alsa;
1921     SoundIoInStream* instream = &is_.pub;
1922 
1923     if (isa.access == SND_PCM_ACCESS_RW_INTERLEAVED) {
1924         for (int ch = 0; ch < instream.layout.channel_count; ch += 1) {
1925             isa.areas[ch].ptr = isa.sample_buffer + ch * instream.bytes_per_sample;
1926             isa.areas[ch].step = instream.bytes_per_frame;
1927         }
1928 
1929         isa.read_frame_count = soundio_int_min(*frame_count, isa.period_size);
1930         *frame_count = isa.read_frame_count;
1931 
1932         snd_pcm_sframes_t commitres = snd_pcm_readi(isa.handle, isa.sample_buffer, isa.read_frame_count);
1933         if (commitres < 0 || commitres != isa.read_frame_count) {
1934             int err = cast(int) ((commitres >= 0) ? -EPIPE : commitres);
1935             err = instream_xrun_recovery(is_, err);
1936             if (err < 0)
1937                 return SoundIoError.Streaming;
1938         }
1939     } else if (isa.access == SND_PCM_ACCESS_RW_NONINTERLEAVED) {
1940         char*[SOUNDIO_MAX_CHANNELS] ptrs;
1941         for (int ch = 0; ch < instream.layout.channel_count; ch += 1) {
1942             isa.areas[ch].ptr = isa.sample_buffer + ch * instream.bytes_per_sample * isa.period_size;
1943             isa.areas[ch].step = instream.bytes_per_sample;
1944             ptrs[ch] = isa.areas[ch].ptr;
1945         }
1946 
1947         isa.read_frame_count = soundio_int_min(*frame_count, isa.period_size);
1948         *frame_count = isa.read_frame_count;
1949 
1950         snd_pcm_sframes_t commitres = snd_pcm_readn(isa.handle, cast(void**)ptrs, isa.read_frame_count);
1951         if (commitres < 0 || commitres != isa.read_frame_count) {
1952             int err = cast(int) ((commitres >= 0) ? -EPIPE : commitres);
1953             err = instream_xrun_recovery(is_, err);
1954             if (err < 0)
1955                 return SoundIoError.Streaming;
1956         }
1957     } else {
1958         const(snd_pcm_channel_area_t)* areas;
1959         snd_pcm_uframes_t frames = *frame_count;
1960         int err;
1961 
1962         err = snd_pcm_mmap_begin(isa.handle, &areas, &isa.offset, &frames);
1963         if (err < 0) {
1964             err = instream_xrun_recovery(is_, err);
1965             if (err < 0)
1966                 return SoundIoError.Streaming;
1967         }
1968 
1969         for (int ch = 0; ch < instream.layout.channel_count; ch += 1) {
1970             if ((areas[ch].first % 8 != 0) || (areas[ch].step % 8 != 0))
1971                 return SoundIoError.IncompatibleDevice;
1972             isa.areas[ch].step = areas[ch].step / 8;
1973             isa.areas[ch].ptr = (cast(char*)areas[ch].addr) + (areas[ch].first / 8) +
1974                 (isa.areas[ch].step * isa.offset);
1975         }
1976 
1977         isa.read_frame_count = cast(int) frames;
1978         *frame_count = isa.read_frame_count;
1979     }
1980 
1981     *out_areas = isa.areas.ptr;
1982     return 0;
1983 }
1984 
1985 static int instream_end_read_alsa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) {
1986     SoundIoInStreamAlsa* isa = &is_.backend_data.alsa;
1987 
1988     if (isa.access == SND_PCM_ACCESS_RW_INTERLEAVED) {
1989         // nothing to do
1990     } else if (isa.access == SND_PCM_ACCESS_RW_NONINTERLEAVED) {
1991         // nothing to do
1992     } else {
1993         snd_pcm_sframes_t commitres = snd_pcm_mmap_commit(isa.handle, isa.offset, isa.read_frame_count);
1994         if (commitres < 0 || commitres != isa.read_frame_count) {
1995             int err = cast(int) ((commitres >= 0) ? -EPIPE : commitres);
1996             err = instream_xrun_recovery(is_, err);
1997             if (err < 0)
1998                 return SoundIoError.Streaming;
1999         }
2000     }
2001 
2002     return 0;
2003 }
2004 
2005 static int instream_pause_alsa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_, bool pause) {
2006     SoundIoInStreamAlsa* isa = &is_.backend_data.alsa;
2007 
2008     if (isa.is_paused == pause)
2009         return 0;
2010 
2011     int err;
2012     err = snd_pcm_pause(isa.handle, pause);
2013     if (err < 0)
2014         return SoundIoError.IncompatibleDevice;
2015 
2016     isa.is_paused = pause;
2017     return 0;
2018 }
2019 
2020 static int instream_get_latency_alsa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_, double* out_latency) {
2021     SoundIoInStream* instream = &is_.pub;
2022     SoundIoInStreamAlsa* isa = &is_.backend_data.alsa;
2023     int err;
2024 
2025     snd_pcm_sframes_t delay;
2026     err = snd_pcm_delay(isa.handle, &delay);
2027     if (err < 0) {
2028         return SoundIoError.Streaming;
2029     }
2030 
2031     *out_latency = delay / cast(double)instream.sample_rate;
2032     return 0;
2033 }
2034 
2035 package int soundio_alsa_init(SoundIoPrivate* si) {
2036     SoundIoAlsa* sia = &si.backend_data.alsa;
2037     int err;
2038 
2039     sia.notify_fd = -1;
2040     sia.notify_wd = -1;
2041     SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(sia.abort_flag);
2042 
2043     sia.mutex = soundio_os_mutex_create();
2044     if (!sia.mutex) {
2045         destroy_alsa(si);
2046         return SoundIoError.NoMem;
2047     }
2048 
2049     sia.cond = soundio_os_cond_create();
2050     if (!sia.cond) {
2051         destroy_alsa(si);
2052         return SoundIoError.NoMem;
2053     }
2054 
2055 
2056     // set up inotify to watch /dev/snd for devices added or removed
2057     sia.notify_fd = inotify_init1(IN_NONBLOCK);
2058     if (sia.notify_fd == -1) {
2059         err = errno;
2060         assert(err != EINVAL);
2061         destroy_alsa(si);
2062         if (err == EMFILE || err == ENFILE) {
2063             return SoundIoError.SystemResources;
2064         } else {
2065             assert(err == ENOMEM);
2066             return SoundIoError.NoMem;
2067         }
2068     }
2069 
2070     sia.notify_wd = inotify_add_watch(sia.notify_fd, "/dev/snd", IN_CREATE | IN_CLOSE_WRITE | IN_DELETE);
2071     if (sia.notify_wd == -1) {
2072         err = errno;
2073         assert(err != EACCES);
2074         assert(err != EBADF);
2075         assert(err != EFAULT);
2076         assert(err != EINVAL);
2077         assert(err != ENAMETOOLONG);
2078         destroy_alsa(si);
2079         if (err == ENOSPC) {
2080             return SoundIoError.SystemResources;
2081         } else if (err == ENOMEM) {
2082             return SoundIoError.NoMem;
2083         } else {
2084             // Kernel must not have ALSA support.
2085             return SoundIoError.InitAudioBackend;
2086         }
2087     }
2088 
2089     if (pipe2(sia.notify_pipe_fd.ptr, O_NONBLOCK)) {
2090         assert(errno != EFAULT);
2091         assert(errno != EINVAL);
2092         assert(errno == EMFILE || errno == ENFILE);
2093         return SoundIoError.SystemResources;
2094     }
2095 
2096     wakeup_device_poll(sia);
2097 
2098     //device_thread_run(si); TODO removeme
2099     err = soundio_os_thread_create(&device_thread_run, si, null, &sia.thread);
2100     if (err) {
2101         destroy_alsa(si);
2102         return err;
2103     }
2104 
2105     si.destroy = &destroy_alsa;
2106     si.flush_events = &flush_events_alsa;
2107     si.wait_events = &wait_events_alsa;
2108     si.wakeup = &wakeup_alsa;
2109     si.force_device_scan = &force_device_scan_alsa;
2110 
2111     si.outstream_open = &outstream_open_alsa;
2112     si.outstream_destroy = &outstream_destroy_alsa;
2113     si.outstream_start = &outstream_start_alsa;
2114     si.outstream_begin_write = &outstream_begin_write_alsa;
2115     si.outstream_end_write = &outstream_end_write_alsa;
2116     si.outstream_clear_buffer = &outstream_clear_buffer_alsa;
2117     si.outstream_pause = &outstream_pause_alsa;
2118     si.outstream_get_latency = &outstream_get_latency_alsa;
2119 
2120     si.instream_open = &instream_open_alsa;
2121     si.instream_destroy = &instream_destroy_alsa;
2122     si.instream_start = &instream_start_alsa;
2123     si.instream_begin_read = &instream_begin_read_alsa;
2124     si.instream_end_read = &instream_end_read_alsa;
2125     si.instream_pause = &instream_pause_alsa;
2126     si.instream_get_latency = &instream_get_latency_alsa;
2127 
2128     return 0;
2129 }