diff options
Diffstat (limited to 'tuner.c')
| -rw-r--r-- | tuner.c | 203 |
1 files changed, 203 insertions, 0 deletions
@@ -0,0 +1,203 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#include <alsa/asoundlib.h> +#include <math.h> + +/* 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; +} |
