#include <sys/time.h>
#include <sys/soundcard.h>
#include <ctype.h>
#include <err.h>
#include <fcntl.h>
#include <langinfo.h>
#include <locale.h>
#include <math.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
static const char *digit[] = {
"-----",
".----",
"..---",
"...--",
"....-",
".....",
"-....",
"--...",
"---..",
"----.",
};
static const char *alph[] = {
".-",
"-...",
"-.-.",
"-..",
".",
"..-.",
"--.",
"....",
"..",
".---",
"-.-",
".-..",
"--",
"-.",
"---",
".--.",
"--.-",
".-.",
"...",
"-",
"..-",
"...-",
".--",
"-..-",
"-.--",
"--..",
};
struct punc {
char c;
const char *morse;
};
static const struct punc other[] = {
{ 'e', "..-.." },
{ ',', "--..--" },
{ '.', ".-.-.-" },
{ '?', "..--.." },
{ '/', "-..-." },
{ '-', "-....-" },
{ ':', "---..." },
{ ';', "-.-.-." },
{ '(', "-.--." },
{ ')', "-.--.-" },
{ '"', ".-..-." },
{ '`', ".-..-." },
{ '\'', ".----." },
{ '+', ".-.-." },
{ '=', "-...-" },
{ '@', ".--.-." },
{ '\n', ".-.-" },
{ '\0', NULL }
};
struct prosign {
const char *c;
const char *morse;
};
static const struct prosign ps[] = {
{ "<AS>", ".-..." },
{ "<CL>", "-.-..-.." },
{ "<CT>", "-.-.-" },
{ "<EE5>", "......" },
{ "<EE5>", "......." },
{ "<EE5>", "........" },
{ "<SK>", "...-.-" },
{ "<SN>", "...-." },
{ "<SOS>", "...---..." },
{ NULL, NULL }
};
struct morsetab {
char inchar;
const char *morse;
};
static const struct morsetab mtab[] = {
{'a', ".-"},
{'b', "-..."},
{'c', "-.-."},
{'d', "-.."},
{'e', "."},
{'f', "..-."},
{'g', "--."},
{'h', "...."},
{'i', ".."},
{'j', ".---"},
{'k', "-.-"},
{'l', ".-.."},
{'m', "--"},
{'n', "-."},
{'o', "---"},
{'p', ".--."},
{'q', "--.-"},
{'r', ".-."},
{'s', "..."},
{'t', "-"},
{'u', "..-"},
{'v', "...-"},
{'w', ".--"},
{'x', "-..-"},
{'y', "-.--"},
{'z', "--.."},
{'0', "-----"},
{'1', ".----"},
{'2', "..---"},
{'3', "...--"},
{'4', "....-"},
{'5', "....."},
{'6', "-...."},
{'7', "--..."},
{'8', "---.."},
{'9', "----."},
{',', "--..--"},
{'.', ".-.-.-"},
{'?', "..--.."},
{'!', "-.-.--"},
{'/', "-..-."},
{'-', "-....-"},
{'_', "..--.."},
{'=', "-...-"},
{':', "---..."},
{';', "-.-.-."},
{'(', "-.--."},
{')', "-.--.-"},
{'$', "...-..-"},
{'+', ".-.-."},
{'\'', ".----."},
{'"', ".-..-."},
{'@', ".--.-."},
{'\0', ""}
};
static const struct morsetab iso8859tab[] = {
{'á', ".--.-"},
{'à', ".--.-"},
{'â', ".--.-"},
{'ä', ".-.-"},
{'ç', "-.-.."},
{'é', "..-.."},
{'è', "..-.."},
{'ê', "-..-."},
{'ö', "---."},
{'ü', "..--"},
{'\0', ""}
};
static const struct morsetab koi8rtab[] = {
{'Á', ".-"},
{'Â', "-..."},
{'×', ".--"},
{'Ç', "--."},
{'Ä', "-.."},
{'Å', "."},
{'£', "."},
{'Ö', "...-"},
{'Ú', "--.."},
{'É', ".."},
{'Ê', ".---"},
{'Ë', "-.-"},
{'Ì', ".-.."},
{'Í', "--"},
{'Î', "-."},
{'Ï', "---"},
{'Ð', ".--."},
{'Ò', ".-."},
{'Ó', "..."},
{'Ô', "-"},
{'Õ', "..-"},
{'Æ', "..-."},
{'È', "...."},
{'Ã', "-.-."},
{'Þ', "---."},
{'Û', "----"},
{'Ý', "--.-"},
{'Ù', "-.--"},
{'Ø', "-..-"},
{'Ü', "..-.."},
{'À', "..--"},
{'Ñ', ".-.-"},
{'\0', ""}
};
struct tone_data {
int16_t *data;
size_t len;
};
static void alloc_soundbuf(struct tone_data *, double, int);
static void morse(char, int);
static void decode(const char *);
static void show(const char *, int);
static void play(const char *, int);
static void ttyout(const char *, int);
static void sighandler(int);
#define GETOPTOPTS "d:ef:lopP:rsw:W:"
#define USAGE \
"usage: morse [-r] [-els] [-p | -o] [-P device] [-d device] [-w speed] [-W speed] [-f frequency] [string ...]\n"
static int lflag, oflag, pflag, rflag, sflag, eflag;
static int wpm = 20;
static int farnsworth = -1;
#define FREQUENCY 600
static int freq = FREQUENCY;
static char *device;
static struct tone_data tone_dot, tone_dash, tone_silence, tone_letter_silence;
#define DSP_RATE 44100
static const char *snddev = NULL;
#define DASH_LEN 3
#define CHAR_SPACE 3
#define WORD_SPACE (7 - CHAR_SPACE)
static float dot_clock, word_clock;
int spkr, line;
struct termios otty, ntty;
int olflags;
static const struct morsetab *hightab;
int
main(int argc, char *argv[])
{
int ch, lflags;
int prosign;
char *p, *codeset;
while ((ch = getopt(argc, argv, GETOPTOPTS)) != -1)
switch ((char) ch) {
case 'd':
device = optarg;
break;
case 'e':
eflag = 1;
setvbuf(stdout, 0, _IONBF, 0);
break;
case 'f':
freq = atoi(optarg);
break;
case 'l':
lflag = 1;
break;
case 'o':
oflag = 1;
case 'p':
pflag = 1;
break;
case 'P':
snddev = optarg;
break;
case 'r':
rflag = 1;
break;
case 's':
sflag = 1;
break;
case 'w':
wpm = atoi(optarg);
break;
case 'W':
farnsworth = atoi(optarg);
break;
case '?':
default:
fputs(USAGE, stderr);
exit(1);
}
if (sflag && lflag) {
fputs("morse: only one of -l and -s allowed\n", stderr);
exit(1);
}
if (pflag + !!device + sflag + lflag > 1) {
fputs("morse: only one of -o, -p, -d and -l, -s allowed\n", stderr);
exit(1);
}
if ((pflag || device) && ((wpm < 1) || (wpm > 60) || (farnsworth > 60))) {
fputs("morse: insane speed\n", stderr);
exit(1);
}
if ((pflag || device) && (freq == 0))
freq = FREQUENCY;
if (pflag || device) {
dot_clock = wpm / 1.2;
dot_clock = 1 / dot_clock;
word_clock = dot_clock;
if (farnsworth > 0)
word_clock = (60.0 / farnsworth - 31 * dot_clock) / 19;
}
if (snddev == NULL) {
if (oflag)
snddev = "-";
else
snddev = "/dev/dsp";
}
if (pflag) {
snd_chan_param param;
if (oflag && strcmp(snddev, "-") == 0)
spkr = STDOUT_FILENO;
else
spkr = open(snddev, O_WRONLY, 0);
if (spkr == -1)
err(1, "%s", snddev);
param.play_rate = DSP_RATE;
param.play_format = AFMT_S16_NE;
param.rec_rate = 0;
param.rec_format = 0;
if (!oflag && ioctl(spkr, AIOSFMT, ¶m) != 0)
err(1, "%s: set format", snddev);
alloc_soundbuf(&tone_dot, dot_clock, 1);
alloc_soundbuf(&tone_dash, DASH_LEN * dot_clock, 1);
alloc_soundbuf(&tone_silence, dot_clock, 0);
alloc_soundbuf(&tone_letter_silence, word_clock, 0);
} else
if (device) {
if ((line = open(device, O_WRONLY | O_NONBLOCK)) == -1) {
perror("open tty line");
exit(1);
}
if (tcgetattr(line, &otty) == -1) {
perror("tcgetattr() failed");
exit(1);
}
ntty = otty;
ntty.c_cflag |= CLOCAL;
tcsetattr(line, TCSANOW, &ntty);
lflags = fcntl(line, F_GETFL);
lflags &= ~O_NONBLOCK;
fcntl(line, F_SETFL, &lflags);
ioctl(line, TIOCMGET, &lflags);
lflags &= ~TIOCM_RTS;
olflags = lflags;
ioctl(line, TIOCMSET, &lflags);
signal(SIGHUP, sighandler);
signal(SIGINT, sighandler);
signal(SIGQUIT, sighandler);
signal(SIGTERM, sighandler);
}
argc -= optind;
argv += optind;
if (setlocale(LC_CTYPE, "") != NULL &&
*(codeset = nl_langinfo(CODESET)) != '\0') {
if (strcmp(codeset, "KOI8-R") == 0)
hightab = koi8rtab;
else if (strcmp(codeset, "ISO8859-1") == 0 ||
strcmp(codeset, "ISO8859-15") == 0)
hightab = iso8859tab;
}
if (rflag) {
if (*argv) {
do {
decode(*argv);
} while (*++argv);
} else {
char foo[10];
int blank, i;
i = 0;
blank = 0;
while ((ch = getchar()) != EOF) {
if (ch == '-' || ch == '.') {
foo[i++] = ch;
if (i == 10) {
i = 0;
putchar('x');
while ((ch = getchar()) != EOF &&
(ch == '.' || ch == '-'))
;
blank = 1;
}
} else if (i) {
foo[i] = '\0';
decode(foo);
i = 0;
blank = 0;
} else if (isspace(ch)) {
if (blank) {
putchar(' ');
blank = 0;
} else
blank = 1;
}
}
}
putchar('\n');
exit(0);
}
if (lflag)
printf("m");
if (*argv) {
do {
prosign = 0;
for (p = *argv; *p; ++p) {
if (eflag)
putchar(*p);
if (*p == '<' || *p == '>') {
prosign = *p == '<';
continue;
}
if (strchr("> \r\n", *(p + 1)) != NULL)
prosign = 0;
morse(*p, prosign);
}
if (eflag)
putchar(' ');
morse(' ', 0);
} while (*++argv);
} else {
prosign = 0;
while ((ch = getchar()) != EOF) {
if (eflag)
putchar(ch);
if (ch == '<') {
prosign = 1;
continue;
}
if (prosign) {
int tch;
tch = getchar();
if (strchr("> \r\n", tch) != NULL)
prosign = 0;
if (tch != '>')
ungetc(tch, stdin);
}
morse(ch, prosign);
}
}
if (device)
tcsetattr(line, TCSANOW, &otty);
exit(0);
}
static void
alloc_soundbuf(struct tone_data *tone, double len, int on)
{
int samples, i;
samples = DSP_RATE * len;
tone->len = samples * sizeof(*tone->data);
tone->data = malloc(tone->len);
if (tone->data == NULL)
err(1, NULL);
if (!on) {
bzero(tone->data, tone->len);
return;
}
for (i = 0; i < samples; i++) {
double filter = 1;
#define FILTER_SAMPLES (DSP_RATE * 8 / 1000)
if (i < FILTER_SAMPLES || i > samples - FILTER_SAMPLES) {
int fi = i;
if (i > FILTER_SAMPLES)
fi = samples - i;
#if defined(TRIANGLE_FILTER)
filter = (double)fi / FILTER_SAMPLES;
#elif defined(GAUSS_FILTER)
filter = exp(-4.0 *
pow((double)(FILTER_SAMPLES - fi) /
FILTER_SAMPLES, 2));
#else
filter = (1 + cos(M_PI * (FILTER_SAMPLES - fi) / FILTER_SAMPLES)) / 2;
#endif
}
tone->data[i] = 32767 * sin((double)i / samples * len * freq * 2 * M_PI) *
filter;
}
}
static void
morse(char c, int prosign)
{
const struct morsetab *m;
if (isalpha((unsigned char)c))
c = tolower((unsigned char)c);
if ((c == '\r') || (c == '\n'))
c = ' ';
if (c == ' ') {
if (pflag) {
play(" ", 0);
return;
} else if (device) {
ttyout(" ", 0);
return;
} else if (lflag) {
printf("\n");
} else {
show("", 0);
return;
}
}
for (m = ((unsigned char)c < 0x80? mtab: hightab);
m != NULL && m->inchar != '\0';
m++) {
if (m->inchar == c) {
if (pflag) {
play(m->morse, prosign);
} else if (device) {
ttyout(m->morse, prosign);
} else
show(m->morse, prosign);
}
}
}
static void
decode(const char *s)
{
int i;
for (i = 0; i < 10; i++)
if (strcmp(digit[i], s) == 0) {
putchar('0' + i);
return;
}
for (i = 0; i < 26; i++)
if (strcmp(alph[i], s) == 0) {
putchar('A' + i);
return;
}
i = 0;
while (other[i].c) {
if (strcmp(other[i].morse, s) == 0) {
putchar(other[i].c);
return;
}
i++;
}
i = 0;
while (ps[i].c) {
if (strcmp(ps[i].morse, s) == 0) {
printf(" %s ", ps[i].c);
return;
}
i++;
}
putchar('x');
}
static void
show(const char *s, int prosign)
{
if (lflag) {
printf("%s ", s);
return;
} else if (sflag)
printf(" %s", s);
else
for (; *s; ++s)
printf(" %s", *s == '.' ? "dit" : "dah");
if (!prosign)
printf("\n");
}
static void
play(const char *s, int prosign)
{
const char *c;
int duration;
struct tone_data *tone;
for (c = s; *c != '\0'; c++) {
switch (*c) {
case '.':
duration = 1;
tone = &tone_dot;
break;
case '-':
duration = 1;
tone = &tone_dash;
break;
case ' ':
duration = WORD_SPACE;
tone = &tone_letter_silence;
break;
default:
errx(1, "invalid morse digit");
}
while (duration-- > 0)
write(spkr, tone->data, tone->len);
if (c[1] != '\0' || prosign)
write(spkr, tone_silence.data, tone_silence.len);
}
if (prosign)
return;
duration = CHAR_SPACE;
while (duration-- > 0)
write(spkr, tone_letter_silence.data, tone_letter_silence.len);
if (!oflag)
ioctl(spkr, SNDCTL_DSP_SYNC, NULL);
}
static void
ttyout(const char *s, int prosign)
{
const char *c;
int duration, on, lflags;
for (c = s; *c != '\0'; c++) {
switch (*c) {
case '.':
on = 1;
duration = dot_clock;
break;
case '-':
on = 1;
duration = dot_clock * DASH_LEN;
break;
case ' ':
on = 0;
duration = word_clock * WORD_SPACE;
break;
default:
on = 0;
duration = 0;
}
if (on) {
ioctl(line, TIOCMGET, &lflags);
lflags |= TIOCM_RTS;
ioctl(line, TIOCMSET, &lflags);
}
duration *= 1000000;
if (duration)
usleep(duration);
ioctl(line, TIOCMGET, &lflags);
lflags &= ~TIOCM_RTS;
ioctl(line, TIOCMSET, &lflags);
duration = dot_clock * 1000000;
if (c[1] != '\0' || prosign)
usleep(duration);
}
if (!prosign) {
duration = word_clock * CHAR_SPACE * 1000000;
usleep(duration);
}
}
static void
sighandler(int signo)
{
ioctl(line, TIOCMSET, &olflags);
tcsetattr(line, TCSANOW, &otty);
signal(signo, SIG_DFL);
kill(getpid(), signo);
}