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 #define SAMP_RATE (48000)
42 #define HUGE_PHASE (1256637061.43593)
44 #define RING_SIZE (01 << 020)
46 pthread_t input, play;
47 sem_t ready, reader, writer;
49 jack_client_t *client;
50 jack_port_t *lport, *rport;
51 jack_ringbuffer_t *lring, *rring;
54 BOOLEAN playing = FALSE;
55 double wpm = 18.0, freq = 750.0, gain = -6.0, ramp = 5.0;
59 // basic mapping, chars -> morse strings
60 char *morse_table[128];
69 double wpm, rise, fall, curr, incr, rate;
73 int ditspacesize, dahspacesize,
74 ditstdysize, dahstdysize,
75 charspacesize, wordspacesize,
77 double riseincr, fallincr;
83 void inlinecmd(char *, int);
85 void jack_ringbuffer_clear(jack_ringbuffer_t *, int);
86 void jack_ringbuffer_restart(jack_ringbuffer_t *, int);
87 void send_sound(COMPLEX *, int);
89 //------------------------------------------------------------
91 // try to map char -> morse string
93 get_morse(int c) { return morse_table[c & 0x7F]; }
95 // translate text input to timed, sub-morse-element
96 // audio segment specs; parcel the segments out
97 // one at a time to the sound player
100 BOOLEAN b = TRUE; // we're coming from silence
104 // keep reading 1 char at a time
105 while ((c = getchar()) != EOF) {
111 while ((c = getchar()) != EOF) {
112 if (c == ESC_R) break;
114 if (++i >= (MAX_ESC - 1)) break;
116 if (c == EOF) goto finish;
122 // is char mapped to morse?
123 if (m = get_morse(c)) {
126 // for each element in morse string
127 // (dit/dah, doesn't matter)
129 // first segment is ramp up...
131 morsel.type = ME_RAMP, morsel.size = risesize;
132 morsel.curr = 0.0, morsel.incr = riseincr;
135 // ...then steady state...
136 // (choose dit/dah here)
138 morsel.type = ME_STDY;
139 morsel.size = e == '.' ? ditstdysize : dahstdysize;
142 // ...then ramp down...
144 morsel.type = ME_RAMP, morsel.size = fallsize;
145 morsel.curr = 1.0, morsel.incr = fallincr;
148 // ...finally, post-element pause
150 morsel.type = ME_ZERO;
151 morsel.size = ditspacesize;
155 // post-character pause
157 morsel.type = ME_ZERO;
158 // (we already emitted a dit-sized space)
159 morsel.size = charspacesize - ditspacesize;
162 // wherever we go next, it won't have been from silence
166 // anything else treated as interword space,
167 // which has only one segment (silence)
169 morsel.type = ME_ZERO;
170 // was previous output also interword space?
172 // yes, use full duration
173 morsel.size = wordspacesize;
175 // no, part of duration already played
176 morsel.size = wordspacesize - charspacesize;
183 // indicate EOF on input
185 morsel.type = ME_EOF;
193 double ofreq, scale, phase = 0.0;
196 // keep looking for sub-element segments, one at a time
199 // pause for next sub-element segment
204 if (morsel.type == ME_EOF) break;
206 // requires playing some tone?
207 if (morsel.type != ME_ZERO) {
208 // yes, reset params and
209 // set up CORDIC tone generation
210 ofreq = freq * 2.0 * M_PI / SAMP_RATE;
211 scale = pow(10.0, gain / 20.0);
212 if (phase > HUGE_PHASE) phase -= HUGE_PHASE;
213 z = Cmplx(cos(phase), sin(phase));
214 delta_z = Cmplx(cos(ofreq), sin(ofreq));
217 // play out this segment
218 for (i = 0; i < morsel.size; i++) {
221 if (morsel.type == ME_ZERO) zout[k] = cxzero;
225 z = Cmul(z, delta_z);
227 // is this a ramping segment?
228 if (morsel.type == ME_RAMP) {
229 morsel.curr += morsel.incr;
230 zout[k] = Cscl(z, scale * sin(morsel.curr * M_PI / 2.0));
232 zout[k] = Cscl(z, scale);
235 // have we played enough to fill a jack buffer?
237 // yes, send to output
239 // wait until some audio has been drained
242 if (morsel.type != ME_ZERO) {
244 if (phase > HUGE_PHASE) phase -= HUGE_PHASE;
245 z = Cmplx(cos(phase), sin(phase));
246 delta_z = Cmplx(cos(ofreq), sin(ofreq));
252 // anything left unsent?
253 if (k > 0) send_sound(zout, k);
258 //------------------------------------------------------------------------
261 jack_ringbuffer_clear(jack_ringbuffer_t *ring, int nbytes) {
264 for (i = 0; i < nbytes; i++)
265 jack_ringbuffer_write(ring, &zero, 1);
269 jack_ringbuffer_restart(jack_ringbuffer_t *ring, int nbytes) {
270 jack_ringbuffer_reset(ring);
271 jack_ringbuffer_clear(ring, nbytes);
275 send_sound(COMPLEX *buff, int len) {
276 if (jack_ringbuffer_write_space(lring) < len * sizeof(float)) {
277 write(2, "overrun\n", 8);
278 jack_ringbuffer_restart(lring, size * sizeof(float));
279 jack_ringbuffer_restart(rring, size * sizeof(float));
282 for (i = 0; i < len; i++) {
283 float l = buff[i].re, r = buff[i].im;
284 jack_ringbuffer_write(lring, (char *) &l, sizeof(float));
285 jack_ringbuffer_write(rring, (char *) &r, sizeof(float));
291 jack_xrun(void *arg) {
292 char *str = "xrun!\n";
293 write(2, str, strlen(str));
297 jack_shutdown(void *arg) {}
300 jack_callback(jack_nframes_t nframes, void *arg) {
302 int nwant = nframes * sizeof(float),
303 nhave = jack_ringbuffer_read_space(lring);
305 lp = jack_port_get_buffer(lport, nframes);
306 rp = jack_port_get_buffer(rport, nframes);
307 if (nhave >= nwant) {
308 jack_ringbuffer_read(lring, lp, nwant);
309 jack_ringbuffer_read(rring, rp, nwant);
312 memset(lp, 0, nwant);
313 memset(rp, 0, nwant);
320 morsel.rise = morsel.fall = ramp;
321 morsel.rate = SAMP_RATE;
323 ditspacesize = SAMP_RATE * 1.2 / morsel.wpm + 0.5;
324 dahspacesize = 3 * ditspacesize;
325 charspacesize = dahspacesize;
326 wordspacesize = 7 * ditspacesize;
328 risesize = SAMP_RATE * morsel.rise / 1e3 + 0.5;
330 riseincr = 1.0 / (risesize - 1);
334 fallsize = SAMP_RATE * morsel.fall / 1e3 + 0.5;
336 fallincr = -1.0 / (fallsize - 1);
340 ditstdysize = ditspacesize - risesize - fallsize;
341 dahstdysize = dahspacesize - risesize - fallsize;
345 main(int argc, char **argv) {
348 for (i = 1; i < argc; i++)
349 if (argv[i][0] == '-')
350 switch (argv[i][1]) {
352 freq = atof(argv[++i]);
355 wpm = atof(argv[++i]);
358 ramp = atof(argv[++i]);
361 fprintf(stderr, "keyd [-w wpm] [-f freq] [-r ramp_ms] [infile]\n");
367 if (!freopen(argv[i], "r", stdin))
368 perror(argv[i]), exit(1);
372 //------------------------------------------------------------
376 //------------------------------------------------------------
378 if (!(client = jack_client_new("keyb")))
379 fprintf(stderr, "can't make client -- jack not running?\n"), exit(1);
380 jack_set_process_callback(client, (void *) jack_callback, 0);
381 jack_on_shutdown(client, (void *) jack_shutdown, 0);
382 jack_set_xrun_callback(client, (void *) jack_xrun, 0);
383 size = jack_get_buffer_size(client);
385 lport = jack_port_register(client,
387 JACK_DEFAULT_AUDIO_TYPE,
390 rport = jack_port_register(client,
392 JACK_DEFAULT_AUDIO_TYPE,
395 lring = jack_ringbuffer_create(RING_SIZE);
396 rring = jack_ringbuffer_create(RING_SIZE);
397 jack_ringbuffer_clear(lring, size * sizeof(float));
398 jack_ringbuffer_clear(rring, size * sizeof(float));
400 //------------------------------------------------------------
402 zout = newvec_COMPLEX(size, "keyb sample buffer");
404 //------------------------------------------------------------
406 sem_init(&ready, 0, 0);
407 sem_init(&reader, 0, 0);
408 sem_init(&writer, 0, 0);
409 pthread_create(&input, 0, (void *) reader_thread, 0);
410 pthread_create(&play, 0, (void *) sound_thread, 0);
412 //------------------------------------------------------------
414 jack_activate(client);
417 if (!(ports = jack_get_ports(client, 0, 0, JackPortIsPhysical | JackPortIsInput))) {
418 fprintf(stderr, "can't find any physical playback ports\n");
421 if (jack_connect(client, jack_port_name(lport), ports[0])) {
422 fprintf(stderr, "can't connect left output\n");
425 if (jack_connect(client, jack_port_name(rport), ports[1])) {
426 fprintf(stderr, "can't connect right output\n");
432 pthread_join(input, 0);
433 pthread_join(play, 0);
434 jack_client_close(client);
436 //------------------------------------------------------------
438 delvec_COMPLEX(zout);
440 //------------------------------------------------------------
442 jack_ringbuffer_free(lring);
443 jack_ringbuffer_free(rring);
445 sem_destroy(&reader);
446 sem_destroy(&writer);
448 //------------------------------------------------------------
453 char *morse_table[128] = {
454 /* 000 NUL */ 0, /* 001 SOH */ 0, /* 002 STX */ 0, /* 003 ETX */ 0,
455 /* 004 EOT */ 0, /* 005 ENQ */ 0, /* 006 ACK */ 0, /* 007 BEL */ 0,
456 /* 008 BS */ 0, /* 009 HT */ 0, /* 010 LF */ 0, /* 011 VT */ 0,
457 /* 012 FF */ 0, /* 013 CR */ 0, /* 014 SO */ 0, /* 015 SI */ 0,
458 /* 016 DLE */ 0, /* 017 DC1 */ 0, /* 018 DC2 */ 0, /* 019 DC3 */ 0,
459 /* 020 DC4 */ 0, /* 021 NAK */ 0, /* 022 SYN */ 0, /* 023 ETB */ 0,
460 /* 024 CAN */ 0, /* 025 EM */ 0, /* 026 SUB */ 0, /* 027 ESC */ 0,
461 /* 028 FS */ 0, /* 029 GS */ 0, /* 030 RS */ 0, /* 031 US */ 0,
463 /* 033 ! */ "...-.", // [SN]
467 /* 037 % */ ".-...", // [AS]
470 /* 040 ( */ "-.--.", // [KN]
472 /* 042 * */ "...-.-", // [SK]
473 /* 043 + */ ".-.-.", // [AR]
474 /* 044 , */ "--..--",
475 /* 045 - */ "-....-",
476 /* 046 . */ ".-.-.-",
491 /* 061 = */ "-...-", // [BT]
493 /* 063 ? */ "..__..", // [IMI]
494 /* 064 @ */ ".--.-.",
561 inlinecmd(char *buf, int len) {
562 if (!buf || len < 1) return;
563 if (!strncmp(buf, "wpm", 3)) {
566 } else if (!strncmp(buf, "ramp", 4)) {
567 ramp = atof(buf + 4);
569 } else if (!strncmp(buf, "freq", 4))
570 freq = atof(buf + 4);
571 else if (!strncmp(buf, "gain", 4))
572 gain = atof(buf + 4);