File: sboard.c
   1 /*
   2 The MIT License (MIT)
   3 
   4 Copyright © 2020-2025 pacman64
   5 
   6 Permission is hereby granted, free of charge, to any person obtaining a copy of
   7 this software and associated documentation files (the “Software”), to deal
   8 in the Software without restriction, including without limitation the rights to
   9 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
  10 of the Software, and to permit persons to whom the Software is furnished to do
  11 so, subject to the following conditions:
  12 
  13 The above copyright notice and this permission notice shall be included in all
  14 copies or substantial portions of the Software.
  15 
  16 THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  22 SOFTWARE.
  23 */
  24 
  25 /*
  26 You can build this command-line app by running
  27 
  28 sudo apt install libpulse-dev
  29 cc -s -O3 -march=native -mtune=native -flto -o ./sboard ./sboard.c \
  30     -lm -lpulse -lpulse-simple
  31 */
  32 
  33 #include <errno.h>
  34 #include <math.h>
  35 #include <stdbool.h>
  36 #include <stdint.h>
  37 #include <stdio.h>
  38 #include <stdlib.h>
  39 #include <string.h>
  40 
  41 #ifdef _WIN32
  42 #include <fcntl.h>
  43 #include <windows.h>
  44 #endif
  45 
  46 #include <pulse/simple.h>
  47 
  48 #ifdef RED_ERRORS
  49 #define ERROR_STYLE "\x1b[38;2;204;0;0m"
  50 #ifdef __APPLE__
  51 #define ERROR_STYLE "\x1b[31m"
  52 #endif
  53 #define RESET_STYLE "\x1b[0m"
  54 #else
  55 #define ERROR_STYLE
  56 #define RESET_STYLE
  57 #endif
  58 
  59 #define ERROR_LINE(MSG) (ERROR_STYLE MSG RESET_STYLE "\n")
  60 
  61 const char* info = ""
  62 "sboard [options...] [sound...] [duration...] [volume...]\n"
  63 "\n"
  64 "\n"
  65 "Sound BOARD plays the sound given by name, lasting the number of seconds\n"
  66 "given, or 1 second by default. The audio output is 16-bit samples at 48khz.\n"
  67 "There's also an optional volume argument, which is 1 by default.\n"
  68 "\n"
  69 "Options, all of which can start with either 1 or 2 dashes:\n"
  70 "\n"
  71 "  -h          show this help message\n"
  72 "  -help       show this help message\n"
  73 "\n"
  74 "  -o          output ringtone to standard output as WAV-format data\n"
  75 "";
  76 
  77 const double tau = 2 * M_PI;
  78 
  79 const uint64_t sample_rate = 48000;
  80 const double dt = 1.0 / sample_rate;
  81 
  82 double beeper(double t) {
  83     return sin(2000 * tau * t) * (fmod(t, 0.5) < 0.0625);
  84 }
  85 
  86 double bell(double t) {
  87     const double u = fmod(t, 1);
  88     return sin(880 * tau * u) * exp(-10 * u);
  89 }
  90 
  91 // busy-phone tone
  92 double busy(double t) {
  93     const double u = fmod(t, 1);
  94     const double k = fmin(1, exp(-90 * (u - 0.5)));
  95     return k * (sin(480 * tau * t) + sin(620 * tau * t)) / 2;
  96 }
  97 
  98 double door_ajar(double t) {
  99     const double u = fmod(t, 1);
 100     return sin(660 * tau * u) * exp(-10 * u);
 101 }
 102 
 103 double heartbeat(double t) {
 104     double sum = 0.0;
 105     double u = fmod(t, 1);
 106     sum += sin(12 * tau * exp(-20 * u)) * exp(-2 * u);
 107     u = fmax(0, u - 0.25);
 108     sum += sin(8 * tau * exp(-20 * u)) * exp(-2 * u);
 109     return 0.5 * sum;
 110 }
 111 
 112 // a once-a-seconds stereotypical laser sound
 113 double laser(double t) {
 114     const double u = fmod(t, 1);
 115     return sin(100 * tau * exp(-40 * u));
 116 }
 117 
 118 // flat/uniform random noise
 119 double noise(double t) {
 120     const double v = (double)rand() / RAND_MAX;
 121     return 2 * v - 1;
 122 }
 123 
 124 // ready-phone tone
 125 double ready(double t) {
 126     return 0.5 * sin(350 * tau * t) + 0.5 * sin(450 * tau * t);
 127 }
 128 
 129 // annoying ringtone
 130 double ringtone(double t) {
 131     const double u = fmod(t, 0.1);
 132     return sin(2048 * tau * t) * exp(-50 * u);
 133 }
 134 
 135 double thud(double t) {
 136     double u = fmod(t, 1);
 137     return sin(12 * tau * exp(-20 * u)) * exp(-2 * u);
 138 }
 139 
 140 // 440hz tuning tone
 141 double tone(double t) {
 142     return sin(440 * tau * t);
 143 }
 144 
 145 double woo_woah_wow(double t) {
 146     // const double period = 1.3;
 147     const double period = 1.25;
 148     const double u = fmod(t, period);
 149     return sin(tau * (260 * sin(tau * u)) * u);
 150 }
 151 
 152 // functions used by fancier tunes
 153 
 154 double kick(double t, double f, double k, double p) {
 155     return sin(tau * f * pow(p, t)) * exp(-k * t);
 156 }
 157 
 158 double default_kick(double t, double f, double k) {
 159     return kick(t, f, k, 0.085);
 160 }
 161 
 162 // flat/uniform random noise
 163 double random_uniform() {
 164     const double v = (double)rand() / RAND_MAX;
 165     return 2 * v - 1;
 166 }
 167 
 168 double hit_hi_hat(double t, double k) {
 169     return random_uniform() * exp(-k * t);
 170 }
 171 
 172 double schedule(double t, double delay, double period) {
 173     return fmod(t + (1 - delay) * period, period);
 174 }
 175 
 176 double arp(double x, double y, double z, double k, double t) {
 177     const double u = fmod(t / 2, k);
 178     return sin(x * (exp(-y * u))) * exp(-z * u);
 179 }
 180 
 181 double linterp(double x, double y, double k) {
 182     return k * x + (1 - k) * y;
 183 }
 184 
 185 double power_synth(double t, double freq) {
 186     // function linspace(a, b, n) {
 187     //     const y = new Array(n);
 188     //     const incr = (b - a) / (n + 1);
 189     //     for (let i = 0; i < n; i++)
 190     //         y[i] = a + incr * i;
 191     //     return y;
 192     // }
 193     // bass_powers = linspace(1e-5, 1, 10).map(x => -0.05 * Math.log(x));
 194 
 195     const double powers[] = {
 196         0.5756462732485115, 0.11988976388990187,
 197         0.08523515466254475, 0.06496281589095718,
 198         0.05057917059158016, 0.03942226802181348,
 199         0.030306373513585217, 0.022598970473683477,
 200         0.015922499056278294, 0.010033423662119907,
 201     };
 202     const size_t n = sizeof(powers) / sizeof(powers[0]);
 203 
 204     double res = 0.0;
 205     for (size_t i = 0; i < n; i++) {
 206         res += powers[i] * cos(tau * (i + 1) * freq * t);
 207     }
 208     return res;
 209 }
 210 
 211 double bass_envelope(double t) {
 212     const double u = fmod(t, 1);
 213     return (u < 0.05) ? 15 * u : exp(-7 * u);
 214 }
 215 
 216 // fancier tunes
 217 
 218 double bust_a_move(double t) {
 219     const double period = 4.09;
 220     const double freqs[] = {
 221         50, 75, 50, 50, 75, 50, 50, 50, 75, 50, 50, 50, 50, 50, 75, 50
 222     };
 223     // const delays = [
 224     //     0, 0.52, 0.77, 1.28, 1.54, 1.79,
 225     //     2.04, 2.3, 2.56, 2.67,
 226     //     3.05, 3.07, 3.2, 3.45, 3.57, 3.82
 227     // ].map(x => x / period);
 228     const double delays[] = {
 229         0.0000000000000000, 0.1271393643031785,
 230         0.1882640586797066, 0.31295843520782396,
 231         0.3765281173594132, 0.43765281173594134,
 232         0.49877750611246946, 0.5623471882640586,
 233         0.6259168704156479, 0.6528117359413202,
 234         0.745721271393643, 0.7506112469437652,
 235         0.78239608801956, 0.8435207823960881,
 236         0.8728606356968215, 0.9339853300733496,
 237     };
 238 
 239     const double u = fmod(t, period);
 240     const size_t half = sizeof(freqs) / sizeof(freqs[0]) / 2;
 241     const size_t start = u < 2.5 ? 0 : half;
 242 
 243     double kicks = 0.0;
 244     for (size_t i = 0; i < half; i++) {
 245         const double d = delays[start + i];
 246         const double f = freqs[start + i];
 247         kicks += default_kick(schedule(t, d, period), f, 50);
 248     }
 249 
 250     double hi_hats = 0.0;
 251     for (size_t i = 0; i < half; i++) {
 252         hi_hats += hit_hi_hat(schedule(t, i / half, period / half), 25);
 253     }
 254     return 0.9 * kicks + 1.0 / 32 * hi_hats;
 255 }
 256 
 257 double crazy(double t) {
 258     const double snares[] = {
 259         0, 0, 1, 0, 0, 0, 1, 0,
 260         0, 0, 1, 0, 0, 0, 1, 0,
 261         0, 0, 1, 0, 0, 0, 1, 0,
 262         0, 0, 1, 0, 0, 0, 1, 0,
 263         0, 0, 1, 0, 0, 0, 1, 0,
 264         0, 0, 1, 0, 0, 0, 1, 0,
 265         0, 0, 1, 0, 0, 0, 1, 0,
 266         0, 0, 0, 0, 0, 0, 0, 0,
 267     };
 268 
 269     const double kicks[] = {
 270         1, 0, 0, 0, 1, 0, 0, 0,
 271         1, 0, 0, 0, 1, 0, 0, 0,
 272         1, 0, 0, 0, 1, 0, 0, 0,
 273         1, 1, 0, 1, 1, 0, 1, 1,
 274         1, 0, 0, 0, 1, 0, 0, 0,
 275         1, 0, 0, 0, 1, 0, 0, 0,
 276         1, 0, 0, 0, 1, 0, 0, 0,
 277         1, 1, 1, 1, 1, 1, 1, 1,
 278     };
 279 
 280     const double bass_speed[] = {
 281         2, 2, 2, 2, 2, 2, 2, 2,
 282         2, 2, 2, 2, 2, 2, 2, 2,
 283         2, 2, 2, 2, 2, 2, 2, 2,
 284         2, 2, 2, 2, 2, 2, 2, 2,
 285         2, 2, 2, 2, 2, 2, 2, 2,
 286         2, 2, 2, 2, 2, 2, 2, 2,
 287         2, 2, 2, 2, 2, 2, 2, 2,
 288         4, 4, 4, 4, 4, 4, 4, 4,
 289     };
 290 
 291     double seq(const double* d, double s, double t) {
 292         return d[(size_t)floor(t / s / 2) % sizeof(kicks)];
 293     }
 294 
 295     t *= 148.0 / 120;
 296     // const double period = 6.5;
 297     // const double u = fmod(t, period);
 298     // const double v = fmod(t, 2*period);
 299     const double rand = linterp(random(), 1, 1.0 / 2);
 300     const double anticlip = fmax(-log(.75 * fmod(8 * t, 1) + 1.0 / M_E), 0);
 301     // const double anticlip = 1;
 302     const double k = anticlip * arp(60, 40, 20, 1.0 / 16, t) *
 303         seq(kicks, 1.0 / 16, t);
 304     const double s = 0.3 * arp(60, 80, 3, 1.0 / 16, t) * rand *
 305         seq(snares, 1.0 / 16, t);
 306     const double su = seq(bass_speed, 1.0 / 16, t);
 307     const double b1 = bass_envelope(su * t) *
 308         power_synth(su * t, 50.0 / su);
 309     const double b2 = bass_envelope(su * (t + 0.5 * 6.5)) *
 310         power_synth(su * (t + 0.5 * 6.5), 60.0 / su);
 311     const double b = b1 + b2;
 312     // return s;
 313     return 0.9 * k + 0.7 * s + 0.8 * b;
 314 }
 315 
 316 double piano(double t, double n) {
 317     const double p = (n - 49) / 12;
 318     const double f = 440 * pow(2, p);
 319     return sin(tau * f * t);
 320 }
 321 
 322 double piano_loop(double t) {
 323     const double period = 1.025;
 324     const double cutoff = 12;
 325     const double p = period;
 326     double y = 0;
 327     y += piano(t, 49) * exp(-cutoff * fmod(t, period));
 328     y += piano(t + 0.25 * p, 50) * exp(-cutoff * fmod(t + 0.25 * p, p));
 329     y += piano(t + 0.50 * p, 54) * exp(-cutoff * fmod(t + 0.50 * p, p));
 330     y += piano(t + 0.75 * p, 51) * exp(-cutoff * fmod(t + 0.75 * p, p));
 331     return 0.75 * y;
 332 }
 333 
 334 double walk_this_way(double t) {
 335     const double period = 2.2;
 336     const double freqs[] = {50, 70, 50, 50, 50, 70};
 337     const double delays[] = {0.000, 0.250, 0.450, 0.500, 0.625, 0.750};
 338     const size_t n = sizeof(delays) / sizeof(delays[0]);
 339 
 340     double kicks = 0;
 341     for (size_t i = 0; i < n; i++) {
 342         kicks += default_kick(schedule(t, delays[i], period), freqs[i], 50);
 343     }
 344     double hi_hats = 1.2 * hit_hi_hat(schedule(t, 0, period), 7);
 345     for (size_t i = 1; i < n; i++) {
 346         hi_hats += hit_hi_hat(schedule(t, i / 8, period / 8), 30);
 347     }
 348     // return 1 * kicks + 1.0 / 24 * hi_hats;
 349     return 1 * kicks + 1.0 / 48 * hi_hats;
 350 }
 351 
 352 const char* aliases[] = {
 353     "beeper", "beeper",
 354     "beeps", "beeper",
 355     "bell", "bells",
 356     "bells", "bells",
 357     "busy", "busy",
 358     "door-ajar", "door-ajar",
 359     "doorajar", "door-ajar",
 360     "heart", "heart",
 361     "heart-beat", "heart",
 362     "heart-beats", "heart",
 363     "heartbeat", "heart",
 364     "heartbeats", "heart",
 365     "laser", "laser",
 366     "noise", "noise",
 367     "ready", "ready",
 368     "ring-tone", "ringtone",
 369     "ringtone", "ringtone",
 370     "thud", "thud",
 371     "440", "tone",
 372     "440hz", "tone",
 373     "tone", "tone",
 374     "woo-woah-wow", "woo-woah-wow",
 375     "woo", "woo-woah-wow",
 376     "woah", "woo-woah-wow",
 377     "wow", "woo-woah-wow",
 378 
 379     "bust-a-move", "bust-a-move",
 380     "bustamove", "bust-a-move",
 381     "crazy", "crazy",
 382     "piano", "piano-loop",
 383     "piano-loop", "piano-loop",
 384     "pianoloop", "piano-loop",
 385     "walk-this-way", "walk-this-way",
 386     "walkthisway", "walk-this-way",
 387 };
 388 
 389 const char* dealias(const char* s) {
 390     const size_t n = sizeof(aliases) / sizeof(aliases[0]);
 391     for (size_t i = 0; i < n; i += 2) {
 392         if (strcmp(s, aliases[i]) == 0) {
 393             return aliases[i + 1];
 394         }
 395     }
 396     return NULL;
 397 }
 398 
 399 typedef struct lookup_entry {
 400     const char* name;
 401     double (*func)(double);
 402     const char* desc;
 403 } lookup_entry;
 404 
 405 lookup_entry lookup[] = {
 406     {"beeper", beeper, "beep some TV-related devices used to make"},
 407     {"bells", bell, "a synthetic bell"},
 408     {"busy", busy, "a busy phone"},
 409     {"door-ajar", door_ajar, "door-ajar warning sound"},
 410     {"heart", heartbeat, "pairs of heart-pulses"},
 411     {"laser", laser, "a stereotypical laser sound, once a second"},
 412     {"noise", noise, "uniform random noise (annoying)"},
 413     {"ready", ready, "a ready phone"},
 414     {"ringtone", ringtone, "a slightly annoying ringtone"},
 415     {"thud", thud, "a low beat, similar to those in nightclubs"},
 416     {"tone", tone, "a 440hz tuning tone"},
 417     {"woo-woah-wow", woo_woah_wow, "a crazy sound (loop: 1.25s)"},
 418 
 419     {"bust-a-move", bust_a_move, "middle part of that song (loop: 4.09s)"},
 420     {"crazy", crazy, "a repeating tune (NOT FULLY WORKING) (loop: 6.5s)"},
 421     {"piano-loop", piano_loop, "a few repeating piano notes (loop: 1.025s)"},
 422     {"walk-this-way", walk_this_way, "drums from that song (loop: 2.2s)"},
 423 };
 424 
 425 // is_help_option simplifies control-flow for func main
 426 bool is_help_option(const char* s) {
 427     return (s[0] == '-') && (
 428         strcmp(s, "-h") == 0 ||
 429         strcmp(s, "-help") == 0 ||
 430         strcmp(s, "--h") == 0 ||
 431         strcmp(s, "--help") == 0
 432     );
 433 }
 434 
 435 // is_output_option simplifies control-flow for func main
 436 bool is_output_option(const char* s) {
 437     return (s[0] == '-') && (
 438         strcmp(s, "-o") == 0 ||
 439         strcmp(s, "--o") == 0
 440     );
 441 }
 442 
 443 static inline void write_int16_le(FILE* w, int16_t v) {
 444     fputc((v >> 0), w);
 445     fputc((v >> 8), w);
 446 }
 447 
 448 static inline void write_uint16_le(FILE* w, uint16_t v) {
 449     fputc((v >> 0), w);
 450     fputc((v >> 8), w);
 451 }
 452 
 453 void write_uint32_le(FILE* w, uint32_t v) {
 454     fputc((v >> 0), w);
 455     fputc((v >> 8), w);
 456     fputc((v >> 16), w);
 457     fputc((v >> 24), w);
 458 }
 459 
 460 static inline void set_int16_le(unsigned char* buf, int16_t v) {
 461     buf[0] = (v >> 0);
 462     buf[1] = (v >> 8);
 463 }
 464 
 465 // is_big_endian checks the platform's endianness; kept for reference; not
 466 // used anymore since playback use little-endian samples on all platforms,
 467 // via function set_int16_le
 468 bool is_big_endian() {
 469     const uint8_t pair[2] = {255, 0};
 470     return *((uint16_t*)pair) >= 256;
 471 }
 472 
 473 int play(double seconds, double volume, double (*emit)(double)) {
 474     if (seconds <= 0) {
 475         return 0;
 476     }
 477     if (volume < 0 || volume > 1 || isnan(seconds) || isinf(seconds)) {
 478         volume = 1;
 479     }
 480 
 481     const uint64_t rate = 48 * 1000;
 482     unsigned char buf[32 * 1024];
 483     pa_simple* pa;
 484     pa_sample_spec spec;
 485     memset(&spec, 0, sizeof(spec));
 486     // spec.format = is_big_endian() ? PA_SAMPLE_S16BE : PA_SAMPLE_S16LE;
 487     spec.format = PA_SAMPLE_S16LE;
 488     spec.rate = rate;
 489     spec.channels = 1;
 490 
 491     pa = pa_simple_new(
 492         NULL, "ringtone", PA_STREAM_PLAYBACK, NULL, "<<ringtone>>", &spec,
 493         NULL, NULL, &errno
 494     );
 495     if (pa == NULL) {
 496         fprintf(stderr, ERROR_LINE("can't use pulse-audio for playback"));
 497         return 1;
 498     }
 499 
 500     uint64_t pos = 0;
 501     const uint64_t samples = (uint64_t)ceil(seconds * rate);
 502 
 503     for (uint64_t i = 0; i < samples; i++) {
 504         const double t = (double)i * dt;
 505         const double v = volume * emit(t);
 506         set_int16_le(&buf[pos], (int16_t)(32767 * v));
 507 
 508         pos += 2;
 509         if (pos == sizeof(buf)) {
 510             if (pa_simple_write(pa, buf, pos, NULL) < 0) {
 511                 pa_simple_drain(pa, NULL);
 512                 pa_simple_free(pa);
 513                 return 1;
 514             }
 515             pos = 0;
 516         }
 517     }
 518 
 519     if (pos > 0) {
 520         if (pa_simple_write(pa, buf, pos, NULL) < 0) {
 521             pa_simple_drain(pa, NULL);
 522             pa_simple_free(pa);
 523             return 1;
 524         }
 525     }
 526 
 527     pa_simple_drain(pa, NULL);
 528     pa_simple_free(pa);
 529     return 0;
 530 }
 531 
 532 // write_wav_intro starts a wave-format data-stream declaring a mono 16-bit
 533 // integer PCM sound for the number of samples given
 534 void write_wav_intro(FILE* w, uint64_t samples) {
 535     const uint16_t integer_pcm = 1;
 536     const uint32_t fmt_chunk_size = 16;
 537 
 538     const uint32_t channels = 1;
 539     const uint32_t bytes_per_sample = 2;
 540     const uint32_t bits_per_sample = 8 * bytes_per_sample;
 541     const uint32_t byte_rate = sample_rate * bytes_per_sample * channels;
 542 
 543     uint64_t data_size = byte_rate * samples;
 544     uint64_t total_size = data_size + 44;
 545     if (data_size > UINT32_MAX) {
 546         data_size = UINT32_MAX;
 547         total_size = UINT32_MAX;
 548     }
 549 
 550     fprintf(w, "RIFF");
 551     write_uint32_le(w, total_size);
 552     fprintf(w, "WAVEfmt ");
 553 
 554     write_uint32_le(w, fmt_chunk_size);
 555     write_uint16_le(w, integer_pcm);
 556     write_uint16_le(w, channels);
 557     write_uint32_le(w, sample_rate);
 558     write_uint32_le(w, byte_rate);
 559     write_uint16_le(w, bytes_per_sample * channels);
 560     write_uint16_le(w, bits_per_sample);
 561 
 562     fprintf(w, "data");
 563     write_uint32_le(w, data_size);
 564 }
 565 
 566 int emit_wav(double seconds, double volume, double (*emit)(double)) {
 567     if (seconds <= 0) {
 568         write_wav_intro(stdout, 0);
 569         return 0;
 570     }
 571 
 572     if (volume < 0 || volume > 1 || isnan(seconds) || isinf(seconds)) {
 573         volume = 1;
 574     }
 575 
 576     const uint64_t samples = (uint64_t)(seconds * sample_rate);
 577     if (samples >= UINT32_MAX) {
 578         fprintf(stderr, ERROR_LINE("duration given exceeds WAV-format max"));
 579         return 1;
 580     }
 581 
 582     write_wav_intro(stdout, samples);
 583 
 584     for (uint64_t i = 0; i < samples; i++) {
 585         const double t = (double)i * dt;
 586         const double v = volume * emit(t);
 587         write_int16_le(stdout, (int16_t)(32767 * v));
 588 
 589         // check if the standard output was closed only occasionally
 590         if ((i % (32 * 1024) == 0) && feof(stdout)) {
 591             return 0;
 592         }
 593     }
 594 
 595     return 0;
 596 }
 597 
 598 void show_help(FILE* w) {
 599     fprintf(w, "%s", info);
 600     fprintf(w, "\n\nSound names available\n\n");
 601 
 602     for (size_t i = 0; i < sizeof(aliases) / sizeof(aliases[0]); i += 2) {
 603         const char* name = aliases[i];
 604         const char* dealiased = aliases[i + 1];
 605 
 606         for (size_t j = 0; j < sizeof(lookup) / sizeof(lookup[0]); j++) {
 607             if (strcmp(lookup[j].name, dealiased) == 0) {
 608                 fprintf(w, "  %-14s  %s\n", name, lookup[j].desc);
 609                 break;
 610             }
 611         }
 612     }
 613 }
 614 
 615 int main(int argc, char** argv) {
 616 #ifdef _WIN32
 617     setmode(fileno(stdin), O_BINARY);
 618     // ensure output lines end in LF instead of CRLF on windows
 619     setmode(fileno(stdout), O_BINARY);
 620     setmode(fileno(stderr), O_BINARY);
 621 #endif
 622 
 623     if (argc < 2 || is_help_option(argv[1])) {
 624         const bool no_args = argc < 2;
 625         show_help(no_args ? stderr : stdout);
 626         return no_args ? 1 : 0;
 627     }
 628 
 629     size_t start_args = 1;
 630     bool raw_output = false;
 631     if (is_output_option(argv[1])) {
 632         raw_output = true;
 633         start_args++;
 634 
 635         // enable full/block-buffering for standard output
 636         setvbuf(stdout, NULL, _IOFBF, 0);
 637     }
 638 
 639     const char* sound_name = "";
 640     sound_name = argv[start_args];
 641     start_args++;
 642 
 643     double seconds = 1.0;
 644     if (argc > start_args) {
 645         char* end;
 646         const double n = strtod(argv[start_args], &end);
 647         if (*end == 0 && argv[start_args] != end) {
 648             seconds = n >= 0 ? n : 1.0;
 649             start_args++;
 650         }
 651     }
 652 
 653     double volume = 1.0;
 654     if (argc > start_args) {
 655         char* end;
 656         const double n = strtod(argv[start_args], &end);
 657         if (*end == 0 && argv[start_args] != end) {
 658             volume = n >= 0 ? n : 1.0;
 659         }
 660     }
 661 
 662     if (sound_name[0] == 0) {
 663         fprintf(stderr, "not given a sound name\n");
 664         return 1;
 665     }
 666 
 667     const char* dealiased = dealias(sound_name);
 668     if (dealiased == NULL) {
 669         fprintf(stderr, "sound named \"%s\" not available\n", sound_name);
 670         return 1;
 671     }
 672 
 673     const double s = seconds;
 674     const double v = volume;
 675     for (size_t i = 0; i < sizeof(lookup) / sizeof(lookup[0]); i++) {
 676         if (strcmp(lookup[i].name, dealiased) == 0) {
 677             srand(0);
 678             return (raw_output ? emit_wav : play)(s, v, lookup[i].func);
 679         }
 680     }
 681 
 682     fprintf(stderr, "sound named \"%s\" not available\n", sound_name);
 683     return 1;
 684 }