]> git.rkrishnan.org Git - pihpsdr.git/commitdiff
CAT CW now works, and many other fixes connect with local
authorc vw <dl1ycf@darc.de>
Tue, 3 Jul 2018 17:11:28 +0000 (19:11 +0200)
committerc vw <dl1ycf@darc.de>
Tue, 3 Jul 2018 17:11:28 +0000 (19:11 +0200)
CW generation, CW tx frequencies, and CW/TUNE side tones.

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

index 8b2aa128e0b355849ebd8c8167b5217785a03442..6ff6c18389deca7a28923f03ce49283e906a76e0 100644 (file)
@@ -691,6 +691,30 @@ void old_protocol_audio_samples(RECEIVER *rx,short left_audio_sample,short right
   }
 }
 
+//
+// This is a copy of old_protocol_iq_samples,
+// but it includes the possibility to send a side tone
+// We use it to provide a side-tone for CW/TUNE, in
+// all other cases side==0 and this routine then is
+// fully equivalent to old_protocol_iq_samples.
+//
+void old_protocol_iq_samples_with_sidetone(int isample, int qsample, int side) {
+  if(isTransmitting()) {
+    output_buffer[output_buffer_index++]=side >> 8;
+    output_buffer[output_buffer_index++]=side;
+    output_buffer[output_buffer_index++]=side >> 8;
+    output_buffer[output_buffer_index++]=side;
+    output_buffer[output_buffer_index++]=isample>>8;
+    output_buffer[output_buffer_index++]=isample;
+    output_buffer[output_buffer_index++]=qsample>>8;
+    output_buffer[output_buffer_index++]=qsample;
+    if(output_buffer_index>=OZY_BUFFER_SIZE) {
+      ozy_send_buffer();
+      output_buffer_index=8;
+    }
+  }
+}
+
 void old_protocol_iq_samples(int isample,int qsample) {
   if(isTransmitting()) {
     output_buffer[output_buffer_index++]=0;
@@ -1201,7 +1225,13 @@ void ozy_send_buffer() {
     mode=vfo[0].mode;
   }
   if(mode==modeCWU || mode==modeCWL) {
-    if(tune) {
+//
+//  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.
+//
+    if(tune || vox) {
       output_buffer[C0]|=0x01;
     }
   } else {
index f179090afcc36ce58ce865a6d814ab43a4a64519..c7cf63cc1eb81a01af0280e05ad562bad2fa29fa 100644 (file)
@@ -29,4 +29,5 @@ extern void old_protocol_set_mic_sample_rate(int rate);
 extern void old_protocol_process_local_mic(unsigned char *buffer,int le);
 extern void old_protocol_audio_samples(RECEIVER *rx,short left_audio_sample,short right_audio_sample);
 extern void old_protocol_iq_samples(int isample,int qsample);
+extern void old_protocol_iq_samples_with_sidetone(int isample,int qsample,int side);
 #endif
index 69f77fc7c93d6979d3da3834de5402109f6bbcba..dfb1dc0224d15f6c90030c4743663502a327d1d4 100644 (file)
@@ -367,11 +367,14 @@ void audio_close_output(RECEIVER *rx) {
 // we have to store the data such that the PA callback function
 // can access it.
 //
-static int apt=0;
 int audio_write (RECEIVER *rx, short r, short l)
 {
   PaError err;
 
+  // this will cause massive underflow errors, since
+  // we do not provide any data while transmitting.
+  // Instead, the side tone version will take over
+  if (rx == active_receiver && isTransmitting()) return 0;
   if (rx->playback_handle != NULL && rx->playback_buffer != NULL) {
     rx->playback_buffer[rx->playback_offset++] = (r + l) *0.000015259;  //   65536 --> 1.0   
     if (rx->playback_offset == audio_buffer_size) {
@@ -386,11 +389,22 @@ int audio_write (RECEIVER *rx, short r, short l)
   return 0;
 }
 
-//
-// CW audio write 
-// This is a dummy here because I think it is not correctly implemented in audio.c
-//
-void cw_audio_write(double sample) {
-}   
+int cw_audio_write(double sample) {
+  PaError err;
+  RECEIVER *rx = active_receiver;
+
+  if (rx->playback_handle != NULL && rx->playback_buffer != NULL) {
+    rx->playback_buffer[rx->playback_offset++] = sample;
+    if (rx->playback_offset == audio_buffer_size) {
+      rx->playback_offset=0;
+      err=Pa_WriteStream(rx->playback_handle, (void *) rx->playback_buffer, (unsigned long) audio_buffer_size);
+      //if (err != paNoError) {
+      //  fprintf(stderr,"PORTAUDIO ERROR: write stream dev=%d: %s\n",out_device_no[rx->audio_device],Pa_GetErrorText(err));
+      //  return -1;
+      // }
+    }
+  }
+  return 0;
+}
 
 #endif
diff --git a/radio.c b/radio.c
index 121fddfc92d494fcae846e3e6222382789bdb30c..a2235ee1eee3887c6fe2d99bfa5cffe51760d175 100644 (file)
--- a/radio.c
+++ b/radio.c
@@ -844,14 +844,22 @@ void setTune(int state) {
       }
       pre_tune_mode=mode;
 
+      // DL1YCF: in USB/DIGU/DSB, tune 1000 Hz above carrier
+      //         in LSB/DIGL,     tune 1000 Hz below carrier
+      //         all other (CW, AM, FM): tune on carrier freq.
+      //         Note: do not look at CW sidetone freq here.
       switch(mode) {
         case modeLSB:
-        case modeCWL:
         case modeDIGL:
-          SetTXAPostGenToneFreq(transmitter->id,-(double)cw_keyer_sidetone_frequency);
+          SetTXAPostGenToneFreq(transmitter->id,-(double)1000.0);
+          break;
+        case modeUSB:
+        case modeDSB:
+        case modeDIGU:
+          SetTXAPostGenToneFreq(transmitter->id,(double)1000.0);
           break;
         default:
-          SetTXAPostGenToneFreq(transmitter->id,(double)cw_keyer_sidetone_frequency);
+          SetTXAPostGenToneFreq(transmitter->id,(double)0.0);
           break;
       }
 
@@ -895,15 +903,9 @@ void radio_cw_setup() {
     mode=vfo[VFO_B].mode;
   }
 
-  double freq=(double)cw_keyer_sidetone_frequency;
-  switch(mode) {
-    case modeCWU:
-      SetTXAPostGenToneFreq(transmitter->id,(double)cw_keyer_sidetone_frequency);
-      break;
-    case modeLSB:
-      SetTXAPostGenToneFreq(transmitter->id,-(double)cw_keyer_sidetone_frequency);
-      break;
-  }
+  // DL1YCF: to be "transceive" in CW, our signal
+  // needs to be spot-on the nominal frequency
+  SetTXAPostGenToneFreq(transmitter->id,(double) 0.0);
   SetTXAPostGenMode(transmitter->id,0);
   SetTXAPostGenToneMag(transmitter->id,0.99999);
 }
index 06a841bdb2d1787863bbca40bd0175c09dd71762..76af632b33d67baa193cbce9eecbf523542c9f90 100644 (file)
--- a/rigctl.c
+++ b/rigctl.c
 #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
 
-#undef RIGCTL_DEBUG
 //#define RIGCTL_DEBUG
 
 int rigctl_port_base=19090;
@@ -76,8 +78,6 @@ static const int TelnetPortC = 19092;
 #define MAXDATASIZE 2000
 
 void parse_cmd ();
-int cw_busy = 0; // Used to signal that the system is in a busy state for sending CW - assert busy back to the user
-int cw_reset = 0; // Signals reset the transceiver
 int connect_cnt = 0;
 
 int rigctlGetFilterLow();
@@ -111,6 +111,7 @@ FILTER * band_filter;
 #define MAX_CLIENTS 3
 static GThread *rigctl_server_thread_id = NULL;
 static GThread *rigctl_set_timer_thread_id = NULL;
+static GThread *rigctl_cw_thread_id = NULL;
 static int server_running;
 
 static GThread *serial_server_thread_id = NULL;
@@ -293,35 +294,74 @@ int convert_ctcss() {
 }
 int vfo_sm=0;   // VFO State Machine - this keeps track of
 
-// Now my stuff
+// 
+//  CW sending stuff
+//
+
+static char cw_buf[30];
+static int  cw_busy=0;
+
+static long dotlen;
+static long dashlen;
+static int  dotsamples;
+static int  dashsamples;
+
+extern int cw_key_up, cw_key_down;
+
+//
+// send_dash()         send a "key-down" of a dashlen, followed by a "key-up" of a dotlen
+// send_dot()          send a "key-down" of a dotlen,  followed by a "key-up" of a dotlen
+// send_space(int len) send a "key_down" of zero,      followed by a "key-up" of len*dotlen
+//
+// The "trick" to get proper timing is, that we really specify  the number of samples
+// for the next element (dash/dot/nothing) and the following pause. 30 wpm is no
+// problem, and without too much "busy waiting". We just take a nap until 10 msec
+// before we have to act, and then wait several times for 1 msec until we can shoot.
 //
 void send_dash() {
-       //long delay = (1200000L * ((long)cw_keyer_weight/10L))/(long)cw_keyer_speed ;
-       int dot_delay = 1200/cw_keyer_speed;
-       int delay = (dot_delay * 3 * cw_keyer_weight)/50;
-       g_idle_add(ext_cw_key, (gpointer)(long)1);
-       usleep((long)delay*3000L);
-       g_idle_add(ext_cw_key, (gpointer)(long)0);
-       usleep((long)delay * 1000L);
-       //fprintf(stderr,"_%d",mox);
-       
+  int TimeToGo;
+  for(;;) {
+    TimeToGo=cw_key_up+cw_key_down;
+    if (TimeToGo == 0) break;
+    // sleep until 10 msec before ignition
+    if (TimeToGo > 500) usleep((long)(TimeToGo-500)*20L);
+    // sleep 1 msec
+    usleep(1000L);
+  }
+  cw_key_down = dashsamples;
+  cw_key_up   = dotsamples;
 }
+
 void send_dot() {
-       int dot_delay = 1200/cw_keyer_speed;
-       g_idle_add(ext_cw_key, (gpointer)(long)1);
-       usleep((long)dot_delay * 1000L);
-       g_idle_add(ext_cw_key, (gpointer)(long)0);
-       usleep((long)dot_delay* 1000L);
-       //fprintf(stderr,".%d",mox);
+  int TimeToGo;
+  for(;;) {
+    TimeToGo=cw_key_up+cw_key_down;
+    if (TimeToGo == 0) break;
+    // sleep until 10 msec before ignition
+    if (TimeToGo > 500) usleep((long)(TimeToGo-500)*20L);
+    // sleep 1 msec
+    usleep(1000L);
+  }
+  cw_key_down = dotsamples;
+  cw_key_up   = dotsamples;
 }
-void send_space() {
-       int dot_delay = 1200/cw_keyer_speed;
-       usleep((long)dot_delay* 7000L);
-       //fprintf(stderr," %d",mox);
+
+void send_space(int len) {
+  int TimeToGo;
+  for(;;) {
+    TimeToGo=cw_key_up+cw_key_down;
+    if (TimeToGo == 0) break;
+    // sleep until 10 msec before ignition
+    if (TimeToGo > 500) usleep((long)(TimeToGo-500)*20L);
+    // sleep 1 msec
+    usleep(1000L);
+  }
+  cw_key_up = len*dotsamples;
 }
 
 void rigctl_send_cw_char(char cw_char) {
     char pattern[9],*ptr;
+    static char last_cw_char=0;
     strcpy(pattern,"");
     ptr = &pattern[0];
     switch (cw_char) {
@@ -358,7 +398,7 @@ void rigctl_send_cw_char(char cw_char) {
        case 'p': 
        case 'P': strcpy(pattern,".--."); break;
        case 'q': 
-       case 'Q': strcpy(pattern,"-.--"); break;
+       case 'Q': strcpy(pattern,"--.-"); break;
        case 'r': 
        case 'R': strcpy(pattern,".-."); break;
        case 's': 
@@ -397,11 +437,8 @@ void rigctl_send_cw_char(char cw_char) {
        case '-': strcpy(pattern,"-....-");break;
        case '_': strcpy(pattern,".--.-.");break;
        case '@': strcpy(pattern,"..--.-");break;
-       case ' ': strcpy(pattern," ");break;
        default:  strcpy(pattern," ");
     }
-    //fprintf(stderr,"Sending %c:",cw_char);
-    g_idle_add(ext_cw_key, (gpointer)(long)0);
      
     while(*ptr != '\0') {
        if(*ptr == '-') {
@@ -410,16 +447,66 @@ void rigctl_send_cw_char(char cw_char) {
        if(*ptr == '.') {
           send_dot();
        }
-       if(*ptr == ' ') {
-          send_space();
-       }
        ptr++;
     }
-    // Need a delay HERE between characters
-    long delay = (1200000L * ((long)cw_keyer_weight/10L))/(long)cw_keyer_speed ;
-    usleep(delay*3L);
-    //fprintf(stderr,"\n");
+    // The last character sent already has one dotlen included.
+    // Therefore, if the character was a "space", we need an additional
+    // inter-word  pause of 6 dotlen, else we need a inter-character
+    // pause of 2 dotlens.
+    // Note that two or more adjacent space characters result in a 
+    // single inter-word distance. This also gets rid of trailing
+    // spaces in the KY command.
+    if (cw_char == ' ') {
+      if (last_cw_char != ' ') send_space(6);
+    } else {
+      send_space(2);
+    }
+    last_cw_char=cw_char;
 }
+
+//
+// This thread constantly looks whether CW data
+// is available, and produces CW in this case.
+// The buffer is copied to a local buffer and
+// immediately released, such that the next
+// chunk can already be prepeared. This way,
+// splitting a large CW text into words, and
+// sending each word with a separate KY command
+// produces perfectly readable CW.
+//
+static gpointer rigctl_cw_thread(gpointer data)
+{ 
+  int index;
+  char c;
+  char local_buf[30];
+  
+  while (server_running) {
+    // wait for cw_buf become filled with data
+    if (!cw_busy) {
+      usleep(100000L);
+      continue;
+    }
+    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')) {
+        rigctl_send_cw_char(c);
+    }
+    vox_untrigger();
+  }
+  // We arrive here if the rigctl server shuts down.
+  rigctl_cw_thread_id = NULL;
+  cw_busy=0;
+  return NULL;
+}
+
 void gui_vfo_move_to(gpointer data) {
    long long freq = *(long long *) data;
    fprintf(stderr,"GUI: %11lld\n",freq);
@@ -486,6 +573,10 @@ static gpointer rigctl_server(gpointer data) {
     client[i].socket=-1;
   }
   server_running=1;
+
+  // must start the thread here in order NOT to inherit a lock
+  if (!rigctl_cw_thread_id) rigctl_cw_thread_id = g_thread_new("RIGCTL cw", rigctl_cw_thread, NULL);
+
   while(server_running) {
     // listen with a max queue of 3
     if(listen(server_socket,3)<0) {
@@ -588,41 +679,6 @@ static gpointer rigctl_client (gpointer data) {
         if(save_flag != 1) {
            work_ptr = strtok(cmd_input,";");
            while(work_ptr != NULL) {
-               if(cw_busy == 1) {
-                  if(strlen(work_ptr)>2) {
-                     cw_check_buf[0] = work_ptr[0];
-                     cw_check_buf[1] = work_ptr[1];
-                     cw_check_buf[2] = '\0';
-                     if(strcmp("ZZ",cw_check_buf)==0) {
-                          if(strlen(work_ptr)>4) {
-                             cw_check_buf[0] = work_ptr[2];
-                             cw_check_buf[1] = work_ptr[3];
-                             cw_check_buf[2] = '\0';
-                          } else {
-                             send_resp(client->socket,"?;");
-                          }
-                     } 
-                   } else {
-                       // Illegal Command
-                       send_resp(client->socket,"?;");
-                   } 
-                   // Got here because we have a legal command in cw_check_buf
-                   // Look for RESET and BUSY which re respond to else - send ?;
-                   if(strcmp("BY",cw_check_buf)==0) {
-                      send_resp(client->socket,"BY11;"); // Indicate that we are BUSY
-                   } else if (strcmp("SR",cw_check_buf) == 0) {
-                      // Reset the transceiver
-                      g_mutex_lock(&mutex_c->m);
-                      cw_reset = 1;
-                      g_mutex_unlock(&mutex_c->m);
-                      // Wait till BUSY clears
-                      while(cw_busy);
-                      g_mutex_lock(&mutex_c->m);
-                      cw_reset = 0;
-                      g_mutex_unlock(&mutex_c->m);
-                   }
-
-               }
                // Lock so only one user goes into this at a time
                g_mutex_lock(&mutex_b->m);
                parse_cmd(work_ptr,strlen(work_ptr),client->socket);
@@ -2048,38 +2104,40 @@ void parse_cmd ( char * cmd_input,int len,int client_sock) {
                                                 } 
                                             }
                                           }  
-        else if((strcmp(cmd_str,"KY")==0) && (zzid_flag == 0)) { 
-                                            // TS-2000 - KY - Convert char to morse code - not supported
-                                             int index = 2;
-                                             long delay = 1200000L/(long)cw_keyer_speed; // uS
-                                             #ifdef  RIGCTL_DEBUG
-                                             fprintf(stderr,"RIGCTL: KY DELAY=%ld, cw_keyer_speed=%d cw_keyer_weight=%d\n",delay,cw_keyer_speed,cw_keyer_weight); 
-                                             #endif
-                                             if(len <=2) {
-                                                send_resp(client_sock,"KY0;");
-                                             } else {
-                                               // Set CW BUSY flag
-                                               g_mutex_lock(&mutex_c->m);
-                                               cw_busy = 1;
-                                               g_mutex_unlock(&mutex_c->m);
-                                               while((cmd_input[index] != '\0') && (!cw_reset)) {
-                                                  //fprintf(stderr,"send=%c\n",cmd_input[index]);
-                                                  rigctl_send_cw_char(cmd_input[index]);
-                                                  //fprintf(stderr,"RIGCTL: 0 mox=%d\n",mox);
-                                                  index++;
-                                               } 
-                                             #ifdef  RIGCTL_DEBUG
-                                               fprintf(stderr,"RIGCTL: KY - Done sending cw\n");
-                                               fprintf(stderr,"RIGCTL: 1 mox=%d\n",mox);
-                                             #endif
-                                             } 
-                                             g_mutex_lock(&mutex_c->m);
-                                             cw_busy = 0;
-                                             g_mutex_unlock(&mutex_c->m);
-                                             #ifdef  RIGCTL_DEBUG
-                                             fprintf(stderr,"RIGCTL: 2 mox=%d\n",mox);
-                                             #endif
-                                          }  
+        else if((strcmp(cmd_str,"KY")==0) && (zzid_flag == 0))
+                                   { 
+                                       // DL1YCF:
+                                       // Hamlib produces errors if we keep begin busy here for
+                                       // seconds. Therefore we just copy the data to be handled
+                                       // by a separate thread.
+                                       // Note that this thread only makes a 0 --> 1 transition for cw_busy,
+                                       // and the CW thread only makes a 1 --> 0 transition
+                                       //
+                                       // Note: for a "KY;" command, we have to return "KY0;" if we can
+                                       // accept new data (buffer space available) and "KY1;" if we cannot,
+                                       //
+                                        if (len <= 2) {
+                                           if (cw_busy) {
+                                               send_resp(client_sock,"KY1;");  // can store no more data
+                                           } else {
+                                               send_resp(client_sock,"KY0;");
+                                               }
+                                       } else {
+                                           // So we have data. We have to init the CW setup because TUNE
+                                           // 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) {
+                                               // Copy data to buffer
+                                               strncpy(cw_buf, cmd_input+2, 30);
+                                               // Kenwood protocol allows for at most 28 characters, so
+                                               // this is pure paranoia
+                                               cw_buf[29]=0;
+                                               cw_busy=1;
+                                           }
+                                       }  
+                                   }
         else if(strcmp(cmd_str,"LK")==0)  { 
                                             // TS-2000 - LK - Set/read key lock function status
                                             // PiHPSDR - ZZLK - Set/read key lock function status
index aeac25094036c7b9abc24035c963c9a08e31f72a..454ea0ccba9da5f36244d0a5e6384c98ae4a8e9c 100644 (file)
@@ -51,6 +51,7 @@
 #include "ext.h"
 
 double getNextSideToneSample();
+double getNextInternalSideToneSample();
 
 #define min(x,y) (x<y?x:y)
 #define max(x,y) (x<y?y:x)
@@ -61,10 +62,13 @@ static int filterHigh;
 static int waterfall_samples=0;
 static int waterfall_resample=8;
 
-int key = 0;
+int cw_key_up = 0;
+int cw_key_down = 0;
+static int *cw_shape_buffer = NULL;
+int cw_shape = 0;
 
-// DL1YCF added next line.
 extern void cw_audio_write(double sample);
+
 static gint update_out_of_band(gpointer data) {
   TRANSMITTER *tx=(TRANSMITTER *)data;
   tx->out_of_band=0;
@@ -559,6 +563,8 @@ fprintf(stderr,"transmitter: allocate buffers: mic_input_buffer=%d iq_output_buf
   tx->iq_output_buffer=malloc(sizeof(double)*2*tx->output_samples);
   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);
 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",
@@ -719,10 +725,11 @@ void tx_set_pre_emphasize(TRANSMITTER *tx,int state) {
 static void full_tx_buffer(TRANSMITTER *tx) {
   long isample;
   long qsample;
-  double gain;
+  double gain,lgain,sidevol;
   int j;
   int error;
-  int mode;
+  int mode,do_shape;
+  int sidetone=0;
 
   switch(protocol) {
     case ORIGINAL_PROTOCOL:
@@ -733,6 +740,17 @@ 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);
 
   fexchange0(tx->id, tx->mic_input_buffer, tx->iq_output_buffer, &error);
@@ -758,14 +776,23 @@ 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*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);
+      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:
-          old_protocol_iq_samples(isample,qsample);
+         // 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);
@@ -788,20 +815,38 @@ void add_mic_sample(TRANSMITTER *tx,short mic_sample) {
     mode=vfo[0].mode;
   }
 
-       if (tune) {
-                 mic_sample_double=0.0;
+  // This is valid if not CW and not tuning
+  mic_sample_double=(double)mic_sample/32768.0;
+
+  if (tune) mic_sample_double=0.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
+//     to the number of samples for the next down/up sequence.
+//     cw_key_down can be zero, for inserting some space
+//
+//     We HAVE TO shape the signal to avoid hard clicks to be
+//     heard way beside our frequency. The envelope goes up
+//       and down linearly within 200 samples (4.16 msec)
+//
+       if (cw_key_down > 0 ) {
+           if (cw_shape < 200) cw_shape++;
+           cw_key_down--;
+       } else {
+           if (cw_key_up >= 0) {
+               // dig into this even if cw_key_up is already zero, to ensure
+               // that cw_shape eventually reaches zero
+               if (cw_shape > 0) cw_shape--;
+               if (cw_key_up > 0) cw_key_up--;
+           }
        }
-    else if(mode==modeCWL || mode==modeCWU) {
-               if (isTransmitting()) {
-                       if (key == 1) {
-                               mic_sample_double = getNextSideToneSample();
-                               cw_audio_write(mic_sample_double * cw_keyer_sidetone_volume/ 127.0);
-                               mic_sample_double = mic_sample_double * 200000; //* amplitude 
-                       } else mic_sample_double=0.0;
-               }
-      } else {
-    mic_sample_double=(double)mic_sample/32768.0;
+       cw_audio_write(0.00003937 * getNextSideToneSample() * cw_keyer_sidetone_volume * cw_shape);
+    }
   }
+  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++;
@@ -920,23 +965,36 @@ void tx_set_ps_sample_rate(TRANSMITTER *tx,int rate) {
 }
 #endif
 
-void cw_sidetone_mute(int mute){
-       key = mute;
+//
+// This is the old key-down/key-up interface for iambic.c
+// but now it also smoothes (cw_shape) the signal
+//
+void cw_sidetone_mute(int mute) {
+  if (mute) {
+    cw_key_down = 480000;    // up to 10 sec, should be OK even for QRSS
+  } else {
+    cw_key_down = 0;
+  }
 }
 
-int asteps = 0;
-double timebase = 0.0;
-#define TIMESTEP (1.0 / 48000)
-#ifndef M_PI
-#define M_PI 3.14159265358979323846
-#endif
+// DL1YCF:
+// somewhat improved, and provided two siblings
+// for generating side tones simultaneously on the
+// HPSDR board and local audio.
+
+#define TWOPIOVERSAMPLERATE 0.0001308996938995747;  // 2 Pi / 48000
+
+static long asteps = 0;
+static long bsteps = 0;
+
 double getNextSideToneSample() {
-       double angle = cw_keyer_sidetone_frequency * 2 * M_PI * timebase;
-       timebase += TIMESTEP;
-       asteps++;
-       if (asteps == 48000) {
-               timebase = 0.0;
-               asteps = 0;
-       }
+       double angle = (asteps*cw_keyer_sidetone_frequency)*TWOPIOVERSAMPLERATE;
+       if (++asteps == 48000) asteps = 0;
+       return sin(angle);
+}
+
+double getNextInternalSideToneSample() {
+       double angle = (bsteps*cw_keyer_sidetone_frequency)*TWOPIOVERSAMPLERATE;
+       if (++bsteps == 48000) bsteps = 0;
        return sin(angle);
 }
diff --git a/vox.c b/vox.c
index 5b9cbb2292c623f2b743b00ae89109a3d33ff553..9058b7d6487d9d986c885de32aa6a0577f5054fc 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);
@@ -36,6 +38,15 @@ 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;
@@ -49,6 +60,9 @@ void update_vox(TRANSMITTER *tx) {
   int i;
   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) {
@@ -74,6 +88,29 @@ 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);