1 /// Translated from C to D
2 module sio_microphone;
3 
4 extern(C): @nogc: nothrow: __gshared:
5 
6 import soundio.api;
7 import soundio.util: printf_stderr;
8 
9 import core.stdc.stdio;
10 import core.stdc.stdarg;
11 import core.stdc.stdlib;
12 import core.stdc..string;
13 import core.stdc.math;
14 
15 SoundIoRingBuffer* ring_buffer = null;
16 
17 private SoundIoFormat[19] prioritized_formats = [
18     SoundIoFormat.Float32NE,
19     SoundIoFormat.Float32FE,
20     SoundIoFormat.S32NE,
21     SoundIoFormat.S32FE,
22     SoundIoFormat.S24NE,
23     SoundIoFormat.S24FE,
24     SoundIoFormat.S16NE,
25     SoundIoFormat.S16FE,
26     SoundIoFormat.Float64NE,
27     SoundIoFormat.Float64FE,
28     SoundIoFormat.U32NE,
29     SoundIoFormat.U32FE,
30     SoundIoFormat.U24NE,
31     SoundIoFormat.U24FE,
32     SoundIoFormat.U16NE,
33     SoundIoFormat.U16FE,
34     SoundIoFormat.S8,
35     SoundIoFormat.U8,
36     SoundIoFormat.Invalid,
37 ];
38 
39 private int[5] prioritized_sample_rates = [
40     48000,
41     44100,
42     96000,
43     24000,
44     0,
45 ];
46 
47 private void panic(T...)(const(char)* format, T args) {
48     printf_stderr(format, args);
49     /+va_list ap;
50     va_start(ap, format);
51     vprintf_stderr(format, ap);
52     printf_stderr("\n");
53     va_end(ap);
54     +/
55     abort();
56 }
57 
58 private int min_int(int a, int b) {
59     return (a < b) ? a : b;
60 }
61 
62 private void read_callback(SoundIoInStream* instream, int frame_count_min, int frame_count_max) {
63     SoundIoChannelArea* areas;
64     char* write_ptr = soundio_ring_buffer_write_ptr(ring_buffer);
65     int free_bytes = soundio_ring_buffer_free_count(ring_buffer);
66     int free_count = free_bytes / instream.bytes_per_frame;
67 
68     if (frame_count_min > free_count)
69         panic("ring buffer overflow");
70 
71     int write_frames = min_int(free_count, frame_count_max);
72     int frames_left = write_frames;
73 
74     for (;;) {
75         int frame_count = frames_left;
76 
77         if (auto err = soundio_instream_begin_read(instream, &areas, &frame_count))
78             panic("begin read error: %s", soundio_strerror(err));
79 
80         if (!frame_count)
81             break;
82 
83         if (!areas) {
84             // Due to an overflow there is a hole. Fill the ring buffer with
85             // silence for the size of the hole.
86             memset(write_ptr, 0, frame_count * instream.bytes_per_frame);
87             printf_stderr("Dropped %d frames due to internal overflow\n", frame_count);
88         } else {
89             for (int frame = 0; frame < frame_count; frame += 1) {
90                 for (int ch = 0; ch < instream.layout.channel_count; ch += 1) {
91                     memcpy(write_ptr, areas[ch].ptr, instream.bytes_per_sample);
92                     areas[ch].ptr += areas[ch].step;
93                     write_ptr += instream.bytes_per_sample;
94                 }
95             }
96         }
97 
98         if (auto err = soundio_instream_end_read(instream))
99             panic("end read error: %s", soundio_strerror(err));
100 
101         frames_left -= frame_count;
102         if (frames_left <= 0)
103             break;
104     }
105 
106     int advance_bytes = write_frames * instream.bytes_per_frame;
107     soundio_ring_buffer_advance_write_ptr(ring_buffer, advance_bytes);
108 }
109 
110 private void write_callback(SoundIoOutStream* outstream, int frame_count_min, int frame_count_max) {
111     SoundIoChannelArea* areas;
112     int frames_left;
113 
114     char* read_ptr = soundio_ring_buffer_read_ptr(ring_buffer);
115     int fill_bytes = soundio_ring_buffer_fill_count(ring_buffer);
116     int fill_count = fill_bytes / outstream.bytes_per_frame;
117 
118     if (frame_count_min > fill_count) {
119         int frame_count;
120         // Ring buffer does not have enough data, fill with zeroes.
121         frames_left = frame_count_min;
122         for (;;) {
123             frame_count = frames_left;
124             if (frame_count <= 0)
125               return;
126             if (auto err = soundio_outstream_begin_write(outstream, &areas, &frame_count))
127                 panic("begin write error: %s", soundio_strerror(err));
128             if (frame_count <= 0)
129                 return;
130             for (int frame = 0; frame < frame_count; frame += 1) {
131                 for (int ch = 0; ch < outstream.layout.channel_count; ch += 1) {
132                     memset(areas[ch].ptr, 0, outstream.bytes_per_sample);
133                     areas[ch].ptr += areas[ch].step;
134                 }
135             }
136             if (auto err = soundio_outstream_end_write(outstream))
137                 panic("end write error: %s", soundio_strerror(err));
138             frames_left -= frame_count;
139         }
140     }
141 
142     int read_count = min_int(frame_count_max, fill_count);
143     frames_left = read_count;
144 
145     while (frames_left > 0) {
146         int frame_count = frames_left;
147 
148         if (auto err = soundio_outstream_begin_write(outstream, &areas, &frame_count))
149             panic("begin write error: %s", soundio_strerror(err));
150 
151         if (frame_count <= 0)
152             break;
153 
154         for (int frame = 0; frame < frame_count; frame += 1) {
155             for (int ch = 0; ch < outstream.layout.channel_count; ch += 1) {
156                 memcpy(areas[ch].ptr, read_ptr, outstream.bytes_per_sample);
157                 areas[ch].ptr += areas[ch].step;
158                 read_ptr += outstream.bytes_per_sample;
159             }
160         }
161 
162         if (auto err = soundio_outstream_end_write(outstream))
163             panic("end write error: %s", soundio_strerror(err));
164 
165         frames_left -= frame_count;
166     }
167 
168     soundio_ring_buffer_advance_read_ptr(ring_buffer, read_count * outstream.bytes_per_frame);
169 }
170 
171 private void underflow_callback(SoundIoOutStream* outstream) {
172     static int count = 0;
173     printf_stderr("underflow %d\n", ++count);
174 }
175 
176 private int usage(char* exe) {
177     printf_stderr("Usage: %s [options]\n"
178             ~ "Options:\n"
179             ~ "  [--backend dummy|alsa|pulseaudio|jack|coreaudio|wasapi]\n"
180             ~ "  [--in-device id]\n"
181             ~ "  [--in-raw]\n"
182             ~ "  [--out-device id]\n"
183             ~ "  [--out-raw]\n"
184             ~ "  [--latency seconds]\n"
185             , exe);
186     return 1;
187 }
188 
189 int main(int argc, char** argv) {
190     char* exe = argv[0];
191     SoundIoBackend backend = SoundIoBackend.None;
192     char* in_device_id = null;
193     char* out_device_id = null;
194     bool in_raw = false;
195     bool out_raw = false;
196 
197     double microphone_latency = 0.2; // seconds
198 
199     for (int i = 1; i < argc; i += 1) {
200         char* arg = argv[i];
201         if (arg[0] == '-' && arg[1] == '-') {
202             if (strcmp(arg, "--in-raw") == 0) {
203                 in_raw = true;
204             } else if (strcmp(arg, "--out-raw") == 0) {
205                 out_raw = true;
206             } else if (++i >= argc) {
207                 return usage(exe);
208             } else if (strcmp(arg, "--backend") == 0) {
209                 if (strcmp("dummy", argv[i]) == 0) {
210                     backend = SoundIoBackend.Dummy;
211                 } else if (strcmp("alsa", argv[i]) == 0) {
212                     backend = SoundIoBackend.Alsa;
213                 } else if (strcmp("pulseaudio", argv[i]) == 0) {
214                     backend = SoundIoBackend.PulseAudio;
215                 } else if (strcmp("jack", argv[i]) == 0) {
216                     backend = SoundIoBackend.Jack;
217                 } else if (strcmp("coreaudio", argv[i]) == 0) {
218                     backend = SoundIoBackend.CoreAudio;
219                 } else if (strcmp("wasapi", argv[i]) == 0) {
220                     backend = SoundIoBackend.Wasapi;
221                 } else {
222                     printf_stderr("Invalid backend: %s\n", argv[i]);
223                     return 1;
224                 }
225             } else if (strcmp(arg, "--in-device") == 0) {
226                 in_device_id = argv[i];
227             } else if (strcmp(arg, "--out-device") == 0) {
228                 out_device_id = argv[i];
229             } else if (strcmp(arg, "--latency") == 0) {
230                 microphone_latency = atof(argv[i]);
231             } else {
232                 return usage(exe);
233             }
234         } else {
235             return usage(exe);
236         }
237     }
238     SoundIo* soundio = soundio_create();
239     if (!soundio)
240         panic("out of memory");
241 
242     if (auto err = (backend == SoundIoBackend.None) ?
243         soundio_connect(soundio) : soundio_connect_backend(soundio, backend))
244         panic("error connecting: %s", soundio_strerror(err));
245 
246     soundio_flush_events(soundio);
247 
248     int default_out_device_index = soundio_default_output_device_index(soundio);
249     if (default_out_device_index < 0)
250         panic("no output device found");
251 
252     int default_in_device_index = soundio_default_input_device_index(soundio);
253     if (default_in_device_index < 0)
254         panic("no input device found");
255 
256     int in_device_index = default_in_device_index;
257     if (in_device_id) {
258         bool found = false;
259         for (int i = 0; i < soundio_input_device_count(soundio); i += 1) {
260             SoundIoDevice* device = soundio_get_input_device(soundio, i);
261             if (device.is_raw == in_raw && strcmp(device.id, in_device_id) == 0) {
262                 in_device_index = i;
263                 found = true;
264                 soundio_device_unref(device);
265                 break;
266             }
267             soundio_device_unref(device);
268         }
269         if (!found)
270             panic("invalid input device id: %s", in_device_id);
271     }
272 
273     int out_device_index = default_out_device_index;
274     if (out_device_id) {
275         bool found = false;
276         for (int i = 0; i < soundio_output_device_count(soundio); i += 1) {
277             SoundIoDevice* device = soundio_get_output_device(soundio, i);
278             if (device.is_raw == out_raw && strcmp(device.id, out_device_id) == 0) {
279                 out_device_index = i;
280                 found = true;
281                 soundio_device_unref(device);
282                 break;
283             }
284             soundio_device_unref(device);
285         }
286         if (!found)
287             panic("invalid output device id: %s", out_device_id);
288     }
289 
290     SoundIoDevice* out_device = soundio_get_output_device(soundio, out_device_index);
291     if (!out_device)
292         panic("could not get output device: out of memory");
293 
294     SoundIoDevice* in_device = soundio_get_input_device(soundio, in_device_index);
295     if (!in_device)
296         panic("could not get input device: out of memory");
297 
298     printf_stderr("Input device: %s\n", in_device.name);
299     printf_stderr("Output device: %s\n", out_device.name);
300 
301     soundio_device_sort_channel_layouts(out_device);
302     const(SoundIoChannelLayout)* layout = soundio_best_matching_channel_layout(
303             out_device.layouts, out_device.layout_count,
304             in_device.layouts, in_device.layout_count);
305 
306     if (!layout)
307         panic("channel layouts not compatible");
308 
309     int* sample_rate;
310     for (sample_rate = prioritized_sample_rates.ptr; *sample_rate; sample_rate += 1) {
311         if (soundio_device_supports_sample_rate(in_device, *sample_rate) &&
312             soundio_device_supports_sample_rate(out_device, *sample_rate))
313         {
314             break;
315         }
316     }
317     if (!*sample_rate)
318         panic("incompatible sample rates");
319 
320     SoundIoFormat* fmt;
321     for (fmt = prioritized_formats.ptr; *fmt != SoundIoFormat.Invalid; fmt += 1) {
322         if (soundio_device_supports_format(in_device, *fmt) &&
323             soundio_device_supports_format(out_device, *fmt))
324         {
325             break;
326         }
327     }
328     if (*fmt == SoundIoFormat.Invalid)
329         panic("incompatible sample formats");
330 
331     SoundIoInStream* instream = soundio_instream_create(in_device);
332     if (!instream)
333         panic("out of memory");
334     instream.format = *fmt;
335     instream.sample_rate = *sample_rate;
336     instream.layout = *layout;
337     instream.software_latency = microphone_latency;
338     instream.read_callback = &read_callback;
339 
340     if (auto err = soundio_instream_open(instream)) {
341         printf_stderr("unable to open input stream: %s", soundio_strerror(err));
342         return 1;
343     }
344 
345     SoundIoOutStream* outstream = soundio_outstream_create(out_device);
346     if (!outstream)
347         panic("out of memory");
348     outstream.format = *fmt;
349     outstream.sample_rate = *sample_rate;
350     outstream.layout = *layout;
351     outstream.software_latency = microphone_latency;
352     outstream.write_callback = &write_callback;
353     outstream.underflow_callback = &underflow_callback;
354 
355     if (auto err = soundio_outstream_open(outstream)) {
356         printf_stderr("unable to open output stream: %s", soundio_strerror(err));
357         return 1;
358     }
359 
360     int capacity = cast(int) (microphone_latency * 2 * instream.sample_rate * instream.bytes_per_frame);
361     ring_buffer = soundio_ring_buffer_create(soundio, capacity);
362     if (!ring_buffer)
363         panic("unable to create ring buffer: out of memory");
364     char* buf = soundio_ring_buffer_write_ptr(ring_buffer);
365     int fill_count = cast(int) (microphone_latency * outstream.sample_rate * outstream.bytes_per_frame);
366     memset(buf, 0, fill_count);
367     soundio_ring_buffer_advance_write_ptr(ring_buffer, fill_count);
368 
369     if (auto err = soundio_instream_start(instream))
370         panic("unable to start input device: %s", soundio_strerror(err));
371 
372     if (auto err = soundio_outstream_start(outstream))
373         panic("unable to start output device: %s", soundio_strerror(err));
374 
375     for (;;)
376         soundio_wait_events(soundio);
377 
378     soundio_outstream_destroy(outstream);
379     soundio_instream_destroy(instream);
380     soundio_device_unref(in_device);
381     soundio_device_unref(out_device);
382     soundio_destroy(soundio);
383     return 0;
384 }