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 }