3 This file is part of a program that implements a Software-Defined Radio.
5 Copyright (C) 2004 by Frank Brickle, AB2KT and Bob McGwier, N4HY
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 The authors can be reached by email at
29 The DTTS Microwave Society
36 #include <splitfields.h>
37 #include <datatypes.h>
41 #include <semaphore.h>
44 #define SAMP_RATE (48000)
45 #define HUGE_PHASE (1256637061.43593)
47 #define RING_SIZE (01 << 020)
49 static pthread_t input, play;
50 static sem_t ready, reader, writer;
52 ringb_t *lring, *rring;
55 static BOOLEAN playing = FALSE;
56 static double wpm = 18.0, freq = 750.0, gain = -6.0, ramp = 5.0;
60 // basic mapping, chars -> morse strings
61 char *morse_table[128];
70 double wpm, rise, fall, curr, incr, rate;
74 int ditspacesize, dahspacesize,
75 ditstdysize, dahstdysize,
76 charspacesize, wordspacesize,
78 double riseincr, fallincr;
84 void inlinecmd(char *, int);
86 void send_sound(COMPLEX *, int);
88 //------------------------------------------------------------
90 // try to map char -> morse string
92 get_morse(int c) { return morse_table[c & 0x7F]; }
94 // translate text input to timed, sub-morse-element
95 // audio segment specs; parcel the segments out
96 // one at a time to the sound player
99 BOOLEAN b = TRUE; // we're coming from silence
103 // keep reading 1 char at a time
104 while ((c = getchar()) != EOF) {
110 while ((c = getchar()) != EOF) {
111 if (c == ESC_R) break;
113 if (++i >= (MAX_ESC - 1)) break;
115 if (c == EOF) goto finish;
121 // is char mapped to morse?
122 if (m = get_morse(c)) {
125 // for each element in morse string
126 // (dit/dah, doesn't matter)
128 // first segment is ramp up...
130 morsel.type = ME_RAMP, morsel.size = risesize;
131 morsel.curr = 0.0, morsel.incr = riseincr;
134 // ...then steady state...
135 // (choose dit/dah here)
137 morsel.type = ME_STDY;
138 morsel.size = e == '.' ? ditstdysize : dahstdysize;
141 // ...then ramp down...
143 morsel.type = ME_RAMP, morsel.size = fallsize;
144 morsel.curr = 1.0, morsel.incr = fallincr;
147 // ...finally, post-element pause
149 morsel.type = ME_ZERO;
150 morsel.size = ditspacesize;
154 // post-character pause
156 morsel.type = ME_ZERO;
157 // (we already emitted a dit-sized space)
158 morsel.size = charspacesize - ditspacesize;
161 // wherever we go next, it won't have been from silence
165 // anything else treated as interword space,
166 // which has only one segment (silence)
168 morsel.type = ME_ZERO;
169 // was previous output also interword space?
171 // yes, use full duration
172 morsel.size = wordspacesize;
174 // no, part of duration already played
175 morsel.size = wordspacesize - charspacesize;
182 // indicate EOF on input
184 morsel.type = ME_EOF;
190 sound_thread_keyb(void) {
192 double ofreq, scale, phase = 0.0;
195 // keep looking for sub-element segments, one at a time
198 // pause for next sub-element segment
203 if (morsel.type == ME_EOF) break;
205 // requires playing some tone?
206 if (morsel.type != ME_ZERO) {
207 // yes, reset params and
208 // set up CORDIC tone generation
209 ofreq = freq * 2.0 * M_PI / SAMP_RATE;
210 scale = pow(10.0, gain / 20.0);
211 if (phase > HUGE_PHASE) phase -= HUGE_PHASE;
212 z = Cmplx(cos(phase), sin(phase));
213 delta_z = Cmplx(cos(ofreq), sin(ofreq));
216 // play out this segment
217 for (i = 0; i < morsel.size; i++) {
220 if (morsel.type == ME_ZERO) zout[k] = cxzero;
224 z = Cmul(z, delta_z);
226 // is this a ramping segment?
227 if (morsel.type == ME_RAMP) {
228 morsel.curr += morsel.incr;
229 zout[k] = Cscl(z, scale * sin(morsel.curr * M_PI / 2.0));
231 zout[k] = Cscl(z, scale);
234 // have we played enough to fill a jack buffer?
236 // yes, send to output
238 // wait until some audio has been drained
241 if (morsel.type != ME_ZERO) {
243 if (phase > HUGE_PHASE) phase -= HUGE_PHASE;
244 z = Cmplx(cos(phase), sin(phase));
245 delta_z = Cmplx(cos(ofreq), sin(ofreq));
251 // anything left unsent?
252 if (k > 0) send_sound(zout, k);
257 //------------------------------------------------------------------------
259 send_sound(COMPLEX *buff, int len) {
260 if (ringb_write_space(lring) < len * sizeof(float)) {
261 //write(2, "overrun\n", 8);
262 ringb_restart(lring, size * sizeof(float));
263 ringb_restart(rring, size * sizeof(float));
266 for (i = 0; i < len; i++) {
267 float l = (float)buff[i].re, r = (float)buff[i].im;
268 ringb_write(lring, (char *) &l, sizeof(float));
269 ringb_write(rring, (char *) &r, sizeof(float));
275 jack_xrun(void *arg) {
276 char *str = "xrun!\n";
277 write(2, str, strlen(str));
281 jack_shutdown(void *arg) {}
284 jack_callback(jack_nframes_t nframes, void *arg) {
286 int nwant = nframes * sizeof(float),
287 nhave = ringb_read_space(lring);
289 lp = jack_port_get_buffer(lport, nframes);
290 rp = jack_port_get_buffer(rport, nframes);
291 if (nhave >= nwant) {
292 ringb_read(lring, lp, nwant);
293 ringb_read(rring, rp, nwant);
296 memset(lp, 0, nwant);
297 memset(rp, 0, nwant);
304 morsel.rise = morsel.fall = ramp;
305 morsel.rate = SAMP_RATE;
307 ditspacesize = (int)(SAMP_RATE * 1.2 / morsel.wpm + 0.5);
308 dahspacesize = (int)(3 * ditspacesize);
309 charspacesize = dahspacesize;
310 wordspacesize = 7 * ditspacesize;
312 risesize = (int)(SAMP_RATE * morsel.rise / 1e3 + 0.5);
314 riseincr = 1.0 / (risesize - 1);
318 fallsize = (int)(SAMP_RATE * morsel.fall / 1e3 + 0.5);
320 fallincr = -1.0 / (fallsize - 1);
324 ditstdysize = ditspacesize - risesize - fallsize;
325 dahstdysize = dahspacesize - risesize - fallsize;
330 main(int argc, char **argv) {
333 for (i = 1; i < argc; i++)
334 if (argv[i][0] == '-')
335 switch (argv[i][1]) {
337 freq = atof(argv[++i]);
340 wpm = atof(argv[++i]);
343 ramp = atof(argv[++i]);
346 fprintf(stderr, "keyd [-w wpm] [-f freq] [-r ramp_ms] [infile]\n");
352 if (!freopen(argv[i], "r", stdin))
353 perror(argv[i]), exit(1);
357 //------------------------------------------------------------
361 //------------------------------------------------------------
363 if (!(client = jack_client_new("keyb")))
364 fprintf(stderr, "can't make client -- jack not running?\n"), exit(1);
365 jack_set_process_callback(client, (void *) jack_callback, 0);
366 jack_on_shutdown(client, (void *) jack_shutdown, 0);
367 jack_set_xrun_callback(client, (void *) jack_xrun, 0);
368 size = jack_get_buffer_size(client);
370 lport = jack_port_register(client,
372 JACK_DEFAULT_AUDIO_TYPE,
375 rport = jack_port_register(client,
377 JACK_DEFAULT_AUDIO_TYPE,
380 lring = ringb_create(RING_SIZE);
381 rring = ringb_create(RING_SIZE);
382 ringb_clear(lring, size * sizeof(float));
383 ringb_clear(rring, size * sizeof(float));
385 //------------------------------------------------------------
387 zout = newvec_COMPLEX(size, "keyb sample buffer");
389 //------------------------------------------------------------
391 sem_init(&ready, 0, 0);
392 sem_init(&reader, 0, 0);
393 sem_init(&writer, 0, 0);
394 pthread_create(&input, 0, (void *) reader_thread, 0);
395 pthread_create(&play, 0, (void *) sound_thread, 0);
397 //------------------------------------------------------------
399 jack_activate(client);
402 if (!(ports = jack_get_ports(client, 0, 0, JackPortIsPhysical | JackPortIsInput))) {
403 fprintf(stderr, "can't find any physical playback ports\n");
406 if (jack_connect(client, jack_port_name(lport), ports[0])) {
407 fprintf(stderr, "can't connect left output\n");
410 if (jack_connect(client, jack_port_name(rport), ports[1])) {
411 fprintf(stderr, "can't connect right output\n");
417 pthread_join(input, 0);
418 pthread_join(play, 0);
419 jack_client_close(client);
421 //------------------------------------------------------------
423 delvec_COMPLEX(zout);
425 //------------------------------------------------------------
430 sem_destroy(&reader);
431 sem_destroy(&writer);
433 //------------------------------------------------------------
438 char *morse_table[128] = {
439 /* 000 NUL */ 0, /* 001 SOH */ 0, /* 002 STX */ 0, /* 003 ETX */ 0,
440 /* 004 EOT */ 0, /* 005 ENQ */ 0, /* 006 ACK */ 0, /* 007 BEL */ 0,
441 /* 008 BS */ 0, /* 009 HT */ 0, /* 010 LF */ 0, /* 011 VT */ 0,
442 /* 012 FF */ 0, /* 013 CR */ 0, /* 014 SO */ 0, /* 015 SI */ 0,
443 /* 016 DLE */ 0, /* 017 DC1 */ 0, /* 018 DC2 */ 0, /* 019 DC3 */ 0,
444 /* 020 DC4 */ 0, /* 021 NAK */ 0, /* 022 SYN */ 0, /* 023 ETB */ 0,
445 /* 024 CAN */ 0, /* 025 EM */ 0, /* 026 SUB */ 0, /* 027 ESC */ 0,
446 /* 028 FS */ 0, /* 029 GS */ 0, /* 030 RS */ 0, /* 031 US */ 0,
448 /* 033 ! */ "...-.", // [SN]
452 /* 037 % */ ".-...", // [AS]
455 /* 040 ( */ "-.--.", // [KN]
457 /* 042 * */ "...-.-", // [SK]
458 /* 043 + */ ".-.-.", // [AR]
459 /* 044 , */ "--..--",
460 /* 045 - */ "-....-",
461 /* 046 . */ ".-.-.-",
476 /* 061 = */ "-...-", // [BT]
478 /* 063 ? */ "..__..", // [IMI]
479 /* 064 @ */ ".--.-.",
546 inlinecmd(char *buf, int len) {
547 if (!buf || len < 1) return;
548 if (!strncmp(buf, "wpm", 3)) {
551 } else if (!strncmp(buf, "ramp", 4)) {
552 ramp = atof(buf + 4);
554 } else if (!strncmp(buf, "freq", 4))
555 freq = atof(buf + 4);
556 else if (!strncmp(buf, "gain", 4))
557 gain = atof(buf + 4);