File: playwave.c 1 /* 2 The MIT License (MIT) 3 4 Copyright © 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 -O2 -march=native -mtune=native -flto -o ./playwwave ./playwwave.c \ 30 -lpulse -lpulse-simple 31 */ 32 33 #include <errno.h> 34 #include <stdbool.h> 35 #include <stdint.h> 36 #include <stdio.h> 37 #include <stdlib.h> 38 #include <string.h> 39 40 #ifdef _WIN32 41 #include <fcntl.h> 42 #include <windows.h> 43 #endif 44 45 #include <pulse/error.h> 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 "playwwave [options...] [volume...]\n" 63 "\n" 64 "Play WAV-format sound data, either from the files given, or standard input.\n" 65 "\n" 66 "The sample formats supported are\n" 67 "\n" 68 " 8-bit integer\n" 69 " 16-bit signed-integer little-endian\n" 70 " 32-bit floating-point little-endian\n" 71 "\n" 72 "Options, all of which can start with either 1 or 2 dashes:\n" 73 "\n" 74 " -h show this help message\n" 75 " -help show this help message\n" 76 ""; 77 78 // read_uint16_le reads a 16-bit little-endian integer platform-independently 79 bool read_uint16_le(FILE* r, uint16_t* v) { 80 const int a = fgetc(r); 81 if (a == EOF) { 82 return false; 83 } 84 const int b = fgetc(r); 85 if (b == EOF) { 86 return false; 87 } 88 89 *v = (b << 8) + a; 90 return true; 91 } 92 93 // read_uint32_le reads a 32-bit little-endian integer platform-independently 94 bool read_uint32_le(FILE* r, uint32_t* v) { 95 const int a = fgetc(r); 96 if (a == EOF) { 97 return false; 98 } 99 const int b = fgetc(r); 100 if (b == EOF) { 101 return false; 102 } 103 const int c = fgetc(r); 104 if (c == EOF) { 105 return false; 106 } 107 const int d = fgetc(r); 108 if (d == EOF) { 109 return false; 110 } 111 112 *v = (a << 0) + (b << 8) + (c << 16) + (d << 24); 113 return true; 114 } 115 116 // wave_info has the wav-format metadata needed to playback sound samples 117 typedef struct wave_info { 118 uint32_t fmt_chunk_size; 119 uint16_t pcm_format; 120 uint16_t channels; 121 uint32_t sample_rate; 122 uint32_t byte_rate; 123 uint16_t bytes_per_sample_round; // could have a better name 124 uint16_t bits_per_sample; 125 uint32_t data_size; 126 } wave_info; 127 128 bool demand_string(FILE* r, const char* s) { 129 for (; *s != 0; s++) { 130 const int b = fgetc(r); 131 if ((b == EOF) || (b != *s)) { 132 return false; 133 } 134 } 135 return true; 136 } 137 138 void bad_input(const char* msg) { 139 fprintf(stderr, ERROR_LINE("invalid input data: %s"), msg); 140 } 141 142 bool skip_bytes(FILE* r, size_t n) { 143 unsigned char buf[32 * 1024]; 144 while (n >= sizeof(buf)) { 145 size_t got = fread(buf, 1, sizeof(buf), r); 146 n -= got; 147 } 148 149 if ((n > 0) && (fread(buf, 1, n, r) != n)) { 150 return false; 151 } 152 return !feof(r); 153 } 154 155 bool seek_data_section(FILE* r) { 156 while (!feof(r)) { 157 const int a = fgetc(r); 158 const int b = fgetc(r); 159 const int c = fgetc(r); 160 const int d = fgetc(r); 161 if (feof(r)) { 162 return false; 163 } 164 165 if ((a == 'd') && (b == 'a') && (c == 't') && (d == 'a')) { 166 return true; 167 } 168 169 uint32_t n = 0; 170 if (!read_uint32_le(r, &n)) { 171 return false; 172 } 173 if (!skip_bytes(r, n)) { 174 return false; 175 } 176 } 177 178 return false; 179 } 180 181 bool read_wave_intro(FILE* r, wave_info* info) { 182 if (feof(r)) { 183 bad_input("empty input"); 184 return false; 185 } 186 187 uint32_t total_size = 0; 188 if (!demand_string(r, "RIFF")) { 189 bad_input("no RIFF WAVE tag"); 190 return false; 191 } 192 if (!read_uint32_le(r, &total_size)) { 193 bad_input("missing RIFF WAVE total-size"); 194 return false; 195 } 196 if (!demand_string(r, "WAVEfmt ")) { 197 bad_input("missing RIFF WAVE format info"); 198 return false; 199 } 200 201 if (!read_uint32_le(r, &info->fmt_chunk_size)) { 202 bad_input("missing size of RIFF WAVE format info"); 203 return false; 204 } 205 // if (info->fmt_chunk_size != 16) { 206 // bad_input("invalid RIFF WAVE format info chunk"); 207 // return false; 208 // } 209 if (!read_uint16_le(r, &info->pcm_format)) { 210 bad_input("missing PCM format in RIFF WAVE format info"); 211 return false; 212 } 213 if (!read_uint16_le(r, &info->channels)) { 214 bad_input("missing channel-count in RIFF WAVE format info"); 215 return false; 216 } 217 if (!read_uint32_le(r, &info->sample_rate)) { 218 bad_input("missing sample-rate in RIFF WAVE format info"); 219 return false; 220 } 221 if (!read_uint32_le(r, &info->byte_rate)) { 222 bad_input("missing byte-rate in RIFF WAVE format info"); 223 return false; 224 } 225 if (!read_uint16_le(r, &info->bytes_per_sample_round)) { 226 bad_input("missing byte-rate in RIFF WAVE format info"); 227 return false; 228 } 229 if (!read_uint16_le(r, &info->bits_per_sample)) { 230 bad_input("missing bits-per-sample in RIFF WAVE format info"); 231 return false; 232 } 233 234 switch (info->pcm_format) { 235 case 1: 236 // integer samples are supported 237 case 3: 238 // floating-point samples are supported 239 break; 240 241 default: 242 bad_input("only integer or floating-point PCM samples are supported"); 243 return false; 244 } 245 246 skip_bytes(r, info->fmt_chunk_size - 16); 247 248 switch (info->bits_per_sample) { 249 case 8: 250 case 16: 251 case 32: 252 // bits-per-sample value is supported 253 break; 254 255 default: 256 bad_input("only 8, 16, and 32 bits per sample are supported"); 257 return false; 258 } 259 260 if (!seek_data_section(r)) { 261 bad_input("no data section found in RIFF WAVE format info"); 262 return false; 263 } 264 if (!read_uint32_le(r, &info->data_size)) { 265 bad_input("invalid data section in RIFF WAVE format info"); 266 return false; 267 } 268 269 return true; 270 } 271 272 // decode_pa_format only handles a subset of all possible WAVE formats; 273 // this function assumes input formats are already checked elsewhere 274 int decode_pa_format(const wave_info* info) { 275 switch (info->pcm_format) { 276 case 1: 277 switch (info->bits_per_sample) { 278 case 8: 279 return PA_SAMPLE_U8; 280 case 16: 281 return PA_SAMPLE_S16LE; 282 case 24: 283 return PA_SAMPLE_S24LE; 284 case 32: 285 return PA_SAMPLE_S32LE; 286 default: 287 return -1; 288 } 289 290 case 3: 291 switch (info->bits_per_sample) { 292 case 32: 293 return PA_SAMPLE_FLOAT32LE; 294 default: 295 return -1; 296 } 297 298 default: 299 return -1; 300 } 301 } 302 303 // keep_volume handles the case when the volume is exactly 1 304 void keep_volume(const void* chunk, size_t n, float volume) { 305 // do nothing on purpose, since the volume is 1 306 } 307 308 // adjust_volume_uint8 handles bytes, so it works the same on any platform 309 void adjust_volume_uint8(void* chunk, size_t n, float volume) { 310 uint8_t* samples = (uint8_t*)chunk; 311 const float k = 255.0 * volume; 312 313 for (size_t i = 0; i < n; i++) { 314 const float v = k * ((float)samples[i] / 255.0); 315 samples[i] = (uint8_t)v; 316 } 317 } 318 319 // adjust_volume_int16le_native is used on little-endian platforms 320 void adjust_volume_int16le_native(void* chunk, size_t n, float volume) { 321 int16_t* samples = (int16_t*)chunk; 322 const float k = 32767.0 * volume; 323 const size_t end = n / sizeof(samples[0]); 324 325 for (size_t i = 0; i < end; i++) { 326 samples[i] = k * ((float)samples[i] / 32767.0); 327 } 328 } 329 330 // adjust_volume_int16_multiplatform handles little-endian 16-bit integers 331 // independently of the platform's endianness; it's only called on big-endian 332 // platforms in practice 333 void adjust_volume_int16_multiplatform(void* chunk, size_t n, float volume) { 334 uint8_t* data = (uint8_t*)chunk; 335 const float k = 32767.0 * volume; 336 const size_t end = n - 2; 337 if (n < 2) { 338 return; 339 } 340 341 for (size_t i = 0; i < end; i += 2) { 342 int16_t v = (data[i + 1] << 8) + (data[i + 0] << 0); 343 const float f = k * ((float)v / 32767.0); 344 v = (int16_t)f; 345 data[i + 0] = (v >> 0); 346 data[i + 1] = (v >> 8); 347 } 348 } 349 350 // adjust_volume_float32le_native is used on little-endian platforms 351 void adjust_volume_float32le_native(void* chunk, size_t n, float volume) { 352 float* samples = (float*)chunk; 353 const size_t end = n / sizeof(samples[0]); 354 355 for (size_t i = 0; i < end; i++) { 356 samples[i] = volume * (float)samples[i]; 357 } 358 } 359 360 // adjust_volume_float32_multiplatform handles little-endian 32-bit floats 361 // independently of the platform's endianness; it's only called on big-endian 362 // platforms in practice 363 void adjust_volume_float32_multiplatform(void* chunk, size_t n, float volume) { 364 uint8_t* data = (uint8_t*)chunk; 365 const size_t end = n - 4; 366 if (n < 4) { 367 return; 368 } 369 370 for (size_t i = 0; i < end; i += 4) { 371 union { 372 uint32_t n; 373 float f; 374 } v; 375 376 v.n = (data[i + 3] << 24) + (data[i + 2] << 16) + 377 (data[i + 1] << 8) + (data[i + 0] << 0); 378 v.f *= volume; 379 380 data[i + 0] = (v.n >> 0); 381 data[i + 1] = (v.n >> 8); 382 data[i + 2] = (v.n >> 16); 383 data[i + 3] = (v.n >> 24); 384 } 385 } 386 387 // is_big_endian checks the platform's endianness when input is 16-bit samples 388 // bool is_big_endian() { 389 // const uint8_t pair[2] = {255, 0}; 390 // return *((uint16_t*)pair) >= 256; 391 // } 392 bool is_big_endian() { 393 return true; 394 } 395 396 // show_format_info has been useful to check assumptions while chasing bugs 397 void show_format_info(FILE* w, const wave_info* info) { 398 fprintf(w, "data size: %ld\n", (long)info->data_size); 399 fprintf(w, "channels: %ld\n", (long)info->channels); 400 fprintf(w, "format: %ld\n", (long)info->pcm_format); 401 fprintf(w, "bps: %ld\n", (long)info->bits_per_sample); 402 fprintf(w, "sample rate: %ld\n", (long)info->sample_rate); 403 fprintf(w, "byte rate: %ld\n", (long)info->byte_rate); 404 fflush(w); 405 } 406 407 int play(FILE* r, const wave_info* info, float volume) { 408 // show_format_info(stderr, info); 409 410 if (info->data_size == 0) { 411 return true; 412 } 413 if (volume < 0 || volume > 1) { 414 volume = 1; 415 } 416 417 // use a large buffer to avoid stuttering during playback 418 unsigned char buf[128 * 1024]; 419 pa_simple* pa; 420 pa_sample_spec spec; 421 memset(&spec, 0, sizeof(spec)); 422 spec.format = decode_pa_format(info); 423 spec.rate = info->sample_rate; 424 spec.channels = info->channels; 425 426 void (*adjust_volume)(void*, size_t, float) = NULL; 427 428 switch (spec.format) { 429 case PA_SAMPLE_U8: 430 adjust_volume = adjust_volume_uint8; 431 break; 432 433 case PA_SAMPLE_S16LE: 434 adjust_volume = is_big_endian() ? 435 adjust_volume_int16_multiplatform : 436 adjust_volume_int16le_native; 437 break; 438 439 case PA_SAMPLE_FLOAT32LE: 440 adjust_volume = is_big_endian() ? 441 adjust_volume_float32_multiplatform : 442 adjust_volume_float32le_native; 443 break; 444 445 default: 446 // technically this case is prevented from happening 447 adjust_volume = (void (*)(void*, size_t, float))keep_volume; 448 break; 449 } 450 451 if (volume == 1) { 452 adjust_volume = (void (*)(void*, size_t, float))keep_volume; 453 } 454 455 pa = pa_simple_new( 456 NULL, "playwave", PA_STREAM_PLAYBACK, NULL, "<<playwave>>", &spec, 457 NULL, NULL, &errno 458 ); 459 if (pa == NULL) { 460 fprintf(stderr, ERROR_LINE("can't use pulse-audio for playback")); 461 fprintf(stderr, ERROR_LINE("%s"), pa_strerror(errno)); 462 return false; 463 } 464 465 const size_t item_size = info->bits_per_sample / 8; 466 const size_t cap = sizeof(buf) - (sizeof(buf) % item_size); 467 int64_t bytes_left = info->data_size; 468 469 // all-bits-on on the data-size means at least those many bytes: using 470 // an enormous value (almost 8 million terabytes) allows playing up to 471 // hundreds of thousands of years of multi-channel high-quality sound 472 if (bytes_left == UINT32_MAX) { 473 bytes_left = INT64_MAX; 474 } 475 476 while (bytes_left > 0) { 477 const size_t ask = bytes_left >= cap ? cap : bytes_left; 478 const size_t len = fread(&buf, 1, ask, r); 479 if (len < 1) { 480 break; 481 } 482 483 adjust_volume(buf, len, volume); 484 if (pa_simple_write(pa, buf, len, NULL) < 0) { 485 fprintf(stderr, ERROR_LINE("can't keep playing data")); 486 pa_simple_drain(pa, NULL); 487 pa_simple_free(pa); 488 return false; 489 } 490 bytes_left -= len; 491 } 492 493 pa_simple_drain(pa, NULL); 494 pa_simple_free(pa); 495 return true; 496 } 497 498 bool handle_reader(FILE* r, double volume) { 499 wave_info wi; 500 if (!read_wave_intro(r, &wi)) { 501 return false; 502 } 503 return play(r, &wi, volume); 504 } 505 506 // handle_file handles data from the filename given; returns false only when 507 // the file can't be opened 508 bool handle_file(const char* path, double volume) { 509 FILE* f = fopen(path, "rb"); 510 if (f == NULL) { 511 fprintf(stderr, ERROR_LINE("can't open file named '%s'"), path); 512 return false; 513 } 514 515 const bool ok = handle_reader(f, volume); 516 fclose(f); 517 return ok; 518 } 519 520 // runs returns the number of errors 521 int run(char** args, size_t nargs, double volume) { 522 size_t dashes = 0; 523 for (size_t i = 0; i < nargs; i++) { 524 if (strcmp(args[i], "-") == 0) { 525 dashes++; 526 } 527 } 528 529 if (dashes > 1) { 530 const char* m = "can't use the standard input (dash) more than once"; 531 fprintf(stderr, ERROR_LINE("%s"), m); 532 return 1; 533 } 534 535 size_t errors = 0; 536 for (size_t i = 0; i < nargs; i++) { 537 if (strcmp(args[i], "-") == 0) { 538 if (!handle_reader(stdin, volume)) { 539 errors++; 540 } 541 continue; 542 } 543 544 if (!handle_file(args[i], volume)) { 545 errors++; 546 } 547 } 548 549 if (nargs == 0) { 550 if (!handle_reader(stdin, volume)) { 551 errors++; 552 } 553 } 554 return errors; 555 } 556 557 int main(int argc, char** argv) { 558 #ifdef _WIN32 559 setmode(fileno(stdin), O_BINARY); 560 // ensure output lines end in LF instead of CRLF on windows 561 setmode(fileno(stdout), O_BINARY); 562 setmode(fileno(stderr), O_BINARY); 563 #endif 564 565 if (argc > 1) { 566 if ( 567 strcmp(argv[1], "-h") == 0 || 568 strcmp(argv[1], "-help") == 0 || 569 strcmp(argv[1], "--h") == 0 || 570 strcmp(argv[1], "--help") == 0 571 ) { 572 fprintf(stdout, "%s", info); 573 return 0; 574 } 575 } 576 577 size_t nargs = argc - 1; 578 char** args = argv + 1; 579 580 double volume = 1.0; 581 if (nargs > 0) { 582 char* end; 583 const double n = strtod(args[0], &end); 584 if (*end == 0 && args[0] != end) { 585 volume = n >= 0 ? n : 1.0; 586 nargs--; 587 args++; 588 } 589 } 590 591 if (nargs > 0 && strcmp(args[0], "--") == 0) { 592 nargs--; 593 args++; 594 } 595 596 // enable full/block-buffering for standard input 597 setvbuf(stdin, NULL, _IOFBF, 0); 598 599 return run(args, nargs, volume) == 0 ? 0 : 1; 600 }