/* See LICENSE file for copyright and license details. */ #include "common.h" #include #include /* TODO add support for online configuration */ /* TODO add support for displaying input note */ /* TODO add support for selecting output device and backend */ /* TODO add support for selecting volume */ /* TODO use bus to listen for incoming phone calls and pause when arriving */ #define UTYPE uint32_t #define SMAX INT32_MAX #define FORMAT SND_PCM_FORMAT_U32 #define SAMPLE_RATE 52000 /* Hz */ #define LATENCY 100000 /* µs */ USAGE("[-a | -u] [-n note] [-o octave]"); static int check_n_lined(const char *s, const char *a, const char *b) { if (!strncmp(s, a, strlen(a))) s = &s[strlen(a)]; else if (!strncmp(s, b, strlen(b))) s = &s[strlen(b)]; else return 0; if (*s == '-' || *s == ' ') s = &s[1]; else if (!strncmp(s, " ", strlen(" "))) s = &s[strlen(" ")]; else return 0; return !strcasecmp(s, "lined"); } int main(int argc, char *argv[]) { UTYPE *buffer; snd_pcm_uframes_t i, buflen, wavelen; snd_pcm_sframes_t n; snd_pcm_t *sound_handle = NULL; double freq = 440; double volume = (double)0.1f, imul, omul, osub; char *env, *note_str = NULL, *octave_str = NULL, *end; int r, ascii, note, octave; env = getenv("TERM"); ascii = (env && !strcmp(env, "linux")); ARGBEGIN { case 'a': ascii = 1; break; case 'n': if (note_str) usage(); note_str = ARG(); break; case 'o': if (octave_str) usage(); octave_str = ARG(); break; case 'u': ascii = 0; break; default: usage(); } ARGEND; if (argc) usage(); if (!note_str) { note = 9; } else { if (note_str[0] == 'C' || note_str[0] == 'c') note = 0; else if (note_str[0] == 'D' || note_str[0] == 'd') note = 2; else if (note_str[0] == 'E' || note_str[0] == 'e') note = 4; else if (note_str[0] == 'F' || note_str[0] == 'f') note = 5; else if (note_str[0] == 'G' || note_str[0] == 'g') note = 7; else if (note_str[0] == 'A' || note_str[0] == 'a') note = 9; else if (note_str[0] == 'B' || note_str[0] == 'b' || note_str[0] == 'H' || note_str[0] == 'h') note = 11; else eprintf("%s is not a recognised note\n", note_str); if (!strcmp(¬e_str[1], "#") || !strcmp(¬e_str[1], "♯")) note += 1; else if (!strcmp(¬e_str[1], "b") || !strcmp(¬e_str[1], "♭")) note -= 1; else if (note_str[1]) eprintf("%s is not a recognised note\n", note_str); } if (!octave_str) { octave = 4; } else if (!strcasecmp(octave_str, "subsubcontra")) { octave = -1; } else if (!strcasecmp(octave_str, "subcontra")) { octave = 0; } else if (!strcasecmp(octave_str, "contra")) { octave = 1; } else if (!strcasecmp(octave_str, "great")) { octave = 2; } else if (!strcasecmp(octave_str, "small")) { octave = 3; } else if (check_n_lined(octave_str, "one", "1")) { octave = 4; } else if (check_n_lined(octave_str, "two", "2")) { octave = 5; } else if (check_n_lined(octave_str, "three", "3")) { octave = 6; } else if (check_n_lined(octave_str, "four", "4")) { octave = 7; } else if (check_n_lined(octave_str, "five", "5")) { octave = 8; } else if (check_n_lined(octave_str, "six", "6")) { octave = 9; } else if (check_n_lined(octave_str, "seven", "7")) { octave = 10; } else if (!isdigit(octave_str[0]) && octave_str[0] != '-') { eprintf("%s is not a recognised octave\n", note_str); } else { long int tmp; errno = 0; tmp = strtol(octave_str, &end, 10); if (errno) eprintf("strol %s:", octave_str); if (*end) eprintf("%s is not a recognised octave\n", note_str); if (tmp < ((long int)INT_MIN + 1 + 9) / 12 + 4 || tmp > ((long int)INT_MAX - 12 + 9) / 12 + 4) eprintf("octave %s is out side the supported domain\n", note_str); octave = (int)tmp; } fprintf(stderr, "Note: %i\n", note); fprintf(stderr, "Octave: %i\n", octave); note += 12 * (octave - 4); freq = 440 * pow(2, (double)(note - 9) / 12); fprintf(stderr, "Frequency: %g Hz\n", freq); fprintf(stderr, "Wavelength: %g mm\n", 343000 / freq); /* Set up audio */ r = snd_pcm_open(&sound_handle, "default", SND_PCM_STREAM_PLAYBACK, 0); if (r < 0) eprintf("snd_pcm_open: %s\n", snd_strerror(r)); if (!sound_handle) eprintf("snd_pcm_open:"); /* Configure audio */ r = snd_pcm_set_params(sound_handle, FORMAT, SND_PCM_ACCESS_RW_INTERLEAVED, 1 /* channels */, SAMPLE_RATE, 1 /* allow resampling? */, LATENCY); if (r < 0) eprintf("snd_pcm_set_params: %s\n", snd_strerror(r)); /* Generate tone */ buflen = wavelen = (snd_pcm_uframes_t)((double)SAMPLE_RATE / freq); if ((snd_pcm_uframes_t)freq > 1) buflen *= (snd_pcm_uframes_t)freq; buffer = emallocn((size_t)buflen, sizeof(*buffer), 0); omul = volume * SMAX * 2; #ifdef __GNUC__ # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wunsuffixed-float-constants" #endif imul = 2 * M_PI * freq / (double)SAMPLE_RATE; #ifdef __GNUC__ # pragma GCC diagnostic pop #endif for (i = 0; i < wavelen; i++) buffer[i] = (UTYPE)(sin(imul * (double)i) * omul - osub); for (i = wavelen; i < buflen; i += wavelen) memcpy(&buffer[i], &buffer[0], wavelen * sizeof(UTYPE)); /* Play tone */ for (;;) { n = snd_pcm_writei(sound_handle, buffer, buflen); if (n < 0) n = snd_pcm_recover(sound_handle, (int)n, 0 /* do not print error reason? */); if (n < 0) eprintf("snd_pcm_writei: %s\n", snd_strerror((int)n)); if (n > 0 && (snd_pcm_uframes_t)n < buflen) eprintf("short write from snd_pcm_writei\n"); } snd_pcm_close(sound_handle); return 0; }