]> git.rkrishnan.org Git - pihpsdr.git/commitdiff
Now CAT CW behaves correct when manually hitting the external
authorc vw <dl1ycf@darc.de>
Fri, 6 Jul 2018 13:06:06 +0000 (15:06 +0200)
committerc vw <dl1ycf@darc.de>
Fri, 6 Jul 2018 13:06:06 +0000 (15:06 +0200)
CW key during CAT CW transmission, or when manually switching
"MOX" during that time.

Makefile
old_protocol.c
radio.c
radio.h
rigctl.c
transmitter.c
vox.c
vox.h

index 169f277e5339cceaa3f1ca7d5158a8c2af941048..5c0a4fa8f0829888637cc1caefebe8a053ccd1ff 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -405,7 +405,7 @@ ext.o \
 error_handler.o
 
 $(PROGRAM):  $(OBJS) $(REMOTE_OBJS) $(USBOZY_OBJS) $(LIMESDR_OBJS) $(FREEDV_OBJS) $(LOCALCW_OBJS) $(I2C_OBJS) $(GPIO_OBJS) $(PSK_OBJS) $(PURESIGNAL_OBJS) $(STEMLAB_OBJS)
-       $(LINK) -o $(PROGRAM) $(OBJS) $(REMOTE_OBJS) $(USBOZY_OBJS) $(I2C_OBJS) $(GPIO_OBJS) $(LIMESDR_OBJS) $(FREEDV_OBJS) $(LOCALCW_OBJS) $(PSK_OBJS) $(LIBS) $(PURESIGNAL_OBJS) $(STEMLAB_OBJS)
+       $(LINK) -o $(PROGRAM) $(OBJS) $(REMOTE_OBJS) $(USBOZY_OBJS) $(I2C_OBJS) $(GPIO_OBJS) $(LIMESDR_OBJS) $(FREEDV_OBJS) $(LOCALCW_OBJS) $(PSK_OBJS) $(PURESIGNAL_OBJS) $(STEMLAB_OBJS) $(LIBS)
 
 all: prebuild  $(PROGRAM) $(HEADERS) $(REMOTE_HEADERS) $(USBOZY_HEADERS) $(LIMESDR_HEADERS) $(FREEDV_HEADERS) $(LOCALCW_HEADERS) $(I2C_HEADERS) $(GPIO_HEADERS) $(PSK_HEADERS) $(PURESIGNAL_HEADERS) $(STEMLAB_HEADERS) $(SOURCES) $(REMOTE_SOURCES) $(USBOZY_SOURCES) $(LIMESDR_SOURCES) $(FREEDV_SOURCES) $(I2C_SOURCES) $(GPIO_SOURCES) $(PSK_SOURCES) $(PURESIGNAL_SOURCES) $(STEMLAB_SOURCES)
 
index 6ff6c18389deca7a28923f03ce49283e906a76e0..4b944f5e54b8e9ca9699836416ea7b61f331283b 100644 (file)
@@ -164,7 +164,6 @@ static int command=1;
 static GThread *receive_thread_id;
 static void start_receive_thread();
 static gpointer receive_thread(gpointer arg);
-// DL1YCF changed buffer to uchar*
 static void process_ozy_input_buffer(unsigned char  *buffer);
 static void process_bandscope_buffer(char  *buffer);
 void ozy_send_buffer();
@@ -173,10 +172,8 @@ static unsigned char metis_buffer[1032];
 static long send_sequence=-1;
 static int metis_offset=8;
 
-// DL1YCF changed buffer to uchar*
 static int metis_write(unsigned char ep,unsigned char* buffer,int length);
 static void metis_start_stop(int command);
-// DL1YCF changed buffer to uchar*
 static void metis_send_buffer(unsigned char* buffer,int length);
 static void metis_restart();
 
@@ -380,7 +377,6 @@ static void start_receive_thread() {
 
 static gpointer receive_thread(gpointer arg) {
   struct sockaddr_in addr;
-  // DL1YCF changed from int to socklen_t
   socklen_t length;
   unsigned char buffer[2048];
   int bytes_read;
@@ -421,7 +417,6 @@ static gpointer receive_thread(gpointer arg) {
               // get the sequence number
               sequence=((buffer[4]&0xFF)<<24)+((buffer[5]&0xFF)<<16)+((buffer[6]&0xFF)<<8)+(buffer[7]&0xFF);
 
-             // DL1YCF: added check on lost packets
               if (sequence != last_seq_num+1) {
                fprintf(stderr,"SEQ ERROR: last %ld, recvd %ld\n", last_seq_num, sequence);
              }
@@ -465,11 +460,9 @@ static gpointer receive_thread(gpointer arg) {
         break;
     }
   }
-  // DL1YCF added return statement to make compiler happy.
   return NULL;
 }
 
-// Dl1YCF changed buffer to uchar*
 static void process_ozy_input_buffer(unsigned char  *buffer) {
   int i,j;
   int r;
@@ -514,6 +507,7 @@ static void process_ozy_input_buffer(unsigned char  *buffer) {
     dot=(control_in[0]&0x04)==0x04;
 
     local_ptt=ptt;
+    if (dot || dash) external_cw_key_hit=1;
     if(vfo[tx_vfo].mode==modeCWL || vfo[tx_vfo].mode==modeCWU) {
       local_ptt=ptt|dot|dash;
     }
@@ -1226,12 +1220,15 @@ void ozy_send_buffer() {
   }
   if(mode==modeCWU || mode==modeCWL) {
 //
-//  VOX is set in CW if we send
-//  morse code via CAT. This is
-//  supported even if we use the
-//  external keyer for our paddles.
+//  The default is doing 'external CW', that is,
+//  CW is entirely done on the HPSDR board. In this
+//  case, we should not set MOX, the PTT switching on
+//  the HPSDR board is done by the board itself.
 //
-    if(tune || vox) {
+//  However, if we are doing local CW or tuning,
+//  we must put the SDR into TX mode.
+//
+    if(isTransmitting() && (tune || local_cw_is_active)) {
       output_buffer[C0]|=0x01;
     }
   } else {
@@ -1320,7 +1317,6 @@ static int ozyusb_write(char* buffer,int length)
 }
 #endif
 
-// DL1YCF change buffer to uchar*
 static int metis_write(unsigned char ep,unsigned char* buffer,int length) {
   int i;
 
@@ -1354,7 +1350,6 @@ static int metis_write(unsigned char ep,unsigned char* buffer,int length) {
 
 static void metis_restart() {
   // reset metis frame
-  // DL1YCF change == to = in the next line
   metis_offset=8;
 
   // reset current rx
@@ -1376,7 +1371,6 @@ static void metis_restart() {
   ozy_send_buffer();
   ozy_send_buffer();
 
-  // DL1YCF: reset for the next commands
   current_rx=0;
   command=1;
 #else
@@ -1422,7 +1416,6 @@ static void metis_start_stop(int command) {
 #endif
 }
 
-// DL1YCF changedbuffer to uchar *
 static void metis_send_buffer(unsigned char* buffer,int length) {
   if(sendto(data_socket,buffer,length,0,(struct sockaddr*)&data_addr,data_addr_length)!=length) {
     perror("sendto socket failed for metis_send_data\n");
diff --git a/radio.c b/radio.c
index a2235ee1eee3887c6fe2d99bfa5cffe51760d175..0b0c17634be184e9e9c03590f64c2a1233594b68 100644 (file)
--- a/radio.c
+++ b/radio.c
@@ -1,4 +1,4 @@
-/* Copyrieht (C)
+/* Copyright (C)
 * 2015 - John Melton, G0ORX/N6LYT
 *
 * This program is free software; you can redistribute it and/or
@@ -288,6 +288,8 @@ double vox_threshold=0.001;
 double vox_gain=10.0;
 double vox_hang=250.0;
 int vox=0;
+int local_cw_is_active=0;
+int external_cw_key_hit=0;
 
 int diversity_enabled=0;
 double i_rotate[2]={1.0,1.0};
diff --git a/radio.h b/radio.h
index d8d6663d9df1f436645e15a8e875d5d0ece1cfa0..c48f1db0b65f29beec908176f355e19df0ada74c 100644 (file)
--- a/radio.h
+++ b/radio.h
@@ -244,6 +244,8 @@ extern double vox_threshold;
 extern double vox_gain;
 extern double vox_hang;
 extern int vox;
+extern int local_cw_is_active;
+extern int external_cw_key_hit;
 
 extern int diversity_enabled;
 extern double i_rotate[2];
index 76af632b33d67baa193cbce9eecbf523542c9f90..42a7668e90d7c582fad6b07703a86d7fde3c7041 100644 (file)
--- a/rigctl.c
+++ b/rigctl.c
@@ -54,9 +54,6 @@
 #include "rigctl_menu.h"
 #include <math.h>
 
-extern void vox_trigger(int lead_in);
-extern void vox_untrigger();
-
 // IP stuff below
 #include <sys/socket.h>
 #include <arpa/inet.h> //inet_addr
@@ -320,6 +317,7 @@ extern int cw_key_up, cw_key_down;
 //
 void send_dash() {
   int TimeToGo;
+  if (external_cw_key_hit) return;
   for(;;) {
     TimeToGo=cw_key_up+cw_key_down;
     if (TimeToGo == 0) break;
@@ -334,6 +332,7 @@ void send_dash() {
 
 void send_dot() {
   int TimeToGo;
+  if (external_cw_key_hit) return;
   for(;;) {
     TimeToGo=cw_key_up+cw_key_down;
     if (TimeToGo == 0) break;
@@ -348,6 +347,7 @@ void send_dot() {
 
 void send_space(int len) {
   int TimeToGo;
+  if (external_cw_key_hit) return;
   for(;;) {
     TimeToGo=cw_key_up+cw_key_down;
     if (TimeToGo == 0) break;
@@ -476,34 +476,78 @@ void rigctl_send_cw_char(char cw_char) {
 //
 static gpointer rigctl_cw_thread(gpointer data)
 { 
-  int index;
+  int i;
   char c;
   char local_buf[30];
   
   while (server_running) {
     // wait for cw_buf become filled with data
+    // (periodically look every 100 msec)
+    external_cw_key_hit=0;
     if (!cw_busy) {
       usleep(100000L);
       continue;
     }
+    strncpy(local_buf, cw_buf, 30);
+    cw_busy=0; // mark buffer free again
+    // these values may have changed
     dotlen = 1200000L/(long)cw_keyer_speed;
     dashlen = (dotlen * 3 * cw_keyer_weight) / 50L;
     dotsamples = 57600 / cw_keyer_speed;
     dashsamples = (3456 * cw_keyer_weight) / cw_keyer_speed;
-    strncpy(local_buf, cw_buf, 30);
-    index=0;
-    cw_busy=0; // mark buffer free again
-    // We need a relatively long lead-in time after triggering VOX
-    // Otherwise the first element is shortened
-    vox_trigger(100);  // lead-in 100 msec, only before the first character is sent.
-    while(((c=local_buf[index++]) != '\0')) {
+    local_cw_is_active=1;
+    if (!mox) {
+       // activate PTT
+        g_idle_add(ext_ptt_update ,(gpointer)1);
+        g_idle_add(ext_cw_key     ,(gpointer)1);
+       // have to wait until it is really there
+        while (!mox) usleep(50000L);
+       // some extra time to settle down
+        usleep(100000L);
+    }
+    i=0;
+    while(((c=local_buf[i++]) != '\0') && !external_cw_key_hit && mox) {
         rigctl_send_cw_char(c);
     }
-    vox_untrigger();
+    //
+    // Either an external CW key has been hit (one connected to the SDR board),
+    // or MOX has manually been switched. In this case swallow any pending
+    // or incoming KY messages for about 0.75 sec. We need this long time since
+    // hamlib waits 0.5 secs after receiving a "KY1" message before trying to
+    // send the next bunch (do PTT update immediately).
+    //
+    if (external_cw_key_hit || !mox) {
+       local_cw_is_active=0;
+       g_idle_add(ext_cw_key     ,(gpointer)0);
+       // If an external CW key has been hit, we continue in TX mode
+       // doing CW manually. Otherwise, switch PTT off.
+       if (!external_cw_key_hit) {
+         g_idle_add(ext_ptt_update ,(gpointer)0);
+       }
+       for (i=0; i< 75; i++) {
+         cw_busy=0;      // mark buffer free
+         usleep(10000L);
+       }
+    }
+    // If the next message is pending, continue
+    if (cw_busy) continue;
+    local_cw_is_active=0;
+    // In case of an abort of local CW, this has already been done.
+    // It is not too harmful to do it again. In case of "normal termination"
+    // of sending a CAT CW message, we have to do it here.
+    g_idle_add(ext_cw_key     ,(gpointer)0);
+    if (!external_cw_key_hit) {
+      g_idle_add(ext_ptt_update ,(gpointer)0);
+    }
   }
   // We arrive here if the rigctl server shuts down.
+  // This very rarely happens. But we should shut down the
+  // local CW system gracefully, in case we were in the mid
+  // of a transmission
   rigctl_cw_thread_id = NULL;
   cw_busy=0;
+  g_idle_add(ext_cw_key     ,(gpointer)0);
+  g_idle_add(ext_ptt_update ,(gpointer)0);
   return NULL;
 }
 
@@ -2127,10 +2171,12 @@ void parse_cmd ( char * cmd_input,int len,int client_sock) {
                                            // changes the WDSP side tone frequency.
                                            g_idle_add(ext_cw_setup,NULL);    // Initialize for external transmit
                                            // We silently ignore buffer overruns. This does not happen with
-                                           // hamlib since I fixed it.
-                                           if (!cw_busy) {
+                                           // hamlib since I fixed it. Note further that the space immediately
+                                           // following "KY" is *not* part of the message.
+                                           // while cleaning up after hitting external CW key, just skip message
+                                           if (!cw_busy && len > 3 && !external_cw_key_hit) {
                                                // Copy data to buffer
-                                               strncpy(cw_buf, cmd_input+2, 30);
+                                               strncpy(cw_buf, cmd_input+3, 30);
                                                // Kenwood protocol allows for at most 28 characters, so
                                                // this is pure paranoia
                                                cw_buf[29]=0;
index 454ea0ccba9da5f36244d0a5e6384c98ae4a8e9c..91f9eb07f9829fd0b555c277a8f786efee21d02d 100644 (file)
@@ -64,6 +64,7 @@ static int waterfall_resample=8;
 
 int cw_key_up = 0;
 int cw_key_down = 0;
+// cw_shape_buffer will eventually be integrated into TRANSMITTER
 static int *cw_shape_buffer = NULL;
 int cw_shape = 0;
 
@@ -564,7 +565,7 @@ fprintf(stderr,"transmitter: allocate buffers: mic_input_buffer=%d iq_output_buf
   tx->samples=0;
   tx->pixel_samples=malloc(sizeof(float)*tx->pixels);
   if (cw_shape_buffer) free(cw_shape_buffer);
-  cw_shape_buffer=malloc(sizeof(double)*tx->buffer_size);
+  cw_shape_buffer=malloc(sizeof(int)*tx->buffer_size);
 fprintf(stderr,"transmitter: allocate buffers: mic_input_buffer=%p iq_output_buffer=%p pixels=%p\n",tx->mic_input_buffer,tx->iq_output_buffer,tx->pixel_samples);
 
   fprintf(stderr,"create_transmitter: OpenChannel id=%d buffer_size=%d fft_size=%d sample_rate=%d dspRate=%d outputRate=%d\n",
@@ -725,10 +726,10 @@ void tx_set_pre_emphasize(TRANSMITTER *tx,int state) {
 static void full_tx_buffer(TRANSMITTER *tx) {
   long isample;
   long qsample;
-  double gain,lgain,sidevol;
+  double gain,fgain,sidevol;
   int j;
   int error;
-  int mode,do_shape;
+  int mode;
   int sidetone=0;
 
   switch(protocol) {
@@ -740,16 +741,10 @@ static void full_tx_buffer(TRANSMITTER *tx) {
       break;
   }
 
-  // do_shape means that we "shape" the signal with the
-  // envelope in cw_shape_buf, with values between 0 and 200
-  // this is the only way to produce click-free CW signals.
   mode=vfo[VFO_A].mode;
   if(split) {
     mode=vfo[VFO_B].mode;
   }
-  do_shape= (mode == modeCWL || mode == modeCWU) && !tune;
-  if (do_shape) sidevol= 1.29 * cw_keyer_sidetone_volume;   // will be multiplied with cw_shape
-  if (tune)     sidevol= 258.0 * cw_keyer_sidetone_volume;
 
   update_vox(tx);
 
@@ -776,31 +771,58 @@ static void full_tx_buffer(TRANSMITTER *tx) {
       }
     }
 
-    if (do_shape) gain=gain*0.005;
-    for(j=0;j<tx->output_samples;j++) {
-      lgain=gain;
-      if (do_shape) lgain=gain*cw_shape_buffer[j];
-      double is=tx->iq_output_buffer[j*2];
-      double qs=tx->iq_output_buffer[(j*2)+1];
-      isample=is>=0.0?(long)floor(is*lgain+0.5):(long)ceil(is*lgain-0.5);
-      qsample=qs>=0.0?(long)floor(qs*lgain+0.5):(long)ceil(qs*lgain-0.5);
-      switch(protocol) {
-        case ORIGINAL_PROTOCOL:
-         // produce a side tone for internal CW and tune on the HPSDR board
-         // since we may have used getNextSidetoneSample above we need
-         // an independent instance thereof here. To be nice to the CW
-         // operator, the audio is shaped the same way as the RF
-          if (tune) sidetone=sidevol * getNextInternalSideToneSample();
-         if (do_shape) sidetone=sidevol * cw_shape_buffer[j] * getNextInternalSideToneSample();
-          old_protocol_iq_samples_with_sidetone(isample,qsample,sidetone);
-          break;
-        case NEW_PROTOCOL:
-          new_protocol_iq_samples(isample,qsample);
-          break;
-      }
+//  Two times essentially the same code: moved the check on "pulse shape" case
+//  out of the loops for computational efficiency
+
+    if ((mode == modeCWL || mode == modeCWU) && !tune) {
+       //
+       // "pulse shape case":
+       // shape the I/Q samples with the envelope function stored in cw_shape_buffer
+       // and produce side tone (again with shaped pulses)
+       //
+       fgain=gain*0.005;                           // will be multiplied with cw_shape
+        sidevol= 1.29 * cw_keyer_sidetone_volume;   // will be multiplied with cw_shape
+        for(j=0;j<tx->output_samples;j++) {
+           gain=fgain*cw_shape_buffer[j];
+           double is=tx->iq_output_buffer[j*2];
+           double qs=tx->iq_output_buffer[(j*2)+1];
+           isample=is>=0.0?(long)floor(is*gain+0.5):(long)ceil(is*gain-0.5);
+           qsample=qs>=0.0?(long)floor(qs*gain+0.5):(long)ceil(qs*gain-0.5);
+           switch(protocol) {
+               case ORIGINAL_PROTOCOL:
+                   // produce a side tone for internal CW on the HPSDR board
+                   // since we may use getNextSidetoneSample for local audio, we need
+                   // an independent instance thereof here. To be nice to the CW
+                   // operator, the audio is shaped the same way as the RF
+                   sidetone=sidevol * cw_shape_buffer[j] * getNextInternalSideToneSample();
+                   old_protocol_iq_samples_with_sidetone(isample,qsample,sidetone);
+                   break;
+               case NEW_PROTOCOL:
+                   // ToDo: how to produce side-tone on the HPSDR board?
+                   new_protocol_iq_samples(isample,qsample);
+                   break;
+           }
+       }
+    } else {
+       //
+       // Original code without pulse shaping and without side tone
+       //
+       for(j=0;j<tx->output_samples;j++) {
+           double is=tx->iq_output_buffer[j*2];
+           double qs=tx->iq_output_buffer[(j*2)+1];
+           isample=is>=0.0?(long)floor(is*gain+0.5):(long)ceil(is*gain-0.5);
+           qsample=qs>=0.0?(long)floor(qs*gain+0.5):(long)ceil(qs*gain-0.5);
+           switch(protocol) {
+               case ORIGINAL_PROTOCOL:
+                   old_protocol_iq_samples_with_sidetone(isample,qsample,0);
+                   break;
+               case NEW_PROTOCOL:
+                   new_protocol_iq_samples(isample,qsample);
+                   break;
+           }
+       }
     }
   }
-
 }
 
 void add_mic_sample(TRANSMITTER *tx,short mic_sample) {
@@ -815,16 +837,24 @@ void add_mic_sample(TRANSMITTER *tx,short mic_sample) {
     mode=vfo[0].mode;
   }
 
-  // This is valid if not CW and not tuning
-  mic_sample_double=(double)mic_sample/32768.0;
+// silence TX audio not transmitting, if tuning, or
+// when doing CW
 
-  if (tune) mic_sample_double=0.0;
+  if (tune || !isTransmitting() || mode==modeCWL || mode==modeCWU) {
+    mic_sample_double=0.0;
+  } else {
+    mic_sample_double=(double)mic_sample/32768.0;
+  }
+
+//This statement takes care that the cw shape buffer is
+//automatically wiped if we are not doing local CW
+
+  cw_shape_buffer[tx->samples]=0;
 
   if((mode==modeCWL || mode==modeCWU)) {
-    mic_sample_double=0.0;
     if (isTransmitting()) {
 //
-//     The CW part sets the variables cw_key_up and cw_key_down
+//     RigCtl CW sets the variables cw_key_up and cw_key_down
 //     to the number of samples for the next down/up sequence.
 //     cw_key_down can be zero, for inserting some space
 //
@@ -844,9 +874,16 @@ void add_mic_sample(TRANSMITTER *tx,short mic_sample) {
            }
        }
        cw_audio_write(0.00003937 * getNextSideToneSample() * cw_keyer_sidetone_volume * cw_shape);
+        cw_shape_buffer[tx->samples]=cw_shape;
+    } else {
+//
+//     Have to reset pulse shaper since RigCtl may wait forever
+//
+       cw_key_up=0;
+       cw_key_down=0;
+       cw_shape=0;
     }
   }
-  cw_shape_buffer[tx->samples]=cw_shape;
   tx->mic_input_buffer[tx->samples*2]=mic_sample_double;
   tx->mic_input_buffer[(tx->samples*2)+1]=0.0; //mic_sample_double;
   tx->samples++;
diff --git a/vox.c b/vox.c
index 9058b7d6487d9d986c885de32aa6a0577f5054fc..204c9b516d1616bc56845fe2a02d75dbe0d5130b 100644 (file)
--- a/vox.c
+++ b/vox.c
 #include "ext.h"
 
 static guint vox_timeout;
-static guint trigger_timeout = -1;
 
 static double peak=0.0;
-static int cwvox=0;
 
 static int vox_timeout_cb(gpointer data) {
   //setVox(0);
@@ -38,16 +36,6 @@ static int vox_timeout_cb(gpointer data) {
   return FALSE;
 }
 
-static int cwvox_timeout_cb(gpointer data) {
-  cwvox=0;
-  trigger_timeout=-1;
-  g_idle_add(ext_vox_changed,(gpointer)0);
-  g_idle_add(ext_cw_key     ,(gpointer)0);
-  g_idle_add(ext_vfo_update,NULL);
-  return FALSE;
-}
-
-
 double vox_get_peak() {
   double result=peak;
   return result;
@@ -61,8 +49,6 @@ void update_vox(TRANSMITTER *tx) {
   double sample;
   peak=0.0;
 
-  if (cwvox) return;  // do not set peak and fiddle around with VOX when in local CW mode
-
   for(i=0;i<tx->buffer_size;i++) {
     sample=tx->mic_input_buffer[i*2];
     if(sample<0.0) {
@@ -88,29 +74,6 @@ void update_vox(TRANSMITTER *tx) {
   }
 }
 
-// vox_trigger activates MOX, vox_enabled does not matter here
-// if VOX this is already pending, 
-void vox_trigger(int lead_in) {
-
-  // delete any pending hang timers
-  if (trigger_timeout >= 0) g_source_remove(trigger_timeout);
-
-  if (!cwvox) {
-    cwvox=1;
-    g_idle_add(ext_vox_changed,(gpointer)1);
-    g_idle_add(ext_cw_key     ,(gpointer)1);
-    g_idle_add(ext_vfo_update,NULL);
-    usleep((long)lead_in*1000L);  // lead-in time is in ms
-  }
-}
-
-// vox_untrigger actives the vox hang timer for cwvox
-// so an immediately following "KY" CAT command will
-// just proceed.
-void vox_untrigger() {
-  trigger_timeout=g_timeout_add((int)vox_hang,cwvox_timeout_cb,NULL);
-}
-
 void vox_cancel() {
   if(vox) {
     g_source_remove(vox_timeout);
diff --git a/vox.h b/vox.h
index 34bd48e24c85a00ed01a955f5a4297b1c66879f1..e6a1f3fa277d19c2a76ad1ea5091c3558942f676 100644 (file)
--- a/vox.h
+++ b/vox.h
@@ -19,4 +19,5 @@
 
 extern void update_vox(TRANSMITTER *tx);
 extern void vox_cancel();
+extern void vox_cw_cancel();
 extern double vox_get_peak();