From: dttsp <dttsp>
Date: Tue, 10 May 2005 12:59:01 +0000 (+0000)
Subject: Major changes. Added metering and power spectrum, other fixes. Rearranged headers... 
X-Git-Url: https://git.rkrishnan.org/specifications/components/com_hotproperty/%22doc.html/flags/rgr-080307.php?a=commitdiff_plain;h=219b895aaec3b93a1e4af62026e7a2f8b5391ac0;p=dttsp.git

Major changes. Added metering and power spectrum, other fixes. Rearranged headers somewhat.
---

diff --git a/jDttSP/Makefile b/jDttSP/Makefile
index f2e978e..d9bfdeb 100644
--- a/jDttSP/Makefile
+++ b/jDttSP/Makefile
@@ -1,5 +1,5 @@
-CFLAGS = -g -O3 -I. -I/usr/local/include
-#CFLAGS = -g -I. -I/usr/local/include
+#CFLAGS = -g -O3 -I. -I/usr/local/include
+CFLAGS = -g -I. -I/usr/local/include
 LIBS = -L/usr/local/lib -ljack -lpthread -lgsl -lgslcblas -lfftw -lm
 #LIBS = -lefence -L/usr/local/lib -ljack -lpthread -lgsl -lgslcblas -lfftw -lm
 
@@ -18,12 +18,14 @@ OBJ =	am_demod.o\
 	filter.o\
 	fm_demod.o\
 	lmadf.o\
+	meter.o\
 	noiseblanker.o\
 	oscillator.o\
 	ovsv.o\
 	ringb.o\
 	sdr.o\
 	sdrexport.o\
+	spectrum.o\
 	speechproc.o\
 	splitfields.o\
 	spottone.o\
@@ -36,7 +38,7 @@ KOBJ = oscillator.o cwtones.o chan.o ringb.o banal.o bufvec.o splitfields.o cxop
 jsdr:	main.o $(OBJ)
 	$(CC) -o jsdr main.o $(OBJ) $(LIBS)
 
-all:	jsdr mkchan ipc metermon keyd keyb
+all:	jsdr mkchan ipc metermon specmon keyd keyb
 
 keyd:	keyd.o keyer.o $(KOBJ)
 	$(CC) -o keyd keyd.o keyer.o $(KOBJ) $(LIBS)
@@ -47,11 +49,11 @@ keyb:	keyb.o keyer.o $(KOBJ)
 
 $(OBJ): sdrexport.h
 
-metermon:	metermon.o chan.o ringb.o bufvec.o cxops.o banal.o
-	$(CC) -o metermon metermon.o chan.o ringb.o bufvec.o cxops.o banal.o $(LIBS)
+metermon:	metermon.o 
+	$(CC) -o metermon metermon.o $(LIBS)
 
-mkchan:	mkchan.o bufvec.o banal.o cxops.o
-	$(CC) -o mkchan mkchan.o bufvec.o banal.o cxops.o $(LIBS)
+specmon:	specmon.o 
+	$(CC) -o specmon specmon.o $(LIBS)
 
 ipc:	mkchan
 	./setup-ipc
@@ -59,13 +61,16 @@ ipc:	mkchan
 obj:	$(OBJ)
 
 clean:
-	/bin/rm *.o jsdr mkchan metermon keyd #$(staticlibname)
+	/bin/rm *.o jsdr keyb keyd metermon #$(staticlibname)
 	#/bin/rm IPC/*
 
 staticlib:	$(OBJ)
 	ar rcs $(staticlibname) $(OBJ)
 	ranlib $(staticlibname)
 
+#mkchan:	mkchan.o bufvec.o banal.o cxops.o
+#	$(CC) -o mkchan mkchan.o bufvec.o banal.o cxops.o $(LIBS)
+
 #CFLAGS = -fPIC -g -O3 -I. -I/usr/local/include
 # sharedlibname=libDttSP.so
 # sharedlibvers=0.0.1
diff --git a/jDttSP/chan.c b/jDttSP/chan.c
index 651274c..ba7e246 100644
--- a/jDttSP/chan.c
+++ b/jDttSP/chan.c
@@ -54,6 +54,17 @@ putChan_nowait(Chan c, char *data, size_t size) {
   } else return FALSE;
 }
 
+size_t
+putChan_force(Chan c, char *data, size_t size) {
+  if (ringb_write_space(c->rb) >= size) {
+    ringb_write(c->rb, data, size);
+    return size;
+  } else {
+    ringb_reset(c->rb);
+    return ringb_write(c->rb, data, size);
+  }
+}
+
 BOOLEAN
 getChan_nowait(Chan c, char *data, size_t size) {
   if (ringb_read_space(c->rb) >= size) {
@@ -62,6 +73,9 @@ getChan_nowait(Chan c, char *data, size_t size) {
   } else return FALSE;
 }
 
+void
+resetChan(Chan c) { ringb_reset(c->rb); }
+
 Chan
 openChan(char *path, size_t want) {
   Chan c = (Chan) safealloc(sizeof(ChanDesc), 1, "Chan header");
diff --git a/jDttSP/chan.h b/jDttSP/chan.h
index bf92625..28197d6 100644
--- a/jDttSP/chan.h
+++ b/jDttSP/chan.h
@@ -75,7 +75,9 @@ struct _chan {
 extern size_t putChan(Chan c, char *data, size_t size);
 extern size_t getChan(Chan c, char *data, size_t size);
 extern BOOLEAN putChan_nowait(Chan c, char *data, size_t size);
+extern size_t putChan_force(Chan c, char *data, size_t size);
 extern BOOLEAN getChan_nowait(Chan c, char *data, size_t size);
+extern void resetChan(Chan c);
 // NB want will be rounded up to a power of 2
 extern Chan openChan(char *path, size_t want);
 extern void closeChan(Chan c);
diff --git a/jDttSP/command-vocabulary b/jDttSP/command-vocabulary
index 7ea1160..9eb1270 100644
--- a/jDttSP/command-vocabulary
+++ b/jDttSP/command-vocabulary
@@ -33,9 +33,6 @@ setcorrectIQ phase gain	// int, int
 setcorrectIQphase phase	// int
 setcorrectIQgain gain	// int
 setSquelch lev		// float, gain, RX only; default -30dB
-setMeterOffset lev	// float, RX only, appears only in squelch calc
-setATTOffset val	// float, RX only, appears only in squelch calc
-setGainOffset		// float, RX only, appears only in squelch calc
 setSquelchSt T|F	// on/off, RX only
 setTRX trx		// trx = RX|TX
 setRunState state	// RUN_MUTE, RUN_PASS, RUN_PLAY
@@ -58,3 +55,12 @@ setRXPan pos		// set azimuth for currently listening receiver to pos (0...1)
 setAuxMixGain [gain [trx]]	// set mixing level for aux inputs
 setAuxMixSt [flag [trx]]	// set aux input mix on/off
 
+setMeterType type [trx] // set meter type for trx, default rx
+			// types: SIG, AVG, REAL, IMAG; default SIG
+setSpectrumType [type [scale [rx]]] // set spectrum type, scale, which rx
+			// types: SEMI_RAW, PRE_FILT, POST_FILT (deflt POST)
+			// scale: PWR, MAG (dflt PWR)
+			// which rx dflt 0
+reqMeter [label]	// sends entire rx or tx meter block to METERPATH
+reqSpectrum [label]	// sends current spec snapshot to SPECPATH
+
diff --git a/jDttSP/common.h b/jDttSP/common.h
index 0f71b86..e31c20c 100644
--- a/jDttSP/common.h
+++ b/jDttSP/common.h
@@ -33,10 +33,10 @@ Bridgewater, NJ 08807
 */
 
 #ifndef _common_h
-
 #define _common_h
 
 #include <fromsys.h>
+#include <defs.h>
 #include <banal.h>
 #include <splitfields.h>
 #include <datatypes.h>
@@ -59,6 +59,7 @@ Bridgewater, NJ 08807
 #include <spottone.h>
 #include <update.h>
 #include <meter.h>
+#include <spectrum.h>
 #include <sdrexport.h>
 #include <local.h>
 
diff --git a/jDttSP/defs.h b/jDttSP/defs.h
new file mode 100644
index 0000000..b75f456
--- /dev/null
+++ b/jDttSP/defs.h
@@ -0,0 +1,68 @@
+/* defs.h */
+/*
+This file is part of a program that implements a Software-Defined Radio.
+
+Copyright (C) 2004 by Frank Brickle, AB2KT and Bob McGwier, N4HY
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+The authors can be reached by email at
+
+ab2kt@arrl.net
+or
+rwmcgwier@comcast.net
+
+or by paper mail at
+
+The DTTS Microwave Society
+6 Kathleen Place
+Bridgewater, NJ 08807
+*/  
+
+#ifndef _defs_h
+#define _defs_h  
+
+#define RINGMULT (4)
+#define METERMULT (20)
+#define SPECMULT (4)
+#define DEFRATE (48000.0)
+#define DEFSIZE (2048)
+#define DEFMODE (SAM)
+#define DEFSPEC (4096)
+
+#define MAXRX (4)
+
+#ifndef MAXPATHLEN
+#define MAXPATHLEN (2048)
+#endif  
+
+typedef enum _sdrmode {
+  LSB,				//  0
+  USB,				//  1
+  DSB,				//  2
+  CWL,				//  3
+  CWU,				//  4
+  FMN,				//  5
+  AM,				//  6
+  PSK,				//  7
+  SPEC,				//  8
+  RTTY,				//  9
+  SAM,				// 10
+  DRM				// 11
+} SDRMODE;
+
+typedef enum _trxmode { RX, TX } TRXMODE;
+
+#endif
diff --git a/jDttSP/keyd.c b/jDttSP/keyd.c
index 12e7797..a9da384 100644
--- a/jDttSP/keyd.c
+++ b/jDttSP/keyd.c
@@ -215,7 +215,7 @@ key_thread(void) {
 //------------------------------------------------------------------------
 
 // update keyer parameters via text input from stdin
-// <wpm xxx> -> set keyer speed to xxx
+// <wpm xxx>  -> set keyer speed to xxx
 // <gain xxx> -> set gain to xxx (dB)
 // <freq xxx> -> set freq to xxx
 // <ramp xxx> -> set attack/decay times to xxx ms
diff --git a/jDttSP/local.h b/jDttSP/local.h
index dda789e..724a8f6 100644
--- a/jDttSP/local.h
+++ b/jDttSP/local.h
@@ -46,17 +46,9 @@ Bridgewater, NJ 08807
 
 #define RCBASE ".DttSPrc"
 #define PARMPATH "./IPC/SDR-1000-0-commands.fifo"
-#define METERPATH "./IPC/SDR-1000-0-meter.chan"
+#define METERPATH "./IPC/SDR-1000-0-meter.fifo"
+#define SPECPATH "./IPC/SDR-1000-0-spec.fifo"
 #define WISDOMPATH "./wisdom"
-#define RINGMULT (4)
-#define METERMULT (2)
-#define DEFRATE (48000.0)
-#define DEFSIZE (2048)
-#define DEFMODE (SAM)
-
-#ifndef MAXPATHLEN
-#define MAXPATHLEN 2048
-#endif  
 
 extern struct _loc {
   char name[MAXPATHLEN];
@@ -64,14 +56,15 @@ extern struct _loc {
     char rcfile[MAXPATHLEN],
          parm[MAXPATHLEN],
          meter[MAXPATHLEN],
+         spec[MAXPATHLEN],
          wisdom[MAXPATHLEN];
   } path;
   struct {
     REAL rate;
-    int size, nrx;
+    int size, nrx, spec;
     SDRMODE mode;
   } def;
-  struct { int ring, meter; } mult;
+  struct { int ring; } mult;
 } loc;
 
 #endif
diff --git a/jDttSP/main.c b/jDttSP/main.c
index b74e9df..7575afb 100644
--- a/jDttSP/main.c
+++ b/jDttSP/main.c
@@ -2,7 +2,7 @@
 
 This file is part of a program that implements a Software-Defined Radio.
 
-Copyright (C) 2004 by Frank Brickle, AB2KT and Bob McGwier, N4HY
+Copyright (C) 2004-5 by Frank Brickle, AB2KT and Bob McGwier, N4HY
 
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -42,18 +42,69 @@ struct _loc loc;
 // most of what little we know here about the inner loop,
 // functionally speaking
 
+extern void reset_meters(void);
+extern void reset_spectrum(void);
+extern void reset_counters(void);
 extern void process_samples(float *, float *, float *, float *, int);
 extern void setup_workspace(void);
 extern void destroy_workspace(void);
 
 //========================================================================
 
-void
-clear_jack_ringbuffer(jack_ringbuffer_t *rb, int nbytes) {
-  int i;
-  char zero = 0;
-  for (i = 0; i < nbytes; i++)
-    jack_ringbuffer_write(rb, &zero, 1);
+PRIVATE void
+spectrum_thread(void) {
+
+  while (top.running) {
+    sem_wait(&top.sync.pws.sem);
+
+    compute_spectrum(&uni.spec);
+
+    if (fwrite((char *) &uni.spec.label, sizeof(int), 1, top.meas.spec.fp)
+	!= 1) {
+      fprintf(stderr, "error writing spectrum label\n");
+      exit(1);
+    }
+
+    if (fwrite((char *) uni.spec.output, sizeof(float), uni.spec.size, top.meas.spec.fp)
+	!= uni.spec.size) {
+      fprintf(stderr, "error writing spectrum\n");
+      exit(1);
+    }
+
+    fflush(top.meas.spec.fp);
+  }
+
+  pthread_exit(0);
+}
+
+PRIVATE void
+meter_thread(void) {
+
+  while (top.running) {
+    sem_wait(&top.sync.mtr.sem);
+
+    if (fwrite((char *) &uni.meter.label, sizeof(int), 1, top.meas.mtr.fp)
+	!= 1) {
+      fprintf(stderr, "error writing meter label\n");
+      exit(1);
+    }
+
+    if (fwrite((char *) uni.meter.snap.rx, sizeof(REAL), MAXRX * RXMETERPTS, top.meas.mtr.fp)
+	!= MAXRX * RXMETERPTS) {
+      fprintf(stderr, "error writing rx meter\n");
+      exit(1);
+    }
+
+    if (fwrite((char *) uni.meter.snap.tx, sizeof(REAL), TXMETERPTS, top.meas.mtr.fp)
+	!= TXMETERPTS) {
+      fprintf(stderr, "error writing tx meter\n");
+      exit(1);
+    }
+
+    fflush(top.meas.mtr.fp);
+  }
+
+  pthread_exit(0);
 }
 
 //========================================================================
@@ -86,6 +137,8 @@ process_updates_thread(void) {
   pthread_exit(0);
 }
 
+//========================================================================
+
 PRIVATE void 
 gethold(void) {
   if (jack_ringbuffer_write_space(top.jack.ring.o.l)
@@ -136,6 +189,8 @@ canhold(void) {
     >= top.hold.size.bytes;
 }
 
+//------------------------------------------------------------------------
+
 PRIVATE void 
 run_mute(void) {
   memset((char *) top.hold.buf.l, 0, top.hold.size.bytes);
@@ -186,19 +241,15 @@ run_swch(void) {
     }
     uni.mode.trx = top.swch.trx.next;
 
-    // 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;
 
     jack_ringbuffer_reset(top.jack.ring.o.l);
     jack_ringbuffer_reset(top.jack.ring.o.r);
+
+    reset_meters();
+    reset_spectrum();
+    reset_counters();
   }
 
   process_samples(top.hold.buf.l, top.hold.buf.r,
@@ -208,6 +259,13 @@ run_swch(void) {
 
 //========================================================================
 
+void
+clear_jack_ringbuffer(jack_ringbuffer_t *rb, int nbytes) {
+  int i;
+  char zero = 0;
+  for (i = 0; i < nbytes; i++)
+    jack_ringbuffer_write(rb, &zero, 1);
+}
 
 PRIVATE void 
 audio_callback(jack_nframes_t nframes, void *arg) {
@@ -276,6 +334,8 @@ audio_callback(jack_nframes_t nframes, void *arg) {
     sem_post(&top.sync.mon.sem);
 }
 
+//========================================================================
+
 PRIVATE void 
 process_samples_thread(void) {
   while (top.running) {
@@ -320,6 +380,10 @@ execute(void) {
   pthread_join(top.thrd.trx.id, 0);
   pthread_join(top.thrd.upd.id, 0);
   pthread_join(top.thrd.mon.id, 0);
+  if (uni.meter.flag)
+    pthread_join(top.thrd.mtr.id, 0);
+  if (uni.spec.flag)
+    pthread_join(top.thrd.pws.id, 0);
   
   // stop audio processing
   jack_client_close(top.jack.client);
@@ -339,6 +403,13 @@ closeup(void) {
   safefree((char *) top.hold.aux.r);
   safefree((char *) top.hold.aux.l);
 
+  fclose(top.parm.fp);
+
+  if (uni.meter.flag)
+    fclose(top.meas.mtr.fp);
+  if (uni.spec.flag)
+    fclose(top.meas.spec.fp);
+
   destroy_workspace();
 
   exit(0);
@@ -351,15 +422,8 @@ usage(void) {
   fprintf(stderr, "flags:\n");
   fprintf(stderr, "	-v		verbose commentary\n");
   fprintf(stderr, "	-m		do metering\n");
+  fprintf(stderr, "	-s		do spectrum\n");
   fprintf(stderr, "	-l file		execute update commands in file at startup\n");
-  fprintf(stderr, "	-P cmdpath	path to command/update pipe\n");
-  fprintf(stderr, "	-S s-mtrpath	path to S-meter output channel\n");
-  fprintf(stderr, "	-W wispath	path to FFTW wisdom file\n");
-  fprintf(stderr, "	-R rate		sampling rate\n");
-  fprintf(stderr, "	-B bufsize	internal DSP buffer size\n");
-  fprintf(stderr, "	-M mode		start up in mode (SAM, USB, LCW, etc.)\n");
-  fprintf(stderr, "	-G num		use num as ringbuffer mult\n");
-  fprintf(stderr, "	-E num		use num as meter chan mult\n");
   fprintf(stderr, "'file' arg unused, but available\n");
   exit(1);
 }
@@ -405,6 +469,16 @@ setup_updates(void) {
     fprintf(stderr, "can't fdopen parm pipe %s\n", loc.path.parm);
     exit(1);
   }
+
+  // do this here 'cuz the update thread is controlling the action
+  if (uni.meter.flag) {
+    top.meas.mtr.path = loc.path.meter;
+    top.meas.mtr.fp = efopen(top.meas.mtr.path, "r+");
+  }
+  if (uni.spec.flag) {
+    top.meas.spec.path = loc.path.spec;
+    top.meas.spec.fp = efopen(top.meas.spec.path, "r+");
+  }
 }
 
 PRIVATE void
@@ -477,6 +551,14 @@ setup_threading(void) {
   pthread_create(&top.thrd.trx.id, NULL, (void *) process_samples_thread, NULL);
   sem_init(&top.sync.mon.sem, 0, 0);
   pthread_create(&top.thrd.mon.id, NULL, (void *) monitor_thread, NULL);
+  if (uni.meter.flag) {
+    sem_init(&top.sync.mtr.sem, 0, 0);
+    pthread_create(&top.thrd.mtr.id, NULL, (void *) meter_thread, NULL);
+  }
+  if (uni.spec.flag) {
+    sem_init(&top.sync.pws.sem, 0, 0);
+    pthread_create(&top.thrd.pws.id, NULL, (void *) spectrum_thread, NULL);
+  }
 } 
 
 //========================================================================
@@ -488,13 +570,14 @@ setup_defaults(void) {
   strcpy(loc.path.rcfile, RCBASE);
   strcpy(loc.path.parm, PARMPATH);
   strcpy(loc.path.meter, METERPATH);
+  strcpy(loc.path.spec, SPECPATH);
   strcpy(loc.path.wisdom, WISDOMPATH);
   loc.def.rate = DEFRATE;
   loc.def.size = DEFSIZE;
   loc.def.mode = DEFMODE;
+  loc.def.spec = DEFSPEC;
   loc.def.nrx = MAXRX;
   loc.mult.ring = RINGMULT;
-  loc.mult.meter = METERMULT;
 
   {
     char *ep;
@@ -502,9 +585,9 @@ setup_defaults(void) {
     if ((ep = getenv("SDR_RCBASE"))) strcpy(loc.path.rcfile, ep);
     if ((ep = getenv("SDR_PARMPATH"))) strcpy(loc.path.parm, ep);
     if ((ep = getenv("SDR_METERPATH"))) strcpy(loc.path.meter, ep);
+    if ((ep = getenv("SDR_SPECPATH"))) strcpy(loc.path.spec, ep);
     if ((ep = getenv("SDR_WISDOMPATH"))) strcpy(loc.path.wisdom, ep);
     if ((ep = getenv("SDR_RINGMULT"))) loc.mult.ring = atoi(ep);
-    if ((ep = getenv("SDR_METERMULT"))) loc.mult.meter = atoi(ep);
     if ((ep = getenv("SDR_DEFRATE"))) loc.def.rate = atof(ep);
     if ((ep = getenv("SDR_DEFSIZE"))) loc.def.size = atoi(ep);
     if ((ep = getenv("SDR_DEFMODE"))) loc.def.mode = atoi(ep);
@@ -531,35 +614,14 @@ setup(int argc, char **argv) {
       case 'v':
 	top.verbose = TRUE;
 	break;
-      case 'l':
-	strcpy(loc.path.rcfile, argv[++i]);
-	break;
       case 'm':
 	uni.meter.flag = TRUE;
 	break;
-      case 'P':
-	strcpy(loc.path.parm, argv[++i]);
+      case 's':
+	uni.spec.flag = TRUE;
 	break;
-      case 'S':
-	strcpy(loc.path.meter, argv[++i]);
-	break;
-      case 'W':
-	strcpy(loc.path.wisdom, argv[++i]);
-	break;
-      case 'R':
-	loc.def.rate = atof(argv[++i]);
-	break;
-      case 'B':
-	loc.def.size = atoi(argv[++i]);
-	break;
-      case 'M':
-	loc.def.mode = atoi(argv[++i]);
-	break;
-      case 'G':
-	loc.mult.ring = atoi(argv[++i]);
-	break;
-      case 'E':
-	loc.mult.meter = atoi(argv[++i]);
+      case 'l':
+	strcpy(loc.path.rcfile, argv[++i]);
 	break;
       default:
 	usage();
diff --git a/jDttSP/meter.h b/jDttSP/meter.h
index 57e7b71..3deb654 100644
--- a/jDttSP/meter.h
+++ b/jDttSP/meter.h
@@ -1,12 +1,98 @@
+/* meter.h */
+/*
+This file is part of a program that implements a Software-Defined Radio.
+
+Copyright (C) 2004-5 by Frank Brickle, AB2KT and Bob McGwier, N4HY
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+The authors can be reached by email at
+
+ab2kt@arrl.net
+or
+rwmcgwier@comcast.net
+
+or by paper mail at
+
+The DTTS Microwave Society
+6 Kathleen Place
+Bridgewater, NJ 08807
+*/  
+
 #ifndef _meter_h
 #define _meter_h
 
+#include <fromsys.h>
+#include <defs.h>
+#include <banal.h>
+#include <splitfields.h>
+#include <datatypes.h>
+#include <bufvec.h>
+#include <cxops.h>
+#include <ringb.h>
+#include <chan.h>
+#include <lmadf.h>
+#include <fftw.h>
+#include <ovsv.h>
+#include <filter.h>
+#include <oscillator.h>
+#include <digitalagc.h>
+#include <am_demod.h>
+#include <fm_demod.h>
+#include <noiseblanker.h>
+#include <correctIQ.h>
+#include <crc16.h>
+#include <speechproc.h>
+#include <spottone.h>
+#include <update.h>
+
 typedef enum {
   SIGNAL_STRENGTH, 
   AVG_SIGNAL_STRENGTH, 
   ADC_REAL, 
-  ADC_IMAG,
-  AGC_GAIN
+  ADC_IMAG
 } METERTYPE;
 
+#define RXMETERPTS (3)
+#define RXMETER_PRE_CONV (0)
+#define RXMETER_PRE_FILT (1)
+#define RXMETER_POST_FILT (2)
+
+#define TXMETERPTS (1)
+#define TXMETER_POST_MOD (0)
+
+typedef
+struct _meter_block {
+  BOOLEAN flag;
+  int label;
+  struct {
+    METERTYPE type;
+    REAL val[MAXRX][RXMETERPTS],
+         avg[MAXRX][RXMETERPTS];
+  } rx;
+  struct {
+    METERTYPE type;
+    REAL val[TXMETERPTS],
+         avg[TXMETERPTS];
+  } tx;
+  struct {
+    REAL rx[MAXRX * RXMETERPTS],
+         tx[TXMETERPTS];
+  } snap;
+} MeterBlock;
+
+extern void snap_meter(MeterBlock *mb, int label);
+
 #endif
diff --git a/jDttSP/metermon.c b/jDttSP/metermon.c
index 1bd875b..09a6b6e 100644
--- a/jDttSP/metermon.c
+++ b/jDttSP/metermon.c
@@ -1,44 +1,62 @@
 /* metermon.c */
 
-#include <chan.h>
+#include <common.h>
 
-#define METERPATH "./IPC/SDR-1000-0-meter.chan"
-#define METERMULT (24)
 #define SLEEP (500000)
 
-jmp_buf here;
+char *cmdsink = "./IPC/SDR-1000-0-commands.fifo",
+     *mtrsrc = "./IPC/SDR-1000-0-meter.fifo";
 
-void
-onsig(int sig) {
-  signal(SIGHUP, SIG_IGN);
-  signal(SIGINT, SIG_IGN);
-  signal(SIGQUIT, SIG_IGN);
-  longjmp(here, TRUE);
-}
+FILE *cmdfp, *mtrfp;
+
+int label;
+REAL rxm[MAXRX][RXMETERPTS];
+REAL txm[TXMETERPTS];
 
 int
 main(int argc, char **argv) {
-  Chan ch = 0;
-  int i = 0;
-  REAL val = 0.0;
-
-  signal(SIGHUP, onsig);
-  signal(SIGINT, onsig);
-  signal(SIGQUIT, onsig);
-
-  if (!(ch = openChan(METERPATH, METERMULT * sizeof(REAL))))
-    perror("openChan"), exit(1);
-
-  while (!setjmp(here)) {
-    if (getChan_nowait(ch, (char *) &val, sizeof(REAL))) {
-      printf("(%d)", i++);
-      do
-	printf(" %f", val);
-      while (getChan_nowait(ch, (char *) &val, sizeof(REAL)));
+  int i = 0, j, k, lab = getpid();
+
+  if (!(cmdfp = fopen(cmdsink, "r+")))
+    perror(cmdsink), exit(1);
+  if (!(mtrfp = fopen(mtrsrc, "r+")))
+    perror(mtrsrc), exit(1);
+
+  fprintf(stderr, "metermon OK\n");
+
+  for (;;) {
+
+    usleep(SLEEP);
+
+    fprintf(cmdfp, "reqMeter %d\n", lab);
+    fflush(cmdfp);
+
+    if (fread((char *) &label, sizeof(int), 1, mtrfp) != 1)
+      perror("fread meter label"), exit(1);
+
+    if (fread((char *) rxm, sizeof(REAL), MAXRX * RXMETERPTS, mtrfp)
+	!= MAXRX * RXMETERPTS)
+      perror("fread meter"), exit(1);
+
+    printf("%d <%d>", i++, label);
+    for (j = 0; j < MAXRX; j++) {
+      for (k = 0; k < RXMETERPTS; k++)
+	printf(" %8.3f", rxm[j][k]);
       putchar('\n');
     }
-    usleep(SLEEP);
+
+    if (fread((char *) txm, sizeof(REAL), TXMETERPTS, mtrfp)
+	!= TXMETERPTS)
+      perror("fread meter"), exit(1);
+
+    printf("%d\n", i++);
+    for (k = 0; k < TXMETERPTS; k++)
+      printf(" %8.3f", txm[k]);
+    putchar('\n');
   }
 
-  closeChan(ch);
+  fclose(cmdfp);
+  fclose(mtrfp);
+
+  exit(0);
 }
diff --git a/jDttSP/sdr.c b/jDttSP/sdr.c
index ead39f0..aad64d3 100644
--- a/jDttSP/sdr.c
+++ b/jDttSP/sdr.c
@@ -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,13 +92,29 @@ 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.tick = 0;
 }
 
@@ -86,7 +122,7 @@ setup_all(void) {
 
 PRIVATE void
 setup_rx(int k) {
-
+  
   /* conditioning */
   rx[k].iqfix = newCorrectIQ(0.0, 1.0);
   rx[k].filt.coef = newFIR_Bandpass_COMPLEX(-4800.0,
@@ -297,7 +333,7 @@ setup_workspace(void) {
   }
   uni.multirx.act[0] = TRUE;
   uni.multirx.nac = 1;
-
+  
   setup_tx();
 }
 
@@ -336,8 +372,7 @@ destroy_workspace(void) {
   }
   
   /* all */
-  if (uni.meter.flag)
-    closeChan(uni.meter.chan.c);
+  finish_spectrum(&uni.spec);
 }
 
 //////////////////////////////////////////////////////////////////////////
@@ -359,43 +394,99 @@ 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.rx.val[k][tap] = 0;
+  
+  switch (uni.meter.rx.type) {
+  case AVG_SIGNAL_STRENGTH:
+    for (i = 0; i < len; i++)
+      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 SIGNAL_STRENGTH:
+    for (i = 0; i < len; i++)
+      uni.meter.rx.val[k][tap] += Csqrmag(vec[i]);
+    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 ADC_REAL:
+    for(i = 0; i < len; i++)
+      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.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;
+  default:
+    break;
+  }
+}
+
+PRIVATE void
+do_tx_meter(CXB buf, int tap) {
+  COMPLEX *vec = CXBbase(buf);
+  int i, len = CXBhave(buf);
   
-  uni.meter.val = 0;
+  uni.meter.tx.val[tap] = 0;
 
-  switch (uni.meter.type) {
+  switch (uni.meter.tx.type) {
   case AVG_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.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.val += Csqrmag(vec[i]);
-    uni.meter.avgval = uni.meter.val = 10.0 * log10(uni.meter.val + 1e-20);
+      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 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.tx.val[tap] = max(fabs(vec[i].re), uni.meter.tx.val[tap]);
+    uni.meter.tx.val[tap] = 20.0 * log10(uni.meter.tx.val[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.tx.val[tap] = max(fabs(vec[i].im), uni.meter.tx.val[tap]);
+    uni.meter.tx.val[tap] = 20.0 * log10(uni.meter.tx.val[tap] + 1e-10);
     break;
   default:
     break;
   }
+}
 
-  putChan_nowait(uni.meter.chan.c,
-		 (char *) &uni.meter.val,
-		 sizeof(uni.meter.val));
+PRIVATE BOOLEAN
+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),
+	   CXBhave(buf)); 
+    uni.spec.fill = (uni.spec.fill + uni.spec.buflen) % uni.spec.size;
+  }
+}
+
+PRIVATE void
+do_tx_spectrum(CXB buf) {
+  memcpy((char *) &CXBdata(uni.spec.accum, uni.spec.fill),
+	 (char *) CXBbase(buf),
+	 CXBhave(buf));
+  uni.spec.fill = (uni.spec.fill + uni.spec.buflen) % uni.spec.size;
 }
 
 //========================================================================
@@ -448,10 +539,6 @@ PRIVATE void
 do_rx_pre(int k) {
   int i, n = min(CXBhave(rx[k].buf.i), uni.buflen);
 
-  //
-  // do shrinkage here?
-  //
-
   if (rx[k].scl.pre.flag)
     for (i = 0; i < n; i++)
       CXBdata(rx[k].buf.i, i) = Cscl(CXBdata(rx[k].buf.i, i),
@@ -460,6 +547,10 @@ do_rx_pre(int k) {
   if (rx[k].nb.flag) noiseblanker(rx[k].nb.gen);
   if (rx[k].nb_sdrom.flag) SDROMnoiseblanker(rx[k].nb_sdrom.gen);
 
+  // metering for uncorrected values here
+
+  do_rx_meter(k, rx[k].buf.i, RXMETER_PRE_CONV);
+
   correctIQ(rx[k].buf.i, rx[k].iqfix);
 
   /* 2nd IF conversion happens here */
@@ -471,33 +562,39 @@ do_rx_pre(int k) {
 				     OSCCdata(rx[k].osc.gen, i));
   } 
 
-  /* filtering, metering, squelch, & AGC */
-
-  if (rx[k].mode != SPEC) {
-
+  /* 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);
-
-    if (uni.meter.flag)
-      do_meter(CXBbase(rx[k].buf.o), uni.buflen);
-
+    
+    do_rx_meter(k, rx[k].buf.o, RXMETER_POST_FILT);
+    do_rx_spectrum(k, rx[k].buf.o, SPEC_POST_FILT);
+    
     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[k].buf.o), uni.buflen);
+    
+  }
 }
 
 PRIVATE void
 do_rx_post(int k) {
   int i, n = CXBhave(rx[k].buf.o);
-
+  
   if (!rx[k].squelch.set)  {
     no_squelch(k);
     // spotting tone
@@ -509,17 +606,17 @@ do_rx_post(int k) {
 				       CXBdata(rx[k].spot.gen->buf, i));
     }
   }
-
+  
   // final scaling
-
+  
   if (rx[k].scl.post.flag)
     for (i = 0; i < n; i++)
       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++)
       CXBdata(rx[k].buf.o, i) = Cscl(rx[k].azim, CXBreal(rx[k].buf.o, i));
@@ -585,8 +682,8 @@ do_rx(int k) {
 PRIVATE void
 do_tx_pre(void) {
 
-  if (tx.scl.pre.flag) {
-    int i, n = CXBhave(tx.buf.i);
+if (tx.scl.pre.flag) {
+int i, n = CXBhave(tx.buf.i);
     for (i = 0; i < n; i++)
       CXBdata(tx.buf.i, i) = Cmplx(CXBreal(tx.buf.i, i) * tx.scl.pre.val, 0.0);
   }
@@ -604,12 +701,20 @@ 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 (uni.spec.flag)
+    do_tx_spectrum(tx.buf.o);
+
   if (tx.osc.gen->Frequency != 0.0) {
     int i;
     ComplexOSC(tx.osc.gen);
@@ -697,11 +802,11 @@ process_samples(float *bufl, float *bufr,
 		float *auxl, float *auxr,
 		int n) {
   int i, k;
-
+  
   switch (uni.mode.trx) {
-
+    
   case RX:
-
+    
     // make copies of the input for all receivers
     for (k = 0; k < uni.multirx.nrx; k++)
       if (uni.multirx.act[k]) {
@@ -720,11 +825,12 @@ process_samples(float *bufl, float *bufr,
 	do_rx(k), rx[k].tick++;
 	// mix
 	for (i = 0; i < n; i++)
-	  bufl[i] += CXBimag(rx[k].buf.o, i),
+          bufl[i] += CXBimag(rx[k].buf.o, i),
 	  bufr[i] += 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] += auxl[i] * uni.mix.rx.gain,
@@ -734,6 +840,7 @@ process_samples(float *bufl, float *bufr,
 
   case TX:
 
+    // early mixing of aux buffers
     if (uni.mix.tx.flag)
       for (i = 0; i < n; i++)
 	bufl[i] += auxl[i] * uni.mix.tx.gain,
diff --git a/jDttSP/sdrexport.h b/jDttSP/sdrexport.h
index 9900339..6303a65 100644
--- a/jDttSP/sdrexport.h
+++ b/jDttSP/sdrexport.h
@@ -34,29 +34,39 @@ Bridgewater, NJ 08807
 #ifndef _sdrexport_h
 #define _sdrexport_h
   
-#include <common.h>
-
+#include <fromsys.h>
+#include <defs.h>
+#include <banal.h>
+#include <splitfields.h>
+#include <datatypes.h>
+#include <bufvec.h>
+#include <cxops.h>
+#include <ringb.h>
+#include <chan.h>
+#include <lmadf.h>
+#include <fftw.h>
+#include <ovsv.h>
+#include <filter.h>
+#include <oscillator.h>
+#include <digitalagc.h>
+#include <am_demod.h>
+#include <fm_demod.h>
+#include <noiseblanker.h>
+#include <correctIQ.h>
+#include <crc16.h>
+#include <speechproc.h>
+#include <spottone.h>
+#include <update.h>
+#include <local.h>
+#include <meter.h>
+#include <spectrum.h>
 //------------------------------------------------------------------------
 // max no. simultaneous receivers
+#ifndef MAXRX
 #define MAXRX (4)
+#endif
 //------------------------------------------------------------------------
 /* modulation types, modes */ 
-typedef enum _sdrmode {
-  LSB,				//  0
-  USB,				//  1
-  DSB,				//  2
-  CWL,				//  3
-  CWU,				//  4
-  FMN,				//  5
-  AM,				//  6
-  PSK,				//  7
-  SPEC,				//  8
-  RTTY,				//  9
-  SAM,				// 10
-  DRM				// 11
-} SDRMODE;
-
-typedef enum _trxmode { RX, TX } TRXMODE;
 
 //========================================================================
 /* RX/TX both */ 
@@ -70,16 +80,8 @@ extern struct _uni {
     TRXMODE trx;
   } mode;
 
-  struct {
-    BOOLEAN flag;
-    struct {
-      char *path;
-      size_t size;
-      Chan c;
-    } chan;
-    REAL val, avgval;
-    METERTYPE type;
-  } meter;
+  MeterBlock meter;
+  SpecBlock spec;
 
   struct {
     BOOLEAN flag;
@@ -238,12 +240,20 @@ extern struct _top {
       unsigned int frames, bytes;
     } size;
   } hold;
+
   struct {
     char *path;
     int fd;
     FILE *fp;
     char buff[4096];
   } parm;
+
+  struct {
+    struct {
+      char *path;
+      FILE *fp;
+    } mtr, spec;
+  } meas;
   
   struct {
     char name[256];
@@ -288,12 +298,13 @@ extern struct _top {
   struct {
     struct {
       pthread_t id;
-    } trx, upd, mon;
+    } trx, upd, mon, pws, mtr;
   } thrd;
+
   struct {
     struct {
       sem_t sem;
-    } buf, upd, mon;
+    } buf, upd, mon, pws, mtr;
   } sync;
   
   // TRX switching
diff --git a/jDttSP/setup-ipc b/jDttSP/setup-ipc
index 3a58d31..dfea683 100755
--- a/jDttSP/setup-ipc
+++ b/jDttSP/setup-ipc
@@ -7,9 +7,12 @@ if [ ! -d ./IPC ]; then mkdir ./IPC; fi
     mkfifo SDR-1000-0-commands.fifo
   fi
 
-  # 192 = METERMULT * sizeof(REAL)
-  if [ ! -f SDR-1000-0-meter.chan ]; then
-    ../mkchan SDR-1000-0-meter.chan 192
+  if [ ! -f SDR-1000-0-rx-meter.fifo ]; then
+    mkfifo SDR-1000-0-meter.fifo
+  fi
+
+  if [ ! -f SDR-1000-0-spec.fifo ]; then
+    mkfifo SDR-1000-0-spec.fifo
   fi)
 
 ls -ld IPC
diff --git a/jDttSP/specmon.c b/jDttSP/specmon.c
new file mode 100644
index 0000000..632fee6
--- /dev/null
+++ b/jDttSP/specmon.c
@@ -0,0 +1,51 @@
+/* specmon.c */
+
+#include <common.h>
+
+#define SLEEP (500000)
+
+char *cmdsink = "./IPC/SDR-1000-0-commands.fifo",
+     *specsrc = "./IPC/SDR-1000-0-spec.fifo";
+
+FILE *cmdfp, *spcfp;
+
+int label;
+float spec[DEFSPEC];
+
+int
+main(int argc, char **argv) {
+  int i = 0, j, k, lab = getpid();
+
+  if (!(cmdfp = fopen(cmdsink, "r+")))
+    perror(cmdsink), exit(1);
+  if (!(spcfp = fopen(specsrc, "r+")))
+    perror(specsrc), exit(1);
+
+  printf("spec OK\n");
+
+  for (;;) {
+
+    usleep(SLEEP);
+
+    fprintf(cmdfp, "reqSpectrum %d\n", lab);
+    fflush(cmdfp);
+
+    if (fread((char *) &label, sizeof(int), 1, spcfp) != 1)
+      perror("fread spectrum label"), exit(1);
+
+    if (fread((char *) spec, sizeof(float), DEFSPEC, spcfp) != DEFSPEC)
+      perror("fread spec"), exit(1);
+
+    printf("%d <%d>", i++, label);
+
+    j = 0;
+    for (k = 1; k < DEFSPEC; k++)
+      if (spec[k] > spec[j]) j = k;
+    printf(" [%d %g]\n", j, spec[j]);
+  }
+
+  fclose(cmdfp);
+  fclose(spcfp);
+
+  exit(0);
+}
diff --git a/jDttSP/spectrum.c b/jDttSP/spectrum.c
new file mode 100644
index 0000000..441f92e
--- /dev/null
+++ b/jDttSP/spectrum.c
@@ -0,0 +1,107 @@
+/* spectrum.c */
+/*
+This file is part of a program that implements a Software-Defined Radio.
+
+Copyright (C) 2004 by Frank Brickle, AB2KT and Bob McGwier, N4HY
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+The authors can be reached by email at
+
+ab2kt@arrl.net
+or
+rwmcgwier@comcast.net
+
+or by paper mail at
+
+The DTTS Microwave Society
+6 Kathleen Place
+Bridgewater, NJ 08807
+*/  
+
+#include <spectrum.h>
+
+// snapshot of current signal
+void
+snap_spectrum(SpecBlock *sb, int label) {
+  int i, j;
+
+  // where most recent signal started
+  j = (sb->fill - sb->buflen + sb->size) % sb->size;
+
+  // copy starting from there in circular fashion,
+  // applying window as we go
+  for (i = 0; i < sb->size; i++) {
+    CXBdata(sb->timebuf, i) = Cscl(CXBdata(sb->timebuf, j), sb->window[i]);
+    j = ++j % sb->size;
+  }
+  
+  sb->label = label;
+}
+
+// snapshot -> frequency domain
+void
+compute_spectrum(SpecBlock *sb) {
+  int i, half = sb->size / 2;
+
+  // assume timebuf has windowed current snapshot
+
+  fftw_one(sb->plan,
+	   (fftw_complex *) CXBbase(sb->timebuf),
+	   (fftw_complex *) CXBbase(sb->freqbuf));
+
+  if (sb->scale == SPEC_MAG) {
+    for (i = 0; i < half; i++)
+      sb->output[i + half] = Cmag(CXBdata(sb->freqbuf, i));
+    for (; i < sb->size; i++)
+      sb->output[i] = Cmag(CXBdata(sb->freqbuf, i));
+
+  } else { // SPEC_PWR
+    for (i = 0; i < half; i++)
+      sb->output[i + half] = 10.0 * log10(Csqrmag(CXBdata(sb->freqbuf, i)) + 1e-60);
+    for (; i < sb->size; i++)
+      sb->output[i] = 10.0 * log10(Csqrmag(CXBdata(sb->freqbuf, i)) + 1e-60);
+  }
+}
+
+void
+init_spectrum(SpecBlock *sb) {
+  sb->fill = sb->size - sb->buflen;
+  sb->accum = newCXB(sb->size, 0, "spectrum accum");
+  sb->timebuf = newCXB(sb->size, 0, "spectrum timebuf");
+  sb->freqbuf = newCXB(sb->size, 0, "spectrum freqbuf");
+  sb->window = newvec_REAL(sb->size, "spectrum window");
+  sb->output = (float *) safealloc(sb->size, sizeof(float), "spectrum output");
+  sb->plan = fftw_create_plan(sb->size, FFTW_FORWARD, sb->planbits);
+}
+
+void
+reinit_spectrum(SpecBlock *sb) {
+  sb->fill = sb->size - sb->buflen;
+  memset((char *) CXBbase(sb->accum), 0, sb->size * sizeof(REAL));
+  memset((char *) sb->output, 0, sb->size * sizeof(float));
+}
+
+void
+finish_spectrum(SpecBlock *sb) {
+  if (sb) {
+    delCXB(sb->accum);
+    delCXB(sb->timebuf);
+    delCXB(sb->freqbuf);
+    delvec_REAL(sb->window);
+    safefree((char *) sb->output);
+    fftw_destroy_plan(sb->plan);
+  }
+}
diff --git a/jDttSP/spectrum.h b/jDttSP/spectrum.h
new file mode 100644
index 0000000..adef3dc
--- /dev/null
+++ b/jDttSP/spectrum.h
@@ -0,0 +1,86 @@
+/* spectrum.h */
+/*
+This file is part of a program that implements a Software-Defined Radio.
+
+Copyright (C) 2004 by Frank Brickle, AB2KT and Bob McGwier, N4HY
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+The authors can be reached by email at
+
+ab2kt@arrl.net
+or
+rwmcgwier@comcast.net
+
+or by paper mail at
+
+The DTTS Microwave Society
+6 Kathleen Place
+Bridgewater, NJ 08807
+*/  
+
+#ifndef _spectrum_h
+#define _spectrum_h
+
+#include <fromsys.h>
+#include <defs.h>
+#include <banal.h>
+#include <splitfields.h>
+#include <datatypes.h>
+#include <bufvec.h>
+#include <cxops.h>
+#include <ringb.h>
+#include <chan.h>
+#include <lmadf.h>
+#include <fftw.h>
+#include <ovsv.h>
+#include <filter.h>
+#include <oscillator.h>
+#include <digitalagc.h>
+#include <am_demod.h>
+#include <fm_demod.h>
+#include <noiseblanker.h>
+#include <correctIQ.h>
+#include <crc16.h>
+#include <speechproc.h>
+#include <spottone.h>
+#include <update.h>
+
+#define SPEC_MAG	(0)
+#define SPEC_PWR	(1)
+
+#define SPEC_SEMI_RAW	(0)
+#define SPEC_PRE_FILT	(1)
+#define SPEC_POST_FILT	(2)
+
+typedef
+struct _spec_block {
+  BOOLEAN flag;
+  int label;
+  CXB accum, timebuf, freqbuf;
+  int fill, buflen, rxk, scale, size, type;
+  REAL *window;
+  float *output;
+  int planbits;
+  fftw_plan plan;
+} SpecBlock;
+
+extern void init_spectrum(SpecBlock *sb);
+extern void reinit_spectrum(SpecBlock *sb);
+extern void snap_spectrum(SpecBlock *sb, int label);
+extern void compute_spectrum(SpecBlock *sb);
+extern void finish_spectrum(SpecBlock *sb);
+
+#endif
diff --git a/jDttSP/update.c b/jDttSP/update.c
index cb6ae7a..833366b 100644
--- a/jDttSP/update.c
+++ b/jDttSP/update.c
@@ -4,7 +4,7 @@ common defs and code for parm update
    
 This file is part of a program that implements a Software-Defined Radio.
 
-Copyright (C) 2004 by Frank Brickle, AB2KT and Bob McGwier, N4HY
+Copyright (C) 2004-5 by Frank Brickle, AB2KT and Bob McGwier, N4HY
 
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -141,7 +141,6 @@ setFilter(int n, char **p) {
 }
 
 // setMode <mode> [TRX]
-
 PRIVATE int
 setMode(int n, char **p) {
   int mode = atoi(p[0]);
@@ -382,6 +381,8 @@ setTXSpeechCompressionGain(int n, char **p) {
 }
 
 //============================================================
+// some changes have been made to a transfer function in vec;
+// apply time-domain window to counter possible artifacts
 
 PRIVATE void
 re_window(COMPLEX *vec, int len) {
@@ -440,6 +441,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.
+// NB these are shelves, not linear or other splines
 
 PRIVATE int
 setTXEQ(int n, char **p) {
@@ -655,8 +657,10 @@ PRIVATE int
 setFinished(int n, char **p) {
   top.running = FALSE;
   pthread_cancel(top.thrd.trx.id);
-  pthread_cancel(top.thrd.upd.id);
   pthread_cancel(top.thrd.mon.id);
+  pthread_cancel(top.thrd.pws.id);
+  pthread_cancel(top.thrd.mtr.id);
+  pthread_cancel(top.thrd.upd.id);
   return 0;
 }
 
@@ -755,6 +759,7 @@ setRXOff(int n, char **p) {
   }
 }
 
+// [pos]  0.0 <= pos <= 1.0
 PRIVATE int
 setRXPan(int n, char **p) {
   REAL pos, theta;
@@ -791,6 +796,7 @@ setAuxMixSt(int n, char **p) {
   }
 }
 
+// [dB] NB both channels
 PRIVATE int
 setAuxMixGain(int n, char **p) {
   if (n < 1) {
@@ -810,11 +816,104 @@ setAuxMixGain(int n, char **p) {
   }
 }
 
+//------------------------------------------------------------------------
+
+// [type]
+PRIVATE int
+setMeterType(int n, char **p) {
+  if (n < 1)
+    uni.meter.rx.type = SIGNAL_STRENGTH;
+  else {
+    METERTYPE type = (METERTYPE) atoi(p[0]);
+    if (n > 1) {
+      int trx = atoi(p[1]);
+      switch (trx) {
+      case TX: uni.meter.tx.type = type; break;
+      case RX:
+      default: uni.meter.rx.type = type; break;
+      }
+    } else
+       uni.meter.rx.type = type;
+  }
+  return 0;
+}
+
+// setSpectrumType [type [scale [rx]]]
+PRIVATE int
+setSpectrumType(int n, char **p) {
+  uni.spec.type  = SPEC_POST_FILT;
+  uni.spec.scale = SPEC_PWR;
+  uni.spec.rxk   = RL;
+  switch (n) {
+  case 3:
+    uni.spec.rxk   = atoi(p[2]);
+  case 2:
+    uni.spec.scale = atoi(p[1]);
+  case 1:
+    uni.spec.type  = atoi(p[0]);
+    break;
+  case 0:
+    return 0;
+  default:
+    return -1;
+  }
+}
+
+#if 0
+PRIVATE int
+setSpectrumType(int n, char **p) {
+  switch (n) {
+  case 3:
+    uni.spec.type = atoi(p[0]);
+    uni.spec.scale = atoi(p[1]);
+    uni.spec.rxk = atoi(p[2]);
+  case 2:
+    uni.spec.type = atoi(p[0]);
+    uni.spec.scale = atoi(p[1]);
+    uni.spec.rxk = RL;
+    break;
+  case 1:
+    uni.spec.type = atoi(p[0]);
+    uni.spec.scale = SPEC_PWR;
+    uni.spec.rxk = RL;
+    break;
+  case 0:
+    uni.spec.type = SPEC_POST_FILT;
+    uni.spec.scale = SPEC_PWR;
+    uni.spec.rxk = RL;
+    break;
+  default:
+    return -1;
+  }
+  return 0;
+}
+#endif
+
+//========================================================================
+
+// save current state while guarded by upd sem
+PRIVATE int
+reqMeter(int n, char **p) {
+  snap_meter(&uni.meter, n > 0 ? atoi(p[0]) : 0);
+  sem_post(&top.sync.mtr.sem);
+  return 0;
+}
+
+// simile modo
+PRIVATE int
+reqSpectrum(int n, char **p) {
+  snap_spectrum(&uni.spec, n > 0 ? atoi(p[0]) : 0);
+  sem_post(&top.sync.pws.sem);
+  return 0;
+}
+
 //========================================================================
 
 #include <thunk.h>
 
 CTE update_cmds[] = {
+  {"reqMeter", reqMeter},
+  {"reqSpectrum", reqSpectrum},
   {"setANF", setANF},
   {"setANFvals", setANFvals},
   {"setBIN", setBIN},
@@ -869,6 +968,8 @@ CTE update_cmds[] = {
   {"setRXPan", setRXPan},
   {"setAuxMixSt", setAuxMixSt},
   {"setAuxMixGain", setAuxMixGain},
+  {"setMeterType", setMeterType},
+  {"setSpectrumType", setSpectrumType},
   { 0, 0 }
 };