1 /// Translated from C to D
2 module sio_record;
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.stdlib;
11 import core.stdc.string;
12 import core.stdc.math;
13 import core.stdc.errno;
14 
15 version (Posix)
16     import core.sys.posix.unistd;
17 else version(Windows)
18     import core.sys.windows.winbase;
19 else
20     static assert(false);
21 
22 struct RecordContext {
23     SoundIoRingBuffer* ring_buffer;
24 }
25 
26 immutable SoundIoFormat[19] prioritized_formats = [
27     SoundIoFormat.Float32NE,
28     SoundIoFormat.Float32FE,
29     SoundIoFormat.S32NE,
30     SoundIoFormat.S32FE,
31     SoundIoFormat.S24NE,
32     SoundIoFormat.S24FE,
33     SoundIoFormat.S16NE,
34     SoundIoFormat.S16FE,
35     SoundIoFormat.Float64NE,
36     SoundIoFormat.Float64FE,
37     SoundIoFormat.U32NE,
38     SoundIoFormat.U32FE,
39     SoundIoFormat.U24NE,
40     SoundIoFormat.U24FE,
41     SoundIoFormat.U16NE,
42     SoundIoFormat.U16FE,
43     SoundIoFormat.S8,
44     SoundIoFormat.U8,
45     SoundIoFormat.Invalid,
46 ];
47 
48 immutable int[5] prioritized_sample_rates = [
49     48000,
50     44100,
51     96000,
52     24000,
53     0,
54 ];
55 
56 static int min_int(int a, int b) {
57     return (a < b) ? a : b;
58 }
59 
60 static void read_callback(SoundIoInStream* instream, int frame_count_min, int frame_count_max) {
61     RecordContext* rc = cast(RecordContext*) instream.userdata;
62     SoundIoChannelArea* areas;
63 
64     char* write_ptr = soundio_ring_buffer_write_ptr(rc.ring_buffer);
65     int free_bytes = soundio_ring_buffer_free_count(rc.ring_buffer);
66     int free_count = free_bytes / instream.bytes_per_frame;
67 
68     if (free_count < frame_count_min) {
69         printf_stderr("ring buffer overflow\n");
70         exit(1);
71     }
72 
73     int write_frames = min_int(free_count, frame_count_max);
74     int frames_left = write_frames;
75 
76     for (;;) {
77         int frame_count = frames_left;
78 
79         if (auto err = soundio_instream_begin_read(instream, &areas, &frame_count)) {
80             printf_stderr("begin read error: %s", soundio_strerror(err));
81             exit(1);
82         }
83 
84         if (!frame_count)
85             break;
86 
87         if (!areas) {
88             // Due to an overflow there is a hole. Fill the ring buffer with
89             // silence for the size of the hole.
90             memset(write_ptr, 0, frame_count * instream.bytes_per_frame);
91         } else {
92             for (int frame = 0; frame < frame_count; frame += 1) {
93                 for (int ch = 0; ch < instream.layout.channel_count; ch += 1) {
94                     memcpy(write_ptr, areas[ch].ptr, instream.bytes_per_sample);
95                     areas[ch].ptr += areas[ch].step;
96                     write_ptr += instream.bytes_per_sample;
97                 }
98             }
99         }
100 
101         if (auto err = soundio_instream_end_read(instream)) {
102             printf_stderr("end read error: %s", soundio_strerror(err));
103             exit(1);
104         }
105 
106         frames_left -= frame_count;
107         if (frames_left <= 0)
108             break;
109     }
110 
111     int advance_bytes = write_frames * instream.bytes_per_frame;
112     soundio_ring_buffer_advance_write_ptr(rc.ring_buffer, advance_bytes);
113 }
114 
115 static void overflow_callback(SoundIoInStream* instream) {
116     static int count = 0;
117     printf_stderr("overflow %d\n", ++count);
118 }
119 
120 static int usage(char* exe) {
121     printf_stderr("Usage: %s [options] outfile\n"
122             ~ "Options:\n"
123             ~ "  [--backend dummy|alsa|pulseaudio|jack|coreaudio|wasapi]\n"
124             ~ "  [--device id]\n"
125             ~ "  [--raw]\n"
126             , exe);
127     return 1;
128 }
129 
130 int main(int argc, char** argv) {
131     char* exe = argv[0];
132     SoundIoBackend backend = SoundIoBackend.None;
133     char* device_id = null;
134     bool is_raw = false;
135     char* outfile = null;
136     for (int i = 1; i < argc; i += 1) {
137         char* arg = argv[i];
138         if (arg[0] == '-' && arg[1] == '-') {
139             if (strcmp(arg, "--raw") == 0) {
140                 is_raw = true;
141             } else if (++i >= argc) {
142                 return usage(exe);
143             } else if (strcmp(arg, "--backend") == 0) {
144                 if (strcmp("dummy", argv[i]) == 0) {
145                     backend = SoundIoBackend.Dummy;
146                 } else if (strcmp("alsa", argv[i]) == 0) {
147                     backend = SoundIoBackend.Alsa;
148                 } else if (strcmp("pulseaudio", argv[i]) == 0) {
149                     backend = SoundIoBackend.PulseAudio;
150                 } else if (strcmp("jack", argv[i]) == 0) {
151                     backend = SoundIoBackend.Jack;
152                 } else if (strcmp("coreaudio", argv[i]) == 0) {
153                     backend = SoundIoBackend.CoreAudio;
154                 } else if (strcmp("wasapi", argv[i]) == 0) {
155                     backend = SoundIoBackend.Wasapi;
156                 } else {
157                     printf_stderr("Invalid backend: %s\n", argv[i]);
158                     return 1;
159                 }
160             } else if (strcmp(arg, "--device") == 0) {
161                 device_id = argv[i];
162             } else {
163                 return usage(exe);
164             }
165         } else if (!outfile) {
166             outfile = argv[i];
167         } else {
168             return usage(exe);
169         }
170     }
171 
172     if (!outfile)
173         return usage(exe);
174 
175     RecordContext rc;
176 
177     SoundIo* soundio = soundio_create();
178     if (!soundio) {
179         printf_stderr("out of memory\n");
180         return 1;
181     }
182 
183     if (auto err = (backend == SoundIoBackend.None) ?
184         soundio_connect(soundio) : soundio_connect_backend(soundio, backend)) {
185         printf_stderr("error connecting: %s", soundio_strerror(err));
186         return 1;
187     }
188 
189     soundio_flush_events(soundio);
190 
191     SoundIoDevice* selected_device = null;
192 
193     if (device_id) {
194         for (int i = 0; i < soundio_input_device_count(soundio); i += 1) {
195             SoundIoDevice* device = soundio_get_input_device(soundio, i);
196             if (device.is_raw == is_raw && strcmp(device.id, device_id) == 0) {
197                 selected_device = device;
198                 break;
199             }
200             soundio_device_unref(device);
201         }
202         if (!selected_device) {
203             printf_stderr("Invalid device id: %s\n", device_id);
204             return 1;
205         }
206     } else {
207         int device_index = soundio_default_input_device_index(soundio);
208         selected_device = soundio_get_input_device(soundio, device_index);
209         if (!selected_device) {
210             printf_stderr("No input devices available.\n");
211             return 1;
212         }
213     }
214 
215     printf_stderr("Device: %s\n", selected_device.name);
216 
217     if (selected_device.probe_error) {
218         printf_stderr("Unable to probe device: %s\n", soundio_strerror(selected_device.probe_error));
219         return 1;
220     }
221 
222     soundio_device_sort_channel_layouts(selected_device);
223 
224     int sample_rate = 0;
225     const(int)* sample_rate_ptr;
226     foreach (sr; prioritized_sample_rates) {
227         if (soundio_device_supports_sample_rate(selected_device, sr)) {
228             sample_rate = sr;
229             break;
230         }
231     }
232     if (!sample_rate)
233         sample_rate = selected_device.sample_rates[0].max;
234 
235     SoundIoFormat fmt = SoundIoFormat.Invalid;
236     const(SoundIoFormat)* fmt_ptr;
237     for (fmt_ptr = prioritized_formats.ptr; *fmt_ptr != SoundIoFormat.Invalid; fmt_ptr += 1) {
238         if (soundio_device_supports_format(selected_device, *fmt_ptr)) {
239             fmt = *fmt_ptr;
240             break;
241         }
242     }
243     if (fmt == SoundIoFormat.Invalid)
244         fmt = selected_device.formats[0];
245 
246     FILE* out_f = fopen(outfile, "wb");
247     if (!out_f) {
248         printf_stderr("unable to open %s: %s\n", outfile, strerror(errno));
249         return 1;
250     }
251     SoundIoInStream* instream = soundio_instream_create(selected_device);
252     if (!instream) {
253         printf_stderr("out of memory\n");
254         return 1;
255     }
256     instream.format = fmt;
257     instream.sample_rate = sample_rate;
258     instream.read_callback = &read_callback;
259     instream.overflow_callback = &overflow_callback;
260     instream.userdata = &rc;
261 
262     if (auto err = soundio_instream_open(instream)) {
263         printf_stderr("unable to open input stream: %s", soundio_strerror(err));
264         return 1;
265     }
266 
267     printf_stderr("%s %dHz %s interleaved\n",
268             instream.layout.name, sample_rate, soundio_format_string(fmt));
269 
270     const(int) ring_buffer_duration_seconds = 30;
271     int capacity = ring_buffer_duration_seconds * instream.sample_rate * instream.bytes_per_frame;
272     rc.ring_buffer = soundio_ring_buffer_create(soundio, capacity);
273     if (!rc.ring_buffer) {
274         printf_stderr("out of memory\n");
275         return 1;
276     }
277 
278     if (auto err = soundio_instream_start(instream)) {
279         printf_stderr("unable to start input device: %s", soundio_strerror(err));
280         return 1;
281     }
282 
283     // Note: in this example, if you send SIGINT (by pressing Ctrl+C for example)
284     // you will lose up to 1 second of recorded audio data. In non-example code,
285     // consider a better shutdown strategy.
286     for (;;) {
287         soundio_flush_events(soundio);
288         version(Posix)
289             sleep(1);
290         else version(Windows)
291             Sleep(1000);
292         else
293             static assert(false);
294         int fill_bytes = soundio_ring_buffer_fill_count(rc.ring_buffer);
295         char* read_buf = soundio_ring_buffer_read_ptr(rc.ring_buffer);
296         size_t amt = fwrite(read_buf, 1, fill_bytes, out_f);
297         if (cast(int)amt != fill_bytes) {
298             printf_stderr("write error: %s\n", strerror(errno));
299             return 1;
300         }
301         soundio_ring_buffer_advance_read_ptr(rc.ring_buffer, fill_bytes);
302     }
303 
304     soundio_instream_destroy(instream);
305     soundio_device_unref(selected_device);
306     soundio_destroy(soundio);
307     return 0;
308 }