1 // ----------------------------------------------------------------------------
2 // view_cw.cxx
3 //
4 // (c) 2014 Mauri Niininen, AG1LE
5 // (c) 2017 Dave Freese, W1HKJ
6 //
7 // This file is part of fldigi.
8 //
9 // Fldigi is free software: you can redistribute it and/or modify
10 // it under the terms of the GNU General Public License as published by
11 // the Free Software Foundation, either version 3 of the License, or
12 // (at your option) any later version.
13 //
14 // Fldigi is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 // GNU General Public License for more details.
18 //
19 // You should have received a copy of the GNU General Public License
20 // along with fldigi. If not, see <http://www.gnu.org/licenses/>.
21 // ----------------------------------------------------------------------------
22
23 // viewpsk is a multi channel psk decoder which allows the parallel processing
24 // of the complete audio spectrum from 400 to 1150 Hz in equal 25 Hz
25 // channels. Each channel is separately decoded and the decoded characters
26 // passed to the user interface routines for presentation. The number of
27 // channels can be up to and including 30.
28
29 #include <config.h>
30
31 #include <stdlib.h>
32 #include <stdio.h>
33 #include <iostream>
34
35 #include "cw.h"
36 #include "view_cw.h"
37
38 #include "pskeval.h"
39 #include "pskcoeff.h"
40 #include "pskvaricode.h"
41 #include "misc.h"
42 #include "configuration.h"
43 #include "Viewer.h"
44 #include "qrunner.h"
45 #include "status.h"
46 #include "waterfall.h"
47
48 extern waterfall *wf;
49
50 #define CH_SPACING 50
51 #define VCW_FFT_SIZE 2048 // must be a factor of 2
52
53 enum {READY, NOT_READY};
54
55 cMorse *CW_CHANNEL::morse = 0;
56
CW_CHANNEL()57 CW_CHANNEL::CW_CHANNEL() {
58 bitfilter.setLength(10);
59 trackingfilter.setLength(16);
60
61 if (!morse) morse = new cMorse;
62
63 VCW_filter = new fftfilt ((CH_SPACING * 0.4) / VCW_SAMPLERATE, VCW_FFT_SIZE);
64
65 smpl_ctr = dec_ctr = 0;
66 phi1 = phi2 = 0;
67
68 }
69
~CW_CHANNEL()70 CW_CHANNEL::~CW_CHANNEL() {
71 if (morse) {
72 delete morse;
73 morse = 0;
74 }
75 delete VCW_filter;
76 }
77
init(int _ch,double _freq)78 void CW_CHANNEL::init(int _ch, double _freq)
79 {
80 ch_freq = _freq;
81 ch = _ch;
82 phase = 0.0;
83 phase_increment = TWOPI * ch_freq / VCW_SAMPLERATE;
84 agc_peak = 0.0;
85 sig_avg = 0.5;
86 timeout = 0;
87 smpl_ctr = 0;
88 dec_ctr = 0;
89 space_sent = true;
90 last_element = 0;
91 curr_element = 0;
92 two_dots = 2 * VKWPM / progdefaults.CWspeed;
93
94 sync_parameters();
95 }
96
reset()97 void CW_CHANNEL::reset()
98 {
99 space_sent = true;
100 last_element = 0;
101 curr_element = 0;
102 decode_str.clear();
103 }
104
update_tracking(int dur_1,int dur_2)105 void CW_CHANNEL::update_tracking(int dur_1, int dur_2)
106 {
107 static int min_dot = (VKWPM / 60) / 2;
108 static int max_dash = 6 * VKWPM / 10;
109 if ((dur_1 > dur_2) && (dur_1 > 4 * dur_2)) return;
110 if ((dur_2 > dur_1) && (dur_2 > 4 * dur_1)) return;
111 if (dur_1 < min_dot || dur_2 < min_dot) return;
112 if (dur_2 > max_dash || dur_2 > max_dash) return;
113
114 two_dots = trackingfilter.run((dur_1 + dur_2) / 2);
115 }
116
sample_count(unsigned int earlier,unsigned int later)117 inline int CW_CHANNEL::sample_count(unsigned int earlier, unsigned int later)
118 {
119 return (earlier >= later) ? 0 : (later - earlier);
120 }
121
sync_parameters()122 void CW_CHANNEL::sync_parameters()
123 {
124 trackingfilter.reset();
125 }
126
127 enum {KEYDOWN, KEYUP, POST_TONE, QUERY };
128
decode_state(int cw_state)129 int CW_CHANNEL::decode_state(int cw_state)
130 {
131 switch (cw_state) {
132 case KEYDOWN:
133 {
134 if (cw_receive_state == KEYDOWN)
135 return NOT_READY;
136 // first tone in idle state reset audio sample counter
137 if (cw_receive_state == KEYUP) {
138 smpl_ctr = 0;
139 rx_rep_buf.clear();
140 }
141 // save the timestamp
142 cw_rr_start_timestamp = smpl_ctr;
143 // Set state to indicate we are inside a tone.
144 cw_receive_state = KEYDOWN;
145 return NOT_READY;
146 break;
147 }
148 case KEYUP:
149 {
150 // The receive state is expected to be inside a tone.
151 if (cw_receive_state != KEYDOWN)
152 return NOT_READY;
153 // Save the current timestamp
154 curr_element = sample_count( cw_rr_start_timestamp, smpl_ctr );
155 cw_rr_end_timestamp = smpl_ctr;
156
157 // If the tone length is shorter than any noise cancelling
158 // threshold that has been set, then ignore this tone.
159 if (curr_element < two_dots / 10) {
160 cw_receive_state = KEYUP;
161 return NOT_READY;
162 }
163
164 // Set up to track speed on dot-dash or dash-dot pairs for this test to
165 // work, we need a dot dash pair or a dash dot pair to validate timing
166 // from and force the speed tracking in the right direction.
167 if (last_element > 0) update_tracking( last_element, curr_element );
168
169 last_element = curr_element;
170 // a dot is anything shorter than 2 dot times
171 if (curr_element <= two_dots) {
172 rx_rep_buf += '.';
173 } else {
174 rx_rep_buf += '-';
175 }
176 // We just added a representation to the receive buffer.
177 // If it's full, then reset everything as it is probably noise
178 if (rx_rep_buf.length() > MAX_MORSE_ELEMENTS) {
179 cw_receive_state = KEYUP;
180 rx_rep_buf.clear();
181 smpl_ctr = 0; // reset audio sample counter
182 return NOT_READY;
183 }
184 // All is well. Move to the more normal after-tone state.
185 cw_receive_state = POST_TONE;
186 return NOT_READY;
187 break;
188 }
189 case QUERY:
190 {
191 // this should be called quite often (faster than inter-character gap) It looks after timing
192 // key up intervals and determining when a character, a word space, or an error char '*' should be returned.
193 // READY is returned when there is a printable character. Nothing to do if we are in a tone
194 if (cw_receive_state == KEYDOWN)
195 return NOT_READY;
196 // in this call we expect a pointer to a char to be valid
197 if (cw_state == KEYDOWN || cw_state == KEYUP) {
198 // else we had no place to put character...
199 cw_receive_state = KEYUP;
200 rx_rep_buf.clear();
201 // reset decoding pointer
202 return NOT_READY;
203 }
204 // compute length of silence so far
205 // sync_parameters();
206 curr_element = sample_count( cw_rr_end_timestamp, smpl_ctr );
207 // SHORT time since keyup... nothing to do yet
208 if (curr_element < two_dots)
209 return NOT_READY;
210 // MEDIUM time since keyup... check for character space
211 // one shot through this code via receive state logic
212 if ((curr_element > two_dots)
213 && (curr_element < 2 * two_dots) ) {
214 // && cw_receive_state == POST_TONE) {
215
216 std::string code = morse->rx_lookup(rx_rep_buf);
217 if (code.empty()) {
218 decode_str.clear();
219 cw_receive_state = KEYUP;
220 rx_rep_buf.clear();
221 space_sent = false;
222 return NOT_READY ;
223 }
224
225 decode_str = code;
226 cw_receive_state = KEYUP;
227 rx_rep_buf.clear();
228 space_sent = false;
229 return READY;
230 }
231 // LONG time since keyup... check for a word space
232 if ((curr_element > 2 * two_dots) && !space_sent) {
233 decode_str = " ";
234 space_sent = true;
235 return READY;
236 }
237 break;
238 }
239 }
240 return NOT_READY;
241 }
242
detect_tone()243 void CW_CHANNEL::detect_tone()
244 {
245 norm_sig = 0;
246 CWupper = 0;
247 CWlower = 0;
248
249 sig_avg = decayavg(sig_avg, value, 1000);
250
251 if (value > sig_avg) {
252 if (value > agc_peak)
253 agc_peak = decayavg(agc_peak, value, 100);
254 else
255 agc_peak = decayavg(agc_peak, value, 1000);
256 }
257
258 if (!agc_peak) return;
259
260 value /= agc_peak;
261 norm_sig = sig_avg / agc_peak;
262
263 // metric = 0.8 * metric;
264 // metric += 0.2 * clamp(20*log10(sig_avg / noise_floor) , 0, 40);
265 metric = clamp(20*log10(sig_avg / noise_floor) , 0, 40);
266
267 CWupper = norm_sig + 0.1;
268 CWlower = norm_sig - 0.1;
269
270 if (metric > progStatus.VIEWER_cwsquelch ) {
271
272 if ((value >= CWupper) && (cw_receive_state != KEYDOWN))
273 decode_state(KEYDOWN);
274 if ((value < CWlower) && (cw_receive_state == KEYDOWN))
275 decode_state(KEYUP);
276 if ((decode_state(QUERY) == READY) ) {
277 for (size_t n = 0; n < decode_str.length(); n++)
278 REQ(&viewaddchr,
279 ch,
280 (int)ch_freq,
281 decode_str[n], (int)MODE_CW);
282 timeout = progdefaults.VIEWERtimeout * VPSKSAMPLERATE / WF_BLOCKSIZE;
283 decode_str.clear();
284 rx_rep_buf.clear();
285 }
286 }
287 }
288
rx_process(const double * buf,int len)289 void CW_CHANNEL::rx_process(const double *buf, int len)
290 {
291 cmplx z, *zp;
292 int n = 0;
293
294 while (len-- > 0) {
295
296 z = cmplx ( *buf * cos(phase), *buf * sin(phase) );
297 buf++;
298
299 phase += phase_increment;
300 if (phase > TWOPI) phase -= TWOPI;
301
302 n = VCW_filter->run(z, &zp);
303
304 if (n) {
305 for (int i = 0; i < n; i++) {
306 if (++dec_ctr < VCW_DEC_RATIO) continue;
307 dec_ctr = 0;
308 smpl_ctr++;
309 value = bitfilter.run(abs(zp[i]));
310 detect_tone();
311 }
312 }
313 }
314 }
315
316 //======================================================================
317 // view_cw
318 //======================================================================
319
view_cw()320 view_cw::view_cw()
321 {
322 for (int i = 0; i < VCW_MAXCH; i++) channel[i].reset();
323
324 viewmode = MODE_CW;
325 }
326
~view_cw()327 view_cw::~view_cw()
328 {
329 }
330
init()331 void view_cw::init()
332 {
333 nchannels = progdefaults.VIEWERchannels;
334
335 for (int i = 0; i < VCW_MAXCH; i++) {
336 channel[i].init(i, 400.0 + CH_SPACING * i);
337 }
338 for (int i = 0; i < nchannels; i++)
339 REQ(&viewclearchannel, i);
340 }
341
restart()342 void view_cw::restart()
343 {
344 for (int i = 0; i < VCW_MAXCH; i++) {
345 channel[i].space_sent = true;
346 channel[i].last_element = 0;
347 channel[i].curr_element = 0;
348 channel[i].two_dots = 2 * VKWPM / progdefaults.CWspeed;
349 }
350 init();
351 }
352
clearch(int n)353 void view_cw::clearch(int n)
354 {
355 REQ( &viewclearchannel, n);
356 REQ( &viewaddchr, n, (int)NULLFREQ, 0, viewmode);
357 }
358
clear()359 void view_cw::clear()
360 {
361 for (int i = 0; i < VCW_MAXCH; i++) clearch(i);
362 }
363
rx_process(const double * buf,int len)364 int view_cw::rx_process(const double *buf, int len)
365 {
366 double nf = 1e8;
367 if (nchannels != progdefaults.VIEWERchannels) init();
368
369 for (int n = 0; n < nchannels; n++) {
370 channel[n].rx_process(buf, len);
371
372 if (nf > channel[n].avg_signal() &&
373 channel[n].avg_signal() > 1e-3) nf = channel[n].avg_signal();
374
375 if (channel[n].timeout)
376 if (! --channel[n].timeout)
377 clearch(n);
378 }
379 if (nf <= 1e-3) nf = 1e-3;
380 for (int n = 0; n < nchannels; n++) channel[n].set_noise_floor(nf);
381
382 return 0;
383 }
384
get_freq(int n)385 int view_cw::get_freq(int n)
386 {
387 return (int)channel[n].ch_freq;
388 }
389