]> git.rkrishnan.org Git - dttsp.git/blobdiff - jDttSP/sdr.c
Major update
[dttsp.git] / jDttSP / sdr.c
index 5028ac3ec952dd6075e39566e8153b3a60f711e9..937481344388e71f1a9f2f53056566a72ffdc13c 100644 (file)
@@ -36,25 +36,45 @@ Bridgewater, NJ 08807
 //========================================================================
 /* initialization and termination */
 
+void
+reset_meters(void) {  
+  if (uni.meter.flag) { // reset metering completely
+    int i, k;
+    for (i = 0; i < RXMETERPTS; i++)
+      for (k = 0; k < MAXRX; k++)
+       uni.meter.rx.val[k][i] = uni.meter.rx.avg[k][i] = -200.0;
+    for (i = 0; i < TXMETERPTS; i++)
+      uni.meter.tx.val[i] = uni.meter.tx.avg[i] = -200.0;
+  }
+}
+
+void
+reset_spectrum(void) {  
+  if (uni.spec.flag)
+    reinit_spectrum(&uni.spec);
+}
+
+void
+reset_counters(void) {
+  int k;
+  for (k = 0; k < uni.multirx.nrx; k++) rx[k].tick = 0;
+  tx.tick = 0;
+}
+
+//========================================================================
+
 /* global and general info,
    not specifically attached to
    tx, rx, or scheduling */
 
 PRIVATE void
 setup_all(void) {
-
+  
   uni.samplerate = loc.def.rate;
   uni.buflen = loc.def.size;
   uni.mode.sdr = loc.def.mode;
   uni.mode.trx = RX;
-
-  if (uni.meter.flag) {
-    uni.meter.chan.path = loc.path.meter;
-    uni.meter.chan.size = loc.mult.ring * sizeof(REAL);
-    uni.meter.val = -200.0;
-    uni.meter.chan.c = openChan(uni.meter.chan.path, uni.meter.chan.size);
-  }
-
+  
   uni.wisdom.path = loc.path.wisdom;
   uni.wisdom.bits = FFTW_OUT_OF_PLACE | FFTW_ESTIMATE;
   {
@@ -72,6 +92,30 @@ setup_all(void) {
       fclose(f);
     }
   }
+  
+  if (uni.meter.flag) {
+    uni.meter.rx.type = SIGNAL_STRENGTH;
+    uni.meter.tx.type = SIGNAL_STRENGTH;
+    reset_meters();
+  }
+  
+  uni.spec.rxk = 0;
+  uni.spec.buflen = uni.buflen;
+  uni.spec.scale = SPEC_PWR;
+  uni.spec.type = SPEC_POST_FILT;
+  uni.spec.size = loc.def.spec;
+  uni.spec.planbits = uni.wisdom.bits;
+  init_spectrum(&uni.spec);
+  
+  // set which receiver is listening to commands
+  uni.multirx.lis = 0;
+  uni.multirx.nrx = loc.def.nrx;
+  
+  // set mixing of input from aux ports
+  uni.mix.rx.flag = uni.mix.tx.flag = FALSE;
+  uni.mix.rx.gain = uni.mix.tx.gain = 1.0;
+  
+  uni.cpdlen = loc.def.comp;
 
   uni.tick = 0;
 }
@@ -79,127 +123,137 @@ setup_all(void) {
 /* purely rx */
 
 PRIVATE void
-setup_rx(void) {
-
+setup_rx(int k) {
+  
   /* conditioning */
-  rx.iqfix = newCorrectIQ(0.0, 1.0);
-  rx.filt.coef = newFIR_Bandpass_COMPLEX(-4800.0,
-                                        4800.0,
-                                        uni.samplerate,
-                                        uni.buflen + 1);
-  rx.filt.ovsv = newFiltOvSv(FIRcoef(rx.filt.coef),
-                            FIRsize(rx.filt.coef),
-                            uni.wisdom.bits);
-  normalize_vec_COMPLEX(rx.filt.ovsv->zfvec,
-                       rx.filt.ovsv->fftlen);
+  rx[k].iqfix = newCorrectIQ(0.0, 1.0);
+  rx[k].filt.coef = newFIR_Bandpass_COMPLEX(-4800.0,
+                                           4800.0,
+                                           uni.samplerate,
+                                           uni.buflen + 1);
+  rx[k].filt.ovsv = newFiltOvSv(FIRcoef(rx[k].filt.coef),
+                               FIRsize(rx[k].filt.coef),
+                               uni.wisdom.bits);
+  normalize_vec_COMPLEX(rx[k].filt.ovsv->zfvec,
+                       rx[k].filt.ovsv->fftlen);
 
   // hack for EQ
-  rx.filt.save = newvec_COMPLEX(rx.filt.ovsv->fftlen, "RX filter cache");
-  memcpy((char *) rx.filt.save,
-        (char *) rx.filt.ovsv->zfvec,
-        rx.filt.ovsv->fftlen * sizeof(COMPLEX));
+  rx[k].filt.save = newvec_COMPLEX(rx[k].filt.ovsv->fftlen, "RX filter cache");
+  memcpy((char *) rx[k].filt.save,
+        (char *) rx[k].filt.ovsv->zfvec,
+        rx[k].filt.ovsv->fftlen * sizeof(COMPLEX));
 
   /* buffers */
   /* note we overload the internal filter buffers
      we just created */
-  rx.buf.i = newCXB(FiltOvSv_fetchsize(rx.filt.ovsv),
-                   FiltOvSv_fetchpoint(rx.filt.ovsv),
-                   "init rx.buf.i");
-  rx.buf.o = newCXB(FiltOvSv_storesize(rx.filt.ovsv),
-                   FiltOvSv_storepoint(rx.filt.ovsv),
-                   "init rx.buf.o");
+  rx[k].buf.i = newCXB(FiltOvSv_fetchsize(rx[k].filt.ovsv),
+                      FiltOvSv_fetchpoint(rx[k].filt.ovsv),
+                      "init rx[k].buf.i");
+  rx[k].buf.o = newCXB(FiltOvSv_storesize(rx[k].filt.ovsv),
+                      FiltOvSv_storepoint(rx[k].filt.ovsv),
+                      "init rx[k].buf.o");
   
   /* conversion */
-  rx.osc.freq = -11025.0;
-  rx.osc.phase = 0.0;
-  rx.osc.gen = newOSC(uni.buflen,
-                     ComplexTone,
-                     rx.osc.freq,
-                     rx.osc.phase,
-                     uni.samplerate,
-                     "SDR RX Oscillator");
-
-  rx.agc.gen = newDigitalAgc(agcMED,   // Mode
-                            7,         // Hang
-                            7,         // Size
-                            48,        // Ramp
-                            3,         // Over
-                            3,         // Rcov
-                            CXBsize(rx.buf.o), // BufSize
-                            100.0,     // MaxGain
-                            0.707,     // Limit
-                            1.0,       // CurGain
-                            CXBbase(rx.buf.o));
-  rx.agc.flag = TRUE;
+  rx[k].osc.freq = -11025.0;
+  rx[k].osc.phase = 0.0;
+  rx[k].osc.gen = newOSC(uni.buflen,
+                        ComplexTone,
+                        rx[k].osc.freq,
+                        rx[k].osc.phase,
+                        uni.samplerate,
+                        "SDR RX Oscillator");
+
+  rx[k].agc.gen = newDigitalAgc(agcSLOW,       // Mode
+                               7,              // Hang
+                               48,             // Ramp
+                               3,              // Over
+                               3,              // Rcov
+                               CXBsize(rx[k].buf.o),   // BufSize
+                               2500.0,         // MaxGain
+                               0.707,          // Limit
+                               1.0,            // CurGain
+                               CXBbase(rx[k].buf.o));
+  rx[k].agc.flag = TRUE;
 
   /* demods */
-  rx.am.gen = newAMD(48000.0,  // REAL samprate
-                    0.0,       // REAL f_initial
-                    -500.0,    // REAL f_lobound,
-                    500.0,     // REAL f_hibound,
-                    400.0,     // REAL f_bandwid,
-                    CXBsize(rx.buf.o), // int size,
-                    CXBbase(rx.buf.o), // COMPLEX *ivec,
-                    CXBbase(rx.buf.o), // COMPLEX *ovec,
-                    AMdet,     // AM Mode AMdet == rectifier,
+  rx[k].am.gen = newAMD(48000.0,       // REAL samprate
+                       0.0,    // REAL f_initial
+                       -500.0, // REAL f_lobound,
+                       500.0,  // REAL f_hibound,
+                       400.0,  // REAL f_bandwid,
+                       CXBsize(rx[k].buf.o),   // int size,
+                       CXBbase(rx[k].buf.o),   // COMPLEX *ivec,
+                       CXBbase(rx[k].buf.o),   // COMPLEX *ovec,
+                       AMdet,  // AM Mode AMdet == rectifier,
                                //         SAMdet == synchronous detector
-                            "AM detector blew");       // char *tag
-  rx.fm.gen = newFMD(48000,    // REAL samprate
-                    0.0,       // REAL f_initial
-                    -6000.0,   // REAL f_lobound
-                    6000.0,    // REAL f_hibound
-                    10000.0,   // REAL f_bandwid
-                    CXBsize(rx.buf.o), // int size
-                    CXBbase(rx.buf.o), // COMPLEX *ivec
-                    CXBbase(rx.buf.o), // COMPLEX *ovec
-                    "New FM Demod structure"); // char *error message;
+                       "AM detector blew");    // char *tag
+  rx[k].fm.gen = newFMD(48000, // REAL samprate
+                       0.0,    // REAL f_initial
+                       -6000.0,        // REAL f_lobound
+                       6000.0, // REAL f_hibound
+                       10000.0,        // REAL f_bandwid
+                       CXBsize(rx[k].buf.o),   // int size
+                       CXBbase(rx[k].buf.o),   // COMPLEX *ivec
+                       CXBbase(rx[k].buf.o),   // COMPLEX *ovec
+                       "New FM Demod structure");      // char *error message;
 
   /* noise reduction */
-  rx.anf.gen = new_lmsr(rx.buf.o,      // CXB signal,
-                       64,             // int delay,
-                       0.01,           // REAL adaptation_rate,
-                       0.00001,        // REAL leakage,
-                       45,             // int adaptive_filter_size,
-                       LMADF_INTERFERENCE);
-  rx.anf.flag = FALSE;
-  rx.anr.gen = new_lmsr(rx.buf.o,      // CXB signal,
-                       64,             // int delay,
-                       0.01,           // REAL adaptation_rate,
-                       0.00001,        // REAL leakage,
-                       45,             // int adaptive_filter_size,
-                       LMADF_NOISE);
-  rx.anr.flag = FALSE;
-
-  rx.nb.thresh = 3.3;
-  rx.nb.gen = new_noiseblanker(rx.buf.i, rx.nb.thresh);
-  rx.nb.flag = FALSE;
-
-  rx.nb_sdrom.thresh = 2.5;
-  rx.nb_sdrom.gen = new_noiseblanker(rx.buf.i, rx.nb_sdrom.thresh);
-  rx.nb_sdrom.flag = FALSE;
-
-  rx.spot.gen = newSpotToneGen(-12.0,  // gain
-                              700.0,   // freq
-                              5.0,     // ms rise
-                              5.0,     // ms fall
-                              uni.buflen,
-                              uni.samplerate);
-
-  rx.scl.pre.val = 1.0;
-  rx.scl.pre.flag = FALSE;
-  rx.scl.post.val = 1.0;
-  rx.scl.post.flag = FALSE;
-
-  memset((char *) &rx.squelch, 0, sizeof(rx.squelch));
-  rx.squelch.thresh = -30.0;
-  rx.squelch.power = 0.0;
-  rx.squelch.flag = rx.squelch.running = rx.squelch.set = FALSE;
-  rx.squelch.num = (int) (0.0395 * uni.samplerate + 0.5);
-
-  rx.mode = uni.mode.sdr;
-  rx.bin.flag = FALSE;
-
-  rx.tick = 0;
+  rx[k].anf.gen = new_lmsr(rx[k].buf.o,        // CXB signal,
+                          64,          // int delay,
+                          0.01,                // REAL adaptation_rate,
+                          0.00001,     // REAL leakage,
+                          45,          // int adaptive_filter_size,
+                          LMADF_INTERFERENCE);
+  rx[k].anf.flag = FALSE;
+  rx[k].anr.gen = new_lmsr(rx[k].buf.o,        // CXB signal,
+                          64,          // int delay,
+                          0.01,                // REAL adaptation_rate,
+                          0.00001,     // REAL leakage,
+                          45,          // int adaptive_filter_size,
+                          LMADF_NOISE);
+  rx[k].anr.flag = FALSE;
+
+  rx[k].nb.thresh = 3.3;
+  rx[k].nb.gen = new_noiseblanker(rx[k].buf.i, rx[k].nb.thresh);
+  rx[k].nb.flag = FALSE;
+
+  rx[k].nb_sdrom.thresh = 2.5;
+  rx[k].nb_sdrom.gen = new_noiseblanker(rx[k].buf.i, rx[k].nb_sdrom.thresh);
+  rx[k].nb_sdrom.flag = FALSE;
+
+  rx[k].spot.gen = newSpotToneGen(-12.0,       // gain
+                                 700.0,        // freq
+                                 5.0,  // ms rise
+                                 5.0,  // ms fall
+                                 uni.buflen,
+                                 uni.samplerate);
+
+  rx[k].scl.pre.val = 1.0;
+  rx[k].scl.pre.flag = FALSE;
+  rx[k].scl.post.val = 1.0;
+  rx[k].scl.post.flag = FALSE;
+
+  memset((char *) &rx[k].squelch, 0, sizeof(rx[k].squelch));
+  rx[k].squelch.thresh = -30.0;
+  rx[k].squelch.power = 0.0;
+  rx[k].squelch.flag = rx[k].squelch.running = rx[k].squelch.set = FALSE;
+  rx[k].squelch.num = uni.buflen - 10;
+
+  rx[k].cpd.gen = newWSCompander(uni.cpdlen,
+                                0.0,
+                                rx[k].buf.o);
+  rx[k].cpd.flag = FALSE;
+
+  rx[k].mode = uni.mode.sdr;
+  rx[k].bin.flag = FALSE;
+
+  {
+    REAL pos = 0.5, // 0 <= pos <= 1, left->right
+         theta = (1.0 - pos) * M_PI / 2.0;
+    rx[k].azim = Cmplx(cos(theta), sin(theta));
+  }
+
+  rx[k].tick = 0;
 }
 
 /* purely tx */
@@ -233,6 +287,9 @@ setup_tx(void) {
                    FiltOvSv_storepoint(tx.filt.ovsv),
                    "init tx.buf.o");
   
+  tx.dcb.flag = FALSE;
+  tx.dcb.gen = newDCBlocker(DCB_MED, tx.buf.i);
+
   /* conversion */
   tx.osc.freq = 0.0;
   tx.osc.phase = 0.0;
@@ -243,12 +300,12 @@ setup_tx(void) {
                      uni.samplerate,
                      "SDR TX Oscillator");
 
+
   tx.agc.gen = newDigitalAgc(agcFAST,  // Mode
                             3,         // Hang
-                            3,         // Size
+                            48,        // Ramp
                             3,         // Over
                             3,         // Rcov
-                            48,        // Ramp
                             CXBsize(tx.buf.o), // BufSize
                             1.0,       // MaxGain
                             0.900,     // Limit
@@ -256,9 +313,14 @@ setup_tx(void) {
                             CXBbase(tx.buf.o));
   tx.agc.flag = TRUE;
 
-  tx.spr.gen = newSpeechProc(0.4, 10.0, CXBbase(tx.buf.i), CXBsize(tx.buf.i));
+  tx.spr.gen = newSpeechProc(0.4, 10.0, CXBbase(tx.buf.o), CXBsize(tx.buf.o));
   tx.spr.flag = FALSE;
 
+  tx.cpd.gen = newWSCompander(uni.cpdlen,
+                             0.0,
+                             tx.buf.o);
+  tx.cpd.flag = FALSE;
+
   tx.scl.dc = cxzero;
   tx.scl.pre.val = 1.0;
   tx.scl.pre.flag = FALSE;
@@ -275,16 +337,29 @@ setup_tx(void) {
 
 void
 setup_workspace(void) {
-  setup_all(), setup_rx(), setup_tx();
+  int k;
+
+  setup_all();
+
+  for (k = 0; k < uni.multirx.nrx; k++) {
+    setup_rx(k);
+    uni.multirx.act[k] = FALSE;
+  }
+  uni.multirx.act[0] = TRUE;
+  uni.multirx.nac = 1;
+  
+  setup_tx();
 }
 
 void
 destroy_workspace(void) {
+  int k;
 
   /* TX */
   delSpeechProc(tx.spr.gen);
   delDigitalAgc(tx.agc.gen);
   delOSC(tx.osc.gen);
+  delDCBlocker(tx.dcb.gen);
   delvec_COMPLEX(tx.filt.save);
   delFiltOvSv(tx.filt.ovsv);
   delFIR_Bandpass_COMPLEX(tx.filt.coef);
@@ -293,25 +368,26 @@ destroy_workspace(void) {
   delCXB(tx.buf.i);
 
   /* RX */
-  delSpotToneGen(rx.spot.gen);
-  delDigitalAgc(rx.agc.gen);
-  del_nb(rx.nb_sdrom.gen);
-  del_nb(rx.nb.gen);
-  del_lmsr(rx.anf.gen);
-  del_lmsr(rx.anr.gen);
-  delAMD(rx.am.gen);
-  delFMD(rx.fm.gen);
-  delOSC(rx.osc.gen);
-  delvec_COMPLEX(rx.filt.save);
-  delFiltOvSv(rx.filt.ovsv);
-  delFIR_Bandpass_COMPLEX(rx.filt.coef);
-  delCorrectIQ(rx.iqfix);
-  delCXB(rx.buf.o);
-  delCXB(rx.buf.i);
-
+  for (k = 0; k < uni.multirx.nrx; k++) {
+    delSpotToneGen(rx[k].spot.gen);
+    delDigitalAgc(rx[k].agc.gen);
+    del_nb(rx[k].nb_sdrom.gen);
+    del_nb(rx[k].nb.gen);
+    del_lmsr(rx[k].anf.gen);
+    del_lmsr(rx[k].anr.gen);
+    delAMD(rx[k].am.gen);
+    delFMD(rx[k].fm.gen);
+    delOSC(rx[k].osc.gen);
+    delvec_COMPLEX(rx[k].filt.save);
+    delFiltOvSv(rx[k].filt.ovsv);
+    delFIR_Bandpass_COMPLEX(rx[k].filt.coef);
+    delCorrectIQ(rx[k].iqfix);
+    delCXB(rx[k].buf.o);
+    delCXB(rx[k].buf.i);
+  }
+  
   /* all */
-  if (uni.meter.flag)
-    closeChan(uni.meter.chan.c);
+  finish_spectrum(&uni.spec);
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -333,210 +409,317 @@ CXBnorm(CXB buff) {
 //========================================================================
 /* all */
 
-/* tap off S-meter from some buf */
+// unfortunate duplication here, due to
+// multirx vs monotx
 
 PRIVATE void
-do_meter(COMPLEX *vec, int len) {
-  int i;
+do_rx_meter(int k, CXB buf, int tap) {
+  COMPLEX *vec = CXBbase(buf);
+  int i, len = CXBhave(buf);
   
-  uni.meter.val = 0;
-
-  switch (uni.meter.type) {
-  case AVG_SIGNAL_STRENGTH:
+  uni.meter.rx.val[k][tap] = 0;
+  
+  switch (uni.meter.rx.type) {
+  case SIGNAL_STRENGTH:
     for (i = 0; i < len; i++)
-      uni.meter.val += Csqrmag(vec[i]);
-    uni.meter.val =
-      uni.meter.avgval = 0.9 * uni.meter.avgval + log10(uni.meter.val + 1e-20);
+      uni.meter.rx.val[k][tap] += Csqrmag(vec[i]);
+    if (tap == 3) rx[k].norm = uni.meter.rx.val[k][tap] / len;
+    uni.meter.rx.avg[k][tap] =
+      uni.meter.rx.val[k][tap] =
+        10.0 * log10(uni.meter.rx.val[k][tap] + 1e-20);
     break;
-  case SIGNAL_STRENGTH:
+  case AVG_SIGNAL_STRENGTH:
     for (i = 0; i < len; i++)
-      uni.meter.val += Csqrmag(vec[i]);
-    uni.meter.avgval = uni.meter.val = 10.0 * log10(uni.meter.val + 1e-20);
+      uni.meter.rx.val[k][tap] += Csqrmag(vec[i]);
+    uni.meter.rx.val[k][tap] =
+      uni.meter.rx.avg[k][tap] =
+        0.9 * uni.meter.rx.avg[k][tap] + log10(uni.meter.rx.val[k][tap] + 1e-20);
     break;
   case ADC_REAL:
     for(i = 0; i < len; i++)
-      uni.meter.val = max(fabs(vec[i].re), uni.meter.val);
-    uni.meter.val = 20.0 * log10(uni.meter.val + 1e-10);
+      uni.meter.rx.val[k][tap] = max(fabs(vec[i].re), uni.meter.rx.val[k][tap]);
+    uni.meter.rx.val[k][tap] = 20.0 * log10(uni.meter.rx.val[k][tap] + 1e-10);
     break;
   case ADC_IMAG:
     for(i = 0; i < len; i++)
-      uni.meter.val = max(fabs(vec[i].im), uni.meter.val);
-    uni.meter.val = 20.0 * log10(uni.meter.val + 1e-10);
+      uni.meter.rx.val[k][tap] = max(fabs(vec[i].im), uni.meter.rx.val[k][tap]);
+    uni.meter.rx.val[k][tap] = 20.0 * log10(uni.meter.rx.val[k][tap] + 1e-10);
+    break;
+  case AGC_GAIN:
+    uni.meter.rx.val[k][tap] = 20.0 * log10(rx[k].agc.gen->gain.now + 1e-80);
+    break;
+  default:
+    break;
+  }
+}
+
+PRIVATE void
+do_tx_meter(CXB buf, int tap) {
+  COMPLEX *vec = CXBbase(buf);
+  int i, len = CXBhave(buf);
+  
+  uni.meter.tx.val[tap] = 0;
+
+  switch (uni.meter.tx.type) {
+  case AVG_SIGNAL_STRENGTH:
+    for (i = 0; i < len; i++)
+      uni.meter.tx.val[tap] += Csqrmag(vec[i]);
+    uni.meter.tx.val[tap] =
+      uni.meter.tx.avg[tap] =
+        0.9 * uni.meter.tx.avg[tap] + log10(uni.meter.tx.val[tap] + 1e-20);
+    break;
+  case SIGNAL_STRENGTH:
+    for (i = 0; i < len; i++)
+      uni.meter.tx.val[tap] += Csqrmag(vec[i]);
+    uni.meter.tx.avg[tap] =
+      uni.meter.tx.val[tap] =
+        10.0 * log10(uni.meter.tx.val[tap] + 1e-20);
+    break;
+  case ALC:
+    {
+      REAL tmp = 20.0 * log10(tx.agc.gen->gain.now);
+      uni.meter.tx.val[tap] =
+       tmp < 0.0 ? tmp : min(20.0, 20.0 * log10(tx.agc.gen->gain.raw));
+    }
+    break;
+  case PWR:
+    for(i = 0, uni.meter.tx.val[tap] = 1e-20; i < CXBhave(tx.buf.o); i++)
+      uni.meter.tx.val[tap] += Csqrmag(CXBdata(tx.buf.o, i));
+    uni.meter.tx.val[tap] /= 2048.0;
+    break;
+  case PKPWR:
+    for(i = 0, uni.meter.tx.val[tap] = 1e-20; i < CXBhave(tx.buf.o); i++) 
+      uni.meter.tx.val[tap] = max(uni.meter.tx.val[tap],
+                                 Csqrmag(CXBdata(tx.buf.o,i)));
     break;
   default:
     break;
   }
+}
+
+PRIVATE void
+do_rx_spectrum(int k, CXB buf, int type) {
+  if (uni.spec.flag && k == uni.spec.rxk && type == uni.spec.type) {
+    memcpy((char *) &CXBdata(uni.spec.accum, uni.spec.fill),
+          (char *) CXBbase(buf),
+          CXBsize(buf) * sizeof(COMPLEX)); 
+    uni.spec.fill = (uni.spec.fill + CXBsize(buf)) % uni.spec.size;
+  }
+}
 
-  putChan_nowait(uni.meter.chan.c,
-                (char *) &uni.meter.val,
-                sizeof(uni.meter.val));
+PRIVATE void
+do_tx_spectrum(CXB buf) {
+  memcpy((char *) &CXBdata(uni.spec.accum, uni.spec.fill),
+        (char *) CXBbase(buf),
+        CXBsize(buf) * sizeof(COMPLEX));
+  uni.spec.fill = (uni.spec.fill + CXBsize(buf)) % uni.spec.size;
 }
 
 //========================================================================
 /* RX processing */ 
 
 PRIVATE BOOLEAN
-should_do_rx_squelch(void) {
-  if (rx.squelch.flag) {
-    int i, n = CXBhave(rx.buf.o);
-    rx.squelch.power = 0.0;
+should_do_rx_squelch(int k) {
+  if (rx[k].squelch.flag) {
+    int i, n = CXBhave(rx[k].buf.o);
+    rx[k].squelch.power = 0.0;
+
     for (i = 0; i < n; i++)
-      rx.squelch.power += Csqrmag(CXBdata(rx.buf.o, i));
-    return rx.squelch.thresh > 10.0 * log10(rx.squelch.power);
+      rx[k].squelch.power += Csqrmag(CXBdata(rx[k].buf.o, i));
+
+    return
+      10.0 * log10(rx[k].squelch.power) < rx[k].squelch.thresh;
+
   } else
-    return rx.squelch.set = FALSE;
+    return rx[k].squelch.set = FALSE;
 }
 
 // apply squelch
 // slew into silence first time
 
 PRIVATE void
-do_squelch(void) {
-  rx.squelch.set = TRUE;
-  if (!rx.squelch.running) {
-    int i, m = rx.squelch.num, n = CXBhave(rx.buf.o) - m;
+do_squelch(int k) {
+  rx[k].squelch.set = TRUE;
+  
+  if (!rx[k].squelch.running) {
+    int i,
+        m = rx[k].squelch.num,
+        n = CXBhave(rx[k].buf.o) - m;
+
     for (i = 0; i < m; i++)
-      CXBdata(rx.buf.o, i) = Cscl(CXBdata(rx.buf.o, i), 1.0 - (REAL) i / m);
-    memset((void *) (CXBbase(rx.buf.o) + m), 0, n * sizeof(COMPLEX));
-    rx.squelch.running = TRUE;
+      CXBdata(rx[k].buf.o, i) =
+       Cscl(CXBdata(rx[k].buf.o, i), 1.0 - (REAL) i / m);
+  
+    memset((void *) (CXBbase(rx[k].buf.o) + m),
+          0,
+          n * sizeof(COMPLEX));
+    rx[k].squelch.running = TRUE;
+
   } else
-    memset((void *) CXBbase(rx.buf.o), 0, CXBhave(rx.buf.o) * sizeof(COMPLEX));
+    memset((void *) CXBbase(rx[k].buf.o),
+          0,
+          CXBhave(rx[k].buf.o) * sizeof(COMPLEX));
 }
 
 // lift squelch
 // slew out from silence to full scale
 
 PRIVATE void
-no_squelch(void) {
-  if (rx.squelch.running) {
-    int i, m = rx.squelch.num;
+no_squelch(int k) {
+  if (rx[k].squelch.running) {
+    int i, m = rx[k].squelch.num;
+
     for (i = 0; i < m; i++)
-      CXBdata(rx.buf.o, i) = Cscl(CXBdata(rx.buf.o, i), (REAL) i / m);
-    rx.squelch.running = FALSE;
+      CXBdata(rx[k].buf.o, i) =
+       Cscl(CXBdata(rx[k].buf.o, i), (REAL) i / m);
+
+    rx[k].squelch.running = FALSE;
   }
 }
 
 /* pre-condition for (nearly) all RX modes */
 
 PRIVATE void
-do_rx_pre(void) {
-  int i, n = min(CXBhave(rx.buf.i), uni.buflen);
-
-  //
-  // do shrinkage here
-  //
+do_rx_pre(int k) {
+  int i, n = min(CXBhave(rx[k].buf.i), uni.buflen);
 
-  if (rx.scl.pre.flag)
+  if (rx[k].scl.pre.flag)
     for (i = 0; i < n; i++)
-      CXBdata(rx.buf.i, i) = Cscl(CXBdata(rx.buf.i, i),
-                                 rx.scl.pre.val); 
+      CXBdata(rx[k].buf.i, i) = Cscl(CXBdata(rx[k].buf.i, i),
+                                    rx[k].scl.pre.val); 
 
-  if (rx.nb.flag) noiseblanker(rx.nb.gen);
-  if (rx.nb_sdrom.flag) SDROMnoiseblanker(rx.nb_sdrom.gen);
+  if (rx[k].nb.flag) noiseblanker(rx[k].nb.gen);
+  if (rx[k].nb_sdrom.flag) SDROMnoiseblanker(rx[k].nb_sdrom.gen);
 
-  correctIQ(rx.buf.i, rx.iqfix);
+  // metering for uncorrected values here
 
-  /* 2nd if conversion happens here */
-  if (rx.osc.gen->Frequency != 0.0) {
-    ComplexOSC(rx.osc.gen);
+  do_rx_meter(k, rx[k].buf.i, RXMETER_PRE_CONV);
+
+  correctIQ(rx[k].buf.i, rx[k].iqfix);
+
+  /* 2nd IF conversion happens here */
+
+  if (rx[k].osc.gen->Frequency != 0.0) {
+    ComplexOSC(rx[k].osc.gen);
     for (i = 0; i < n; i++)
-      CXBdata(rx.buf.i, i) = Cmul(CXBdata(rx.buf.i, i),
-                                 OSCCdata(rx.osc.gen, i));
+      CXBdata(rx[k].buf.i, i) = Cmul(CXBdata(rx[k].buf.i, i),
+                                    OSCCdata(rx[k].osc.gen, i));
   } 
 
-  /* filtering, metering, squelch, & AGC */
-  if (rx.mode != SPEC) {
-    if (rx.tick == 0) reset_OvSv(rx.filt.ovsv);
-    filter_OvSv(rx.filt.ovsv);
-    CXBhave(rx.buf.o) = CXBhave(rx.buf.i);
-    if (uni.meter.flag) do_meter(CXBbase(rx.buf.o), uni.buflen);
-    if (should_do_rx_squelch()) do_squelch();
-    if (rx.agc.flag) DigitalAgc(rx.agc.gen, rx.tick);
-  } else if (uni.meter.flag)
-    do_meter(CXBbase(rx.buf.o), uni.buflen);
+  /* filtering, metering, spectrum, squelch, & AGC */
+  
+  if (rx[k].mode == SPEC)
+    
+    do_rx_spectrum(k, rx[k].buf.i, SPEC_SEMI_RAW);
+  
+  else {
+    
+    do_rx_meter(k, rx[k].buf.i, RXMETER_PRE_FILT);
+    do_rx_spectrum(k, rx[k].buf.i, SPEC_PRE_FILT);
+    
+    if (rx[k].tick == 0)
+      reset_OvSv(rx[k].filt.ovsv);
+    
+    filter_OvSv(rx[k].filt.ovsv);
+    CXBhave(rx[k].buf.o) = CXBhave(rx[k].buf.i);
+    
+    do_rx_meter(k, rx[k].buf.o, RXMETER_POST_FILT);
+    do_rx_spectrum(k, rx[k].buf.o, SPEC_POST_FILT);
+    
+    if (rx[k].cpd.flag)
+      WSCompand(rx[k].cpd.gen);
+
+    if (should_do_rx_squelch(k))
+      do_squelch(k);
+    
+    else if (rx[k].agc.flag)
+      DigitalAgc(rx[k].agc.gen, rx[k].tick);
+
+    do_rx_spectrum(k, rx[k].buf.o, SPEC_POST_AGC);
+  }
 }
 
 PRIVATE void
-do_rx_post(void) {
-  int i, n = CXBhave(rx.buf.o);
-
-  if (!rx.squelch.set)  {
-    no_squelch();
+do_rx_post(int k) {
+  int i, n = CXBhave(rx[k].buf.o);
+  
+  if (!rx[k].squelch.set)  {
+    no_squelch(k);
     // spotting tone
-    if (rx.spot.flag) {
+    if (rx[k].spot.flag) {
       // remember whether it's turned itself off during this pass
-      rx.spot.flag = SpotTone(rx.spot.gen);
+      rx[k].spot.flag = SpotTone(rx[k].spot.gen);
       for (i = 0; i < n; i++)
-       CXBdata(rx.buf.o, i) = Cadd(CXBdata(rx.buf.o, i),
-                                   CXBdata(rx.spot.gen->buf, i));
+       CXBdata(rx[k].buf.o, i) = Cadd(CXBdata(rx[k].buf.o, i),
+                                      CXBdata(rx[k].spot.gen->buf, i));
     }
   }
-
-  //
-  // mix in sidetone here
-  //
-
-  if (rx.scl.post.flag)
+  
+  // final scaling
+  
+  if (rx[k].scl.post.flag)
     for (i = 0; i < n; i++)
-      CXBdata(rx.buf.o, i) = Cscl(CXBdata(rx.buf.o, i),
-                                 rx.scl.post.val);
-
-  // not binaural? collapse
-  if (!rx.bin.flag)
+      CXBdata(rx[k].buf.o, i) = Cscl(CXBdata(rx[k].buf.o, i),
+                                    rx[k].scl.post.val);
+  
+  // not binaural?
+  // position in stereo field
+  
+  if (!rx[k].bin.flag)
     for (i = 0; i < n; i++)
-      CXBimag(rx.buf.o, i) = CXBreal(rx.buf.o, i);
+      CXBdata(rx[k].buf.o, i) = Cscl(rx[k].azim, CXBreal(rx[k].buf.o, i));
 }
 
 /* demod processing */
 
 PRIVATE void
-do_rx_SBCW(void) {
-  if (rx.anr.flag) lmsr_adapt(rx.anr.gen);
-  if (rx.anf.flag) lmsr_adapt(rx.anf.gen);
+do_rx_SBCW(int k) {
+  if (rx[k].anr.flag) lmsr_adapt(rx[k].anr.gen);
+  if (rx[k].anf.flag) lmsr_adapt(rx[k].anf.gen);
 }
 
 PRIVATE void
-do_rx_AM(void) { AMDemod(rx.am.gen); }
+do_rx_AM(int k) { AMDemod(rx[k].am.gen); }
 
 PRIVATE void
-do_rx_FM(void) { FMDemod(rx.fm.gen); }
+do_rx_FM(int k) { FMDemod(rx[k].fm.gen); }
 
 PRIVATE void
-do_rx_DRM(void) {}
+do_rx_DRM(int k) {}
 
 PRIVATE void
-do_rx_SPEC(void) {
-  memcpy(CXBbase(rx.buf.o),
-        CXBbase(rx.buf.i),
-        sizeof(COMPLEX) * CXBhave(rx.buf.i));
-  if (rx.agc.flag) DigitalAgc(rx.agc.gen, rx.tick);
+do_rx_SPEC(int k) {
+  memcpy(CXBbase(rx[k].buf.o),
+        CXBbase(rx[k].buf.i),
+        sizeof(COMPLEX) * CXBhave(rx[k].buf.i));
+  if (rx[k].agc.flag) DigitalAgc(rx[k].agc.gen, rx[k].tick);
 }
 
 PRIVATE void
-do_rx_NIL(void) {
-  int i, n = min(CXBhave(rx.buf.i), uni.buflen);
-  for (i = 0; i < n; i++) CXBdata(rx.buf.o, i) = cxzero;
+do_rx_NIL(int k) {
+  int i, n = min(CXBhave(rx[k].buf.i), uni.buflen);
+  for (i = 0; i < n; i++) CXBdata(rx[k].buf.o, i) = cxzero;
 }
 
 /* overall dispatch for RX processing */
 
 PRIVATE void
-do_rx(void) {
-  do_rx_pre();
-  switch (rx.mode) {
+do_rx(int k) {
+  do_rx_pre(k);
+  switch (rx[k].mode) {
   case USB:
   case LSB:
   case CWU:
   case CWL:
-  case DSB:  do_rx_SBCW(); break;
+  case DSB:  do_rx_SBCW(k); break;
   case AM:
-  case SAM:  do_rx_AM(); break;
-  case FMN:  do_rx_FM();   break;
-  case DRM:  do_rx_DRM();  break;
+  case SAM:  do_rx_AM(k); break;
+  case FMN:  do_rx_FM(k);   break;
+  case DRM:  do_rx_DRM(k);  break;
   case SPEC:
-    default: do_rx_SPEC(); break;
+    default: do_rx_SPEC(k); break;
   }
-  do_rx_post();
+  do_rx_post(k);
 }  
 
 //==============================================================
@@ -553,16 +736,13 @@ do_tx_pre(void) {
       CXBdata(tx.buf.i, i) = Cmplx(CXBreal(tx.buf.i, i) * tx.scl.pre.val, 0.0);
   }
 
-  //
-  // mix in CW tone here?
-  //
-
   correctIQ(tx.buf.i, tx.iqfix);
 
-  if (tx.spr.flag) SpeechProcessor(tx.spr.gen);
+  if (tx.dcb.flag) DCBlock(tx.dcb.gen);
 
   if (tx.tick == 0) reset_OvSv(tx.filt.ovsv);
   filter_OvSv(tx.filt.ovsv);
+
 }
 
 PRIVATE void
@@ -570,12 +750,23 @@ do_tx_post(void) {
   CXBhave(tx.buf.o) = CXBhave(tx.buf.i);
 
   if (tx.agc.flag) DigitalAgc(tx.agc.gen, tx.tick);
+
+  // meter modulated signal
+
+  do_tx_meter(tx.buf.o, TXMETER_POST_MOD);
+
   if (tx.scl.post.flag) {
     int i, n = CXBhave(tx.buf.o);
     for (i = 0; i < n; i++)
       CXBdata(tx.buf.o, i) = Cscl(CXBdata(tx.buf.o, i), tx.scl.post.val);
   }
-  if (uni.meter.flag) do_meter(CXBbase(tx.buf.o), uni.buflen);
+
+  if (tx.spr.flag) SpeechProcessor(tx.spr.gen);
+  if (tx.cpd.flag) WSCompand(tx.cpd.gen);
+
+  if (uni.spec.flag)
+    do_tx_spectrum(tx.buf.o);
+
   if (tx.osc.gen->Frequency != 0.0) {
     int i;
     ComplexOSC(tx.osc.gen);
@@ -659,24 +850,54 @@ do_tx(void) {
    come here when there are buffers to work on */
 
 void
-process_samples(float *bufl, float *bufr, int n) {
-  int i;
-
+process_samples(float *bufl, float *bufr,
+               float *auxl, float *auxr,
+               int n) {
+  int i, k;
+  
   switch (uni.mode.trx) {
-
+    
   case RX:
-    for (i = 0; i < n; i++)
-      CXBimag(rx.buf.i, i) = bufl[i], CXBreal(rx.buf.i, i) = bufr[i];
-    CXBhave(rx.buf.i) = n;
-
-    do_rx(), rx.tick++;
+    
+    // make copies of the input for all receivers
+    for (k = 0; k < uni.multirx.nrx; k++)
+      if (uni.multirx.act[k]) {
+       for (i = 0; i < n; i++)
+         CXBimag(rx[k].buf.i, i) = bufl[i], CXBreal(rx[k].buf.i, i) = bufr[i];
+       CXBhave(rx[k].buf.i) = n;
+      }
+
+    // prepare buffers for mixing
+    memset((char *) bufl, 0, n * sizeof(float));
+    memset((char *) bufr, 0, n * sizeof(float));
+
+    // run all receivers
+    for (k = 0; k < uni.multirx.nrx; k++)
+      if (uni.multirx.act[k]) {
+       do_rx(k), rx[k].tick++;
+       // mix
+       for (i = 0; i < n; i++)
+          bufl[i] += (float) CXBimag(rx[k].buf.o, i),
+         bufr[i] += (float) CXBreal(rx[k].buf.o, i);
+       CXBhave(rx[k].buf.o) = n;
+      }
+
+    // late mixing of aux buffers
+    if (uni.mix.rx.flag)
+      for (i = 0; i < n; i++)
+       bufl[i] += (float) (auxl[i] * uni.mix.rx.gain),
+       bufr[i] += (float) (auxr[i] * uni.mix.rx.gain);
 
-    for (i = 0; i < n; i++)
-      bufl[i] = (float) CXBimag(rx.buf.o, i), bufr[i] = (float) CXBreal(rx.buf.o, i);
-    CXBhave(rx.buf.o) = n;
     break;
 
   case TX:
+
+    // early mixing of aux buffers
+    if (uni.mix.tx.flag)
+      for (i = 0; i < n; i++)
+       bufl[i] += (float) (auxl[i] * uni.mix.tx.gain),
+       bufr[i] += (float) (auxr[i] * uni.mix.tx.gain);
+
     for (i = 0; i < n; i++)
       CXBimag(tx.buf.i, i) = bufl[i], CXBreal(tx.buf.i, i) = bufr[i];
     CXBhave(tx.buf.i) = n;
@@ -686,6 +907,7 @@ process_samples(float *bufl, float *bufr, int n) {
     for (i = 0; i < n; i++)
       bufl[i] = (float) CXBimag(tx.buf.o, i), bufr[i] = (float) CXBreal(tx.buf.o, i);
     CXBhave(tx.buf.o) = n;
+
     break;
   }