File: ringtone.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 cc -Wall -s -O2 -o ./ringtone ./ringtone.c -lm
  29 */
  30 
  31 #include <math.h>
  32 #include <stdbool.h>
  33 #include <stdint.h>
  34 #include <stdio.h>
  35 #include <stdlib.h>
  36 #include <string.h>
  37 
  38 #ifdef _WIN32
  39 #include <fcntl.h>
  40 #include <windows.h>
  41 #endif
  42 
  43 const char* info = ""
  44 "ringtone [options...] [duration...]\n"
  45 "\n"
  46 "Emit a ringtone as a wave-sound, lasting the number of seconds given, or 1\n"
  47 "second by default. The audio output format is RIFF WAVE (wav) sampled at\n"
  48 "48khz.\n"
  49 "\n"
  50 "\n"
  51 "Options, all of which can start with either 1 or 2 dashes:\n"
  52 "\n"
  53 "\n"
  54 "  -h          show this help message\n"
  55 "  -help       show this help message\n"
  56 "";
  57 
  58 const uint64_t sample_rate = 48000;
  59 const double dt = 1.0 / sample_rate;
  60 
  61 // is_help_option simplifies control-flow for func main
  62 bool is_help_option(const char* s) {
  63     return (s[0] == '-') && (
  64         strcmp(s, "-h") == 0 ||
  65         strcmp(s, "-help") == 0 ||
  66         strcmp(s, "--h") == 0 ||
  67         strcmp(s, "--help") == 0
  68     );
  69 }
  70 
  71 void write_int16_le(FILE* w, int16_t v) {
  72     putc((v >> 0) & 0b11111111, w);
  73     putc((v >> 8) & 0b11111111, w);
  74 }
  75 
  76 void write_uint16_le(FILE* w, uint16_t v) {
  77     putc((v >> 0) & 0b11111111, w);
  78     putc((v >> 8) & 0b11111111, w);
  79 }
  80 
  81 void write_uint32_le(FILE* w, uint32_t v) {
  82     putc((v >> 0) & 0b11111111, w);
  83     putc((v >> 8) & 0b11111111, w);
  84     putc((v >> 16) & 0b11111111, w);
  85     putc((v >> 24) & 0b11111111, w);
  86 }
  87 
  88 // write_wav_intro starts a wave-format data-stream declaring a mono 16-bit
  89 // integer PCM sound for the number of samples given
  90 void write_wav_intro(FILE* w, uint32_t samples) {
  91     const uint16_t integer_pcm = 1;
  92     const uint32_t fmt_chunk_size = 16;
  93 
  94     const uint32_t channels = 1;
  95     const uint32_t bytes_per_sample = 2;
  96     const uint32_t bits_per_sample = 8 * bytes_per_sample;
  97     const uint32_t byte_rate = sample_rate * bytes_per_sample * channels;
  98     const uint32_t data_size = byte_rate * samples;
  99     const uint32_t total_size = data_size + 44;
 100 
 101     fprintf(w, "RIFF");
 102     write_uint32_le(w, total_size);
 103     fprintf(w, "WAVEfmt ");
 104 
 105     write_uint32_le(w, fmt_chunk_size);
 106     write_uint16_le(w, integer_pcm);
 107     write_uint16_le(w, channels);
 108     write_uint32_le(w, sample_rate);
 109     write_uint32_le(w, byte_rate);
 110     write_uint16_le(w, bytes_per_sample * channels);
 111     write_uint16_le(w, bits_per_sample);
 112 
 113     fprintf(w, "data");
 114     write_uint32_le(w, data_size);
 115 }
 116 
 117 int main(int argc, char** argv) {
 118 #ifdef _WIN32
 119     setmode(fileno(stdin), O_BINARY);
 120     // ensure output lines end in LF instead of CRLF on windows
 121     setmode(fileno(stdout), O_BINARY);
 122     setmode(fileno(stderr), O_BINARY);
 123 #endif
 124 
 125     // handle any of the help options, if given
 126     if (argc > 1 && is_help_option(argv[1])) {
 127         puts(info);
 128         return 0;
 129     }
 130 
 131     double seconds = 1.0;
 132     if (argc > 1) {
 133         char* end;
 134         const double n = strtod(argv[1], &end);
 135         if (*end == 0) {
 136             seconds = n >= 0 ? n : 1.0;
 137         }
 138     }
 139 
 140     if (seconds < 0) {
 141         seconds = 0;
 142     }
 143 
 144     const uint64_t samples = (uint64_t)(seconds * sample_rate);
 145     if (samples >= 0xffffffff) {
 146         const char* m = "the duration given exceeds the WAV-format maximum";
 147         fprintf(stderr, "\x1b[31m%s\x1b[0m\n", m);
 148         return 1;
 149     }
 150 
 151     // enable full/block-buffering for standard output
 152     setvbuf(stdout, NULL, _IOFBF, 0);
 153 
 154     write_wav_intro(stdout, samples);
 155 
 156     const double tau = 2 * M_PI;
 157 
 158     for (uint64_t i = 0; i < samples; i++) {
 159         const double t = (double)i * dt;
 160         const double v = sin(2024 * tau * t) * exp(-50 * fmod(t, 0.1));
 161         write_int16_le(stdout, (int16_t)(32767 * v));
 162 
 163         // check if the standard output was closed only occasionally
 164         if ((i % (16 * 1024) == 0) && feof(stdout)) {
 165             return 0;
 166         }
 167     }
 168 
 169     return 0;
 170 }