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