1 /// Translated from C to D
2 module soundio.soundio;
3 
4 @nogc nothrow:
5 extern(C): __gshared:
6 
7 
8 import soundio.atomics;
9 import soundio.soundio_private;
10 import soundio.util;
11 import soundio.os;
12 import soundio.config;
13 import core.stdc.string;
14 import core.stdc.assert_;
15 import core.stdc.stdio;
16 import core.stdc.stdlib: qsort, free;
17 
18 package:
19 
20 private extern(D) immutable SoundIoBackend[] available_backends = () {
21     SoundIoBackend[] result;
22     version(SOUNDIO_HAVE_JACK) result ~= SoundIoBackend.Jack;
23     version(SOUNDIO_HAVE_PULSEAUDIO) result ~= SoundIoBackend.PulseAudio;
24     version(SOUNDIO_HAVE_ALSA) result ~= SoundIoBackend.Alsa;
25     version(SOUNDIO_HAVE_COREAUDIO) result ~= SoundIoBackend.CoreAudio;
26     version(SOUNDIO_HAVE_WASAPI) result ~= SoundIoBackend.Wasapi;
27     return result;
28 } ();
29 
30 alias backend_init_t = int function(SoundIoPrivate*);
31 
32 immutable backend_init_t[7] backend_init_fns = () {
33     backend_init_t[7] result = null;
34     result[0] = null; // None backend
35     version(SOUNDIO_HAVE_JACK) result[1] = &soundio_jack_init;
36     version(SOUNDIO_HAVE_PULSEAUDIO) result[2] = &soundio_pulseaudio_init;
37     version(SOUNDIO_HAVE_ALSA) result[3] = &soundio_alsa_init;
38     version(SOUNDIO_HAVE_COREAUDIO) result[4] = &soundio_coreaudio_init;
39     version(SOUNDIO_HAVE_WASAPI) result[5] = &soundio_wasapi_init;
40     result[6] = &soundio_dummy_init;
41     return result;
42 } ();
43 
44 const(char)* soundio_strerror(int error) {
45     switch (cast(SoundIoError)error) {
46         case SoundIoError.None: return "(no error)";
47         case SoundIoError.NoMem: return "out of memory";
48         case SoundIoError.InitAudioBackend: return "unable to initialize audio backend";
49         case SoundIoError.SystemResources: return "system resource not available";
50         case SoundIoError.OpeningDevice: return "unable to open device";
51         case SoundIoError.NoSuchDevice: return "no such device";
52         case SoundIoError.Invalid: return "invalid value";
53         case SoundIoError.BackendUnavailable: return "backend unavailable";
54         case SoundIoError.Streaming: return "unrecoverable streaming failure";
55         case SoundIoError.IncompatibleDevice: return "incompatible device";
56         case SoundIoError.NoSuchClient: return "no such client";
57         case SoundIoError.IncompatibleBackend: return "incompatible backend";
58         case SoundIoError.BackendDisconnected: return "backend disconnected";
59         case SoundIoError.Interrupted: return "interrupted; try again";
60         case SoundIoError.Underflow: return "buffer underflow";
61         case SoundIoError.EncodingString: return "failed to encode string";
62         default: return "(invalid error)";
63     }
64 }
65 
66 int soundio_get_bytes_per_sample(SoundIoFormat format) {
67     switch (format) {
68     case SoundIoFormat.U8:         return 1;
69     case SoundIoFormat.S8:         return 1;
70     case SoundIoFormat.S16LE:      return 2;
71     case SoundIoFormat.S16BE:      return 2;
72     case SoundIoFormat.U16LE:      return 2;
73     case SoundIoFormat.U16BE:      return 2;
74     case SoundIoFormat.S24LE:      return 4;
75     case SoundIoFormat.S24BE:      return 4;
76     case SoundIoFormat.U24LE:      return 4;
77     case SoundIoFormat.U24BE:      return 4;
78     case SoundIoFormat.S32LE:      return 4;
79     case SoundIoFormat.S32BE:      return 4;
80     case SoundIoFormat.U32LE:      return 4;
81     case SoundIoFormat.U32BE:      return 4;
82     case SoundIoFormat.Float32LE:  return 4;
83     case SoundIoFormat.Float32BE:  return 4;
84     case SoundIoFormat.Float64LE:  return 8;
85     case SoundIoFormat.Float64BE:  return 8;
86 
87     case SoundIoFormat.Invalid:    return -1;
88     default: break;
89     }
90     return -1;
91 }
92 
93 const(char)* soundio_format_string(SoundIoFormat format) {
94     switch (format) {
95     case SoundIoFormat.S8:         return "signed 8-bit";
96     case SoundIoFormat.U8:         return "unsigned 8-bit";
97     case SoundIoFormat.S16LE:      return "signed 16-bit LE";
98     case SoundIoFormat.S16BE:      return "signed 16-bit BE";
99     case SoundIoFormat.U16LE:      return "unsigned 16-bit LE";
100     case SoundIoFormat.U16BE:      return "unsigned 16-bit LE";
101     case SoundIoFormat.S24LE:      return "signed 24-bit LE";
102     case SoundIoFormat.S24BE:      return "signed 24-bit BE";
103     case SoundIoFormat.U24LE:      return "unsigned 24-bit LE";
104     case SoundIoFormat.U24BE:      return "unsigned 24-bit BE";
105     case SoundIoFormat.S32LE:      return "signed 32-bit LE";
106     case SoundIoFormat.S32BE:      return "signed 32-bit BE";
107     case SoundIoFormat.U32LE:      return "unsigned 32-bit LE";
108     case SoundIoFormat.U32BE:      return "unsigned 32-bit BE";
109     case SoundIoFormat.Float32LE:  return "float 32-bit LE";
110     case SoundIoFormat.Float32BE:  return "float 32-bit BE";
111     case SoundIoFormat.Float64LE:  return "float 64-bit LE";
112     case SoundIoFormat.Float64BE:  return "float 64-bit BE";
113     case SoundIoFormat.Invalid:
114     default: break;
115     }
116     return "(invalid sample format)";
117 }
118 
119 
120 const(char)* soundio_backend_name(SoundIoBackend backend) {
121     switch (backend) {
122         case SoundIoBackend.None: return "(none)";
123         case SoundIoBackend.Jack: return "JACK";
124         case SoundIoBackend.PulseAudio: return "PulseAudio";
125         case SoundIoBackend.Alsa: return "ALSA";
126         case SoundIoBackend.CoreAudio: return "CoreAudio";
127         case SoundIoBackend.Wasapi: return "WASAPI";
128         case SoundIoBackend.Dummy: return "Dummy";
129         default: break;
130     }
131     return "(invalid backend)";
132 }
133 
134 void soundio_destroy(SoundIo* soundio) {
135     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
136 
137     soundio_disconnect(soundio);
138 
139     free(si);
140 }
141 
142 static void do_nothing_cb(SoundIo* soundio) { }
143 static void default_msg_callback(const(char)* msg) { }
144 
145 static void default_backend_disconnect_cb(SoundIo* soundio, int err) {
146     soundio_panic("libsoundio: backend disconnected: %s", soundio_strerror(err));
147 }
148 
149 static SoundIoAtomicFlag rtprio_seen = SoundIoAtomicFlag.init;
150 static void default_emit_rtprio_warning() {
151     if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(rtprio_seen)) {
152         printf_stderr("warning: unable to set high priority thread: Operation not permitted\n");
153         printf_stderr("See "
154             ~ "https://github.com/andrewrk/genesis/wiki/warning:-unable-to-set-high-priority-thread:-Operation-not-permitted\n");
155     }
156 }
157 
158 SoundIo* soundio_create() {
159     if (auto err = soundio_os_init())
160         return null;
161     SoundIoPrivate* si = ALLOCATE!SoundIoPrivate(1);
162     if (!si)
163         return null;
164     SoundIo* soundio = &si.pub;
165     soundio.on_devices_change = &do_nothing_cb;
166     soundio.on_backend_disconnect = &default_backend_disconnect_cb;
167     soundio.on_events_signal = &do_nothing_cb;
168     soundio.app_name = "SoundIo";
169     soundio.emit_rtprio_warning = &default_emit_rtprio_warning;
170     soundio.jack_info_callback = &default_msg_callback;
171     soundio.jack_error_callback = &default_msg_callback;
172     return soundio;
173 }
174 
175 int soundio_connect(SoundIo* soundio) {
176     int err = 0;
177 
178     for (int i = 0; i < available_backends.length; i += 1) {
179         SoundIoBackend backend = available_backends[i];
180         err = soundio_connect_backend(soundio, backend);
181         if (!err)
182             return 0;
183         if (err != SoundIoError.InitAudioBackend)
184             return err;
185     }
186 
187     return err;
188 }
189 
190 int soundio_connect_backend(SoundIo* soundio, SoundIoBackend backend) {
191     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
192 
193     if (soundio.current_backend)
194         return SoundIoError.Invalid;
195 
196     if (backend <= 0 || backend > SoundIoBackend.Dummy)
197         return SoundIoError.Invalid;
198 
199     extern(C) int function(SoundIoPrivate*) fn = backend_init_fns[backend];
200 
201     if (!fn)
202         return SoundIoError.BackendUnavailable;
203 
204     if (auto err = backend_init_fns[backend](si)) {
205         soundio_disconnect(soundio);
206         return err;
207     }
208     soundio.current_backend = backend;
209 
210     return 0;
211 }
212 
213 void soundio_disconnect(SoundIo* soundio) {
214     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
215 
216     if (!si)
217         return;
218 
219     if (si.destroy)
220         si.destroy(si);
221     memset(&si.backend_data, 0, SoundIoBackendData.sizeof);
222 
223     soundio.current_backend = SoundIoBackend.None;
224 
225     soundio_destroy_devices_info(si.safe_devices_info);
226     si.safe_devices_info = null;
227 
228     si.destroy = null;
229     si.flush_events = null;
230     si.wait_events = null;
231     si.wakeup = null;
232     si.force_device_scan = null;
233 
234     si.outstream_open = null;
235     si.outstream_destroy = null;
236     si.outstream_start = null;
237     si.outstream_begin_write = null;
238     si.outstream_end_write = null;
239     si.outstream_clear_buffer = null;
240     si.outstream_pause = null;
241     si.outstream_get_latency = null;
242     si.outstream_set_volume = null;
243 
244     si.instream_open = null;
245     si.instream_destroy = null;
246     si.instream_start = null;
247     si.instream_begin_read = null;
248     si.instream_end_read = null;
249     si.instream_pause = null;
250     si.instream_get_latency = null;
251 }
252 
253 void soundio_flush_events(SoundIo* soundio) {
254     assert(soundio.current_backend != SoundIoBackend.None);
255     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
256     si.flush_events(si);
257 }
258 
259 int soundio_input_device_count(SoundIo* soundio) {
260     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
261 
262     assert(si.safe_devices_info);
263     if (!si.safe_devices_info)
264         return -1;
265 
266     assert(soundio.current_backend != SoundIoBackend.None);
267     if (soundio.current_backend == SoundIoBackend.None)
268         return -1;
269 
270     return si.safe_devices_info.input_devices.length;
271 }
272 
273 int soundio_output_device_count(SoundIo* soundio) {
274     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
275 
276     assert(si.safe_devices_info);
277     if (!si.safe_devices_info)
278         return -1;
279 
280     assert(soundio.current_backend != SoundIoBackend.None);
281     if (soundio.current_backend == SoundIoBackend.None)
282         return -1;
283 
284     return si.safe_devices_info.output_devices.length;
285 }
286 
287 int soundio_default_input_device_index(SoundIo* soundio) {
288     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
289 
290     assert(si.safe_devices_info);
291     if (!si.safe_devices_info)
292         return -1;
293 
294     assert(soundio.current_backend != SoundIoBackend.None);
295     if (soundio.current_backend == SoundIoBackend.None)
296         return -1;
297 
298     return si.safe_devices_info.default_input_index;
299 }
300 
301 int soundio_default_output_device_index(SoundIo* soundio) {
302     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
303 
304     assert(si.safe_devices_info);
305     if (!si.safe_devices_info)
306         return -1;
307 
308     assert(soundio.current_backend != SoundIoBackend.None);
309     if (soundio.current_backend == SoundIoBackend.None)
310         return -1;
311 
312     return si.safe_devices_info.default_output_index;
313 }
314 
315 SoundIoDevice* soundio_get_input_device(SoundIo* soundio, int index) {
316     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
317 
318     assert(soundio.current_backend != SoundIoBackend.None);
319     if (soundio.current_backend == SoundIoBackend.None)
320         return null;
321 
322     assert(si.safe_devices_info);
323     if (!si.safe_devices_info)
324         return null;
325 
326     assert(index >= 0);
327     assert(index < si.safe_devices_info.input_devices.length);
328     if (index < 0 || index >= si.safe_devices_info.input_devices.length)
329         return null;
330 
331     SoundIoDevice* device = si.safe_devices_info.input_devices.val_at(index);
332     soundio_device_ref(device);
333     return device;
334 }
335 
336 SoundIoDevice* soundio_get_output_device(SoundIo* soundio, int index) {
337     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
338 
339     assert(soundio.current_backend != SoundIoBackend.None);
340     if (soundio.current_backend == SoundIoBackend.None)
341         return null;
342 
343     assert(si.safe_devices_info);
344     if (!si.safe_devices_info)
345         return null;
346 
347     assert(index >= 0);
348     assert(index < si.safe_devices_info.output_devices.length);
349     if (index < 0 || index >= si.safe_devices_info.output_devices.length)
350         return null;
351 
352     SoundIoDevice* device = si.safe_devices_info.output_devices.val_at(index);
353     soundio_device_ref(device);
354     return device;
355 }
356 
357 void soundio_device_unref(SoundIoDevice* device) {
358     if (!device)
359         return;
360 
361     device.ref_count -= 1;
362     assert(device.ref_count >= 0);
363 
364     if (device.ref_count == 0) {
365         SoundIoDevicePrivate* dev = cast(SoundIoDevicePrivate*)device;
366         if (dev.destruct)
367             dev.destruct(dev);
368 
369         free(device.id);
370         free(device.name);
371 
372         if (device.sample_rates != &dev.prealloc_sample_rate_range &&
373             device.sample_rates != dev.sample_rates.items)
374         {
375             free(device.sample_rates);
376         }
377         dev.sample_rates.deinit();
378 
379         if (device.formats != &dev.prealloc_format)
380             free(device.formats);
381 
382         if (device.layouts != &device.current_layout)
383             free(device.layouts);
384 
385         free(dev);
386     }
387 }
388 
389 void soundio_device_ref(SoundIoDevice* device) {
390     assert(device);
391     device.ref_count += 1;
392 }
393 
394 void soundio_wait_events(SoundIo* soundio) {
395     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
396     si.wait_events(si);
397 }
398 
399 void soundio_wakeup(SoundIo* soundio) {
400     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
401     si.wakeup(si);
402 }
403 
404 void soundio_force_device_scan(SoundIo* soundio) {
405     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
406     si.force_device_scan(si);
407 }
408 
409 int soundio_outstream_begin_write(SoundIoOutStream* outstream, SoundIoChannelArea** areas, int* frame_count) {
410     SoundIo* soundio = outstream.device.soundio;
411     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
412     SoundIoOutStreamPrivate* os = cast(SoundIoOutStreamPrivate*)outstream;
413     if (*frame_count <= 0)
414         return SoundIoError.Invalid;
415     return si.outstream_begin_write(si, os, areas, frame_count);
416 }
417 
418 int soundio_outstream_end_write(SoundIoOutStream* outstream) {
419     SoundIo* soundio = outstream.device.soundio;
420     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
421     SoundIoOutStreamPrivate* os = cast(SoundIoOutStreamPrivate*)outstream;
422     return si.outstream_end_write(si, os);
423 }
424 
425 static void default_outstream_error_callback(SoundIoOutStream* os, int err) {
426     soundio_panic("libsoundio: %s", soundio_strerror(err));
427 }
428 
429 static void default_underflow_callback(SoundIoOutStream* outstream) { }
430 
431 SoundIoOutStream* soundio_outstream_create(SoundIoDevice* device) {
432     SoundIoOutStreamPrivate* os = ALLOCATE!SoundIoOutStreamPrivate(1);
433     SoundIoOutStream* outstream = &os.pub;
434 
435     if (!os)
436         return null;
437     if (!device)
438         return null;
439 
440     outstream.device = device;
441     soundio_device_ref(device);
442 
443     outstream.error_callback = &default_outstream_error_callback;
444     outstream.underflow_callback = &default_underflow_callback;
445 
446     return outstream;
447 }
448 
449 int soundio_outstream_open(SoundIoOutStream* outstream) {
450     SoundIoDevice* device = outstream.device;
451 
452     if (device.aim != SoundIoDeviceAim.Output)
453         return SoundIoError.Invalid;
454 
455     if (device.probe_error)
456         return device.probe_error;
457 
458     if (outstream.layout.channel_count > SOUNDIO_MAX_CHANNELS)
459         return SoundIoError.Invalid;
460 
461     if (outstream.format == SoundIoFormat.Invalid) {
462         outstream.format = soundio_device_supports_format(device, SoundIoFormatFloat32NE) ?
463             SoundIoFormatFloat32NE : device.formats[0];
464     }
465 
466     if (outstream.format <= SoundIoFormat.Invalid)
467         return SoundIoError.Invalid;
468 
469     if (!outstream.layout.channel_count) {
470         const(SoundIoChannelLayout)* stereo = soundio_channel_layout_get_builtin(SoundIoChannelLayoutId.Stereo);
471         outstream.layout = soundio_device_supports_layout(device, stereo) ? *stereo : device.layouts[0];
472     }
473 
474     if (!outstream.sample_rate)
475         outstream.sample_rate = soundio_device_nearest_sample_rate(device, 48000);
476 
477     SoundIoOutStreamPrivate* os = cast(SoundIoOutStreamPrivate*)outstream;
478     outstream.bytes_per_frame = soundio_get_bytes_per_frame(outstream.format, outstream.layout.channel_count);
479     outstream.bytes_per_sample = soundio_get_bytes_per_sample(outstream.format);
480 
481     SoundIo* soundio = device.soundio;
482     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
483     return si.outstream_open(si, os);
484 }
485 
486 void soundio_outstream_destroy(SoundIoOutStream* outstream) {
487     if (!outstream)
488         return;
489 
490     SoundIoOutStreamPrivate* os = cast(SoundIoOutStreamPrivate*)outstream;
491     SoundIo* soundio = outstream.device.soundio;
492     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
493 
494     if (si.outstream_destroy)
495         si.outstream_destroy(si, os);
496 
497     soundio_device_unref(outstream.device);
498     free(os);
499 }
500 
501 int soundio_outstream_start(SoundIoOutStream* outstream) {
502     SoundIo* soundio = outstream.device.soundio;
503     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
504     SoundIoOutStreamPrivate* os = cast(SoundIoOutStreamPrivate*)outstream;
505     return si.outstream_start(si, os);
506 }
507 
508 int soundio_outstream_pause(SoundIoOutStream* outstream, bool pause) {
509     SoundIo* soundio = outstream.device.soundio;
510     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
511     SoundIoOutStreamPrivate* os = cast(SoundIoOutStreamPrivate*)outstream;
512     return si.outstream_pause(si, os, pause);
513 }
514 
515 int soundio_outstream_clear_buffer(SoundIoOutStream* outstream) {
516     SoundIo* soundio = outstream.device.soundio;
517     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
518     SoundIoOutStreamPrivate* os = cast(SoundIoOutStreamPrivate*)outstream;
519     return si.outstream_clear_buffer(si, os);
520 }
521 
522 int soundio_outstream_get_latency(SoundIoOutStream* outstream, double* out_latency) {
523     SoundIo* soundio = outstream.device.soundio;
524     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
525     SoundIoOutStreamPrivate* os = cast(SoundIoOutStreamPrivate*)outstream;
526     return si.outstream_get_latency(si, os, out_latency);
527 }
528 
529 int soundio_outstream_set_volume(SoundIoOutStream* outstream, double volume) {
530     SoundIo* soundio = outstream.device.soundio;
531     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
532     SoundIoOutStreamPrivate* os = cast(SoundIoOutStreamPrivate*)outstream;
533     return si.outstream_set_volume(si, os, volume);
534 }
535 
536 static void default_instream_error_callback(SoundIoInStream* is_, int err) {
537     soundio_panic("libsoundio: %s", soundio_strerror(err));
538 }
539 
540 static void default_overflow_callback(SoundIoInStream* instream) { }
541 
542 SoundIoInStream* soundio_instream_create(SoundIoDevice* device) {
543     SoundIoInStreamPrivate* is_ = ALLOCATE!SoundIoInStreamPrivate(1);
544     SoundIoInStream* instream = &is_.pub;
545 
546     if (!is_)
547         return null;
548     if (!device)
549         return null;
550 
551     instream.device = device;
552     soundio_device_ref(device);
553 
554     instream.error_callback = &default_instream_error_callback;
555     instream.overflow_callback = &default_overflow_callback;
556 
557     return instream;
558 }
559 
560 int soundio_instream_open(SoundIoInStream* instream) {
561     SoundIoDevice* device = instream.device;
562     if (device.aim != SoundIoDeviceAim.Input)
563         return SoundIoError.Invalid;
564 
565     if (instream.format <= SoundIoFormat.Invalid)
566         return SoundIoError.Invalid;
567 
568     if (instream.layout.channel_count > SOUNDIO_MAX_CHANNELS)
569         return SoundIoError.Invalid;
570 
571     if (device.probe_error)
572         return device.probe_error;
573 
574     if (instream.format == SoundIoFormat.Invalid) {
575         instream.format = soundio_device_supports_format(device, SoundIoFormat.Float32NE) ?
576             SoundIoFormat.Float32NE : device.formats[0];
577     }
578 
579     if (!instream.layout.channel_count) {
580         const(SoundIoChannelLayout)* stereo = soundio_channel_layout_get_builtin(SoundIoChannelLayoutId.Stereo);
581         instream.layout = soundio_device_supports_layout(device, stereo) ? *stereo : device.layouts[0];
582     }
583 
584     if (!instream.sample_rate)
585         instream.sample_rate = soundio_device_nearest_sample_rate(device, 48000);
586 
587 
588     instream.bytes_per_frame = soundio_get_bytes_per_frame(instream.format, instream.layout.channel_count);
589     instream.bytes_per_sample = soundio_get_bytes_per_sample(instream.format);
590     SoundIo* soundio = device.soundio;
591     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
592     SoundIoInStreamPrivate* is_ = cast(SoundIoInStreamPrivate*)instream;
593     return si.instream_open(si, is_);
594 }
595 
596 int soundio_instream_start(SoundIoInStream* instream) {
597     SoundIo* soundio = instream.device.soundio;
598     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
599     SoundIoInStreamPrivate* is_ = cast(SoundIoInStreamPrivate*)instream;
600     return si.instream_start(si, is_);
601 }
602 
603 void soundio_instream_destroy(SoundIoInStream* instream) {
604     if (!instream)
605         return;
606 
607     SoundIoInStreamPrivate* is_ = cast(SoundIoInStreamPrivate*)instream;
608     SoundIo* soundio = instream.device.soundio;
609     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
610 
611     if (si.instream_destroy)
612         si.instream_destroy(si, is_);
613 
614     soundio_device_unref(instream.device);
615     free(is_);
616 }
617 
618 int soundio_instream_pause(SoundIoInStream* instream, bool pause) {
619     SoundIo* soundio = instream.device.soundio;
620     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
621     SoundIoInStreamPrivate* is_ = cast(SoundIoInStreamPrivate*)instream;
622     return si.instream_pause(si, is_, pause);
623 }
624 
625 int soundio_instream_begin_read(SoundIoInStream* instream, SoundIoChannelArea** areas, int* frame_count) {
626     SoundIo* soundio = instream.device.soundio;
627     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
628     SoundIoInStreamPrivate* is_ = cast(SoundIoInStreamPrivate*)instream;
629     return si.instream_begin_read(si, is_, areas, frame_count);
630 }
631 
632 int soundio_instream_end_read(SoundIoInStream* instream) {
633     SoundIo* soundio = instream.device.soundio;
634     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
635     SoundIoInStreamPrivate* is_ = cast(SoundIoInStreamPrivate*)instream;
636     return si.instream_end_read(si, is_);
637 }
638 
639 int soundio_instream_get_latency(SoundIoInStream* instream, double* out_latency) {
640     SoundIo* soundio = instream.device.soundio;
641     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
642     SoundIoInStreamPrivate* is_ = cast(SoundIoInStreamPrivate*)instream;
643     return si.instream_get_latency(si, is_, out_latency);
644 }
645 
646 void soundio_destroy_devices_info(SoundIoDevicesInfo* devices_info) {
647     if (!devices_info)
648         return;
649 
650     for (int i = 0; i < devices_info.input_devices.length; i += 1)
651         soundio_device_unref(devices_info.input_devices.val_at(i));
652     for (int i = 0; i < devices_info.output_devices.length; i += 1)
653         soundio_device_unref(devices_info.output_devices.val_at(i));
654 
655     devices_info.input_devices.deinit();
656     devices_info.output_devices.deinit();
657 
658     free(devices_info);
659 }
660 
661 bool soundio_have_backend(SoundIoBackend backend) {
662     assert(backend > 0);
663     assert(backend <= SoundIoBackend.max);
664     return cast(bool) backend_init_fns[backend];
665 }
666 
667 int soundio_backend_count(SoundIo* soundio) {
668     return cast(int) available_backends.length;
669 }
670 
671 SoundIoBackend soundio_get_backend(SoundIo* soundio, int index) {
672     return available_backends[index];
673 }
674 
675 static bool layout_contains(const(SoundIoChannelLayout)* available_layouts, int available_layouts_count, const(SoundIoChannelLayout)* target_layout) {
676     for (int i = 0; i < available_layouts_count; i += 1) {
677         const(SoundIoChannelLayout)* available_layout = &available_layouts[i];
678         if (soundio_channel_layout_equal(target_layout, available_layout))
679             return true;
680     }
681     return false;
682 }
683 
684 const(SoundIoChannelLayout)* soundio_best_matching_channel_layout(const(SoundIoChannelLayout)* preferred_layouts, int preferred_layouts_count, const(SoundIoChannelLayout)* available_layouts, int available_layouts_count) {
685     for (int i = 0; i < preferred_layouts_count; i += 1) {
686         const(SoundIoChannelLayout)* preferred_layout = &preferred_layouts[i];
687         if (layout_contains(available_layouts, available_layouts_count, preferred_layout))
688             return preferred_layout;
689     }
690     return null;
691 }
692 
693 static int compare_layouts(const(void)* a, const(void)* b) {
694     const(SoundIoChannelLayout)* layout_a = cast(const(SoundIoChannelLayout)*)a;
695     const(SoundIoChannelLayout)* layout_b = cast(const(SoundIoChannelLayout)*)b;
696     if (layout_a.channel_count > layout_b.channel_count)
697         return -1;
698     else if (layout_a.channel_count < layout_b.channel_count)
699         return 1;
700     else
701         return 0;
702 }
703 
704 void soundio_sort_channel_layouts(SoundIoChannelLayout* layouts, int layouts_count) {
705     if (!layouts)
706         return;
707 
708     qsort(layouts, layouts_count, SoundIoChannelLayout.sizeof, &compare_layouts);
709 }
710 
711 void soundio_device_sort_channel_layouts(SoundIoDevice* device) {
712     soundio_sort_channel_layouts(device.layouts, device.layout_count);
713 }
714 
715 bool soundio_device_supports_format(SoundIoDevice* device, SoundIoFormat format) {
716     for (int i = 0; i < device.format_count; i += 1) {
717         if (device.formats[i] == format)
718             return true;
719     }
720     return false;
721 }
722 
723 bool soundio_device_supports_layout(SoundIoDevice* device, const(SoundIoChannelLayout)* layout) {
724     for (int i = 0; i < device.layout_count; i += 1) {
725         if (soundio_channel_layout_equal(&device.layouts[i], layout))
726             return true;
727     }
728     return false;
729 }
730 
731 bool soundio_device_supports_sample_rate(SoundIoDevice* device, int sample_rate) {
732     for (int i = 0; i < device.sample_rate_count; i += 1) {
733         SoundIoSampleRateRange* range = &device.sample_rates[i];
734         if (sample_rate >= range.min && sample_rate <= range.max)
735             return true;
736     }
737     return false;
738 }
739 
740 static int abs_diff_int(int a, int b) {
741     int x = a - b;
742     return (x >= 0) ? x : -x;
743 }
744 
745 int soundio_device_nearest_sample_rate(SoundIoDevice* device, int sample_rate) {
746     int best_rate = -1;
747     int best_delta = -1;
748     for (int i = 0; i < device.sample_rate_count; i += 1) {
749         SoundIoSampleRateRange* range = &device.sample_rates[i];
750         int candidate_rate = soundio_int_clamp(range.min, sample_rate, range.max);
751         if (candidate_rate == sample_rate)
752             return candidate_rate;
753 
754         int delta = abs_diff_int(candidate_rate, sample_rate);
755         bool best_rate_too_small = best_rate < sample_rate;
756         bool candidate_rate_too_small = candidate_rate < sample_rate;
757         if (best_rate == -1 ||
758             (best_rate_too_small && !candidate_rate_too_small) ||
759             ((best_rate_too_small || !candidate_rate_too_small) && delta < best_delta))
760         {
761             best_rate = candidate_rate;
762             best_delta = delta;
763         }
764     }
765     return best_rate;
766 }
767 
768 bool soundio_device_equal(const(SoundIoDevice)* a, const(SoundIoDevice)* b) {
769     return a.is_raw == b.is_raw && a.aim == b.aim && strcmp(a.id, b.id) == 0;
770 }
771 
772 const(char)* soundio_version_string() {
773     return SOUNDIO_VERSION_STRING;
774 }
775 
776 int soundio_version_major() {
777     return SOUNDIO_VERSION_MAJOR;
778 }
779 
780 int soundio_version_minor() {
781     return SOUNDIO_VERSION_MINOR;
782 }
783 
784 int soundio_version_patch() {
785     return SOUNDIO_VERSION_PATCH;
786 }