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