summaryrefslogtreecommitdiffstats
path: root/tuner.c
diff options
context:
space:
mode:
Diffstat (limited to 'tuner.c')
-rw-r--r--tuner.c203
1 files changed, 203 insertions, 0 deletions
diff --git a/tuner.c b/tuner.c
new file mode 100644
index 0000000..f8f5a3a
--- /dev/null
+++ b/tuner.c
@@ -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(&note_str[1], "#") || !strcmp(&note_str[1], "♯"))
+ note += 1;
+ else if (!strcmp(&note_str[1], "b") || !strcmp(&note_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;
+}