From: dttsp <dttsp>
Date: Fri, 29 Apr 2005 22:10:59 +0000 (+0000)
Subject: multiple receivers implemented
X-Git-Url: https://git.rkrishnan.org/Site/Content/Exhibitors/%22doc.html/flags/%3C?a=commitdiff_plain;h=00027b8895e28eedb988f3f1a67679a3b3dfe770;p=dttsp.git

multiple receivers implemented
---

diff --git a/jDttSP/Makefile b/jDttSP/Makefile
index b13744d..f2e978e 100644
--- a/jDttSP/Makefile
+++ b/jDttSP/Makefile
@@ -59,7 +59,7 @@ ipc:	mkchan
 obj:	$(OBJ)
 
 clean:
-	/bin/rm *.o jsdr mkchan metermon keyd keyb #$(staticlibname)
+	/bin/rm *.o jsdr mkchan metermon keyd #$(staticlibname)
 	#/bin/rm IPC/*
 
 staticlib:	$(OBJ)
diff --git a/jDttSP/command-vocabulary b/jDttSP/command-vocabulary
index 3f479be..2655e4c 100644
--- a/jDttSP/command-vocabulary
+++ b/jDttSP/command-vocabulary
@@ -50,4 +50,8 @@ setTXPostSclVal valQ	// dB
 setSWCH trx [zap]	// trx = RX|TX, int (always zaps at least 1)
 setSpotToneVals gain freq rise fall // dB, Hz, msec, msec [-12, 700, 5, 5]
 setSpotTone T|F		// turn on, off
-setFinished		// shutdown gracefully
\ No newline at end of file
+setFinished		// shutdown gracefully
+setRXListen rx		// tell receiver rx to listen to commands to follow
+setRXOn [rx]		// turn currently listening receiver on, or receiver rx
+setRXOff [rx]		// turn currently listening receiver off, or receiver rx
+setRXPan pos		// set azimuth for currently listening receiver to pos (0...1)
diff --git a/jDttSP/fastrig.c b/jDttSP/fastrig.c
index 52c77b1..1ea11a2 100644
--- a/jDttSP/fastrig.c
+++ b/jDttSP/fastrig.c
@@ -1,6 +1,6 @@
 
 /****************************************************************
- *   Fast Trigonometric Routines Used for Imbedded Systems      *
+ *   Fast Trigonometric Routines Used for Embedded Systems      *
  *   Programmer:  Bob McGwier, IDA CCR-P, June, 2000            *
  ***************************************************************/
 /* This file is part of a program that implements a Software-Defined Radio.
diff --git a/jDttSP/keyd.c b/jDttSP/keyd.c
index 091f665..12e7797 100644
--- a/jDttSP/keyd.c
+++ b/jDttSP/keyd.c
@@ -47,8 +47,6 @@ Bridgewater, NJ 08807
 #define SAMP_RATE (48000)
 
 // # times key is sampled per sec
-// > 64 requires root on Linux
-//#define RTC_RATE (128)
 #define RTC_RATE (64)
 
 // # samples generated during 1 clock tick at RTC_RATE
diff --git a/jDttSP/local.h b/jDttSP/local.h
index 942fc2b..dda789e 100644
--- a/jDttSP/local.h
+++ b/jDttSP/local.h
@@ -39,11 +39,11 @@ Bridgewater, NJ 08807
 /* #include <fftw.h> */
 /* #include <sdrexport.h> */
 
-#include <common.h>
-
 #ifndef _local_h
 #define _local_h
 
+#include <common.h>
+
 #define RCBASE ".DttSPrc"
 #define PARMPATH "./IPC/SDR-1000-0-commands.fifo"
 #define METERPATH "./IPC/SDR-1000-0-meter.chan"
@@ -68,7 +68,7 @@ extern struct _loc {
   } path;
   struct {
     REAL rate;
-    int size;
+    int size, nrx;
     SDRMODE mode;
   } def;
   struct { int ring, meter; } mult;
diff --git a/jDttSP/main.c b/jDttSP/main.c
index 4aa5884..b68ba95 100644
--- a/jDttSP/main.c
+++ b/jDttSP/main.c
@@ -171,7 +171,15 @@ run_swch(void) {
       top.hold.buf.l[i] *= w, top.hold.buf.r[i] *= w;
     }
     uni.mode.trx = top.swch.trx.next;
-    rx.tick = tx.tick = 0;
+
+    // move this out of main! -----------------------------
+    {
+      int k;
+      for (k = 0; k < uni.multirx.nrx; k++) rx[k].tick = 0;
+    }
+    tx.tick = 0;
+    //-----------------------------------------------------
+
     top.state = top.swch.run.last;
     top.swch.bfct.want = top.swch.bfct.have = 0;
 
@@ -440,6 +448,7 @@ setup_defaults(void) {
   loc.def.rate = DEFRATE;
   loc.def.size = DEFSIZE;
   loc.def.mode = DEFMODE;
+  loc.def.nrx = MAXRX;
   loc.mult.ring = RINGMULT;
   loc.mult.meter = METERMULT;
 
diff --git a/jDttSP/sdr.c b/jDttSP/sdr.c
index 5028ac3..d500676 100644
--- a/jDttSP/sdr.c
+++ b/jDttSP/sdr.c
@@ -73,133 +73,141 @@ setup_all(void) {
     }
   }
 
+  uni.multirx.lis = 0;
+  uni.multirx.nrx = loc.def.nrx;
   uni.tick = 0;
 }
 
 /* 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.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
+  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(agcMED,	// Mode
 			     7,		// Hang
 			     7,		// Size
 			     48,	// Ramp
 			     3,		// Over
 			     3,		// Rcov
-			     CXBsize(rx.buf.o),	// BufSize
+			     CXBsize(rx[k].buf.o),	// BufSize
 			     100.0,	// MaxGain
 			     0.707,	// Limit
 			     1.0,	// CurGain
-			     CXBbase(rx.buf.o));
-  rx.agc.flag = TRUE;
+			     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 = (int) (0.0395 * uni.samplerate + 0.5);
+
+  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 */
@@ -275,11 +283,23 @@ 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);
@@ -293,22 +313,24 @@ 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);
@@ -376,167 +398,189 @@ do_meter(COMPLEX *vec, int len) {
 /* 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 rx[k].squelch.thresh > 10.0 * log10(rx[k].squelch.power);
   } 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_rx_pre(int k) {
+  int i, n = min(CXBhave(rx[k].buf.i), uni.buflen);
 
   //
-  // do shrinkage here
+  // do shrinkage here?
   //
 
-  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);
+  correctIQ(rx[k].buf.i, rx[k].iqfix);
 
-  /* 2nd if conversion happens here */
-  if (rx.osc.gen->Frequency != 0.0) {
-    ComplexOSC(rx.osc.gen);
+  /* 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);
+
+  if (rx[k].mode != SPEC) {
+
+    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);
+
+    if (uni.meter.flag)
+      do_meter(CXBbase(rx[k].buf.o), uni.buflen);
+
+    if (should_do_rx_squelch(k))
+      do_squelch(k);
+
+    else if (rx[k].agc.flag)
+      DigitalAgc(rx[k].agc.gen, rx[k].tick);
+
   } else if (uni.meter.flag)
-    do_meter(CXBbase(rx.buf.o), uni.buflen);
+    do_meter(CXBbase(rx[k].buf.o), uni.buflen);
 }
 
 PRIVATE void
-do_rx_post(void) {
-  int i, n = CXBhave(rx.buf.o);
+do_rx_post(int k) {
+  int i, n = CXBhave(rx[k].buf.o);
 
-  if (!rx.squelch.set)  {
-    no_squelch();
+  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
+  // 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);
+      CXBdata(rx[k].buf.o, i) = Cscl(CXBdata(rx[k].buf.o, i),
+				     rx[k].scl.post.val);
 
-  // not binaural? collapse
-  if (!rx.bin.flag)
+  // not binaural?
+  // position in stereo field
+
+  if (!rx[k].bin.flag)
+    for (i = 0; i < n; i++)
+      CXBdata(rx[k].buf.o, i) = Cscl(rx[k].azim, CXBreal(rx[k].buf.o, i));
+
+#if 0
+  if (!rx[k].bin.flag)
     for (i = 0; i < n; i++)
-      CXBimag(rx.buf.o, i) = CXBreal(rx.buf.o, i);
+      CXBimag(rx[k].buf.o, i) = CXBreal(rx[k].buf.o, i);
+#endif
 }
 
 /* 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);
 }  
 
 //==============================================================
@@ -660,29 +704,42 @@ do_tx(void) {
 
 void
 process_samples(float *bufl, float *bufr, int n) {
-  int i;
+  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] += CXBimag(rx[k].buf.o, i),
+	  bufr[i] += CXBreal(rx[k].buf.o, i);
+	CXBhave(rx[k].buf.o) = n;
+      }
 
-    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:
     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;
-
     do_tx(), tx.tick++;
-
     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;
diff --git a/jDttSP/sdrexport.c b/jDttSP/sdrexport.c
index 1830bd5..4213ef3 100644
--- a/jDttSP/sdrexport.c
+++ b/jDttSP/sdrexport.c
@@ -34,6 +34,6 @@ Bridgewater, NJ 08807
 #include <common.h>
 
 struct _uni uni;
-struct _rx rx;
+struct _rx rx[MAXRX];
 struct _tx tx;
 struct _top top;
diff --git a/jDttSP/sdrexport.h b/jDttSP/sdrexport.h
index 860e3bf..4802d5b 100644
--- a/jDttSP/sdrexport.h
+++ b/jDttSP/sdrexport.h
@@ -36,6 +36,9 @@ Bridgewater, NJ 08807
   
 #include <common.h>
 
+//------------------------------------------------------------------------
+// max no. simultaneous receivers
+#define MAXRX (4)
 //------------------------------------------------------------------------
 /* modulation types, modes */ 
 typedef enum _sdrmode {
@@ -93,6 +96,11 @@ extern struct _uni {
     int bits;
   } wisdom;
 
+  struct {
+    BOOLEAN act[MAXRX];
+    int lis, nac, nrx;
+  } multirx;
+
   long tick;
   
 } uni;
@@ -152,9 +160,10 @@ extern struct _rx {
   } squelch;
   SDRMODE mode;
   struct { BOOLEAN flag; } bin;
-  long tick;
   REAL norm;
-} rx;
+  COMPLEX azim;
+  long tick;
+} rx[MAXRX];
 
 //------------------------------------------------------------------------
 /* TX */ 
diff --git a/jDttSP/update.c b/jDttSP/update.c
index a090b76..aeed07b 100644
--- a/jDttSP/update.c
+++ b/jDttSP/update.c
@@ -35,6 +35,11 @@ Bridgewater, NJ 08807
 
 #include <common.h>
 
+////////////////////////////////////////////////////////////////////////////
+// for commands affecting RX, which RX is Listening
+
+#define RL (uni.multirx.lis)
+
 ////////////////////////////////////////////////////////////////////////////
 
 PRIVATE REAL
@@ -52,28 +57,32 @@ setRXFilter(int n, char **p) {
   if (fabs(low_frequency) >= 0.5 * uni.samplerate) return -1;
   if (fabs(high_frequency) >= 0.5 * uni.samplerate) return -2;
   if ((low_frequency + 10) >= high_frequency) return -3;
-  delFIR_COMPLEX(rx.filt.coef);
+  delFIR_COMPLEX(rx[RL].filt.coef);
 
-  rx.filt.coef = newFIR_Bandpass_COMPLEX(low_frequency,
-					 high_frequency,
-					 uni.samplerate,
-					 ncoef);
+  rx[RL].filt.coef = newFIR_Bandpass_COMPLEX(low_frequency,
+					     high_frequency,
+					     uni.samplerate,
+					     ncoef);
 
   zcvec = newvec_COMPLEX(fftlen, "filter z vec in setFilter");
   ptmp = fftw_create_plan(fftlen, FFTW_FORWARD, uni.wisdom.bits);
 #ifdef LHS
-  for (i = 0; i < ncoef; i++) zcvec[i] = rx.filt.coef->coef[i];
+  for (i = 0; i < ncoef; i++)
+    zcvec[i] = rx[RL].filt.coef->coef[i];
 #else
-  for (i = 0; i < ncoef; i++) zcvec[fftlen - ncoef + i] = rx.filt.coef->coef[i];
+  for (i = 0; i < ncoef; i++)
+    zcvec[fftlen - ncoef + i] = rx[RL].filt.coef->coef[i];
 #endif
-  fftw_one(ptmp, (fftw_complex *) zcvec, (fftw_complex *) rx.filt.ovsv->zfvec);
+  fftw_one(ptmp,
+	   (fftw_complex *) zcvec,
+	   (fftw_complex *) rx[RL].filt.ovsv->zfvec);
   fftw_destroy_plan(ptmp);
   delvec_COMPLEX(zcvec);
-  normalize_vec_COMPLEX(rx.filt.ovsv->zfvec,
-			rx.filt.ovsv->fftlen);
-  memcpy((char *) rx.filt.save,
-	 (char *) rx.filt.ovsv->zfvec,
-	 rx.filt.ovsv->fftlen * sizeof(COMPLEX));
+  normalize_vec_COMPLEX(rx[RL].filt.ovsv->zfvec,
+			rx[RL].filt.ovsv->fftlen);
+  memcpy((char *) rx[RL].filt.save,
+	 (char *) rx[RL].filt.ovsv->zfvec,
+	 rx[RL].filt.ovsv->fftlen * sizeof(COMPLEX));
 
   return 0;
 }
@@ -100,11 +109,15 @@ setTXFilter(int n, char **p) {
   zcvec = newvec_COMPLEX(fftlen, "filter z vec in setFilter");
   ptmp = fftw_create_plan(fftlen, FFTW_FORWARD, uni.wisdom.bits);
 #ifdef LHS
-  for (i = 0; i < ncoef; i++) zcvec[i] = tx.filt.coef->coef[i];
+  for (i = 0; i < ncoef; i++)
+    zcvec[i] = tx.filt.coef->coef[i];
 #else
-  for (i = 0; i < ncoef; i++) zcvec[fftlen - ncoef + i] = tx.filt.coef->coef[i];
+  for (i = 0; i < ncoef; i++)
+    zcvec[fftlen - ncoef + i] = tx.filt.coef->coef[i];
 #endif
-  fftw_one(ptmp, (fftw_complex *) zcvec, (fftw_complex *) tx.filt.ovsv->zfvec);
+  fftw_one(ptmp,
+	   (fftw_complex *) zcvec,
+	   (fftw_complex *) tx.filt.ovsv->zfvec);
   fftw_destroy_plan(ptmp);
   delvec_COMPLEX(zcvec);
   normalize_vec_COMPLEX(tx.filt.ovsv->zfvec,
@@ -137,12 +150,12 @@ setMode(int n, char **p) {
     switch (trx) {
     case TX: tx.mode = mode; break;
     case RX: 
-    default: rx.mode = mode; break;
+    default: rx[RL].mode = mode; break;
     }
   } else
-    tx.mode = rx.mode = uni.mode.sdr = mode;
-  if (rx.mode == AM) rx.am.gen->mode = AMdet;
-  if (rx.mode == SAM) rx.am.gen->mode = SAMdet;
+    tx.mode = rx[RL].mode = uni.mode.sdr = mode;
+  if (rx[RL].mode == AM) rx[RL].am.gen->mode = AMdet;
+  if (rx[RL].mode == SAM) rx[RL].am.gen->mode = SAMdet;
   return 0;
 }
 
@@ -157,10 +170,10 @@ setOsc(int n, char **p) {
     switch (trx) {
     case TX: tx.osc.gen->Frequency = newfreq; break;
     case RX:
-    default: rx.osc.gen->Frequency = newfreq; break;
+    default: rx[RL].osc.gen->Frequency = newfreq; break;
     }
   } else
-    tx.osc.gen->Frequency = rx.osc.gen->Frequency = newfreq;
+    tx.osc.gen->Frequency = rx[RL].osc.gen->Frequency = newfreq;
   return 0;
 }
 
@@ -173,45 +186,45 @@ setSampleRate(int n, char **p) {
 
 PRIVATE int
 setNR(int n, char **p) {
-  rx.anr.flag = atoi(p[0]);
+  rx[RL].anr.flag = atoi(p[0]);
   return 0;
 }
 
 PRIVATE int
 setANF(int n, char **p) {
-  rx.anf.flag = atoi(p[0]);
+  rx[RL].anf.flag = atoi(p[0]);
   return 0;
 }
 
 PRIVATE int
 setNB(int n, char **p) {
-  rx.nb.flag = atoi(p[0]);
+  rx[RL].nb.flag = atoi(p[0]);
   return 0;
 }
 
 PRIVATE int
 setNBvals(int n, char **p) {
- REAL threshold = atof(p[0]);
-  rx.nb.gen->threshold = rx.nb.thresh = threshold;
+  REAL threshold = atof(p[0]);
+  rx[RL].nb.gen->threshold = rx[RL].nb.thresh = threshold;
   return 0;
 }
 
 PRIVATE int
 setSDROM(int n, char **p) {
-  rx.nb_sdrom.flag = atoi(p[0]);
+  rx[RL].nb_sdrom.flag = atoi(p[0]);
   return 0;
 }
 
 PRIVATE int
 setSDROMvals(int n, char **p) {
  REAL threshold = atof(p[0]);
-  rx.nb_sdrom.gen->threshold = rx.nb_sdrom.thresh = threshold;
+  rx[RL].nb_sdrom.gen->threshold = rx[RL].nb_sdrom.thresh = threshold;
   return 0;
 }
 
 PRIVATE int
 setBIN(int n, char **p) {
-  rx.bin.flag = atoi(p[0]);
+  rx[RL].bin.flag = atoi(p[0]);
   return 0;
 }
 
@@ -224,10 +237,10 @@ setfixedAGC(int n, char **p) {
     switch(trx) {
     case TX: tx.agc.gen->gain.now = gain; break;
     case RX:
-    default: rx.agc.gen->gain.now = gain; break;
+    default: rx[RL].agc.gen->gain.now = gain; break;
     }
   } else
-    tx.agc.gen->gain.now = rx.agc.gen->gain.now = gain;
+    tx.agc.gen->gain.now = rx[RL].agc.gen->gain.now = gain;
   return 0;
 }
 
@@ -236,28 +249,28 @@ setRXAGC(int n, char **p) {
   int setit = atoi(p[0]);
   switch (setit) {
   case agcOFF:
-    rx.agc.gen->mode = agcOFF;
-    rx.agc.flag = TRUE;
+    rx[RL].agc.gen->mode = agcOFF;
+    rx[RL].agc.flag = TRUE;
     break;
   case agcSLOW:
-    rx.agc.gen->mode = agcSLOW;
-    rx.agc.gen->hang = 10;
-    rx.agc.flag = TRUE;
+    rx[RL].agc.gen->mode = agcSLOW;
+    rx[RL].agc.gen->hang = 10;
+    rx[RL].agc.flag = TRUE;
     break;
   case agcMED:
-    rx.agc.gen->mode = agcMED;
-    rx.agc.gen->hang = 6;
-    rx.agc.flag = TRUE;
+    rx[RL].agc.gen->mode = agcMED;
+    rx[RL].agc.gen->hang = 6;
+    rx[RL].agc.flag = TRUE;
     break;
   case agcFAST:
-    rx.agc.gen->mode = agcFAST;
-    rx.agc.gen->hang = 3;
-    rx.agc.flag = TRUE;
+    rx[RL].agc.gen->mode = agcFAST;
+    rx[RL].agc.gen->hang = 3;
+    rx[RL].agc.flag = TRUE;
     break;
   case agcLONG:
-    rx.agc.gen->mode = agcLONG;
-    rx.agc.gen->hang = 23;
-    rx.agc.flag = TRUE;
+    rx[RL].agc.gen->mode = agcLONG;
+    rx[RL].agc.gen->hang = 23;
+    rx[RL].agc.flag = TRUE;
     break;
   }
   return 0;
@@ -266,14 +279,14 @@ setRXAGC(int n, char **p) {
 PRIVATE int
 setRXAGCCompression(int n, char **p) {
   REAL rxcompression = atof(p[0]);
-  rx.agc.gen->gain.top = pow(10.0 , rxcompression * 0.05);
+  rx[RL].agc.gen->gain.top = pow(10.0 , rxcompression * 0.05);
   return 0;
 }
 
 PRIVATE int
 setRXAGCHang(int n, char **p) {
   int hang = atoi(p[0]);
-  rx.agc.gen->hang =
+  rx[RL].agc.gen->hang =
     max(1,
 	min(23,
 	    hang * uni.samplerate / (1e3 * uni.buflen)));
@@ -283,7 +296,7 @@ setRXAGCHang(int n, char **p) {
 PRIVATE int
 setRXAGCLimit(int n, char **p) {
   REAL limit = atof(p[0]);
-  rx.agc.gen->gain.lim = 0.001 * limit;
+  rx[RL].agc.gen->gain.lim = 0.001 * limit;
   return 0;
 }
 
@@ -387,7 +400,7 @@ apply_txeq_band(REAL lof, REAL dB, REAL hif) {
   REAL g = dB2lin(dB);
   COMPLEX *src = tx.filt.save,
           *trg = tx.filt.ovsv->zfvec;
-   for (i = lox; i < hix; i++) {
+  for (i = lox; i < hix; i++) {
     trg[i] = Cscl(src[i], g);
     if (i) {
       int j = l - i;
@@ -400,6 +413,7 @@ apply_txeq_band(REAL lof, REAL dB, REAL hif) {
 // 0 dB1 75 dB2 150 dB3 300 dB4 600 dB5 1200 dB6 2000 dB7 2800 dB8 3600
 // approximates W2IHY bandcenters.
 // no args, revert to no EQ.
+
 PRIVATE int
 setTXEQ(int n, char **p) {
   if (n < 3) {
@@ -429,10 +443,10 @@ apply_rxeq_band(REAL lof, REAL dB, REAL hif) {
   int i,
       lox = f2x(lof),
       hix = f2x(hif),
-      l = rx.filt.ovsv->fftlen;
+      l = rx[RL].filt.ovsv->fftlen;
   REAL g = dB2lin(dB);
-  COMPLEX *src = rx.filt.save,
-          *trg = rx.filt.ovsv->zfvec;
+  COMPLEX *src = rx[RL].filt.save,
+          *trg = rx[RL].filt.ovsv->zfvec;
   for (i = lox; i < hix; i++) {
     trg[i] = Cscl(src[i], g);
     if (i) {
@@ -446,9 +460,9 @@ PRIVATE int
 setRXEQ(int n, char **p) {
   if (n < 3) {
     // revert to no EQ
-    memcpy((char *) rx.filt.ovsv->zfvec,
-	   (char *) rx.filt.save,
-	   rx.filt.ovsv->fftlen * sizeof(COMPLEX));
+    memcpy((char *) rx[RL].filt.ovsv->zfvec,
+	   (char *) rx[RL].filt.save,
+	   rx[RL].filt.ovsv->fftlen * sizeof(COMPLEX));
     return 0;
   } else {
     int i;
@@ -472,10 +486,10 @@ setANFvals(int n, char **p) {
       delay = atoi(p[1]);
   REAL gain = atof(p[2]),
        leak = atof(p[3]);
-  rx.anf.gen->adaptive_filter_size = taps;
-  rx.anf.gen->delay = delay;
-  rx.anf.gen->adaptation_rate = gain;
-  rx.anf.gen->leakage = leak;
+  rx[RL].anf.gen->adaptive_filter_size = taps;
+  rx[RL].anf.gen->delay = delay;
+  rx[RL].anf.gen->adaptation_rate = gain;
+  rx[RL].anf.gen->leakage = leak;
   return 0;
 }
 
@@ -485,10 +499,10 @@ setNRvals(int n, char **p) {
       delay = atoi(p[1]);
   REAL gain = atof(p[2]),
        leak = atof(p[3]);
-  rx.anr.gen->adaptive_filter_size = taps;
-  rx.anr.gen->delay = delay;
-  rx.anr.gen->adaptation_rate = gain;
-  rx.anr.gen->leakage = leak;
+  rx[RL].anr.gen->adaptive_filter_size = taps;
+  rx[RL].anr.gen->delay = delay;
+  rx[RL].anr.gen->adaptation_rate = gain;
+  rx[RL].anr.gen->leakage = leak;
   return 0;
 }
 
@@ -496,34 +510,34 @@ PRIVATE int
 setcorrectIQ(int n, char **p) {
   int phaseadjustment = atoi(p[0]),
       gainadjustment  = atoi(p[1]);
-  rx.iqfix->phase = 0.001 * (REAL) phaseadjustment;
-  rx.iqfix->gain  = 1.0+ 0.001 * (REAL) gainadjustment;
+  rx[RL].iqfix->phase = 0.001 * (REAL) phaseadjustment;
+  rx[RL].iqfix->gain  = 1.0+ 0.001 * (REAL) gainadjustment;
   return 0;
 }
 
 PRIVATE int
 setcorrectIQphase(int n, char **p) {
   int phaseadjustment = atoi(p[0]);
-  rx.iqfix->phase = 0.001 * (REAL) phaseadjustment;
+  rx[RL].iqfix->phase = 0.001 * (REAL) phaseadjustment;
   return 0;
 }
 
 PRIVATE int
 setcorrectIQgain(int n, char **p) {
   int gainadjustment = atoi(p[0]);
-  rx.iqfix->gain = 1.0 + 0.001 * (REAL) gainadjustment;
+  rx[RL].iqfix->gain = 1.0 + 0.001 * (REAL) gainadjustment;
   return 0;
 }
 
 PRIVATE int
 setSquelch(int n, char **p) {
-  rx.squelch.thresh = -atof(p[0]);
+  rx[RL].squelch.thresh = -atof(p[0]);
   return 0;
 }
 
 PRIVATE int
 setSquelchSt(int n, char **p) {
-  rx.squelch.flag = atoi(p[0]);
+  rx[RL].squelch.flag = atoi(p[0]);
   return 0;
 }
 
@@ -546,29 +560,29 @@ setSpotToneVals(int n, char **p) {
        freq = atof(p[1]),
        rise = atof(p[2]),
        fall = atof(p[3]);
-  setSpotToneGenVals(rx.spot.gen, gain, freq, rise, fall);
+  setSpotToneGenVals(rx[RL].spot.gen, gain, freq, rise, fall);
   return 0;
 }
 
 PRIVATE int
 setSpotTone(int n, char **p) {
   if (atoi(p[0])) {
-    SpotToneOn(rx.spot.gen);
-    rx.spot.flag = TRUE;
+    SpotToneOn(rx[RL].spot.gen);
+    rx[RL].spot.flag = TRUE;
   } else
-    SpotToneOff(rx.spot.gen);
+    SpotToneOff(rx[RL].spot.gen);
   return 0;
 }
 
 PRIVATE int
 setRXPreScl(int n, char **p) {
-  rx.scl.pre.flag = atoi(p[0]);
+  rx[RL].scl.pre.flag = atoi(p[0]);
   return 0;
 }
 
 PRIVATE int
 setRXPreSclVal(int n, char **p) {
-  rx.scl.pre.val = dB2lin(atof(p[0]));
+  rx[RL].scl.pre.val = dB2lin(atof(p[0]));
   return 0;
 }
 
@@ -586,13 +600,13 @@ setTXPreSclVal(int n, char **p) {
 
 PRIVATE int
 setRXPostScl(int n, char **p) {
-  rx.scl.post.flag = atoi(p[0]);
+  rx[RL].scl.post.flag = atoi(p[0]);
   return 0;
 }
 
 PRIVATE int
 setRXPostSclVal(int n, char **p) {
-  rx.scl.post.val = dB2lin(atof(p[0]));
+  rx[RL].scl.post.val = dB2lin(atof(p[0]));
   return 0;
 }
 
@@ -647,6 +661,88 @@ setRingBufferReset(int n, char **p) {
   return 0;
 }
 
+PRIVATE int
+setRXListen(int n, char **p) {
+  int lis = atoi(p[0]);
+  if (lis < 0 || lis >= uni.multirx.nrx)
+    return -1;
+  else {
+    uni.multirx.lis = lis;
+    return 0;
+  }
+}
+
+PRIVATE int
+setRXOn(int n, char **p) {
+  if (n < 1) {
+    if (uni.multirx.act[RL])
+      return -1;
+    else {
+      uni.multirx.act[RL] = TRUE;
+      uni.multirx.nac++;
+      rx[RL].tick = 0;
+      return 0;
+    }
+  } else {
+    int k = atoi(p[0]);
+    if (k < 0 || k >= uni.multirx.nrx)
+      return -1;
+    else {
+      if (uni.multirx.act[k])
+	return -1;
+      else {
+	uni.multirx.act[k] = TRUE;
+	uni.multirx.nac++;
+	rx[k].tick = 0;
+	return 0;
+      }
+    }
+  }
+}
+
+PRIVATE int
+setRXOff(int n, char **p) {
+  if (n < 1) {
+    if (!uni.multirx.act[RL])
+      return -1;
+    else {
+      uni.multirx.act[RL] = FALSE;
+      --uni.multirx.nac;
+      return 0;
+    }
+  } else {
+    int k = atoi(p[0]);
+    if (k < 0 || k >= uni.multirx.nrx)
+      return -1;
+    else {
+      if (!uni.multirx.act[k])
+	return -1;
+      else {
+	uni.multirx.act[k] = FALSE;
+	--uni.multirx.nac;
+	return 0;
+      }
+    }
+  }
+}
+
+PRIVATE int
+setRXPan(int n, char **p) {
+  REAL pos, theta;
+  if (n < 1) {
+    pos = 0.5;
+    theta = (1.0 - pos) * M_PI / 2.0;
+    rx[RL].azim = Cmplx(cos(theta), sin(theta));
+    return 0;
+  } else {
+    if ((pos = atof(p[0])) < 0.0 || pos > 1.0)
+      return -1;
+    theta = (1.0 - pos) * M_PI / 2.0;
+    rx[RL].azim = Cmplx(cos(theta), sin(theta));
+    return 0;
+  }
+}
+
 //========================================================================
 
 #include <thunk.h>
@@ -700,6 +796,10 @@ CTE update_cmds[] = {
   {"setfixedAGC", setfixedAGC},
   {"setMonDump", setMonDump},
   {"setRingBufferReset", setRingBufferReset},
+  {"setRXListen", setRXListen},
+  {"setRXOn", setRXOn},
+  {"setRXOff", setRXOff},
+  {"setRXPan", setRXPan},
   { 0, 0 }
 };