]> git.rkrishnan.org Git - dttsp.git/blob - jDttSP/sdr.c
d500676afe07ad0f9e29560ad1121c5849e5470a
[dttsp.git] / jDttSP / sdr.c
1 /* sdr.c
2
3 This file is part of a program that implements a Software-Defined Radio.
4
5 Copyright (C) 2004 by Frank Brickle, AB2KT and Bob McGwier, N4HY
6
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.
11
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.
16
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
20
21 The authors can be reached by email at
22
23 ab2kt@arrl.net
24 or
25 rwmcgwier@comcast.net
26
27 or by paper mail at
28
29 The DTTS Microwave Society
30 6 Kathleen Place
31 Bridgewater, NJ 08807
32 */
33
34 #include <common.h>
35
36 //========================================================================
37 /* initialization and termination */
38
39 /* global and general info,
40    not specifically attached to
41    tx, rx, or scheduling */
42
43 PRIVATE void
44 setup_all(void) {
45
46   uni.samplerate = loc.def.rate;
47   uni.buflen = loc.def.size;
48   uni.mode.sdr = loc.def.mode;
49   uni.mode.trx = RX;
50
51   if (uni.meter.flag) {
52     uni.meter.chan.path = loc.path.meter;
53     uni.meter.chan.size = loc.mult.ring * sizeof(REAL);
54     uni.meter.val = -200.0;
55     uni.meter.chan.c = openChan(uni.meter.chan.path, uni.meter.chan.size);
56   }
57
58   uni.wisdom.path = loc.path.wisdom;
59   uni.wisdom.bits = FFTW_OUT_OF_PLACE | FFTW_ESTIMATE;
60   {
61     FILE *f = fopen(uni.wisdom.path, "r");
62     if (f) {
63 #define WBUFLEN 2048
64 #define WSTRLEN 64      
65       char *line = alloca(WBUFLEN);
66       fgets(line, WBUFLEN, f);
67       if ((strlen(line) > WSTRLEN) &&
68           (fftw_import_wisdom_from_string(line) != FFTW_FAILURE))
69         uni.wisdom.bits = FFTW_OUT_OF_PLACE | FFTW_MEASURE | FFTW_USE_WISDOM;
70 #undef WSTRLEN
71 #undef WBUFLEN      
72       fclose(f);
73     }
74   }
75
76   uni.multirx.lis = 0;
77   uni.multirx.nrx = loc.def.nrx;
78   uni.tick = 0;
79 }
80
81 /* purely rx */
82
83 PRIVATE void
84 setup_rx(int k) {
85
86   /* conditioning */
87   rx[k].iqfix = newCorrectIQ(0.0, 1.0);
88   rx[k].filt.coef = newFIR_Bandpass_COMPLEX(-4800.0,
89                                             4800.0,
90                                             uni.samplerate,
91                                             uni.buflen + 1);
92   rx[k].filt.ovsv = newFiltOvSv(FIRcoef(rx[k].filt.coef),
93                                 FIRsize(rx[k].filt.coef),
94                                 uni.wisdom.bits);
95   normalize_vec_COMPLEX(rx[k].filt.ovsv->zfvec,
96                         rx[k].filt.ovsv->fftlen);
97
98   // hack for EQ
99   rx[k].filt.save = newvec_COMPLEX(rx[k].filt.ovsv->fftlen, "RX filter cache");
100   memcpy((char *) rx[k].filt.save,
101          (char *) rx[k].filt.ovsv->zfvec,
102          rx[k].filt.ovsv->fftlen * sizeof(COMPLEX));
103
104   /* buffers */
105   /* note we overload the internal filter buffers
106      we just created */
107   rx[k].buf.i = newCXB(FiltOvSv_fetchsize(rx[k].filt.ovsv),
108                        FiltOvSv_fetchpoint(rx[k].filt.ovsv),
109                        "init rx.buf.i");
110   rx[k].buf.o = newCXB(FiltOvSv_storesize(rx[k].filt.ovsv),
111                        FiltOvSv_storepoint(rx[k].filt.ovsv),
112                        "init rx[k].buf.o");
113   
114   /* conversion */
115   rx[k].osc.freq = -11025.0;
116   rx[k].osc.phase = 0.0;
117   rx[k].osc.gen = newOSC(uni.buflen,
118                          ComplexTone,
119                          rx[k].osc.freq,
120                          rx[k].osc.phase,
121                          uni.samplerate,
122                          "SDR RX Oscillator");
123
124   rx[k].agc.gen = newDigitalAgc(agcMED, // Mode
125                              7,         // Hang
126                              7,         // Size
127                              48,        // Ramp
128                              3,         // Over
129                              3,         // Rcov
130                              CXBsize(rx[k].buf.o),      // BufSize
131                              100.0,     // MaxGain
132                              0.707,     // Limit
133                              1.0,       // CurGain
134                              CXBbase(rx[k].buf.o));
135   rx[k].agc.flag = TRUE;
136
137   /* demods */
138   rx[k].am.gen = newAMD(48000.0,        // REAL samprate
139                         0.0,    // REAL f_initial
140                         -500.0, // REAL f_lobound,
141                         500.0,  // REAL f_hibound,
142                         400.0,  // REAL f_bandwid,
143                         CXBsize(rx[k].buf.o),   // int size,
144                         CXBbase(rx[k].buf.o),   // COMPLEX *ivec,
145                         CXBbase(rx[k].buf.o),   // COMPLEX *ovec,
146                         AMdet,  // AM Mode AMdet == rectifier,
147                                 //         SAMdet == synchronous detector
148                         "AM detector blew");    // char *tag
149   rx[k].fm.gen = newFMD(48000,  // REAL samprate
150                         0.0,    // REAL f_initial
151                         -6000.0,        // REAL f_lobound
152                         6000.0, // REAL f_hibound
153                         10000.0,        // REAL f_bandwid
154                         CXBsize(rx[k].buf.o),   // int size
155                         CXBbase(rx[k].buf.o),   // COMPLEX *ivec
156                         CXBbase(rx[k].buf.o),   // COMPLEX *ovec
157                         "New FM Demod structure");      // char *error message;
158
159   /* noise reduction */
160   rx[k].anf.gen = new_lmsr(rx[k].buf.o, // CXB signal,
161                            64,          // int delay,
162                            0.01,                // REAL adaptation_rate,
163                            0.00001,     // REAL leakage,
164                            45,          // int adaptive_filter_size,
165                            LMADF_INTERFERENCE);
166   rx[k].anf.flag = FALSE;
167   rx[k].anr.gen = new_lmsr(rx[k].buf.o, // CXB signal,
168                            64,          // int delay,
169                            0.01,                // REAL adaptation_rate,
170                            0.00001,     // REAL leakage,
171                            45,          // int adaptive_filter_size,
172                            LMADF_NOISE);
173   rx[k].anr.flag = FALSE;
174
175   rx[k].nb.thresh = 3.3;
176   rx[k].nb.gen = new_noiseblanker(rx[k].buf.i, rx[k].nb.thresh);
177   rx[k].nb.flag = FALSE;
178
179   rx[k].nb_sdrom.thresh = 2.5;
180   rx[k].nb_sdrom.gen = new_noiseblanker(rx[k].buf.i, rx[k].nb_sdrom.thresh);
181   rx[k].nb_sdrom.flag = FALSE;
182
183   rx[k].spot.gen = newSpotToneGen(-12.0,        // gain
184                                   700.0,        // freq
185                                   5.0,  // ms rise
186                                   5.0,  // ms fall
187                                   uni.buflen,
188                                   uni.samplerate);
189
190   rx[k].scl.pre.val = 1.0;
191   rx[k].scl.pre.flag = FALSE;
192   rx[k].scl.post.val = 1.0;
193   rx[k].scl.post.flag = FALSE;
194
195   memset((char *) &rx[k].squelch, 0, sizeof(rx[k].squelch));
196   rx[k].squelch.thresh = -30.0;
197   rx[k].squelch.power = 0.0;
198   rx[k].squelch.flag = rx[k].squelch.running = rx[k].squelch.set = FALSE;
199   rx[k].squelch.num = (int) (0.0395 * uni.samplerate + 0.5);
200
201   rx[k].mode = uni.mode.sdr;
202   rx[k].bin.flag = FALSE;
203
204   {
205     REAL pos = 0.5, // 0 <= pos <= 1, left->right
206          theta = (1.0 - pos) * M_PI / 2.0;
207     rx[k].azim = Cmplx(cos(theta), sin(theta));
208   }
209
210   rx[k].tick = 0;
211 }
212
213 /* purely tx */
214
215 PRIVATE void
216 setup_tx(void) {
217
218   /* conditioning */
219   tx.iqfix = newCorrectIQ(0.0, 1.0);
220   tx.filt.coef = newFIR_Bandpass_COMPLEX(300.0,
221                                          3000.0,
222                                          uni.samplerate,
223                                          uni.buflen + 1);
224   tx.filt.ovsv = newFiltOvSv(FIRcoef(tx.filt.coef),
225                              FIRsize(tx.filt.coef),
226                              uni.wisdom.bits);
227   normalize_vec_COMPLEX(tx.filt.ovsv->zfvec,
228                         tx.filt.ovsv->fftlen);
229
230   // hack for EQ
231   tx.filt.save = newvec_COMPLEX(tx.filt.ovsv->fftlen, "TX filter cache");
232   memcpy((char *) tx.filt.save,
233          (char *) tx.filt.ovsv->zfvec,
234          tx.filt.ovsv->fftlen * sizeof(COMPLEX));
235
236   /* buffers */
237   tx.buf.i = newCXB(FiltOvSv_fetchsize(tx.filt.ovsv),
238                     FiltOvSv_fetchpoint(tx.filt.ovsv),
239                     "init tx.buf.i");
240   tx.buf.o = newCXB(FiltOvSv_storesize(tx.filt.ovsv),
241                     FiltOvSv_storepoint(tx.filt.ovsv),
242                     "init tx.buf.o");
243   
244   /* conversion */
245   tx.osc.freq = 0.0;
246   tx.osc.phase = 0.0;
247   tx.osc.gen = newOSC(uni.buflen,
248                       ComplexTone,
249                       tx.osc.freq,
250                       tx.osc.phase,
251                       uni.samplerate,
252                       "SDR TX Oscillator");
253
254   tx.agc.gen = newDigitalAgc(agcFAST,   // Mode
255                              3,         // Hang
256                              3,         // Size
257                              3,         // Over
258                              3,         // Rcov
259                              48,        // Ramp
260                              CXBsize(tx.buf.o), // BufSize
261                              1.0,       // MaxGain
262                              0.900,     // Limit
263                              1.0,       // CurGain
264                              CXBbase(tx.buf.o));
265   tx.agc.flag = TRUE;
266
267   tx.spr.gen = newSpeechProc(0.4, 10.0, CXBbase(tx.buf.i), CXBsize(tx.buf.i));
268   tx.spr.flag = FALSE;
269
270   tx.scl.dc = cxzero;
271   tx.scl.pre.val = 1.0;
272   tx.scl.pre.flag = FALSE;
273   tx.scl.post.val = 1.0;
274   tx.scl.post.flag = FALSE;
275
276   tx.mode = uni.mode.sdr;
277
278   tx.tick = 0;
279   /* not much else to do for TX */
280 }
281
282 /* how the outside world sees it */
283
284 void
285 setup_workspace(void) {
286   int k;
287
288   setup_all();
289
290   for (k = 0; k < uni.multirx.nrx; k++) {
291     setup_rx(k);
292     uni.multirx.act[k] = FALSE;
293   }
294   uni.multirx.act[0] = TRUE;
295   uni.multirx.nac = 1;
296
297   setup_tx();
298 }
299
300 void
301 destroy_workspace(void) {
302   int k;
303
304   /* TX */
305   delSpeechProc(tx.spr.gen);
306   delDigitalAgc(tx.agc.gen);
307   delOSC(tx.osc.gen);
308   delvec_COMPLEX(tx.filt.save);
309   delFiltOvSv(tx.filt.ovsv);
310   delFIR_Bandpass_COMPLEX(tx.filt.coef);
311   delCorrectIQ(tx.iqfix);
312   delCXB(tx.buf.o);
313   delCXB(tx.buf.i);
314
315   /* RX */
316   for (k = 0; k < uni.multirx.nrx; k++) {
317     delSpotToneGen(rx[k].spot.gen);
318     delDigitalAgc(rx[k].agc.gen);
319     del_nb(rx[k].nb_sdrom.gen);
320     del_nb(rx[k].nb.gen);
321     del_lmsr(rx[k].anf.gen);
322     del_lmsr(rx[k].anr.gen);
323     delAMD(rx[k].am.gen);
324     delFMD(rx[k].fm.gen);
325     delOSC(rx[k].osc.gen);
326     delvec_COMPLEX(rx[k].filt.save);
327     delFiltOvSv(rx[k].filt.ovsv);
328     delFIR_Bandpass_COMPLEX(rx[k].filt.coef);
329     delCorrectIQ(rx[k].iqfix);
330     delCXB(rx[k].buf.o);
331     delCXB(rx[k].buf.i);
332   }
333   
334   /* all */
335   if (uni.meter.flag)
336     closeChan(uni.meter.chan.c);
337 }
338
339 //////////////////////////////////////////////////////////////////////////
340 // execution
341 //////////////////////////////////////////////////////////////////////////
342
343 //========================================================================
344 // util
345
346 PRIVATE REAL
347 CXBnorm(CXB buff) {
348   int i;
349   double sum = 0.0;
350   for (i = 0; i < CXBhave(buff); i++)
351     sum += Csqrmag(CXBdata(buff, i));
352   return sqrt(sum);
353 }
354
355 //========================================================================
356 /* all */
357
358 /* tap off S-meter from some buf */
359
360 PRIVATE void
361 do_meter(COMPLEX *vec, int len) {
362   int i;
363   
364   uni.meter.val = 0;
365
366   switch (uni.meter.type) {
367   case AVG_SIGNAL_STRENGTH:
368     for (i = 0; i < len; i++)
369       uni.meter.val += Csqrmag(vec[i]);
370     uni.meter.val =
371       uni.meter.avgval = 0.9 * uni.meter.avgval + log10(uni.meter.val + 1e-20);
372     break;
373   case SIGNAL_STRENGTH:
374     for (i = 0; i < len; i++)
375       uni.meter.val += Csqrmag(vec[i]);
376     uni.meter.avgval = uni.meter.val = 10.0 * log10(uni.meter.val + 1e-20);
377     break;
378   case ADC_REAL:
379     for(i = 0; i < len; i++)
380       uni.meter.val = max(fabs(vec[i].re), uni.meter.val);
381     uni.meter.val = 20.0 * log10(uni.meter.val + 1e-10);
382     break;
383   case ADC_IMAG:
384     for(i = 0; i < len; i++)
385       uni.meter.val = max(fabs(vec[i].im), uni.meter.val);
386     uni.meter.val = 20.0 * log10(uni.meter.val + 1e-10);
387     break;
388   default:
389     break;
390   }
391
392   putChan_nowait(uni.meter.chan.c,
393                  (char *) &uni.meter.val,
394                  sizeof(uni.meter.val));
395 }
396
397 //========================================================================
398 /* RX processing */ 
399
400 PRIVATE BOOLEAN
401 should_do_rx_squelch(int k) {
402   if (rx[k].squelch.flag) {
403     int i, n = CXBhave(rx[k].buf.o);
404     rx[k].squelch.power = 0.0;
405     for (i = 0; i < n; i++)
406       rx[k].squelch.power += Csqrmag(CXBdata(rx[k].buf.o, i));
407     return rx[k].squelch.thresh > 10.0 * log10(rx[k].squelch.power);
408   } else
409     return rx[k].squelch.set = FALSE;
410 }
411
412 // apply squelch
413 // slew into silence first time
414
415 PRIVATE void
416 do_squelch(int k) {
417   rx[k].squelch.set = TRUE;
418   if (!rx[k].squelch.running) {
419     int i, m = rx[k].squelch.num, n = CXBhave(rx[k].buf.o) - m;
420     for (i = 0; i < m; i++)
421       CXBdata(rx[k].buf.o, i) = Cscl(CXBdata(rx[k].buf.o, i), 1.0 - (REAL) i / m);
422     memset((void *) (CXBbase(rx[k].buf.o) + m), 0, n * sizeof(COMPLEX));
423     rx[k].squelch.running = TRUE;
424   } else
425     memset((void *) CXBbase(rx[k].buf.o), 0, CXBhave(rx[k].buf.o) * sizeof(COMPLEX));
426 }
427
428 // lift squelch
429 // slew out from silence to full scale
430
431 PRIVATE void
432 no_squelch(int k) {
433   if (rx[k].squelch.running) {
434     int i, m = rx[k].squelch.num;
435     for (i = 0; i < m; i++)
436       CXBdata(rx[k].buf.o, i) = Cscl(CXBdata(rx[k].buf.o, i), (REAL) i / m);
437     rx[k].squelch.running = FALSE;
438   }
439 }
440
441 /* pre-condition for (nearly) all RX modes */
442
443 PRIVATE void
444 do_rx_pre(int k) {
445   int i, n = min(CXBhave(rx[k].buf.i), uni.buflen);
446
447   //
448   // do shrinkage here?
449   //
450
451   if (rx[k].scl.pre.flag)
452     for (i = 0; i < n; i++)
453       CXBdata(rx[k].buf.i, i) = Cscl(CXBdata(rx[k].buf.i, i),
454                                      rx[k].scl.pre.val); 
455
456   if (rx[k].nb.flag) noiseblanker(rx[k].nb.gen);
457   if (rx[k].nb_sdrom.flag) SDROMnoiseblanker(rx[k].nb_sdrom.gen);
458
459   correctIQ(rx[k].buf.i, rx[k].iqfix);
460
461   /* 2nd IF conversion happens here */
462
463   if (rx[k].osc.gen->Frequency != 0.0) {
464     ComplexOSC(rx[k].osc.gen);
465     for (i = 0; i < n; i++)
466       CXBdata(rx[k].buf.i, i) = Cmul(CXBdata(rx[k].buf.i, i),
467                                      OSCCdata(rx[k].osc.gen, i));
468   } 
469
470   /* filtering, metering, squelch, & AGC */
471
472   if (rx[k].mode != SPEC) {
473
474     if (rx[k].tick == 0)
475       reset_OvSv(rx[k].filt.ovsv);
476     
477     filter_OvSv(rx[k].filt.ovsv);
478     CXBhave(rx[k].buf.o) = CXBhave(rx[k].buf.i);
479
480     if (uni.meter.flag)
481       do_meter(CXBbase(rx[k].buf.o), uni.buflen);
482
483     if (should_do_rx_squelch(k))
484       do_squelch(k);
485
486     else if (rx[k].agc.flag)
487       DigitalAgc(rx[k].agc.gen, rx[k].tick);
488
489   } else if (uni.meter.flag)
490     do_meter(CXBbase(rx[k].buf.o), uni.buflen);
491 }
492
493 PRIVATE void
494 do_rx_post(int k) {
495   int i, n = CXBhave(rx[k].buf.o);
496
497   if (!rx[k].squelch.set)  {
498     no_squelch(k);
499     // spotting tone
500     if (rx[k].spot.flag) {
501       // remember whether it's turned itself off during this pass
502       rx[k].spot.flag = SpotTone(rx[k].spot.gen);
503       for (i = 0; i < n; i++)
504         CXBdata(rx[k].buf.o, i) = Cadd(CXBdata(rx[k].buf.o, i),
505                                        CXBdata(rx[k].spot.gen->buf, i));
506     }
507   }
508
509   //
510   // mix in sidetone here?
511   //
512
513   // final scaling
514
515   if (rx[k].scl.post.flag)
516     for (i = 0; i < n; i++)
517       CXBdata(rx[k].buf.o, i) = Cscl(CXBdata(rx[k].buf.o, i),
518                                      rx[k].scl.post.val);
519
520   // not binaural?
521   // position in stereo field
522
523   if (!rx[k].bin.flag)
524     for (i = 0; i < n; i++)
525       CXBdata(rx[k].buf.o, i) = Cscl(rx[k].azim, CXBreal(rx[k].buf.o, i));
526
527 #if 0
528   if (!rx[k].bin.flag)
529     for (i = 0; i < n; i++)
530       CXBimag(rx[k].buf.o, i) = CXBreal(rx[k].buf.o, i);
531 #endif
532 }
533
534 /* demod processing */
535
536 PRIVATE void
537 do_rx_SBCW(int k) {
538   if (rx[k].anr.flag) lmsr_adapt(rx[k].anr.gen);
539   if (rx[k].anf.flag) lmsr_adapt(rx[k].anf.gen);
540 }
541
542 PRIVATE void
543 do_rx_AM(int k) { AMDemod(rx[k].am.gen); }
544
545 PRIVATE void
546 do_rx_FM(int k) { FMDemod(rx[k].fm.gen); }
547
548 PRIVATE void
549 do_rx_DRM(int k) {}
550
551 PRIVATE void
552 do_rx_SPEC(int k) {
553   memcpy(CXBbase(rx[k].buf.o),
554          CXBbase(rx[k].buf.i),
555          sizeof(COMPLEX) * CXBhave(rx[k].buf.i));
556   if (rx[k].agc.flag) DigitalAgc(rx[k].agc.gen, rx[k].tick);
557 }
558
559 PRIVATE void
560 do_rx_NIL(int k) {
561   int i, n = min(CXBhave(rx[k].buf.i), uni.buflen);
562   for (i = 0; i < n; i++) CXBdata(rx[k].buf.o, i) = cxzero;
563 }
564
565 /* overall dispatch for RX processing */
566
567 PRIVATE void
568 do_rx(int k) {
569   do_rx_pre(k);
570   switch (rx[k].mode) {
571   case USB:
572   case LSB:
573   case CWU:
574   case CWL:
575   case DSB:  do_rx_SBCW(k); break;
576   case AM:
577   case SAM:  do_rx_AM(k); break;
578   case FMN:  do_rx_FM(k);   break;
579   case DRM:  do_rx_DRM(k);  break;
580   case SPEC:
581     default: do_rx_SPEC(k); break;
582   }
583   do_rx_post(k);
584 }  
585
586 //==============================================================
587 /* TX processing */
588
589 /* pre-condition for (nearly) all TX modes */
590
591 PRIVATE void
592 do_tx_pre(void) {
593
594   if (tx.scl.pre.flag) {
595     int i, n = CXBhave(tx.buf.i);
596     for (i = 0; i < n; i++)
597       CXBdata(tx.buf.i, i) = Cmplx(CXBreal(tx.buf.i, i) * tx.scl.pre.val, 0.0);
598   }
599
600   //
601   // mix in CW tone here?
602   //
603
604   correctIQ(tx.buf.i, tx.iqfix);
605
606   if (tx.spr.flag) SpeechProcessor(tx.spr.gen);
607
608   if (tx.tick == 0) reset_OvSv(tx.filt.ovsv);
609   filter_OvSv(tx.filt.ovsv);
610 }
611
612 PRIVATE void
613 do_tx_post(void) {
614   CXBhave(tx.buf.o) = CXBhave(tx.buf.i);
615
616   if (tx.agc.flag) DigitalAgc(tx.agc.gen, tx.tick);
617   if (tx.scl.post.flag) {
618     int i, n = CXBhave(tx.buf.o);
619     for (i = 0; i < n; i++)
620       CXBdata(tx.buf.o, i) = Cscl(CXBdata(tx.buf.o, i), tx.scl.post.val);
621   }
622   if (uni.meter.flag) do_meter(CXBbase(tx.buf.o), uni.buflen);
623   if (tx.osc.gen->Frequency != 0.0) {
624     int i;
625     ComplexOSC(tx.osc.gen);
626     for (i = 0; i < CXBhave(tx.buf.o); i++)
627       CXBdata(tx.buf.o, i) = Cmul(CXBdata(tx.buf.o, i), OSCCdata(tx.osc.gen, i));
628   }
629 }
630
631 /* modulator processing */
632
633 PRIVATE void
634 do_tx_SBCW(void) {
635   int i, n = min(CXBhave(tx.buf.o), uni.buflen); 
636
637   if ((tx.norm = CXBnorm(tx.buf.o)) > 0.0)
638     for (i = 0; i < n; i++) {
639       tx.scl.dc = Cadd(Cscl(tx.scl.dc, 0.99),
640                        Cscl(CXBdata(tx.buf.o, i), -0.01));
641       CXBdata(tx.buf.o, i) = Cadd(CXBdata(tx.buf.o, i), tx.scl.dc);
642     }
643 }
644
645 PRIVATE void
646 do_tx_AM(void) {
647   int i, n = min(CXBhave(tx.buf.o), uni.buflen); 
648
649   if ((tx.norm = CXBnorm(tx.buf.o)) > 0.0)
650     for (i = 0; i < n; i++) { 
651       tx.scl.dc = Cadd(Cscl(tx.scl.dc, 0.999),
652                        Cscl(CXBdata(tx.buf.o, i), -0.001));
653       CXBreal(tx.buf.o, i) =
654         0.49995 + 0.49995 * (CXBreal(tx.buf.o, i) - tx.scl.dc.re);
655       CXBimag(tx.buf.o, i) = 0.0;
656     }
657 }
658
659 PRIVATE void
660 do_tx_FM(void) {
661   int i, n = min(CXBhave(tx.buf.o), uni.buflen);
662   if ((tx.norm = CXBnorm(tx.buf.o)) > 0.0)
663     for (i = 0; i < n; i++) {
664       tx.scl.dc = Cadd(Cscl(tx.scl.dc, 0.999),
665                        Cscl(CXBdata(tx.buf.o, i), 0.001));
666       tx.osc.phase += (CXBreal(tx.buf.o, i) - tx.scl.dc.re) * CvtMod2Freq;
667       if (tx.osc.phase >= TWOPI) tx.osc.phase -= TWOPI;
668       if (tx.osc.phase < 0.0) tx.osc.phase += TWOPI;
669       CXBdata(tx.buf.o, i) =
670         Cscl(Cmplx(cos(tx.osc.phase), sin(tx.osc.phase)), 0.99999);
671     }
672 }
673
674 PRIVATE void
675 do_tx_NIL(void) {
676   int i, n = min(CXBhave(tx.buf.i), uni.buflen);
677   for (i = 0; i < n; i++) CXBdata(tx.buf.o, i) = cxzero;
678 }
679
680 /* general TX processing dispatch */
681
682 PRIVATE void
683 do_tx(void) {
684   do_tx_pre();
685   switch (tx.mode) {
686   case USB:
687   case LSB:
688   case CWU:
689   case CWL:
690   case DSB:  do_tx_SBCW(); break;
691   case AM:
692   case SAM:  do_tx_AM();   break;
693   case FMN:  do_tx_FM();   break;
694   case DRM:
695   case SPEC:
696     default: do_tx_NIL(); break;
697   }
698   do_tx_post();
699 }
700
701 //========================================================================
702 /* overall buffer processing;
703    come here when there are buffers to work on */
704
705 void
706 process_samples(float *bufl, float *bufr, int n) {
707   int i, k;
708
709   switch (uni.mode.trx) {
710
711   case RX:
712
713     // make copies of the input for all receivers
714     for (k = 0; k < uni.multirx.nrx; k++)
715       if (uni.multirx.act[k]) {
716         for (i = 0; i < n; i++)
717           CXBimag(rx[k].buf.i, i) = bufl[i], CXBreal(rx[k].buf.i, i) = bufr[i];
718         CXBhave(rx[k].buf.i) = n;
719       }
720
721     // prepare buffers for mixing
722     memset((char *) bufl, 0, n * sizeof(float));
723     memset((char *) bufr, 0, n * sizeof(float));
724
725     // run all receivers
726     for (k = 0; k < uni.multirx.nrx; k++)
727       if (uni.multirx.act[k]) {
728         do_rx(k), rx[k].tick++;
729         // mix
730         for (i = 0; i < n; i++)
731           bufl[i] += CXBimag(rx[k].buf.o, i),
732           bufr[i] += CXBreal(rx[k].buf.o, i);
733         CXBhave(rx[k].buf.o) = n;
734       }
735
736     break;
737
738   case TX:
739     for (i = 0; i < n; i++)
740       CXBimag(tx.buf.i, i) = bufl[i], CXBreal(tx.buf.i, i) = bufr[i];
741     CXBhave(tx.buf.i) = n;
742     do_tx(), tx.tick++;
743     for (i = 0; i < n; i++)
744       bufl[i] = (float) CXBimag(tx.buf.o, i), bufr[i] = (float) CXBreal(tx.buf.o, i);
745     CXBhave(tx.buf.o) = n;
746     break;
747   }
748
749   uni.tick++;
750 }