/** * Copyright © 2015 Mattias Andrée (maandree@member.fsf.org) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include /* -lm */ #include #include /* dep: alsa-lib (pkg-config alsa) */ #define UTYPE uint32_t #define SMIN INT32_MIN #define SMAX INT32_MAX #define FORMAT SND_PCM_FORMAT_U32 #define SAMPLE_RATE 52000 /* Hz */ #define DURATION 100 /* ms */ /* 100 ms → 10 Bd → 5 B/s */ #define LATENCY 100000 /* µs */ #define CHAR_ESCAPE 0x10 /* Data link escape */ #define CHAR_CANCEL 0x18 /* Cancel */ #define CHAR_END 0x04 /* End of transmission */ #define N(buf) (sizeof(buf) / sizeof(*buf)) static const char* argv0; static snd_pcm_t* sound_handle = NULL; void get_freq(int nibble, int* low, int* high) { static const int LOW[] = { 697, 770, 852, 941 }; static const int HIGH[] = { 1209, 1336, 1477, 1663 }; *low = LOW[(nibble >> 0) & 0x03]; *high = HIGH[(nibble >> 2) & 0x03]; } int send_tones(int low, int high) { #define GENERATE_TONE(tone) \ sin(2 * M_PI * ((double)i / (SAMPLE_RATE / (tone)))) static UTYPE buffer[SAMPLE_RATE / 1000 * DURATION]; snd_pcm_sframes_t frames; int r; size_t i; for (i = 0; i < N(buffer); i++) buffer[i] = (GENERATE_TONE(low * 1) + GENERATE_TONE(high * 1) + GENERATE_TONE(low * 4) + GENERATE_TONE(high * 4)) * (SMAX / 4) - SMIN; r = frames = snd_pcm_writei(sound_handle, buffer, N(buffer)); if (frames < 0) r = frames = snd_pcm_recover(sound_handle, r, 0 /* do not print error reason? */); if (r < 0) { fprintf(stderr, "%s: snd_pcm_writei: %s\n", argv0, snd_strerror(r)); return -1; } if ((r > 0) && ((size_t)r < N(buffer))) printf("%s: short write: expected %zu, wrote %zu\n", argv0, N(buffer), (size_t)frames); return 0; } int send_byte(int c) { int low1, high1, low2, high2; get_freq((c >> 0) & 0x0F, &low1, &high1); get_freq((c >> 4) & 0x0F, &low2, &high2); if (send_tones(low1, high1)) return -1; if (send_tones(low2, high2)) return -1; return 0; } int main(int argc, char* argv[]) { int r; char buf[1024]; int c; ssize_t n, i; (void) argc; argv0 = argv[0]; r = snd_pcm_open(&sound_handle, "default", SND_PCM_STREAM_PLAYBACK, 0); if (r < 0) return fprintf(stderr, "%s: snd_pcm_open: %s\n", *argv, snd_strerror(r)), 1; r = snd_pcm_set_params(sound_handle, FORMAT, SND_PCM_ACCESS_RW_INTERLEAVED, 1, /* channels */ SAMPLE_RATE, 1, /* allow resampling? */ LATENCY); if (r < 0) { fprintf(stderr, "%s: snd_pcm_set_params: %s\n", *argv, snd_strerror(r)); goto snd_fail; } for (;;) { n = read(STDIN_FILENO, buf, sizeof(buf)); if (n < 0) goto fail; if (n == 0) break; for (i = 0; i < n; i++) { c = buf[i]; if ((c == CHAR_ESCAPE) || (c == CHAR_CANCEL) || (c == CHAR_END)) if (send_byte(CHAR_ESCAPE)) goto snd_fail; if (send_byte(c)) goto snd_fail; } } if (send_byte(CHAR_END)) goto snd_fail; snd_pcm_close(sound_handle); return 0; fail: perror(argv0); send_byte(CHAR_CANCEL); return 1; snd_fail: snd_pcm_close(sound_handle); send_byte(CHAR_CANCEL); return 1; }