1 /// Translated from C to D
2 module sio_sine;
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.stdint;
13 import core.stdc.math;
14 
15 /// Note: the original C example assumes an amplitude of 1, but that is really loud,
16 /// so I added this constant to make the sound less annoying
17 enum sineAmplitude = 0.25;
18 
19 static int usage(char* exe) {
20     printf_stderr("Usage: %s [options]\n"
21             ~ "Options:\n"
22             ~ "  [--backend dummy|alsa|pulseaudio|jack|coreaudio|wasapi]\n"
23             ~ "  [--device id]\n"
24             ~ "  [--raw]\n"
25             ~ "  [--name stream_name]\n"
26             ~ "  [--latency seconds]\n"
27             ~ "  [--sample-rate hz]\n"
28             , exe);
29     return 1;
30 }
31 
32 static void write_sample_s16ne(char* ptr, double sample) {
33     short* buf = cast(short*)ptr;
34     double range = cast(double)short.max - cast(double)short.min;
35     double val = sample * range / 2.0;
36     *buf = cast(short) val;
37 }
38 
39 static void write_sample_s32ne(char* ptr, double sample) {
40     int* buf = cast(int*)ptr;
41     double range = cast(double)int.max - cast(double)int.min;
42     double val = sample * range / 2.0;
43     *buf = cast(int) val;
44 }
45 
46 static void write_sample_float32ne(char* ptr, double sample) {
47     float* buf = cast(float*)ptr;
48     *buf = sample;
49 }
50 
51 static void write_sample_float64ne(char* ptr, double sample) {
52     double* buf = cast(double*)ptr;
53     *buf = sample;
54 }
55 
56 static void function(char* ptr, double sample) write_sample;
57 static const(double) PI = 3.14159265358979323846264338328;
58 static double seconds_offset = 0.0;
59 static /*volatile*/ bool want_pause = false;
60 static void write_callback(SoundIoOutStream* outstream, int frame_count_min, int frame_count_max) {
61     double float_sample_rate = outstream.sample_rate;
62     double seconds_per_frame = 1.0 / float_sample_rate;
63     SoundIoChannelArea* areas;
64 
65     int frames_left = frame_count_max;
66 
67     for (;;) {
68         int frame_count = frames_left;
69         if (auto err = soundio_outstream_begin_write(outstream, &areas, &frame_count)) {
70             printf_stderr("unrecoverable stream error: %s\n", soundio_strerror(err));
71             exit(1);
72         }
73 
74         if (!frame_count)
75             break;
76 
77         const(SoundIoChannelLayout)* layout = &outstream.layout;
78 
79         double pitch = 440.0;
80         double radians_per_second = pitch * 2.0 * PI;
81         for (int frame = 0; frame < frame_count; frame += 1) {
82             double sample = sineAmplitude * sin((seconds_offset + frame * seconds_per_frame) * radians_per_second);
83             for (int channel = 0; channel < layout.channel_count; channel += 1) {
84                 write_sample(areas[channel].ptr, sample);
85                 areas[channel].ptr += areas[channel].step;
86             }
87         }
88         seconds_offset = fmod(seconds_offset + seconds_per_frame * frame_count, 1.0);
89 
90         if (auto err = soundio_outstream_end_write(outstream)) {
91             if (err == SoundIoError.Underflow)
92                 return;
93             printf_stderr("unrecoverable stream error: %s\n", soundio_strerror(err));
94             exit(1);
95         }
96 
97         frames_left -= frame_count;
98         if (frames_left <= 0)
99             break;
100     }
101 
102     soundio_outstream_pause(outstream, want_pause);
103 }
104 
105 static void underflow_callback(SoundIoOutStream* outstream) {
106     static int count = 0;
107     printf_stderr("underflow %d\n", count++);
108 }
109 
110 int main(int argc, char** argv) {
111     char* exe = argv[0];
112     SoundIoBackend backend = SoundIoBackend.None;
113     char* device_id = null;
114     bool raw = false;
115     char* stream_name = null;
116     double latency = 0.0;
117     int sample_rate = 0;
118     for (int i = 1; i < argc; i += 1) {
119         char* arg = argv[i];
120         if (arg[0] == '-' && arg[1] == '-') {
121             if (strcmp(arg, "--raw") == 0) {
122                 raw = true;
123             } else {
124                 i += 1;
125                 if (i >= argc) {
126                     return usage(exe);
127                 } else if (strcmp(arg, "--backend") == 0) {
128                     if (strcmp(argv[i], "dummy") == 0) {
129                         backend = SoundIoBackend.Dummy;
130                     } else if (strcmp(argv[i], "alsa") == 0) {
131                         backend = SoundIoBackend.Alsa;
132                     } else if (strcmp(argv[i], "pulseaudio") == 0) {
133                         backend = SoundIoBackend.PulseAudio;
134                     } else if (strcmp(argv[i], "jack") == 0) {
135                         backend = SoundIoBackend.Jack;
136                     } else if (strcmp(argv[i], "coreaudio") == 0) {
137                         backend = SoundIoBackend.CoreAudio;
138                     } else if (strcmp(argv[i], "wasapi") == 0) {
139                         backend = SoundIoBackend.Wasapi;
140                     } else {
141                         printf_stderr("Invalid backend: %s\n", argv[i]);
142                         return 1;
143                     }
144                 } else if (strcmp(arg, "--device") == 0) {
145                     device_id = argv[i];
146                 } else if (strcmp(arg, "--name") == 0) {
147                     stream_name = argv[i];
148                 } else if (strcmp(arg, "--latency") == 0) {
149                     latency = atof(argv[i]);
150                 } else if (strcmp(arg, "--sample-rate") == 0) {
151                     sample_rate = atoi(argv[i]);
152                 } else {
153                     return usage(exe);
154                 }
155             }
156         } else {
157             return usage(exe);
158         }
159     }
160 
161     SoundIo* soundio = soundio_create();
162     if (!soundio) {
163         printf_stderr("out of memory\n");
164         return 1;
165     }
166 
167     if (auto err = (backend == SoundIoBackend.None) ? soundio_connect(soundio) : soundio_connect_backend(soundio, backend)) {
168         printf_stderr("Unable to connect to backend: %s\n", soundio_strerror(err));
169         return 1;
170     }
171 
172     printf_stderr("Backend: %s\n", soundio_backend_name(soundio.current_backend));
173 
174     soundio_flush_events(soundio);
175 
176     int selected_device_index = -1;
177     if (device_id) {
178         int device_count = soundio_output_device_count(soundio);
179         for (int i = 0; i < device_count; i += 1) {
180             SoundIoDevice* device = soundio_get_output_device(soundio, i);
181             bool select_this_one = strcmp(device.id, device_id) == 0 && device.is_raw == raw;
182             soundio_device_unref(device);
183             if (select_this_one) {
184                 selected_device_index = i;
185                 break;
186             }
187         }
188     } else {
189         selected_device_index = soundio_default_output_device_index(soundio);
190     }
191 
192     if (selected_device_index < 0) {
193         printf_stderr("Output device not found\n");
194         return 1;
195     }
196 
197     SoundIoDevice* device = soundio_get_output_device(soundio, selected_device_index);
198     if (!device) {
199         printf_stderr("out of memory\n");
200         return 1;
201     }
202 
203     printf_stderr("Output device: %s\n", device.name);
204 
205     if (device.probe_error) {
206         printf_stderr("Cannot probe device: %s\n", soundio_strerror(device.probe_error));
207         return 1;
208     }
209 
210     SoundIoOutStream* outstream = soundio_outstream_create(device);
211     if (!outstream) {
212         printf_stderr("out of memory\n");
213         return 1;
214     }
215 
216     outstream.write_callback = &write_callback;
217     outstream.underflow_callback = &underflow_callback;
218     outstream.name = stream_name;
219     outstream.software_latency = latency;
220     outstream.sample_rate = sample_rate;
221 
222     if (soundio_device_supports_format(device, SoundIoFormatFloat32NE)) {
223         outstream.format = SoundIoFormatFloat32NE;
224         write_sample = &write_sample_float32ne;
225     } else if (soundio_device_supports_format(device, SoundIoFormatFloat64NE)) {
226         outstream.format = SoundIoFormatFloat64NE;
227         write_sample = &write_sample_float64ne;
228     } else if (soundio_device_supports_format(device, SoundIoFormatS32NE)) {
229         outstream.format = SoundIoFormatS32NE;
230         write_sample = &write_sample_s32ne;
231     } else if (soundio_device_supports_format(device, SoundIoFormatS16NE)) {
232         outstream.format = SoundIoFormatS16NE;
233         write_sample = &write_sample_s16ne;
234     } else {
235         printf_stderr("No suitable device format available.\n");
236         return 1;
237     }
238 
239     if (auto err = soundio_outstream_open(outstream)) {
240         printf_stderr("unable to open device: %s", soundio_strerror(err));
241         return 1;
242     }
243 
244     printf_stderr("Software latency: %f\n", outstream.software_latency);
245     printf_stderr(
246             "'p\\n' - pause\n"
247             ~ "'u\\n' - unpause\n"
248             ~ "'P\\n' - pause from within callback\n"
249             ~ "'c\\n' - clear buffer\n"
250             ~ "'q\\n' - quit\n");
251 
252     if (outstream.layout_error)
253         printf_stderr("unable to set channel layout: %s\n", soundio_strerror(outstream.layout_error));
254 
255     if (auto err = soundio_outstream_start(outstream)) {
256         printf_stderr("unable to start device: %s\n", soundio_strerror(err));
257         return 1;
258     }
259 
260     for (;;) {
261         soundio_flush_events(soundio);
262         version(CRuntime_Microsoft) {
263             int c = 0; // stdin not initialized
264             if (c == 0) continue;
265         } else {
266             int c = getc(stdin);
267         }
268         if (c == 'p') {
269             printf_stderr("pausing result: %s\n",
270                     soundio_strerror(soundio_outstream_pause(outstream, true)));
271         } else if (c == 'P') {
272             want_pause = true;
273         } else if (c == 'u') {
274             want_pause = false;
275             printf_stderr("unpausing result: %s\n",
276                     soundio_strerror(soundio_outstream_pause(outstream, false)));
277         } else if (c == 'c') {
278             printf_stderr("clear buffer result: %s\n",
279                     soundio_strerror(soundio_outstream_clear_buffer(outstream)));
280         } else if (c == 'q') {
281             break;
282         } else if (c == '\r' || c == '\n') {
283             // ignore
284         } else {
285             printf_stderr("Unrecognized command: %c\n", c);
286         }
287     }
288 
289     soundio_outstream_destroy(outstream);
290     soundio_device_unref(device);
291     soundio_destroy(soundio);
292     return 0;
293 }