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