From: dttsp <dttsp>
Date: Tue, 26 Apr 2005 15:49:24 +0000 (+0000)
Subject: Bug fixes to jsdr, keyer
X-Git-Url: https://git.rkrishnan.org/simplejson/components/com_hotproperty/frontends/%22doc.html/nxhtml.html?a=commitdiff_plain;h=47feea048caddbf954f5fa6cf8b28f1fe659b1c7;p=dttsp.git

Bug fixes to jsdr, keyer
---

diff --git a/jDttSP/Makefile b/jDttSP/Makefile
index 4c39051..b13744d 100644
--- a/jDttSP/Makefile
+++ b/jDttSP/Makefile
@@ -1,7 +1,7 @@
 CFLAGS = -g -O3 -I. -I/usr/local/include
 #CFLAGS = -g -I. -I/usr/local/include
-LIBS = -L/usr/local/lib -ljack -lpthread -lfftw -lm
-#LIBS = -lefence -L/usr/local/lib -ljack -lpthread -lfftw -lm
+LIBS = -L/usr/local/lib -ljack -lpthread -lgsl -lgslcblas -lfftw -lm
+#LIBS = -lefence -L/usr/local/lib -ljack -lpthread -lgsl -lgslcblas -lfftw -lm
 
 staticlibname=libDttSP.a
 
@@ -31,9 +31,20 @@ OBJ =	am_demod.o\
 	window.o\
 	update.o
 
+KOBJ = oscillator.o cwtones.o chan.o ringb.o banal.o bufvec.o splitfields.o cxops.o
+
 jsdr:	main.o $(OBJ)
 	$(CC) -o jsdr main.o $(OBJ) $(LIBS)
 
+all:	jsdr mkchan ipc metermon keyd keyb
+
+keyd:	keyd.o keyer.o $(KOBJ)
+	$(CC) -o keyd keyd.o keyer.o $(KOBJ) $(LIBS)
+
+keyb:	keyb.o keyer.o $(KOBJ)
+	$(CC) -o keyb keyb.o keyer.o $(KOBJ) $(LIBS)
+
+
 $(OBJ): sdrexport.h
 
 metermon:	metermon.o chan.o ringb.o bufvec.o cxops.o banal.o
@@ -42,13 +53,14 @@ metermon:	metermon.o chan.o ringb.o bufvec.o cxops.o banal.o
 mkchan:	mkchan.o bufvec.o banal.o cxops.o
 	$(CC) -o mkchan mkchan.o bufvec.o banal.o cxops.o $(LIBS)
 
-ipc:
+ipc:	mkchan
 	./setup-ipc
 
 obj:	$(OBJ)
 
 clean:
-	/bin/rm *.o jsdr mkchan metermon $(staticlibname)
+	/bin/rm *.o jsdr mkchan metermon keyd keyb #$(staticlibname)
+	#/bin/rm IPC/*
 
 staticlib:	$(OBJ)
 	ar rcs $(staticlibname) $(OBJ)
@@ -60,3 +72,4 @@ staticlib:	$(OBJ)
 # sharedlib=$(sharedlibname).$(sharedlibvers)
 # sharedlib:	$(OBJ)
 # 	gcc -shared -Wl,-soname,$(sharedlib) -o $(sharedlib) $(OBJ) -lc
+
diff --git a/jDttSP/banal.c b/jDttSP/banal.c
index a5e8447..8bc08f1 100644
--- a/jDttSP/banal.c
+++ b/jDttSP/banal.c
@@ -195,3 +195,5 @@ find_rcfile(char *base) {
   return 0;
 }
 
+//------------------------------------------------------------------------
+
diff --git a/jDttSP/banal.h b/jDttSP/banal.h
index 1ca3ffe..3aca035 100644
--- a/jDttSP/banal.h
+++ b/jDttSP/banal.h
@@ -54,7 +54,7 @@
 #define FALSE 0
 
 extern void nilfunc(void);
-extern double sqr(double);
+extern INLINE double sqr(double);
 extern int popcnt(int);
 extern int npoof2(int);
 extern int nblock2(int);
diff --git a/jDttSP/command-vocabulary b/jDttSP/command-vocabulary
index f927b5d..3f479be 100644
--- a/jDttSP/command-vocabulary
+++ b/jDttSP/command-vocabulary
@@ -1,54 +1,53 @@
 [TRX] indicates optional arg (RX or TX), RX default
 T|F indicates TRUE or FALSE
-(see enums.m4)
 
-setANF T|F		// on/off, RX only
-setANFvals taps delay gain leak	// int, int, float, float, RX only
-setATTOffset val	// float, RX only, appears only in squelch calc
-setBIN T|F		// binaural mode, on/off, RX only
 setFilter low-freq high-freq TRX
-setFinished		// shutdown gracefully
-setGainOffset		// float, RX only, appears only in squelch calc
-setMeterOffset lev	// float, RX only, appears only in squelch calc
 setMode mode [TRX]	// mode = USB, LSB, CWL, CWU, etc.
+setOsc freq [TRX]	// freq in Hz (float)
+setSampleRate rate	// Hz (float)
+setNR T|F		// on/off, RX only
+setANF T|F		// on/off, RX only
 setNB T|F		// on/off, RX only
+setBIN T|F		// binaural mode, on/off, RX only
 setNBvals thresh	// float, RX only
-setNR T|F		// on/off, RX only
-setNRvals taps delay gain leak	// int, int, float, float; RX only
-setOsc freq [TRX]	// freq in Hz (float)
+setfixedAGC gain [TRX]	// float
 setRXAGC T|F		// on/off
-setRXAGC mode		// mode = agcOFF, agcSLOW, etc.
 setRXAGCCompression lev	// float
 setRXAGCHang dur	// float
 setRXAGCLimit lim	// float
-setRXEQ <bandspec>	// f0 dB0 f1 dB1 f2 dB2 ... fN (see setTXEQ)
-setRXPostScl T|F	// on/off
-setRXPostSclVal valQ	// dB
-setRXPreScl T|F		// on/off
-setRXPreSclVal valQ	// dB
-setRunState state	// RUN_MUTE, RUN_PASS, RUN_PLAY
-setSWCH trx [zap]	// trx = RX|TX, int (always zaps at least 1)
-setSampleRate rate	// Hz (float)
-setSpotTone T|F		// turn on, off
-setSpotToneVals gain freq rise fall // dB, Hz, msec, msec [-12, 700, 5, 5]
-setSquelch lev		// float, gain, RX only; default -30dB
-setSquelchSt T|F	// on/off, RX only
-setTRX trx		// trx = RX|TX
 setTXAGC T|F		// on/off
 setTXAGCCompression lev	// float
 setTXAGCHang dur	// float
 setTXAGCLimit lim	// float
+setTXSpeechCompression T|F	// on/off
+setTXSpeechCompressionGain gain	// float
+setRXEQ <bandspec>	// f0 dB0 f1 dB1 f2 dB2 ... fN
 setTXEQ <bandspec>	// f0 dB0 f1 dB1 f2 dB2 ... fN
 	// typical:
 	// 0 dB1 75 dB2 150 dB3 300 dB4 600 dB5 1200 dB6 2000 dB7 2800 dB8 3600
 	// approximates W2IHY bandcenters
-setTXPostScl T|F	// on/off
-setTXPostSclVal valQ	// dB
-setTXPreScl T|F		// on/off
-setTXPreSclVal valQ	// dB
-setTXSpeechCompression T|F	// on/off
-setTXSpeechCompressionGain gain	// float
+setRXAGC mode		// mode = agcOFF, agcSLOW, etc.
+setANFvals taps delay gain leak	// int, int, float, float, RX only
+setNRvals taps delay gain leak	// int, int, float, float, RX only
 setcorrectIQ phase gain	// int, int
-setcorrectIQgain gain	// int
 setcorrectIQphase phase	// int
-setfixedAGC gain [TRX]	// float
+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
+setRXPreScl T|F		// on/off
+setRXPreSclVal valQ	// dB
+setTXPreScl T|F		// on/off
+setTXPreSclVal valQ	// dB
+setRXPostScl T|F	// on/off
+setRXPostSclVal valQ	// dB
+setTXPostScl T|F	// on/off
+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
diff --git a/jDttSP/cwtones.c b/jDttSP/cwtones.c
new file mode 100644
index 0000000..012694e
--- /dev/null
+++ b/jDttSP/cwtones.c
@@ -0,0 +1,183 @@
+/* cwtones.c */
+/*
+This file is part of a program that implements a Software-Defined Radio.
+
+Copyright (C) 2005 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 <cwtones.h>
+
+//------------------------------------------------------------------------
+// An ASR envelope on a complex phasor,
+// with asynchronous trigger for R stage.
+// A/R use sine shaping.
+//------------------------------------------------------------------------
+
+BOOLEAN
+CWTone(CWToneGen cwt) {
+  int i, n = cwt->size;
+
+  ComplexOSC(cwt->osc.gen);
+
+  for (i = 0; i < n; i++) {
+
+    // in an envelope stage?
+
+    if (cwt->stage == CWTone_RISE) {
+
+      // still going?
+      if (cwt->rise.have++ < cwt->rise.want) {
+	cwt->curr += cwt->rise.incr;
+	cwt->mul = cwt->scl * sin(cwt->curr * M_PI / 2.0);
+      } else {
+	// no, assert steady-state, force level
+	cwt->curr = 1.0;
+	cwt->mul = cwt->scl;
+	cwt->stage = CWTone_STDY;
+	// won't come back into envelopes
+	// until FALL asserted from outside
+      }
+
+    } else if (cwt->stage == CWTone_FALL) {
+
+      // still going?
+      if (cwt->fall.have++ < cwt->fall.want) {
+	cwt->curr -= cwt->fall.incr;
+	cwt->mul = cwt->scl * sin(cwt->curr * M_PI / 2.0);
+      } else {
+	// no, assert trailing, force level
+	cwt->curr = 0.0;
+	cwt->mul = 0.0;
+	cwt->stage = CWTone_HOLD;
+	// won't come back into envelopes hereafter
+      }
+    }
+
+    // apply envelope
+    // (same base as osc.gen internal buf)
+    CXBdata(cwt->buf, i) = Cscl(CXBdata(cwt->buf, i), cwt->mul);
+  }
+
+  // indicate whether it's turned itself off
+  // sometime during this pass
+  
+  return cwt->stage != CWTone_HOLD;
+}
+
+//------------------------------------------------------------------------
+// turn tone on with current settings
+
+void
+CWToneOn(CWToneGen cwt) {
+
+  // gain is in dB
+
+  cwt->scl = pow(10.0, cwt->gain / 20.0);
+  cwt->curr = cwt->mul = 0.0;
+  
+  // A/R times are in msec
+
+  cwt->rise.want = (int) (0.5 + cwt->sr * (cwt->rise.dur / 1e3));
+  cwt->rise.have = 0;
+  if (cwt->rise.want <= 1)
+    cwt->rise.incr = 1.0;
+  else
+    cwt->rise.incr = 1.0 / (cwt->rise.want - 1);
+  
+  cwt->fall.want = (int) (0.5 + cwt->sr * (cwt->fall.dur / 1e3));
+  cwt->fall.have = 0;
+  if (cwt->fall.want <= 1)
+    cwt->fall.incr = 1.0;
+  else
+    cwt->fall.incr = 1.0 / (cwt->fall.want - 1);
+
+  // freq is in Hz
+
+  OSCfreq(cwt->osc.gen) = 2.0 * M_PI * cwt->osc.freq / cwt->sr;
+  OSCphase(cwt->osc.gen) = 0.0;
+
+  cwt->stage = CWTone_RISE;
+}
+
+//------------------------------------------------------------------------
+// initiate turn-off
+
+void
+CWToneOff(CWToneGen cwt) { cwt->stage = CWTone_FALL; }
+
+//------------------------------------------------------------------------
+
+void
+setCWToneGenVals(CWToneGen cwt,
+		 REAL gain,
+		 REAL freq,
+		 REAL rise,
+		 REAL fall) {
+  cwt->gain = gain;
+  cwt->osc.freq = freq;
+  cwt->rise.dur = rise;
+  cwt->fall.dur = fall;
+}
+
+CWToneGen
+newCWToneGen(REAL gain,	// dB
+	     REAL freq,
+	     REAL rise,	// ms
+	     REAL fall,	// ms
+	     int size,
+	     REAL samplerate) {
+
+  CWToneGen cwt = (CWToneGen) safealloc(1, sizeof(CWToneGenDesc),
+				       "CWToneGenDesc");
+
+  setCWToneGenVals(cwt, gain, freq, rise, fall);
+  cwt->size = size;
+  cwt->sr = samplerate;
+
+  cwt->osc.gen = newOSC(cwt->size,
+			ComplexTone,
+			cwt->osc.freq,
+			0.0,
+			cwt->sr,
+			"CWTone osc");
+
+  // overload oscillator buf
+  cwt->buf = newCXB(cwt->size, OSCCbase(cwt->osc.gen), "CWToneGen buf");
+
+  return cwt;
+}
+
+void
+delCWToneGen(CWToneGen cwt) {
+  if (cwt) {
+    delCXB(cwt->buf);
+    delOSC(cwt->osc.gen);
+    safefree((char *) cwt);
+  }
+}
diff --git a/jDttSP/cwtones.h b/jDttSP/cwtones.h
new file mode 100644
index 0000000..fe3bafd
--- /dev/null
+++ b/jDttSP/cwtones.h
@@ -0,0 +1,81 @@
+/* spottone.h */
+/*
+This file is part of a program that implements a Software-Defined Radio.
+
+Copyright (C) 2005 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 _spottone_h
+#define _spottone_h
+
+#include <fromsys.h>
+#include <banal.h>
+#include <datatypes.h>
+#include <bufvec.h>
+#include <cxops.h>
+#include <oscillator.h>
+
+#define CWTone_IDLE (0)
+#define CWTone_WAIT (1)
+#define CWTone_RISE (2)
+#define CWTone_STDY (3)
+#define CWTone_FALL (4)
+#define CWTone_HOLD (5)
+
+typedef struct _spot_tone_gen {
+  REAL curr, gain, mul, scl, sr;
+  struct {
+    REAL freq;
+    OSC gen;
+  } osc;
+  struct {
+    REAL dur, incr;
+    int want, have;
+  } rise, fall;
+  int size, stage;
+  CXB buf;
+} CWToneGenDesc, *CWToneGen;
+
+extern CWToneGen newCWToneGen(REAL gain,	// dB
+			      REAL freq,	// Hz
+			      REAL rise,	// msec
+			      REAL fall,	// msec
+			      int size,		// buflen
+			      REAL samplerate);
+extern void delCWToneGen(CWToneGen gen);
+extern void setCWToneGenVals(CWToneGen gen,
+			     REAL gain,
+			     REAL freq,
+			     REAL rise,
+			     REAL fall);
+extern void CWToneOn(CWToneGen gen);
+extern void CWToneOff(CWToneGen gen);
+extern BOOLEAN CWTone(CWToneGen gen);
+
+#endif
diff --git a/jDttSP/cxops.c b/jDttSP/cxops.c
index b54dbef..4927de7 100644
--- a/jDttSP/cxops.c
+++ b/jDttSP/cxops.c
@@ -107,3 +107,13 @@ Cexp(COMPLEX z) {
   REAL r = exp(z.re);
   return Cmplx(r * cos(z.im), r * sin(z.im));
 }
+
+COMPLEX
+Cp2r(COMPLEX z) {
+  return Cmplx(z.re * cos(z.im), z.re * sin(z.im));
+}
+
+COMPLEX
+Cr2p(COMPLEX z) {
+  return Cmplx(sqrt(sqr(z.re) + sqr(z.im)), ATAN2(z.im, z.re));
+}
diff --git a/jDttSP/cxops.h b/jDttSP/cxops.h
index 83d2e26..00ebae8 100644
--- a/jDttSP/cxops.h
+++ b/jDttSP/cxops.h
@@ -35,6 +35,7 @@ Bridgewater, NJ 08807
 #define _cxops_h
 
 #include <datatypes.h>
+#include <fastrig.h>
 
 extern COMPLEX cxzero;
 extern COMPLEX cxone;
@@ -54,5 +55,8 @@ extern INLINE COMPLEX Cmplx(REAL, IMAG);
 extern INLINE COMPLEX Conjg(COMPLEX);
 extern INLINE COMPLEX Cexp(COMPLEX);
 
+extern INLINE COMPLEX Cp2r(COMPLEX);
+extern INLINE COMPLEX Cr2p(COMPLEX);
+
 #endif
 
diff --git a/jDttSP/digitalagc.c b/jDttSP/digitalagc.c
index 5d559a1..352ebb2 100644
--- a/jDttSP/digitalagc.c
+++ b/jDttSP/digitalagc.c
@@ -106,7 +106,7 @@ DigitalAgc(DIGITALAGC a, int tick) {
       a->size = hang;
       a->hist[k] = a->gain.lim / peak;
       for (i = 1, a->gain.now = a->hist[0]; i < hang; i++)
-	a->gain.now = min(a->hist[i], a->gain.now);
+	a->gain.now = max(a->hist[i], a->gain.now);
     }
     a->gain.now = min(a->gain.now, a->gain.top);
     
diff --git a/jDttSP/fastrig.c b/jDttSP/fastrig.c
index 5c4d8ff..52c77b1 100644
--- a/jDttSP/fastrig.c
+++ b/jDttSP/fastrig.c
@@ -203,10 +203,14 @@ fast_atan2(REAL y, REAL x) {
     /* normalize to +/- 45 degree range */
     y_abs = fabs(y);
     x_abs = fabs(x);
-    z = (y_abs < x_abs ? y_abs / x_abs : x_abs / y_abs);
+    //z = (y_abs < x_abs ? y_abs / x_abs : x_abs / y_abs);
+    if (y_abs < x_abs)
+      z = y_abs / x_abs;
+    else
+      z = x_abs / y_abs;
     /* when ratio approaches the table resolution, the angle is */
     /*      best approximated with the argument itself...       */
-    if (z < TAN_MAP_RES)  base_angle = z;
+    if (z < TAN_MAP_RES) base_angle = z;
     else {
       /* find index and interpolation value */
       alpha = z * (REAL) TAN_MAP_SIZE - .5;
diff --git a/jDttSP/fastrig.h b/jDttSP/fastrig.h
index ae8485b..b1162f4 100644
--- a/jDttSP/fastrig.h
+++ b/jDttSP/fastrig.h
@@ -38,7 +38,10 @@ Bridgewater, NJ 08807
 #include <splitfields.h>
 #include <datatypes.h>
 #include <bufvec.h>
+
+#ifdef notdef
 #include <cxops.h>
+#endif
 
 #define SIN_TABLE_SIZE 4096
 #define SIN_TABLE_SIZE_M1 4095
diff --git a/jDttSP/fromsys.h b/jDttSP/fromsys.h
index 56e6a8f..12b7498 100644
--- a/jDttSP/fromsys.h
+++ b/jDttSP/fromsys.h
@@ -62,4 +62,11 @@ Bridgewater, NJ 08807
 #include <jack/jack.h>
 #include <jack/ringbuffer.h>
 
+#include <gsl/gsl_errno.h>
+#include <gsl/gsl_interp.h>
+#include <gsl/gsl_spline.h>
+#include <gsl/gsl_wavelet.h>
+#include <gsl/gsl_sf_erf.h>
+#include <gsl/gsl_cdf.h>
+
 #endif
diff --git a/jDttSP/keyb.c b/jDttSP/keyb.c
new file mode 100644
index 0000000..591fbb6
--- /dev/null
+++ b/jDttSP/keyb.c
@@ -0,0 +1,509 @@
+/* keyb.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 <fromsys.h>
+#include <banal.h>
+#include <splitfields.h>
+#include <datatypes.h>
+#include <bufvec.h>
+#include <cxops.h>
+
+#define SAMP_RATE (48000)
+#define HUGE_PHASE 1256637061.43593
+
+#define RING_SIZE (01 << 020)
+
+pthread_t input, play;
+sem_t ready, reader, writer;
+
+jack_client_t *client;
+jack_port_t *lport, *rport;
+jack_ringbuffer_t *lring, *rring;
+jack_nframes_t size;
+
+BOOLEAN playing = FALSE;
+double wpm = 18.0, freq = 750.0, gain = -6.0;
+
+COMPLEX *zout = 0;
+
+// basic mapping, chars -> morse strings
+char *morse_table[128];
+
+// CW tone segments
+#define ME_EOF (-1)
+#define ME_ZERO (0)
+#define ME_RAMP (1)
+#define ME_STDY (2)
+
+struct {
+  double wpm, rise, fall, curr, incr, rate;
+  int type, size;
+} morsel;
+
+int ditspacesize, dahspacesize,
+    ditstdysize, dahstdysize,
+    charspacesize, wordspacesize,
+    risesize, fallsize;
+double riseincr, fallincr;
+
+void jack_ringbuffer_clear(jack_ringbuffer_t *, int);
+void jack_ringbuffer_restart(jack_ringbuffer_t *, int);
+void send_sound(COMPLEX *, int);
+
+//------------------------------------------------------------
+
+// map char -> morse string
+char *
+get_morse(int c) { return morse_table[c & 0x7F]; }
+
+void
+reader_thread(void) {
+  BOOLEAN b = TRUE; // we're coming from silence
+  int c, e;
+  char *m;
+  
+  // keep reading 1 char at a time
+  while ((c = getchar()) != EOF) {
+    
+    // is char mapped to morse?
+    if (m = get_morse(c)) {
+      
+      // yup
+      // for each sub-element (dit/dah)
+      while (e = *m++) {
+	// first segment is slew in...
+	sem_wait(&reader);
+	morsel.type = ME_RAMP, morsel.size = risesize;
+	morsel.curr = 0.0, morsel.incr = riseincr;
+	sem_post(&writer);
+	
+	// ...then steady state...
+	sem_wait(&reader);
+	morsel.type = ME_STDY;
+	morsel.size = e == '.' ? ditstdysize : dahstdysize;
+	sem_post(&writer);
+	
+	// ...then slew out...
+	sem_wait(&reader);
+	morsel.type = ME_RAMP, morsel.size = fallsize;
+	morsel.curr = 1.0, morsel.incr = fallincr;
+	sem_post(&writer);
+	
+	// ...finally, post-element pause
+	sem_wait(&reader);
+	morsel.type = ME_ZERO;
+	morsel.size = ditspacesize;
+	sem_post(&writer);
+      }
+      
+      // post-character pause
+      sem_wait(&reader);
+      morsel.type = ME_ZERO;
+      // (we already emitted a dit-sized space)
+      morsel.size = charspacesize - ditspacesize;
+      sem_post(&writer);
+      
+      // wherever we go next, it won't have been from silence
+      b = FALSE;
+
+    } else {
+      // anything else treated as interword space
+      sem_wait(&reader);
+      morsel.type = ME_ZERO;
+      // was previous output also interword space?
+      if (b)
+	// yes, use full duration
+	morsel.size = wordspacesize;
+      else
+	// no, part of duration already played
+	morsel.size = wordspacesize - charspacesize;
+      b = TRUE;
+      sem_post(&writer);
+    }
+  }
+  
+  // indicate EOF on input
+  sem_wait(&reader);
+  morsel.type = ME_EOF;
+  sem_post(&writer);
+  pthread_exit(0);
+}
+
+void
+sound_thread(void) {
+  int i, k = 0;
+  double ofreq = freq * 2.0 * M_PI / SAMP_RATE,
+         phase = 0.0,
+         scale = pow(10.0, gain / 20.0);
+  COMPLEX z, delta_z;
+
+  // as long as there's been no EOF on the input...
+  for (;;) {
+
+    // pause for next sub-element
+    sem_post(&reader);
+    sem_wait(&writer);
+
+    // no more data?
+    if (morsel.type == ME_EOF) break;
+
+    // interword space == silence?
+    if (morsel.type != ME_ZERO) {
+      // no, set up CORDIC tone generation
+      if (phase > HUGE_PHASE) phase -= HUGE_PHASE;
+      z = Cmplx(cos(phase), sin(phase));
+      delta_z = Cmplx(cos(ofreq), sin(ofreq));
+    }
+
+    // play out this sub-segment
+    for (i = 0; i < morsel.size; i++) {
+
+      // make silence
+      if (morsel.type == ME_ZERO) zout[k] = cxzero;
+      
+      // make tone
+      else {
+	z = Cmul(z, delta_z);
+	phase += ofreq;
+	// slewing segment?
+	if (morsel.type == ME_RAMP) {
+	  morsel.curr += morsel.incr;
+	  zout[k] = Cscl(z, scale * sin(morsel.curr * M_PI / 2.0));
+	} else
+	  zout[k] = Cscl(z, scale);
+      }
+
+      // one jack bufferful yet?
+      if (++k >= size) {
+	// yes, send to output
+	send_sound(zout, k);
+	// wait until it's been taken away
+	sem_wait(&ready);
+	k = 0;
+	// reset CORDIC
+	if (morsel.type != ME_ZERO) {
+	  if (phase > HUGE_PHASE) phase -= HUGE_PHASE;
+	  z = Cmplx(cos(phase), sin(phase));
+	  delta_z = Cmplx(cos(ofreq), sin(ofreq));
+	}
+      }
+    }
+  }
+
+  // anything left unsent in buffer?
+  if (k > 0) send_sound(zout, k);
+
+  pthread_exit(0);
+}
+
+//------------------------------------------------------------------------
+
+void
+jack_ringbuffer_clear(jack_ringbuffer_t *ring, int nbytes) {
+  int i;
+  char zero = 0;
+  for (i = 0; i < nbytes; i++)
+    jack_ringbuffer_write(ring, &zero, 1);
+}
+
+void
+jack_ringbuffer_restart(jack_ringbuffer_t *ring, int nbytes) {
+  jack_ringbuffer_reset(ring);
+  jack_ringbuffer_clear(ring, nbytes);
+}
+
+void
+send_sound(COMPLEX *buff, int len) {
+  if (jack_ringbuffer_write_space(lring) < len * sizeof(float)) {
+    write(2, "overrun\n", 8);
+    jack_ringbuffer_restart(lring, size * sizeof(float));
+    jack_ringbuffer_restart(rring, size * sizeof(float));
+  } else {
+    int i;
+    for (i = 0; i < len; i++) {
+      float l = buff[i].re, r = buff[i].im;
+      jack_ringbuffer_write(lring, (char *) &l, sizeof(float));
+      jack_ringbuffer_write(rring, (char *) &r, sizeof(float));
+    }
+  }
+}
+
+PRIVATE void
+jack_xrun(void *arg) {
+  char *str = "xrun!\n";
+  write(2, str, strlen(str));
+}
+
+PRIVATE void
+jack_shutdown(void *arg) {}
+
+PRIVATE void
+jack_callback(jack_nframes_t nframes, void *arg) {
+  char *lp, *rp;
+  int nwant = nframes * sizeof(float),
+      nhave = jack_ringbuffer_read_space(lring);
+
+  lp = jack_port_get_buffer(lport, nframes);
+  rp = jack_port_get_buffer(rport, nframes);
+  if (nhave >= nwant) {
+    jack_ringbuffer_read(lring, lp, nwant);
+    jack_ringbuffer_read(rring, rp, nwant);
+    sem_post(&ready);
+  } else {
+    memset(lp, 0, nwant);
+    memset(rp, 0, nwant);
+  }
+}
+
+int
+main(int argc, char **argv) {
+  int i;
+
+  for (i = 1; i < argc; i++)
+    if (argv[i][0] == '-')
+      switch (argv[i][1]) {
+      case 'f':
+	freq = atof(argv[++i]);
+	break;
+      case 'w':
+	wpm = atof(argv[++i]);
+	break;
+      default:
+	fprintf(stderr, "keyd [-w wpm] [-f freq] [infile]\n");
+	exit(1);
+      }
+    else break;
+
+  if (i < argc) {
+    if (!freopen(argv[i], "r", stdin))
+      perror(argv[i]), exit(1);
+    i++;
+  }
+
+  //------------------------------------------------------------
+
+  morsel.wpm = wpm;
+  morsel.rise = morsel.fall = 5.0; // ms
+  morsel.rate = SAMP_RATE;
+
+  ditspacesize = SAMP_RATE * 1.2 / morsel.wpm + 0.5;
+  dahspacesize = 3 * ditspacesize;
+  charspacesize = dahspacesize;
+  wordspacesize = 7 * ditspacesize;
+
+  risesize = SAMP_RATE * morsel.rise / 1e3 + 0.5;
+  if (risesize > 1)
+    riseincr = 1.0 / (risesize - 1);
+  else
+    riseincr = 1.0;
+
+  fallsize = SAMP_RATE * morsel.fall / 1e3 + 0.5;
+  if (fallsize > 1)
+    fallincr = -1.0 / (fallsize - 1);
+  else
+    fallincr = -1.0;
+
+  ditstdysize = ditspacesize - risesize - fallsize;
+  dahstdysize = dahspacesize - risesize - fallsize;
+
+  //------------------------------------------------------------
+
+  if (!(client = jack_client_new("keyb")))
+    fprintf(stderr, "can't make client -- jack not running?\n"), exit(1);
+  jack_set_process_callback(client, (void *) jack_callback, 0);
+  jack_on_shutdown(client, (void *) jack_shutdown, 0);
+  jack_set_xrun_callback(client, (void *) jack_xrun, 0);
+  size = jack_get_buffer_size(client);
+
+  lport = jack_port_register(client,
+			     "ol",
+			     JACK_DEFAULT_AUDIO_TYPE,
+			     JackPortIsOutput,
+			     0);
+  rport = jack_port_register(client,
+			     "or",
+			     JACK_DEFAULT_AUDIO_TYPE,
+			     JackPortIsOutput,
+			     0);
+  lring = jack_ringbuffer_create(RING_SIZE);
+  rring = jack_ringbuffer_create(RING_SIZE);
+  jack_ringbuffer_clear(lring, size * sizeof(float));
+  jack_ringbuffer_clear(rring, size * sizeof(float));
+  
+  //------------------------------------------------------------
+
+  zout = newvec_COMPLEX(size, "keyb sample buffer");
+
+  //------------------------------------------------------------
+
+  sem_init(&ready, 0, 0);
+  sem_init(&reader, 0, 0);
+  sem_init(&writer, 0, 0);
+  pthread_create(&input, 0, (void *) reader_thread, 0);
+  pthread_create(&play, 0, (void *) sound_thread, 0);
+
+  //------------------------------------------------------------
+
+  jack_activate(client);
+  {
+    const char **ports;
+    if (!(ports = jack_get_ports(client, 0, 0, JackPortIsPhysical | JackPortIsInput))) {
+      fprintf(stderr, "can't find any physical playback ports\n");
+      exit(1);
+    }
+    if (jack_connect(client, jack_port_name(lport), ports[0])) {
+      fprintf(stderr, "can't connect left output\n");
+      exit(1);
+    }
+    if (jack_connect(client, jack_port_name(rport), ports[1])) {
+      fprintf(stderr, "can't connect left output\n");
+      exit(1);
+    }
+    free(ports);
+  }
+
+  pthread_join(input, 0);
+  pthread_join(play, 0);
+  jack_client_close(client);
+
+  //------------------------------------------------------------
+
+  delvec_COMPLEX(zout);
+
+  //------------------------------------------------------------
+
+  jack_ringbuffer_free(lring);
+  jack_ringbuffer_free(rring);
+  sem_destroy(&ready);
+  sem_destroy(&reader);
+  sem_destroy(&writer);
+
+  //------------------------------------------------------------
+
+  exit(0);
+}
+
+char *morse_table[128] = {
+  /* 000 NUL */ 0, /* 001 SOH */ 0, /* 002 STX */ 0, /* 003 ETX */ 0,
+  /* 004 EOT */ 0, /* 005 ENQ */ 0, /* 006 ACK */ 0, /* 007 BEL */ 0,
+  /* 008  BS */ 0, /* 009  HT */ 0, /* 010  LF */ 0, /* 011  VT */ 0,
+  /* 012  FF */ 0, /* 013  CR */ 0, /* 014  SO */ 0, /* 015  SI */ 0,
+  /* 016 DLE */ 0, /* 017 DC1 */ 0, /* 018 DC2 */ 0, /* 019 DC3 */ 0,
+  /* 020 DC4 */ 0, /* 021 NAK */ 0, /* 022 SYN */ 0, /* 023 ETB */ 0,
+  /* 024 CAN */ 0, /* 025  EM */ 0, /* 026 SUB */ 0, /* 027 ESC */ 0,
+  /* 028  FS */ 0, /* 029  GS */ 0, /* 030  RS */ 0, /* 031  US */ 0,
+  /* 032  SP */ 0,
+  /* 033   ! */ "...-.",	// [SN]
+  /* 034   " */ 0, /* 035   # */ 0, /* 036   $ */ 0,
+  /* 037   % */ ".-...",	// [AS]
+  /* 038   & */ 0, /* 039   ' */ 0,
+  /* 040   ( */ "-.--.",	// [KN]
+  /* 041   ) */ 0,
+  /* 042   * */ "...-.-",	// [SK]
+  /* 043   + */ ".-.-.",	// [AR]
+  /* 044   , */ "--..--",
+  /* 045   - */ "-....-",
+  /* 046   . */ ".-.-.-",
+  /* 047   / */ "-..-.",
+  /* 048   0 */ "-----",
+  /* 049   1 */ ".----",
+  /* 050   2 */ "..---",
+  /* 051   3 */ "...--",
+  /* 052   4 */ "....-",
+  /* 053   5 */ ".....",
+  /* 054   6 */ "-....",
+  /* 055   7 */ "--...",
+  /* 056   8 */ "---..",
+  /* 057   9 */ "----.",
+  /* 058   : */ 0, /* 059   ; */ 0, /* 060   < */ 0,
+  /* 061   = */ "-...-",	// [BT]
+  /* 062   > */ 0,
+  /* 063   ? */ "..__..",	// [IMI]
+  /* 064   @ */ ".--.-.",
+  /* 065   A */ ".-",
+  /* 066   B */ "-...",
+  /* 067   C */ "-.-.",
+  /* 068   D */ "-..",
+  /* 069   E */ ".",
+  /* 070   F */ "..-.",
+  /* 071   G */ "--.",
+  /* 072   H */ "....",
+  /* 073   I */ "..",
+  /* 074   J */ ".---",
+  /* 075   K */ "-.-",
+  /* 076   L */ ".-..",
+  /* 077   M */ "--",
+  /* 078   N */ "-.",
+  /* 079   O */ "---",
+  /* 080   P */ ".--.",
+  /* 081   Q */ "--.-",
+  /* 082   R */ ".-.",
+  /* 083   S */ "...",
+  /* 084   T */ "-",
+  /* 085   U */ "..-",
+  /* 086   V */ "...-",
+  /* 087   W */ ".--",
+  /* 088   X */ "-..-",
+  /* 089   Y */ "-.--",
+  /* 090   Z */ "--..",
+  /* 091   [ */ 0, /* 092   \ */ 0, /* 093   ] */ 0, /* 094   ^ */ 0,
+  /* 095   _ */ 0, /* 096   ` */ 0,
+  /* 097   a */ ".-",
+  /* 098   b */ "-...",
+  /* 099   c */ "-.-.",
+  /* 100   d */ "-..",
+  /* 101   e */ ".",
+  /* 102   f */ "..-.",
+  /* 103   g */ "--.",
+  /* 104   h */ "....",
+  /* 105   i */ "..",
+  /* 106   j */ ".---",
+  /* 107   k */ "-.-",
+  /* 108   l */ ".-..",
+  /* 109   m */ "--",
+  /* 110   n */ "-.",
+  /* 111   o */ "---",
+  /* 112   p */ ".--.",
+  /* 113   q */ "--.-",
+  /* 114   r */ ".-.",
+  /* 115   s */ "...",
+  /* 116   t */ "-",
+  /* 117   u */ "..-",
+  /* 118   v */ "...-",
+  /* 119   w */ ".--",
+  /* 120   x */ "-..-",
+  /* 121   y */ "-.--",
+  /* 122   z */ "--..",
+  /* 123   { */ 0, /* 124   | */ 0, /* 125   } */ 0, /* 126   ~ */ 0,
+  /* 127 DEL */ 0
+};
diff --git a/jDttSP/keyd.c b/jDttSP/keyd.c
new file mode 100644
index 0000000..61d7ac8
--- /dev/null
+++ b/jDttSP/keyd.c
@@ -0,0 +1,388 @@
+/* keyd.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 <linux/rtc.h>
+#include <fromsys.h>
+#include <banal.h>
+#include <splitfields.h>
+#include <datatypes.h>
+#include <bufvec.h>
+#include <cxops.h>
+#include <ringb.h>
+#include <chan.h>
+#include <oscillator.h>
+#include <cwtones.h>
+#include <keyer.h>
+
+#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
+#define TONE_SIZE (SAMP_RATE / RTC_RATE)
+
+// ring buffer size; > 1 sec at this sr
+#define RING_SIZE (01 << 020)
+
+KeyerState ks;
+KeyerLogic kl;
+
+pthread_t play, key;
+sem_t clock_fired, keyer_started;
+
+int fdser, fdrtc;
+
+jack_client_t *client;
+jack_port_t *lport, *rport;
+jack_ringbuffer_t *lring, *rring;
+jack_nframes_t size;
+
+CWToneGen gen;
+BOOLEAN playing = FALSE, iambic = FALSE;
+double wpm = 18.0, freq = 750.0;
+
+//------------------------------------------------------------
+
+void
+jack_ringbuffer_clear(jack_ringbuffer_t *ring, int nbytes) {
+  int i;
+  char zero = 0;
+  for (i = 0; i < nbytes; i++)
+    jack_ringbuffer_write(ring, &zero, 1);
+}
+
+void
+jack_ringbuffer_restart(jack_ringbuffer_t *ring, int nbytes) {
+  jack_ringbuffer_reset(ring);
+  jack_ringbuffer_clear(ring, nbytes);
+}
+
+//------------------------------------------------------------
+
+// generated tone -> output ringbuffer
+void
+send_tone(void) {
+  if (jack_ringbuffer_write_space(lring) < TONE_SIZE * sizeof(float)) {
+    write(2, "overrun tone\n", 13);
+    jack_ringbuffer_restart(lring, TONE_SIZE * sizeof(float));
+    jack_ringbuffer_restart(rring, TONE_SIZE * sizeof(float));
+  } else {
+    int i;
+    for (i = 0; i < gen->size; i++) {
+      float l = CXBreal(gen->buf, i),
+	    r = CXBimag(gen->buf, i);
+      jack_ringbuffer_write(lring, (char *) &l, sizeof(float));
+      jack_ringbuffer_write(rring, (char *) &r, sizeof(float));
+    }
+  }
+}
+
+// silence -> output ringbuffer
+void
+send_silence(void) {
+  if (jack_ringbuffer_write_space(lring) < TONE_SIZE * sizeof(float)) {
+    write(2, "overrun zero\n", 13);
+    jack_ringbuffer_restart(lring, TONE_SIZE * sizeof(float));
+    jack_ringbuffer_restart(rring, TONE_SIZE * sizeof(float));
+  } else {
+    int i;
+    for (i = 0; i < gen->size; i++) {
+      float zero = 0.0;
+      jack_ringbuffer_write(lring, (char *) &zero, sizeof(float));
+      jack_ringbuffer_write(rring, (char *) &zero, sizeof(float));
+    }
+  }
+}
+
+// sound/silence generation
+// tone turned on/off asynchronously
+void
+sound_thread(void) {
+  for (;;) {
+    sem_wait(&clock_fired);
+
+    if (playing) {
+      // CWTone keeps playing for awhile after it's turned off,
+      // in order to allow for a decay envelope;
+      // returns FALSE when it's actually done.
+      playing = CWTone(gen);
+      send_tone();
+    } else
+      send_silence();
+  }
+
+  pthread_exit(0);
+}
+
+// basic heartbeat
+// returns actual dur in msec since last tick;
+// uses Linux rtc interrupts.
+// other strategies will work too, so long as they
+// provide a measurable delay in msec.
+double
+timed_delay(void) {
+  double del, std = 1000 / (double) RTC_RATE;
+  static int cnt = 0;
+  unsigned long data;
+  
+  if (read(fdrtc, &data, sizeof(unsigned long)) == -1) {
+    perror("read");
+    exit(1);
+  }
+  // indicate whether an interrupt was missed
+  // not really important except for performance tweaks
+  if ((del = (data >> 010) * 1000 / (double) RTC_RATE) != std)
+    fprintf(stderr, "%d %g ms\n", ++cnt, del);
+  return del;
+}
+
+// key down? (real or via keyer logic)
+BOOLEAN
+read_key(double del) {
+  if (iambic)
+    return read_iambic_key_serial(ks, fdser, kl, del);
+  else
+    return read_straight_key_serial(ks, fdser);
+}
+
+// main keyer loop
+// 
+void
+key_thread(void) {
+
+  sem_wait(&keyer_started);
+
+  for (;;) {
+    // wait for next tick, get actual dur since last one
+    double del = timed_delay();
+    // read key; tell keyer elapsed time since last call
+    BOOLEAN keydown = read_key(del);
+
+    if (!playing && keydown)
+      CWToneOn(gen), playing = TRUE;
+    else if (playing && !keydown)
+      CWToneOff(gen);
+
+    sem_post(&clock_fired);
+  }
+
+  pthread_exit(0);
+}
+
+PRIVATE void
+jack_xrun(void *arg) {
+  char *str = "xrun";
+  write(2, str, strlen(str));
+}
+
+PRIVATE void
+jack_shutdown(void *arg) {}
+
+PRIVATE void
+jack_callback(jack_nframes_t nframes, void *arg) {
+  float *lp, *rp;
+  int nbytes = nframes * sizeof(float);
+  if (nframes == size) {
+    // output: copy from ring to port
+    lp = (float *) jack_port_get_buffer(lport, nframes);
+    rp = (float *) jack_port_get_buffer(rport, nframes);
+    if (jack_ringbuffer_read_space(lring) >= nbytes) {
+      jack_ringbuffer_read(lring, (char *) lp, nbytes);
+      jack_ringbuffer_read(rring, (char *) rp, nbytes);
+    } else { // rb pathology
+      memset((char *) lp, 0, nbytes);
+      memset((char *) rp, 0, nbytes);
+      jack_ringbuffer_reset(lring);
+      jack_ringbuffer_reset(rring);
+      jack_ringbuffer_clear(lring, nbytes);
+      jack_ringbuffer_clear(rring, nbytes);
+      //write(2, "underrun\n", 9); 
+    }
+  }
+}
+
+int
+main(int argc, char **argv) {
+  int i;
+  char *serialdev = "/dev/ttyS0",
+       *clockdev = "/dev/rtc";
+  int serstatus;
+
+  for (i = 1; i < argc; i++)
+    if (argv[i][0] == '-')
+      switch (argv[i][1]) {
+      case 'f':
+	freq = atof(argv[++i]);
+	break;
+      case 'i':
+	iambic = TRUE;
+	break;
+      case 'w':
+	wpm = atof(argv[++i]);
+	break;
+      default:
+	fprintf(stderr, "keyd [-i] [-w wpm]\n");
+	exit(1);
+      }
+    else break;
+
+  //------------------------------------------------------------
+
+  gen = newCWToneGen(-3.0, freq, 5.0, 5.0, TONE_SIZE, 48000.0);
+
+  //------------------------------------------------------------
+
+  kl = newKeyerLogic();
+  ks = newKeyerState();
+  ks->flag.iambic = TRUE;
+  ks->flag.revpdl = TRUE; // depends on port wiring
+  ks->flag.autospace.khar = ks->flag.autospace.word = FALSE;
+  ks->debounce = 1; // could be more if sampled faster
+  ks->mode = MODE_B;
+  ks->weight = 50;
+  ks->wpm = wpm;
+
+  //------------------------------------------------------------
+
+  if (!(client = jack_client_new("keyd")))
+    fprintf(stderr, "can't make client -- jack not running?\n"), exit(1);
+  jack_set_process_callback(client, (void *) jack_callback, 0);
+  jack_on_shutdown(client, (void *) jack_shutdown, 0);
+  jack_set_xrun_callback(client, (void *) jack_xrun, 0);
+  size = jack_get_buffer_size(client);
+  lport = jack_port_register(client,
+			     "ol",
+			     JACK_DEFAULT_AUDIO_TYPE,
+			     JackPortIsOutput,
+			     0);
+  rport = jack_port_register(client,
+			     "or",
+			     JACK_DEFAULT_AUDIO_TYPE,
+			     JackPortIsOutput,
+			     0);
+  lring = jack_ringbuffer_create(RING_SIZE);
+  rring = jack_ringbuffer_create(RING_SIZE);
+  jack_ringbuffer_clear(lring, size * sizeof(float));
+  jack_ringbuffer_clear(rring, size * sizeof(float));
+  
+  //------------------------------------------------------------
+
+  // key
+  if ((fdser = open(serialdev, O_WRONLY)) == -1) {
+    fprintf(stderr, "cannot open serial device %s", serialdev);
+    exit(1);
+  }
+  if (ioctl(fdser, TIOCMGET, &serstatus) == -1) {
+    close(fdser);
+    fprintf(stderr, "cannot get serial device status");
+    exit(1);
+  }
+  serstatus |= TIOCM_DTR;
+  if (ioctl(fdser, TIOCMSET, &serstatus) == -1) {
+    close(fdser);
+    fprintf(stderr, "cannot set serial device status");
+    exit(1);
+  }
+
+  // rtc
+  if ((fdrtc = open(clockdev, O_RDONLY)) == -1) {
+    perror(clockdev);
+    exit(1);
+  }
+  if (ioctl(fdrtc, RTC_IRQP_SET, RTC_RATE) == -1) {
+    perror("ioctl irqp");
+    exit(1);
+  }
+  if (ioctl(fdrtc, RTC_PIE_ON, 0) == -1) {
+    perror("ioctl pie on");
+    exit(1);
+  }
+
+  //------------------------------------------------------------
+
+  sem_init(&clock_fired, 0, 0);
+  sem_init(&keyer_started, 0, 0);
+  pthread_create(&play, 0, (void *) sound_thread, 0);
+  pthread_create(&key, 0, (void *) key_thread, 0);
+
+  //------------------------------------------------------------
+
+  jack_activate(client);
+  {
+    const char **ports;
+    if (!(ports = jack_get_ports(client, 0, 0, JackPortIsPhysical | JackPortIsInput))) {
+      fprintf(stderr, "can't find any physical playback ports\n");
+      exit(1);
+    }
+    if (jack_connect(client, jack_port_name(lport), ports[0])) {
+      fprintf(stderr, "can't connect left output\n");
+      exit(1);
+    }
+    if (jack_connect(client, jack_port_name(rport), ports[1])) {
+      fprintf(stderr, "can't connect left output\n");
+      exit(1);
+    }
+    free(ports);
+  }
+  sem_post(&keyer_started);
+
+  pthread_join(play, 0);
+  pthread_join(key, 0);
+  jack_client_close(client);
+
+  //------------------------------------------------------------
+
+  if (ioctl(fdrtc, RTC_PIE_OFF, 0) == -1) {
+    perror("ioctl pie off");
+    exit(1);
+  }
+  close(fdrtc);
+  close(fdser);
+
+  jack_ringbuffer_free(lring);
+  jack_ringbuffer_free(rring);
+
+  sem_destroy(&clock_fired);
+  sem_destroy(&keyer_started);
+
+  delCWToneGen(gen);
+  delKeyerState(ks);
+  delKeyerLogic(kl);
+
+  //------------------------------------------------------------
+
+  exit(0);
+}
diff --git a/jDttSP/keyer.c b/jDttSP/keyer.c
new file mode 100644
index 0000000..581e51e
--- /dev/null
+++ b/jDttSP/keyer.c
@@ -0,0 +1,335 @@
+/* keyer.c */
+/*
+This file is part of a program that implements a Software-Defined Radio.
+
+The code in this file is derived from routines originally written by
+Pierre-Philippe Coupard for his CWirc X-chat program. That program
+is issued under the GPL and is
+Copyright (C) Pierre-Philippe Coupard - 18/06/2003
+
+This derived version is
+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
+*/  
+
+// see below for places where serial port is read
+// and needs replacement for parallel port hookup
+
+#include <keyer.h>
+
+//========================================================================
+// nothing affected by physical port connection here
+
+BOOLEAN
+klogic(KeyerLogic kl,
+       BOOLEAN dit,
+       BOOLEAN dah,
+       double wpm,
+       int iambicmode,
+       BOOLEAN midelementmodeB,
+       BOOLEAN ditmemory,
+       BOOLEAN dahmemory,
+       BOOLEAN autocharspacing,
+       BOOLEAN autowordspacing,
+       int weight,
+       double ticklen) {
+
+  double ditlen = 1200 / wpm;
+  int set_element_timeouts = NO_TIMEOUTS_SCHED;
+
+  /* Do we need to initialize the keyer? */
+  if (!kl->flag.init) {
+    kl->flag.prev.dit = dit;
+    kl->flag.prev.dah = dah;
+    kl->element.last = kl->element.curr = NO_ELEMENT;
+    kl->element.iamb = NO_PADDLE_SQUEEZE;
+    kl->element.psqam = 0;
+    kl->element.invtd = 0;
+    kl->timeout.midl = kl->timeout.beep = kl->timeout.elem = 0;
+    kl->timeout.dlay = 0;
+    kl->dlay_type = NO_DELAY;
+    kl->flag.init = 1;
+  }
+
+  /* Decrement the timeouts */
+  kl->timeout.dlay -= kl->timeout.dlay > 0 ? ticklen : 0;
+  if (kl->timeout.dlay <= 0) {
+    /* If nothing is scheduled to play, and we just did a character
+       spacing delay, and we do auto word spacing, wait for a word
+       spacing delay, otherwise resume the normal element timeout
+       countdowns */
+    if (kl->timeout.elem <= 0 &&
+	kl->dlay_type == CHAR_SPACING_DELAY &&
+	autowordspacing) {
+      kl->timeout.dlay = ditlen * 4;
+      kl->dlay_type = WORD_SPACING_DELAY;
+    } else {
+      kl->dlay_type = NO_DELAY;
+      kl->timeout.midl -= kl->timeout.midl > 0 ? ticklen : 0;
+      kl->timeout.beep -= kl->timeout.beep > 0 ? ticklen : 0;
+      kl->timeout.elem -= kl->timeout.elem > 0 ? ticklen : 0;
+    }
+  }
+
+  /* Are both paddles squeezed? */
+  if (dit && dah) {
+    kl->element.iamb = PADDLES_SQUEEZED;
+
+    /* Are the paddles squeezed past the middle of the element? */
+    if (kl->timeout.midl <= 0)
+      kl->element.psqam = 1;
+  } else
+    /* Are both paddles released and we had gotten a squeeze in this element? */
+  if (!dit && !dah && kl->element.iamb == PADDLES_SQUEEZED)
+    kl->element.iamb = PADDLES_RELEASED;
+
+  /* Is the current element finished? */
+  if (kl->timeout.elem <= 0 && kl->element.curr != NO_ELEMENT) {
+    kl->element.last = kl->element.curr;
+
+    /* Should we insert an inverted element? */
+    if (((dit && dah) ||
+	 (kl->element.invtd &&
+	  kl->element.iamb != PADDLES_RELEASED) ||
+	 (kl->element.iamb == PADDLES_RELEASED &&
+	  iambicmode == MODE_B &&
+	  (!midelementmodeB || kl->element.psqam)))) {
+      if (kl->element.last == DAH)
+	set_element_timeouts = kl->element.curr = DIT;
+      else
+	set_element_timeouts = kl->element.curr = DAH;
+    } else {
+      /* No more element */
+      kl->element.curr = NO_ELEMENT;
+
+      /* Do we do automatic character spacing? */
+      if (autocharspacing && !dit && !dah) {
+	kl->timeout.dlay = ditlen * 2;
+	kl->dlay_type = CHAR_SPACING_DELAY;
+      }
+    }
+
+    kl->element.invtd = 0;
+    kl->element.iamb = NO_PADDLE_SQUEEZE;
+    kl->element.psqam = 0;
+  }
+
+  /* Is an element currently being played? */
+  if (kl->element.curr == NO_ELEMENT) {
+    if (dah)			/* Dah paddle down ? */
+      set_element_timeouts = kl->element.curr = DAH;
+    else if (dit)		/* Dit paddle down ? */
+      set_element_timeouts = kl->element.curr = DIT;
+  }
+
+  /* Do the dah memory */
+  if (kl->element.curr == DIT &&
+      !kl->flag.prev.dah &&
+      dah &&
+      dahmemory)
+    kl->element.invtd = 1;
+
+  /* Do the dit memory */
+  if (kl->element.curr == DAH &&
+      !kl->flag.prev.dit &&
+      dit &&
+      ditmemory)
+    kl->element.invtd = 1;
+
+  /* If we had a dit (or dah) scheduled to be played after a delay,
+     and the operator lifted both paddles before the end of the delay,
+     and we have no dit (or dah) memory, forget it */
+
+  if (kl->timeout.dlay > 0 &&
+      !dit &&
+      !dah &&
+      ((kl->element.curr == DIT &&
+	!ditmemory) ||
+       (kl->element.curr == DAH
+	&& !dahmemory)))
+    set_element_timeouts = kl->element.curr = NO_ELEMENT;
+
+  /* Do we need to set the playing timeouts of an element? */
+  switch (set_element_timeouts) {
+  case NO_ELEMENT:		/* Cancel any dit or dah */
+    kl->timeout.beep = 0;
+    kl->timeout.midl = 0;
+    kl->timeout.elem = 0;
+    break;
+
+  case DIT:			/* Schedule a dit? */
+    kl->timeout.beep = (ditlen * (double) weight) / 50;
+    kl->timeout.midl = kl->timeout.beep / 2;
+    kl->timeout.elem = ditlen * 2;
+    break;
+
+  case DAH:			/* Schedule a dah? */
+    kl->timeout.beep = (ditlen * (double) weight) / 50 + ditlen * 2;
+    kl->timeout.midl = kl->timeout.beep / 2;
+    kl->timeout.elem = ditlen * 4;
+    break;
+  }
+
+  kl->flag.prev.dit = dit;
+  kl->flag.prev.dah = dah;
+
+  return kl->timeout.beep > 0 && kl->timeout.dlay <= 0;
+}
+
+//========================================================================
+
+/* Read a straight key connected to a serial port, do debouncing, then
+   return the key state */
+
+BOOLEAN
+read_straight_key_serial(KeyerState ks, int fd) {
+  int i, j, serstatus;
+  static BOOLEAN keystate = 0;
+  static int debounce_buf_i = 0,
+             debounce_buf[DEBOUNCE_BUF_MAX_SIZE];
+
+  //***************************************************
+  // replace this with parallel port logic if necessary
+  //***************************************************
+  //
+  /* Read the key state */
+  if (ioctl(fd, TIOCMGET, &serstatus) != -1) {
+    debounce_buf[debounce_buf_i] =
+      (serstatus & (TIOCM_DSR | TIOCM_CTS)) ?
+      DSR_LINE_CLOSED_KEY : !DSR_LINE_CLOSED_KEY;
+
+    debounce_buf_i++;
+  }
+  //
+  //***************************************************
+  // back to business as usual
+  //***************************************************
+
+  /* If the debounce buffer is full, determine the state of the key */
+  if (debounce_buf_i >= ks->debounce) {
+    debounce_buf_i = 0;
+
+    j = 0;
+    for (i = 0; i < ks->debounce; i++)
+      if (debounce_buf[i])
+	j++;
+    keystate = (j > ks->debounce / 2) ? 1 : 0;
+  }
+
+  return keystate;
+}
+
+//------------------------------------------------------------------------
+
+/* Read an iambic key connected to a serial port, do debouncing, emulate a
+   straight key, then return the emulated key state */
+
+BOOLEAN
+read_iambic_key_serial(KeyerState ks, int fd, KeyerLogic kl, double ticklen) {
+  int i, j, serstatus;
+  static BOOLEAN dah_debounce_buf[DEBOUNCE_BUF_MAX_SIZE],
+                 dit_debounce_buf[DEBOUNCE_BUF_MAX_SIZE];
+  static int dah = 0, debounce_buf_i = 0, dit = 0;
+
+  //***************************************************
+  // replace this with parallel port logic if necessary
+  //***************************************************
+  //
+  /* Read the key states */
+  if (ioctl(fd, TIOCMGET, &serstatus) != -1) {
+    if (ks->flag.revpdl) {
+      dah_debounce_buf[debounce_buf_i] =
+	(serstatus & TIOCM_DSR) ? DSR_LINE_CLOSED_KEY : !DSR_LINE_CLOSED_KEY;
+      dit_debounce_buf[debounce_buf_i] =
+	(serstatus & TIOCM_CTS) ? CTS_LINE_CLOSED_KEY : !CTS_LINE_CLOSED_KEY;
+    } else {
+      dit_debounce_buf[debounce_buf_i] =
+	(serstatus & TIOCM_DSR) ? DSR_LINE_CLOSED_KEY : !DSR_LINE_CLOSED_KEY;
+      dah_debounce_buf[debounce_buf_i] =
+	(serstatus & TIOCM_CTS) ? CTS_LINE_CLOSED_KEY : !CTS_LINE_CLOSED_KEY;
+    }
+
+    debounce_buf_i++;
+  }
+  //
+  //***************************************************
+  // back to business as usual
+  //***************************************************
+
+  /* If the debounce buffer is full, determine the state of the keys */
+  if (debounce_buf_i >= ks->debounce) {
+    debounce_buf_i = 0;
+
+    j = 0;
+    for (i = 0; i < ks->debounce; i++)
+      if (dah_debounce_buf[i]) j++;
+    dah = (j > ks->debounce / 2) ? 1 : 0;
+
+    j = 0;
+    for (i = 0; i < ks->debounce; i++)
+      if (dit_debounce_buf[i]) j++;
+    dit = (j > ks->debounce / 2) ? 1 : 0;
+  }
+
+  return klogic(kl,
+		dit,
+		dah,
+		ks->wpm,
+		ks->mode,
+		ks->flag.mdlmdB,
+		ks->flag.memory.dit,
+		ks->flag.memory.dah,
+		ks->flag.autospace.khar,
+		ks->flag.autospace.word,
+		ks->weight,
+		ticklen);
+}
+
+//========================================================================
+
+KeyerState
+newKeyerState(void) {
+  return (KeyerState) safealloc(1, sizeof(KeyerStateInfo), "newKeyerState");
+}
+
+void
+delKeyerState(KeyerState ks) {
+  safefree((char *) ks);
+}
+
+KeyerLogic
+newKeyerLogic(void) {
+  return (KeyerLogic) safealloc(1, sizeof(KeyerLogicInfo), "newKeyerLogic");
+}
+
+void
+delKeyerLogic(KeyerLogic kl) {
+  safefree((char *) kl);
+}
+
+//========================================================================
diff --git a/jDttSP/keyer.h b/jDttSP/keyer.h
new file mode 100644
index 0000000..16184d0
--- /dev/null
+++ b/jDttSP/keyer.h
@@ -0,0 +1,154 @@
+/* keyer.h */
+/*
+This file is part of a program that implements a Software-Defined Radio.
+
+The code in this file is derived from routines originally written by
+Pierre-Philippe Coupard for his CWirc X-chat program. That program
+is issued under the GPL and is
+Copyright (C) Pierre-Philippe Coupard - 18/06/2003
+
+This derived version is
+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 _keyer_h
+#define _keyer_h
+
+#include <fromsys.h>
+#include <banal.h>
+#include <splitfields.h>
+#include <datatypes.h>
+
+//========================================================================
+
+#define DSR_LINE_CLOSED_KEY	 (1)
+#define CTS_LINE_CLOSED_KEY	 (1)
+#define DTR_LINE_SET		 (0)
+#define RTS_LINE_SET		 (0)
+
+#define NO_TIMEOUTS_SCHED	(-2)
+#define NO_ELEMENT		(-1)
+#define DIT			 (0)
+#define DAH			 (1)
+#define MODE_A			 (0)
+#define MODE_B			 (1)
+#define NO_PADDLE_SQUEEZE	 (0)
+#define PADDLES_SQUEEZED	 (1)
+#define PADDLES_RELEASED	 (2)
+#define NO_DELAY		 (0)
+#define CHAR_SPACING_DELAY	 (1)
+#define WORD_SPACING_DELAY	 (2)
+#define DEBOUNCE_BUF_MAX_SIZE	(30)
+
+//========================================================================
+
+typedef
+struct _keyer_state {
+
+  struct {
+
+    BOOLEAN iambic,	// iambic or straight
+            mdlmdB,
+            revpdl;	// paddles reversed
+
+    struct {
+      BOOLEAN dit, dah;
+    } memory;
+
+    struct {
+      BOOLEAN khar, word;
+    } autospace;
+
+  } flag;
+
+  int debounce,	// # seconds to read paddles
+      mode,	// 0 = mode A, 1 = mode B
+      weight;	// 15 -> 85%
+
+  double wpm;	// for iambic keyer
+
+} KeyerStateInfo, *KeyerState;
+
+extern KeyerState newKeyerState(void);
+extern void delKeyerState(KeyerState ks);
+
+//------------------------------------------------------------------------
+
+typedef
+struct _keyer_logic {
+
+  struct {
+    BOOLEAN init;
+
+    struct {
+      BOOLEAN dit, dah;
+    } prev;
+
+  } flag;
+
+  struct {
+    BOOLEAN invtd, // insert inverted element
+            psqam; // paddles squeezed after mid-element
+    int curr, // -1 = nothing, 0 = dit, 1 = dah
+        iamb, //  0 = none, 1 = squeezed, 2 = released
+        last; // -1 = nothing, 0 = dit, 1 = dah
+  } element;
+
+  struct {
+    double beep, dlay, elem, midl;
+  } timeout;
+
+  int dlay_type; // 0 = none, 1 = interchar, 2 = interword
+
+} KeyerLogicInfo, *KeyerLogic;
+
+extern KeyerLogic newKeyerLogic(void);
+extern void delKeyerLogic(KeyerLogic kl);
+
+//========================================================================
+
+extern BOOLEAN klogic(KeyerLogic kl,
+		      BOOLEAN dit,
+		      BOOLEAN dah,
+		      double wpm,
+		      int iambicmode,
+		      BOOLEAN midelementmodeB,
+		      BOOLEAN ditmemory,
+		      BOOLEAN dahmemory,
+		      BOOLEAN autocharspacing,
+		      BOOLEAN autowordspacing,
+		      int weight,
+		      double ticklen);
+
+extern BOOLEAN read_straight_key_serial(KeyerState ks, int fd);
+extern BOOLEAN read_iambic_key_serial(KeyerState ks, int fd, KeyerLogic kl, double ticklen);
+
+//========================================================================
+
+#endif
diff --git a/jDttSP/main.c b/jDttSP/main.c
index a4611e3..4aa5884 100644
--- a/jDttSP/main.c
+++ b/jDttSP/main.c
@@ -48,6 +48,16 @@ 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
 monitor_thread(void) {
   while (top.running) {
@@ -164,6 +174,9 @@ run_swch(void) {
     rx.tick = 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);
   }
 
   process_samples(top.hold.buf.l, top.hold.buf.r, top.hold.size.frames);
@@ -171,13 +184,6 @@ 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) {
@@ -407,8 +413,8 @@ setup_system_audio(void) {
   top.jack.ring.i.r = jack_ringbuffer_create(top.hold.size.bytes * loc.mult.ring);
   top.jack.ring.o.l = jack_ringbuffer_create(top.hold.size.bytes * loc.mult.ring);
   top.jack.ring.o.r = jack_ringbuffer_create(top.hold.size.bytes * loc.mult.ring);
-  clear_jack_ringbuffer(top.jack.ring.o.l, top.hold.size.bytes);
-  clear_jack_ringbuffer(top.jack.ring.o.r, top.hold.size.bytes);
+  clear_jack_ringbuffer(top.jack.ring.o.l, top.jack.size * sizeof(float));
+  clear_jack_ringbuffer(top.jack.ring.o.r, top.jack.size * sizeof(float));
 }
 
 PRIVATE void 
diff --git a/jDttSP/ovsv.c b/jDttSP/ovsv.c
index 3575ff4..dfc2f0f 100644
--- a/jDttSP/ovsv.c
+++ b/jDttSP/ovsv.c
@@ -92,6 +92,11 @@ filter_OvSv_par(FiltOvSv pflt) {
   for (i = 0, j = n; i < n; i++, j++) zrvec[i] = zrvec[j];
 }
 
+void
+reset_OvSv(FiltOvSv pflt) {
+  memset((char *) pflt->zrvec, 0, pflt->fftlen * sizeof(COMPLEX));
+}
+
 /*------------------------------------------------------------*/
 /* info: */
 /* NB strategy. This is the address we pass to newCXB as
diff --git a/jDttSP/ovsv.h b/jDttSP/ovsv.h
index f4baa16..7429d54 100644
--- a/jDttSP/ovsv.h
+++ b/jDttSP/ovsv.h
@@ -71,4 +71,6 @@ extern COMPLEX *FiltOvSv_storept_par(FiltOvSv pflt, int parity);
 
 extern void filter_OvSv(FiltOvSv pflt);
 extern void filter_OvSv_par(FiltOvSv pflt);
+extern void reset_OvSv(FiltOvSv pflt);
+
 #endif
diff --git a/jDttSP/sdr.c b/jDttSP/sdr.c
index f04d641..a198d21 100644
--- a/jDttSP/sdr.c
+++ b/jDttSP/sdr.c
@@ -318,6 +318,18 @@ destroy_workspace(void) {
 // execution
 //////////////////////////////////////////////////////////////////////////
 
+//========================================================================
+// util
+
+PRIVATE REAL
+CXBnorm(CXB buff) {
+  int i;
+  double sum = 0.0;
+  for (i = 0; i < CXBhave(buff); i++)
+    sum += Csqrmag(CXBdata(buff, i));
+  return sqrt(sum);
+}
+
 //========================================================================
 /* all */
 
@@ -367,15 +379,10 @@ PRIVATE BOOLEAN
 should_do_rx_squelch(void) {
   if (rx.squelch.flag) {
     int i, n = CXBhave(rx.buf.o);
-    REAL tst;
     rx.squelch.power = 0.0;
     for (i = 0; i < n; i++)
       rx.squelch.power += Csqrmag(CXBdata(rx.buf.o, i));
-    tst = (10.0 * log10(rx.squelch.power)
-	    + rx.squelch.offset.meter
-	    + rx.squelch.offset.att
-	    + rx.squelch.offset.gain);
-    return rx.squelch.thresh > tst;
+    return rx.squelch.thresh > 10.0 * log10(rx.squelch.power);
   } else
     return rx.squelch.set = FALSE;
 }
@@ -439,6 +446,7 @@ do_rx_pre(void) {
 
   /* filtering, metering, & 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);
@@ -449,7 +457,7 @@ do_rx_pre(void) {
 
 PRIVATE void
 do_rx_post(void) {
-  int i, n = CXBhave(rx.buf.o);;
+  int i, n = CXBhave(rx.buf.o);
 
   if (!rx.squelch.set)  {
     no_squelch();
@@ -537,6 +545,7 @@ do_rx(void) {
 
 PRIVATE void
 do_tx_pre(void) {
+
   if (tx.scl.pre.flag) {
     int i, n = CXBhave(tx.buf.i);
     for (i = 0; i < n; i++)
@@ -544,12 +553,14 @@ do_tx_pre(void) {
   }
 
   //
-  // mix in CW tone here
+  // mix in CW tone here?
   //
 
   correctIQ(tx.buf.i, tx.iqfix);
 
   if (tx.spr.flag) SpeechProcessor(tx.spr.gen);
+
+  if (tx.tick == 0) reset_OvSv(tx.filt.ovsv);
   filter_OvSv(tx.filt.ovsv);
 }
 
@@ -576,38 +587,43 @@ do_tx_post(void) {
 
 PRIVATE void
 do_tx_SBCW(void) {
-  int i, n = min(CXBhave(tx.buf.i), uni.buflen); 
-  for (i = 0; i < n; i++) {
-    tx.scl.dc = Cadd(Cscl(tx.scl.dc, 0.99),
-		     Cscl(CXBdata(tx.buf.o,i), -0.01));
-    CXBdata(tx.buf.o, i) = Cadd(CXBdata(tx.buf.o,i), tx.scl.dc);
-  }
+  int i, n = min(CXBhave(tx.buf.o), uni.buflen); 
+
+  if ((tx.norm = CXBnorm(tx.buf.o)) > 0.0)
+    for (i = 0; i < n; i++) {
+      tx.scl.dc = Cadd(Cscl(tx.scl.dc, 0.99),
+		       Cscl(CXBdata(tx.buf.o, i), -0.01));
+      CXBdata(tx.buf.o, i) = Cadd(CXBdata(tx.buf.o, i), tx.scl.dc);
+    }
 }
 
 PRIVATE void
 do_tx_AM(void) {
-  int i, n = min(CXBhave(tx.buf.i), uni.buflen); 
-  for (i = 0; i < n; i++) { 
-    tx.scl.dc = Cadd(Cscl(tx.scl.dc, 0.999),
-		     Cscl(CXBdata(tx.buf.o,i), -0.001));
-    CXBreal(tx.buf.o, i) =
-      0.49995 + 0.49995 * (CXBreal(tx.buf.o,i) - tx.scl.dc.re);
-    CXBimag(tx.buf.o, i) = 0.0;
-  }
+  int i, n = min(CXBhave(tx.buf.o), uni.buflen); 
+
+  if ((tx.norm = CXBnorm(tx.buf.o)) > 0.0)
+    for (i = 0; i < n; i++) { 
+      tx.scl.dc = Cadd(Cscl(tx.scl.dc, 0.999),
+		       Cscl(CXBdata(tx.buf.o, i), -0.001));
+      CXBreal(tx.buf.o, i) =
+	0.49995 + 0.49995 * (CXBreal(tx.buf.o, i) - tx.scl.dc.re);
+      CXBimag(tx.buf.o, i) = 0.0;
+    }
 }
 
 PRIVATE void
 do_tx_FM(void) {
-  int i, n = min(CXBhave(tx.buf.i), uni.buflen);
-  for (i = 0; i < n; i++) {
-    tx.scl.dc = Cadd(Cscl(tx.scl.dc,0.999),
-		     Cscl(CXBdata(tx.buf.o,i), 0.001));
-    tx.osc.phase += (CXBreal(tx.buf.o, i)- tx.scl.dc.re) * CvtMod2Freq;
-    if (tx.osc.phase >= TWOPI) tx.osc.phase -= TWOPI;
-    if (tx.osc.phase < 0.0) tx.osc.phase += TWOPI;
-    CXBdata(tx.buf.o, i) =
-      Cscl(Cmplx(cos(tx.osc.phase), sin(tx.osc.phase)), 0.99999);
-  }
+  int i, n = min(CXBhave(tx.buf.o), uni.buflen);
+  if ((tx.norm = CXBnorm(tx.buf.o)) > 0.0)
+    for (i = 0; i < n; i++) {
+      tx.scl.dc = Cadd(Cscl(tx.scl.dc, 0.999),
+		       Cscl(CXBdata(tx.buf.o, i), 0.001));
+      tx.osc.phase += (CXBreal(tx.buf.o, i) - tx.scl.dc.re) * CvtMod2Freq;
+      if (tx.osc.phase >= TWOPI) tx.osc.phase -= TWOPI;
+      if (tx.osc.phase < 0.0) tx.osc.phase += TWOPI;
+      CXBdata(tx.buf.o, i) =
+	Cscl(Cmplx(cos(tx.osc.phase), sin(tx.osc.phase)), 0.99999);
+    }
 }
 
 PRIVATE void
@@ -668,7 +684,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(rx.buf.o) = n;
+    CXBhave(tx.buf.o) = n;
     break;
   }
 
diff --git a/jDttSP/sdrexport.h b/jDttSP/sdrexport.h
index b561bf7..860e3bf 100644
--- a/jDttSP/sdrexport.h
+++ b/jDttSP/sdrexport.h
@@ -147,15 +147,13 @@ extern struct _rx {
   } scl;
   struct {
     REAL thresh, power;
-    struct {
-      REAL meter, att, gain;
-    } offset;
     BOOLEAN flag, running, set;
     int num;
   } squelch;
   SDRMODE mode;
   struct { BOOLEAN flag; } bin;
   long tick;
+  REAL norm;
 } rx;
 
 //------------------------------------------------------------------------
@@ -197,6 +195,7 @@ extern struct _tx {
   } scl;
   SDRMODE mode;
   long tick;
+  REAL norm;
 } tx;
 
 //------------------------------------------------------------------------
diff --git a/jDttSP/spottone.c b/jDttSP/spottone.c
index e4b2aa2..713ee13 100644
--- a/jDttSP/spottone.c
+++ b/jDttSP/spottone.c
@@ -1,5 +1,37 @@
 /* spottone.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 <spottone.h>
 
 //------------------------------------------------------------------------
diff --git a/jDttSP/spottone.h b/jDttSP/spottone.h
index 1b46240..8bd02d5 100644
--- a/jDttSP/spottone.h
+++ b/jDttSP/spottone.h
@@ -1,4 +1,35 @@
 /* spottone.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 _spottone_h
 #define _spottone_h
diff --git a/jDttSP/thunk.c b/jDttSP/thunk.c
index e52d80a..21762f6 100644
--- a/jDttSP/thunk.c
+++ b/jDttSP/thunk.c
@@ -21,3 +21,14 @@ Thunk_lookup(CTB ctb, char *key) {
   }
   return (Thunk) 0;
 }
+
+#ifdef notdef
+unsigned long
+hash(unsigned char *str) {
+  unsigned long hash = 5381;
+  int c;
+  while (c = *str++)
+    hash = ((hash << 5) + hash) + c; // (hash * 33 + c) better
+  return hash;
+}
+#endif
diff --git a/jDttSP/update.c b/jDttSP/update.c
index 6d2594a..a090b76 100644
--- a/jDttSP/update.c
+++ b/jDttSP/update.c
@@ -521,24 +521,6 @@ setSquelch(int n, char **p) {
   return 0;
 }
 
-PRIVATE int
-setMeterOffset(int n, char **p) {
-  rx.squelch.offset.meter = atof(p[0]);
-  return 0;
-}
-
-PRIVATE int
-setATTOffset(int n, char **p) {
-  rx.squelch.offset.att = atof(p[0]);
-  return 0;
-}
-
-PRIVATE int
-setGainOffset(int n, char **p) {
-  rx.squelch.offset.gain = atof(p[0]);
-  return 0;
-}
-
 PRIVATE int
 setSquelchSt(int n, char **p) {
   rx.squelch.flag = atoi(p[0]);
@@ -672,12 +654,9 @@ setRingBufferReset(int n, char **p) {
 CTE update_cmds[] = {
   {"setANF", setANF},
   {"setANFvals", setANFvals},
-  {"setATTOffset", setATTOffset},
   {"setBIN", setBIN},
   {"setFilter", setFilter},
   {"setFinished", setFinished},
-  {"setGainOffset", setGainOffset},
-  {"setMeterOffset", setMeterOffset},
   {"setMode", setMode},
   {"setNB", setNB},
   {"setNBvals", setNBvals},