--- /dev/null
+/* keyb.c */
+/*
+This file is part of a program that implements a Software-Defined Radio.
+
+Copyright (C) 2004 by Frank Brickle, AB2KT and Bob McGwier, N4HY
+
+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 2 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, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+The authors can be reached by email at
+
+ab2kt@arrl.net
+or
+rwmcgwier@comcast.net
+
+or by paper mail at
+
+The DTTS Microwave Society
+6 Kathleen Place
+Bridgewater, NJ 08807
+*/
+
+#include <fromsys.h>
+#include <banal.h>
+#include <splitfields.h>
+#include <datatypes.h>
+#include <bufvec.h>
+#include <cxops.h>
+#include <pthread.h>
+#include <semaphore.h>
+#include <ringb.h>
+
+#define SAMP_RATE (48000)
+#define HUGE_PHASE (1256637061.43593)
+
+#define RING_SIZE (01 << 020)
+
+static pthread_t input, play;
+static sem_t ready, reader, writer;
+
+ringb_t *lring, *rring;
+int size;
+
+static BOOLEAN playing = FALSE;
+static double wpm = 18.0, freq = 750.0, gain = -6.0, ramp = 5.0;
+
+COMPLEX *zout = 0;
+
+// basic mapping, chars -> morse strings
+char *morse_table[128];
+
+// CW tone segments
+#define ME_EOF (-1)
+#define ME_ZERO (0)
+#define ME_RAMP (1)
+#define ME_STDY (2)
+
+struct {
+ double wpm, rise, fall, curr, incr, rate;
+ int type, size;
+} morsel;
+
+int ditspacesize, dahspacesize,
+ ditstdysize, dahstdysize,
+ charspacesize, wordspacesize,
+ risesize, fallsize;
+double riseincr, fallincr;
+
+#define MAX_ESC (512)
+#define ESC_L '<'
+#define ESC_R '>'
+
+void inlinecmd(char *, int);
+
+void send_sound(COMPLEX *, int);
+
+//------------------------------------------------------------
+
+// try to map char -> morse string
+char *
+get_morse(int c) { return morse_table[c & 0x7F]; }
+
+// translate text input to timed, sub-morse-element
+// audio segment specs; parcel the segments out
+// one at a time to the sound player
+void
+reader_thread(void) {
+ BOOLEAN b = TRUE; // we're coming from silence
+ int c, e;
+ char *m;
+
+ // keep reading 1 char at a time
+ while ((c = getchar()) != EOF) {
+
+ // inline command?
+ if (c == ESC_L) {
+ int i = 0;
+ char buf[MAX_ESC];
+ while ((c = getchar()) != EOF) {
+ if (c == ESC_R) break;
+ buf[i] = c;
+ if (++i >= (MAX_ESC - 1)) break;
+ }
+ if (c == EOF) goto finish;
+ buf[i] = 0;
+ inlinecmd(buf, i);
+ continue;
+ }
+
+ // is char mapped to morse?
+ if (m = get_morse(c)) {
+
+ // yup
+ // for each element in morse string
+ // (dit/dah, doesn't matter)
+ while (e = *m++) {
+ // first segment is ramp up...
+ sem_wait(&reader);
+ morsel.type = ME_RAMP, morsel.size = risesize;
+ morsel.curr = 0.0, morsel.incr = riseincr;
+ sem_post(&writer);
+
+ // ...then steady state...
+ // (choose dit/dah here)
+ sem_wait(&reader);
+ morsel.type = ME_STDY;
+ morsel.size = e == '.' ? ditstdysize : dahstdysize;
+ sem_post(&writer);
+
+ // ...then ramp down...
+ sem_wait(&reader);
+ morsel.type = ME_RAMP, morsel.size = fallsize;
+ morsel.curr = 1.0, morsel.incr = fallincr;
+ sem_post(&writer);
+
+ // ...finally, post-element pause
+ sem_wait(&reader);
+ morsel.type = ME_ZERO;
+ morsel.size = ditspacesize;
+ sem_post(&writer);
+ }
+
+ // post-character pause
+ sem_wait(&reader);
+ morsel.type = ME_ZERO;
+ // (we already emitted a dit-sized space)
+ morsel.size = charspacesize - ditspacesize;
+ sem_post(&writer);
+
+ // wherever we go next, it won't have been from silence
+ b = FALSE;
+
+ } else {
+ // anything else treated as interword space,
+ // which has only one segment (silence)
+ sem_wait(&reader);
+ morsel.type = ME_ZERO;
+ // was previous output also interword space?
+ if (b)
+ // yes, use full duration
+ morsel.size = wordspacesize;
+ else
+ // no, part of duration already played
+ morsel.size = wordspacesize - charspacesize;
+ b = TRUE;
+ sem_post(&writer);
+ }
+ }
+
+ finish:
+ // indicate EOF on input
+ sem_wait(&reader);
+ morsel.type = ME_EOF;
+ sem_post(&writer);
+ pthread_exit(0);
+}
+
+void
+sound_thread_keyb(void) {
+ int i, k = 0;
+ double ofreq, scale, phase = 0.0;
+ COMPLEX z, delta_z;
+
+ // keep looking for sub-element segments, one at a time
+ for (;;) {
+
+ // pause for next sub-element segment
+ sem_post(&reader);
+ sem_wait(&writer);
+
+ // no more data?
+ if (morsel.type == ME_EOF) break;
+
+ // requires playing some tone?
+ if (morsel.type != ME_ZERO) {
+ // yes, reset params and
+ // set up CORDIC tone generation
+ ofreq = freq * 2.0 * M_PI / SAMP_RATE;
+ scale = pow(10.0, gain / 20.0);
+ if (phase > HUGE_PHASE) phase -= HUGE_PHASE;
+ z = Cmplx(cos(phase), sin(phase));
+ delta_z = Cmplx(cos(ofreq), sin(ofreq));
+ }
+
+ // play out this segment
+ for (i = 0; i < morsel.size; i++) {
+
+ // make silence
+ if (morsel.type == ME_ZERO) zout[k] = cxzero;
+
+ // make tone
+ else {
+ z = Cmul(z, delta_z);
+ phase += ofreq;
+ // is this a ramping segment?
+ if (morsel.type == ME_RAMP) {
+ morsel.curr += morsel.incr;
+ zout[k] = Cscl(z, scale * sin(morsel.curr * M_PI / 2.0));
+ } else
+ zout[k] = Cscl(z, scale);
+ }
+
+ // have we played enough to fill a jack buffer?
+ if (++k >= size) {
+ // yes, send to output
+ send_sound(zout, k);
+ // wait until some audio has been drained
+ sem_wait(&ready);
+ k = 0;
+ if (morsel.type != ME_ZERO) {
+ // reset CORDIC
+ if (phase > HUGE_PHASE) phase -= HUGE_PHASE;
+ z = Cmplx(cos(phase), sin(phase));
+ delta_z = Cmplx(cos(ofreq), sin(ofreq));
+ }
+ }
+ }
+ }
+
+ // anything left unsent?
+ if (k > 0) send_sound(zout, k);
+
+ pthread_exit(0);
+}
+
+//------------------------------------------------------------------------
+void
+send_sound(COMPLEX *buff, int len) {
+ if (ringb_write_space(lring) < len * sizeof(float)) {
+ //write(2, "overrun\n", 8);
+ ringb_restart(lring, size * sizeof(float));
+ ringb_restart(rring, size * sizeof(float));
+ } else {
+ int i;
+ for (i = 0; i < len; i++) {
+ float l = (float)buff[i].re, r = (float)buff[i].im;
+ ringb_write(lring, (char *) &l, sizeof(float));
+ ringb_write(rring, (char *) &r, sizeof(float));
+ }
+ }
+}
+#ifndef _WINDOWS
+PRIVATE void
+jack_xrun(void *arg) {
+ char *str = "xrun!\n";
+ write(2, str, strlen(str));
+}
+
+PRIVATE void
+jack_shutdown(void *arg) {}
+
+PRIVATE void
+jack_callback(jack_nframes_t nframes, void *arg) {
+ char *lp, *rp;
+ int nwant = nframes * sizeof(float),
+ nhave = ringb_read_space(lring);
+
+ lp = jack_port_get_buffer(lport, nframes);
+ rp = jack_port_get_buffer(rport, nframes);
+ if (nhave >= nwant) {
+ ringb_read(lring, lp, nwant);
+ ringb_read(rring, rp, nwant);
+ sem_post(&ready);
+ } else {
+ memset(lp, 0, nwant);
+ memset(rp, 0, nwant);
+ }
+}
+#endif
+void
+resetparam(void) {
+ morsel.wpm = wpm;
+ morsel.rise = morsel.fall = ramp;
+ morsel.rate = SAMP_RATE;
+
+ ditspacesize = (int)(SAMP_RATE * 1.2 / morsel.wpm + 0.5);
+ dahspacesize = (int)(3 * ditspacesize);
+ charspacesize = dahspacesize;
+ wordspacesize = 7 * ditspacesize;
+
+ risesize = (int)(SAMP_RATE * morsel.rise / 1e3 + 0.5);
+ if (risesize > 1)
+ riseincr = 1.0 / (risesize - 1);
+ else
+ riseincr = 1.0;
+
+ fallsize = (int)(SAMP_RATE * morsel.fall / 1e3 + 0.5);
+ if (fallsize > 1)
+ fallincr = -1.0 / (fallsize - 1);
+ else
+ fallincr = -1.0;
+
+ ditstdysize = ditspacesize - risesize - fallsize;
+ dahstdysize = dahspacesize - risesize - fallsize;
+}
+
+#ifndef _WINDOWS
+int
+main(int argc, char **argv) {
+ int i;
+
+ for (i = 1; i < argc; i++)
+ if (argv[i][0] == '-')
+ switch (argv[i][1]) {
+ case 'f':
+ freq = atof(argv[++i]);
+ break;
+ case 'w':
+ wpm = atof(argv[++i]);
+ break;
+ case 'r':
+ ramp = atof(argv[++i]);
+ break;
+ default:
+ fprintf(stderr, "keyd [-w wpm] [-f freq] [-r ramp_ms] [infile]\n");
+ exit(1);
+ }
+ else break;
+
+ if (i < argc) {
+ if (!freopen(argv[i], "r", stdin))
+ perror(argv[i]), exit(1);
+ i++;
+ }
+
+ //------------------------------------------------------------
+
+ resetparam();
+
+ //------------------------------------------------------------
+
+ if (!(client = jack_client_new("keyb")))
+ fprintf(stderr, "can't make client -- jack not running?\n"), exit(1);
+ jack_set_process_callback(client, (void *) jack_callback, 0);
+ jack_on_shutdown(client, (void *) jack_shutdown, 0);
+ jack_set_xrun_callback(client, (void *) jack_xrun, 0);
+ size = jack_get_buffer_size(client);
+
+ lport = jack_port_register(client,
+ "ol",
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsOutput,
+ 0);
+ rport = jack_port_register(client,
+ "or",
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsOutput,
+ 0);
+ lring = ringb_create(RING_SIZE);
+ rring = ringb_create(RING_SIZE);
+ ringb_clear(lring, size * sizeof(float));
+ ringb_clear(rring, size * sizeof(float));
+
+ //------------------------------------------------------------
+
+ zout = newvec_COMPLEX(size, "keyb sample buffer");
+
+ //------------------------------------------------------------
+
+ sem_init(&ready, 0, 0);
+ sem_init(&reader, 0, 0);
+ sem_init(&writer, 0, 0);
+ pthread_create(&input, 0, (void *) reader_thread, 0);
+ pthread_create(&play, 0, (void *) sound_thread, 0);
+
+ //------------------------------------------------------------
+
+ jack_activate(client);
+ {
+ const char **ports;
+ if (!(ports = jack_get_ports(client, 0, 0, JackPortIsPhysical | JackPortIsInput))) {
+ fprintf(stderr, "can't find any physical playback ports\n");
+ exit(1);
+ }
+ if (jack_connect(client, jack_port_name(lport), ports[0])) {
+ fprintf(stderr, "can't connect left output\n");
+ exit(1);
+ }
+ if (jack_connect(client, jack_port_name(rport), ports[1])) {
+ fprintf(stderr, "can't connect right output\n");
+ exit(1);
+ }
+ free(ports);
+ }
+
+ pthread_join(input, 0);
+ pthread_join(play, 0);
+ jack_client_close(client);
+
+ //------------------------------------------------------------
+
+ delvec_COMPLEX(zout);
+
+ //------------------------------------------------------------
+
+ ringb_free(lring);
+ ringb_free(rring);
+ sem_destroy(&ready);
+ sem_destroy(&reader);
+ sem_destroy(&writer);
+
+ //------------------------------------------------------------
+
+ exit(0);
+}
+#endif
+char *morse_table[128] = {
+ /* 000 NUL */ 0, /* 001 SOH */ 0, /* 002 STX */ 0, /* 003 ETX */ 0,
+ /* 004 EOT */ 0, /* 005 ENQ */ 0, /* 006 ACK */ 0, /* 007 BEL */ 0,
+ /* 008 BS */ 0, /* 009 HT */ 0, /* 010 LF */ 0, /* 011 VT */ 0,
+ /* 012 FF */ 0, /* 013 CR */ 0, /* 014 SO */ 0, /* 015 SI */ 0,
+ /* 016 DLE */ 0, /* 017 DC1 */ 0, /* 018 DC2 */ 0, /* 019 DC3 */ 0,
+ /* 020 DC4 */ 0, /* 021 NAK */ 0, /* 022 SYN */ 0, /* 023 ETB */ 0,
+ /* 024 CAN */ 0, /* 025 EM */ 0, /* 026 SUB */ 0, /* 027 ESC */ 0,
+ /* 028 FS */ 0, /* 029 GS */ 0, /* 030 RS */ 0, /* 031 US */ 0,
+ /* 032 SP */ 0,
+ /* 033 ! */ "...-.", // [SN]
+ /* 034 " */ 0,
+ /* 035 # */ 0,
+ /* 036 $ */ 0,
+ /* 037 % */ ".-...", // [AS]
+ /* 038 & */ 0,
+ /* 039 ' */ 0,
+ /* 040 ( */ "-.--.", // [KN]
+ /* 041 ) */ 0,
+ /* 042 * */ "...-.-", // [SK]
+ /* 043 + */ ".-.-.", // [AR]
+ /* 044 , */ "--..--",
+ /* 045 - */ "-....-",
+ /* 046 . */ ".-.-.-",
+ /* 047 / */ "-..-.",
+ /* 048 0 */ "-----",
+ /* 049 1 */ ".----",
+ /* 050 2 */ "..---",
+ /* 051 3 */ "...--",
+ /* 052 4 */ "....-",
+ /* 053 5 */ ".....",
+ /* 054 6 */ "-....",
+ /* 055 7 */ "--...",
+ /* 056 8 */ "---..",
+ /* 057 9 */ "----.",
+ /* 058 : */ 0,
+ /* 059 ; */ 0,
+ /* 060 < */ 0,
+ /* 061 = */ "-...-", // [BT]
+ /* 062 > */ 0,
+ /* 063 ? */ "..__..", // [IMI]
+ /* 064 @ */ ".--.-.",
+ /* 065 A */ ".-",
+ /* 066 B */ "-...",
+ /* 067 C */ "-.-.",
+ /* 068 D */ "-..",
+ /* 069 E */ ".",
+ /* 070 F */ "..-.",
+ /* 071 G */ "--.",
+ /* 072 H */ "....",
+ /* 073 I */ "..",
+ /* 074 J */ ".---",
+ /* 075 K */ "-.-",
+ /* 076 L */ ".-..",
+ /* 077 M */ "--",
+ /* 078 N */ "-.",
+ /* 079 O */ "---",
+ /* 080 P */ ".--.",
+ /* 081 Q */ "--.-",
+ /* 082 R */ ".-.",
+ /* 083 S */ "...",
+ /* 084 T */ "-",
+ /* 085 U */ "..-",
+ /* 086 V */ "...-",
+ /* 087 W */ ".--",
+ /* 088 X */ "-..-",
+ /* 089 Y */ "-.--",
+ /* 090 Z */ "--..",
+ /* 091 [ */ 0,
+ /* 092 \ */ 0,
+ /* 093 ] */ 0,
+ /* 094 ^ */ 0,
+ /* 095 _ */ 0,
+ /* 096 ` */ 0,
+ /* 097 a */ ".-",
+ /* 098 b */ "-...",
+ /* 099 c */ "-.-.",
+ /* 100 d */ "-..",
+ /* 101 e */ ".",
+ /* 102 f */ "..-.",
+ /* 103 g */ "--.",
+ /* 104 h */ "....",
+ /* 105 i */ "..",
+ /* 106 j */ ".---",
+ /* 107 k */ "-.-",
+ /* 108 l */ ".-..",
+ /* 109 m */ "--",
+ /* 110 n */ "-.",
+ /* 111 o */ "---",
+ /* 112 p */ ".--.",
+ /* 113 q */ "--.-",
+ /* 114 r */ ".-.",
+ /* 115 s */ "...",
+ /* 116 t */ "-",
+ /* 117 u */ "..-",
+ /* 118 v */ "...-",
+ /* 119 w */ ".--",
+ /* 120 x */ "-..-",
+ /* 121 y */ "-.--",
+ /* 122 z */ "--..",
+ /* 123 { */ 0,
+ /* 124 | */ 0,
+ /* 125 } */ 0,
+ /* 126 ~ */ 0,
+ /* 127 DEL */ 0
+};
+
+void
+inlinecmd(char *buf, int len) {
+ if (!buf || len < 1) return;
+ if (!strncmp(buf, "wpm", 3)) {
+ wpm = atof(buf + 3);
+ resetparam();
+ } else if (!strncmp(buf, "ramp", 4)) {
+ ramp = atof(buf + 4);
+ resetparam();
+ } else if (!strncmp(buf, "freq", 4))
+ freq = atof(buf + 4);
+ else if (!strncmp(buf, "gain", 4))
+ gain = atof(buf + 4);
+}