clean:
-rm -f *.o
- -rm -f $(PROGRAM)
+ -rm -f $(PROGRAM) hpsdrsim
install: $(PROGRAM)
cp $(PROGRAM) /usr/local/bin
-rm -f *.o
-rm -f $(PROGRAM)
-rm -rf $(PROGRAM).app
+ -rm -f hpsdrsim
install: $(PROGRAM)
cp $(PROGRAM) /usr/local/bin
$(CC) -c -O $(PORTAUDIO_OPTIONS) hpsdrsim.c
hpsdrsim: hpsdrsim.o
- $(LINK) -o hpsdrsim hpsdrsim.o -lm -lpthread $(AUDIO_LIBS)
+ $(LINK) -o hpsdrsim hpsdrsim.o $(AUDIO_LIBS) -lm -lpthread
#############################################################################
#
//wdsp_set_agc(CHANNEL_RX0, agc);
set_agc(active_receiver, active_receiver->agc);
vfo_update();
- // DL1YCF added return statement to make the compiler happy.
- // however I am unsure about the correct return value.
- // I would have coded this as a void function.
return FALSE;
}
int height=gtk_widget_get_allocated_height (widget);
fprintf(stderr,"audio: waterfall_configure_event: width=%d height=%d\n",width,height);
pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, width, height);
- // DL1YCF changed to uchar *
unsigned char *pixels = gdk_pixbuf_get_pixels (pixbuf);
memset(pixels, 0, width*height*3);
int i;
if(pixbuf) {
- // DL1YCF changed to uchar *
unsigned char *pixels = gdk_pixbuf_get_pixels (pixbuf);
int width=gdk_pixbuf_get_width(pixbuf);
memmove(&pixels[rowstride*(header+1)],&pixels[rowstride*header],(height-(header+1))*rowstride);
float sample;
- // DL1YCF changed to uchar *
unsigned char *p;
int average=0;
p=&pixels[rowstride*header];
last_band=widget;
set_button_text_color(last_band,"orange");
vfo_band_changed(b);
- // DL1YCF added return statement to make the compiler happy.
- // however I am unsure about the correct return value.
- // I would have coded this as a void function.
return FALSE;
}
last_bandstack=widget;
set_button_text_color(last_bandstack,"orange");
vfo_bandstack_changed(b);
- // DL1YCF added return statement to make the compiler happy.
- // however I am unsure about the correct return value.
- // I would have coded this as a void function.
return FALSE;
}
}
static void nr2_gain_cb(GtkWidget *widget, gpointer data) {
-// DL1YCF changed == to =
active_receiver->nr2_gain_method=(uintptr_t)data;
SetRXAEMNRgainMethod(active_receiver->id, active_receiver->nr2_gain_method);
}
gtk_range_set_value(GTK_RANGE(mid_scale),(double)tx_equalizer[2]);
gtk_range_set_value(GTK_RANGE(high_scale),(double)tx_equalizer[3]);
}
- // DL1YCF added return statement to make the compiler happy.
- // however I am unsure about the correct return value.
- // I would have coded this as a void function.
return FALSE;
}
gtk_range_set_value(GTK_RANGE(mid_scale),(double)rx_equalizer[2]);
gtk_range_set_value(GTK_RANGE(high_scale),(double)rx_equalizer[3]);
}
- // DL1YCF added return statement to make the compiler happy.
- // however I am unsure about the correct return value.
- // I would have coded this as a void function.
return FALSE;
}
enable_rx_equalizer=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
SetRXAEQRun(active_receiver->id, enable_rx_equalizer);
}
- // DL1YCF added return statement to make the compiler happy.
- // however I am unsure about the correct return value.
- // I would have coded this as a void function.
return FALSE;
}
gtk_widget_show(label);
timer=g_timeout_add(5000,timeout_cb,NULL);
int result=gtk_dialog_run(GTK_DIALOG(dialog));
- // DL1YCF added return statement to make the compiler happy.
- // however I am unsure about the correct return value.
- // I would have coded this as a void function.
return FALSE;
}
last_filter=widget;
set_button_text_color(last_filter,"orange");
vfo_update();
- // DL1YCF added return statement to make the compiler happy.
- // however I am unsure about the correct return value.
- // I would have coded this as a void function.
return FALSE;
}
}
}
vfo_update();
- // DL1YCF added return statement
return FALSE;
}
+//#define MICSAMPLES // define to send microphone samples (triggered by attenuator slider)
+//#define TX_ENVELOPE // define to dump TX envelope 1 sec after first PTT
/*
- * HPSDR simulator, (C) Christoph van Wuellen, April 2019
+ * HPSDR simulator, (C) Christoph van Wuellen, April/Mai 2019
*
* This program simulates a HPSDR board.
* If an SDR program such as phipsdr "connects" with this program, it
*
* RF1: ADC noise (16-bit ADC) plus a 800 Hz signal at -100dBm
* RF2: ADC noise (16-bit ADC) plus a 2000 Hz signal at - 80dBm
- * RF3: TX feedback signal with some distortion. This signal is modulated
+ * RF3: TX feedback signal with some distortion. Signal strength
* according to the "TX drive" and "TX ATT" settings.
- * RF4: TX signal with a peak value of 0.400
+ * RF4: normalized undistorted TX signal with a peak value of 0.400
*
- * RF1 and RF2 are attenuated according to the preamp/attenuator settings.
- * RF3 respects the "TX drive" and "TX ATT" settings
- * RF4 is the TX signal multiplied with 0.4 (ignores TX_DRIVE and TX_ATT).
+ * RF1 and RF2 respect the Preamp and Attenuator settings
*
* Depending on the device type, the receivers see different signals
* (This is necessary for PURESIGNAL). We chose the association such that
* DEVICE=HERMES: RX3=RF3, RX4=RF4 (also for DEVICE=StemLab)
* DEVICE=ORION2: RX4=RF3, RX5=RF5 (also for DEVICE=ANGELIA and ORION)
*
+ *
+ * Audio sent to the "radio" is played via the first available output channel.
+ * This works on MacOS (PORTAUDIO) and Linux (ALSA).
+ *
+ * Additional feature include the recording of the TX envelope of the first second
+ * of TXing, and the possiblity to read a file with mic samples and "send"
+ * them to the SDR. Both features are meant for testing RX/TX timings.
*/
#include <stdio.h>
#include <errno.h>
// Forward declarations for the audio functions
void audio_get_cards(void);
void audio_open_output();
-void audio_write(short, short);
+void audio_write(int16_t, int16_t);
#ifndef __APPLE__
static int MicPTT = -1;
static int tip_ring = -1;
static int MicBias = -1;
-static int ptt=-1;
-static int att=-1;
+static int ptt=0;
+static int AlexAtt=-1;
static int TX_class_E = -1;
static int OpenCollectorOutputs=-1;
static long tx_freq=-1;
// floating-point represeners of TX att, RX att, and RX preamp settings
-static double txdrv_dbl = 1.0;
+static double txdrv_dbl = 0.99;
static double txatt_dbl = 1.0;
static double rxatt_dbl[4] = {1.0, 1.0, 1.0, 1.0}; // this reflects both ATT and PREAMP
+#ifdef TX_ENVELOPE
+//
+// This records the size of the first MAXENV TX samples after the first RX-TX transition.
+// This can be used to detect whether RX/TX transitions are fast enough not to chop
+// the first part of an SSB transmission using VOX, or of the first CW element.
+//
+#define MAXENV 48000
+static float envelope[MAXENV];
+int envptr=0;
+#endif
+
+#ifdef MICSAMPLES
+//
+// Raw audio data (48000 16-bit int words) is read from a file and stored.
+// As soon as the ALEX attenuators are engaged, these mic samples are
+// sent to the SDR program ONCE.
+//
+static int16_t micsamples[48000];
+int micsamples_ptr=-2;
+int micsamples_rate=0;
+#endif
+
static int sock_ep2;
static struct sockaddr_in addr_ep6;
int udp_retries=0;
int bytes_read, bytes_left;
uint32_t *code0 = (uint32_t *) buffer; // fast access to code of first buffer
-
+ int fd;
+
+#ifdef MICSAMPLES
+ fd=open("micsamples", O_RDONLY);
+ if (fd >= 0) {
+ if (read(fd, micsamples, 48000*sizeof(int16_t)) == 48000*sizeof(int16_t)) {
+ fprintf(stderr,"MIC SAMPLES available\n");
+ micsamples_ptr=-1;
+ }
+ }
+#endif
+
audio_get_cards();
audio_open_output();
/*
sample |= (int) ((signed char) *bp++ & 0xFF);
dqsample=(double) sample / 32768.0;
+#ifdef TX_ENVELOPE
+ if (ptt || envptr > 0) {
+ if (envptr < MAXENV) envelope[envptr++]=(disample*disample+dqsample*dqsample);
+ if (envptr == MAXENV) {
+ fd=open("dump.envelope", O_RDWR | O_CREAT | O_TRUNC, (mode_t) 0777);
+ if (fd >= 0) {
+ write (fd, envelope, MAXENV*sizeof(float));
+ close (fd);
+ fprintf(stderr,"TX ENV dumped\n");
+ }
+ envptr++;
+ }
+ }
+#endif
switch (rate) {
case 0: // RX sample rate = TX sample rate = 48000
isample[txptr ]=disample;
chk_data(frame[2] & 1, TX_class_E, "TX CLASS-E");
chk_data((frame[2] & 0xfe) >> 1, OpenCollectorOutputs,"OpenCollector");
- chk_data((frame[3] & 0x03) >> 0, att, "ALEX Attenuator");
+ chk_data((frame[3] & 0x03) >> 0, AlexAtt, "ALEX Attenuator");
chk_data((frame[3] & 0x04) >> 2, preamp, "ALEX preamp");
chk_data((frame[3] & 0x08) >> 3, LTdither, "LT2208 Dither");
chk_data((frame[3] & 0x10) >> 4, LTrandom, "LT2208 Random");
chk_data(((frame[4] >> 7) & 1), CommonMercuryFreq,"Common Mercury Freq");
if (isc25) {
- // Charly25: has two 18-dB preamps that are switched with "preamp" and "dither"
- // and two attenuators encoded in Alex-ATT
- // Both only applies to RX1!
- rxatt_dbl[0]=pow(10.0, -0.05*(12*att-18*LTdither-18*preamp));
- rxatt_dbl[1]=1.0;
+ // Charly25: has two 18-dB preamps that are switched with "preamp" and "dither"
+ // and two attenuators encoded in Alex-ATT
+ // Both only applies to RX1!
+ rxatt_dbl[0]=pow(10.0, -0.05*(12*AlexAtt-18*LTdither-18*preamp));
+ rxatt_dbl[1]=1.0;
+ } else {
+ // Assume that it has ALEX attenuators in addition to the Step Attenuators
+ rxatt_dbl[0]=pow(10.0, -0.05*(10*AlexAtt+rx_att[0]));
+ rxatt_dbl[1]=1.0;
}
break;
chk_data(frame[3] & 0x40,lna6m,"ALEX 6m LNA");
chk_data(frame[3] & 0x80,alexTRdisable,"ALEX T/R disable");
chk_data(frame[4],alex_lpf,"ALEX LPF");
- // reset TX level
- txdrv_dbl=(double) txdrive / 255.0;
+ // reset TX level. Leve a little head-room for noise
+ txdrv_dbl=(double) txdrive / 256.0;
break;
case 20:
chk_data((frame[4] & 0x1F) >> 0, rx_att[0], "RX1 ATT");
chk_data((frame[4] & 0x20) >> 5, rx1_attE, "RX1 ATT enable");
+#ifdef MICSAMPLES
+ if (rx_att[0] > 20 && micsamples_ptr == -1) micsamples_ptr = 0;
+ if (rx_att[0] < 10 && micsamples_ptr == 48000) micsamples_ptr = -1;
+#endif
if (!isc25) {
- // Set RX amplification factors. Assume 20 dB preamps
- rxatt_dbl[0]=pow(10.0, -0.05*(rx_att[0]-20*rx_preamp[0]));
- rxatt_dbl[1]=pow(10.0, -0.05*(rx_att[1]-20*rx_preamp[1]));
- rxatt_dbl[2]=pow(10.0, (double) rx_preamp[2]);
- rxatt_dbl[3]=pow(10.0, (double) rx_preamp[3]);
+ // Set RX amplification factors. No switchable preamps available normally.
+ rxatt_dbl[0]=pow(10.0, -0.05*(10*AlexAtt+rx_att[0]));
+ rxatt_dbl[1]=pow(10.0, -0.05*(rx_att[1]));
+ rxatt_dbl[2]=1.0;
+ rxatt_dbl[3]=1.0;
}
break;
chk_data(frame[4] & 127, cw_weight,"CW WEIGHT");
chk_data((frame[4] >> 7) & 1, cw_spacing, "CW SPACING");
- // Set RX amplification factors. Assume 20 dB preamps
- rxatt_dbl[1]=pow(10.0, -0.05*(rx_att[1]-20*rx_preamp[1]));
+ // Set RX amplification factors.
+ rxatt_dbl[1]=pow(10.0, -0.05*(rx_att[1]));
break;
case 24:
int32_t rf4isample,rf4qsample;
int32_t myisample,myqsample;
+ int16_t ssample;
struct timespec delay;
#ifdef __APPLE__
rf2qsample=noiseQtab[noiseIQpt] * 8388607.0;
rf2qsample += T2000Qtab[pt2000] * 838.86070 * rxatt_dbl[1];
//
- // RF3: TX signal distorted
+ // RF3: TX signal distorted, with some ADC noise
//
i1=isample[rxptr]*txdrv_dbl;
q1=qsample[rxptr]*txdrv_dbl;
fac=IM3a+IM3b*(i1*i1+q1*q1);
- rf3isample= txatt_dbl*i1*fac * 8388607.0;
- rf3qsample= txatt_dbl*q1*fac * 8388607.0;
+ rf3isample= (txatt_dbl*i1*fac+noiseItab[noiseIQpt]) * 8388607.0;
+ rf3qsample= (txatt_dbl*q1*fac+noiseItab[noiseIQpt]) * 8388607.0;
//
// RF4: TX signal with peak=0.4
//
*pointer++ = (myqsample >> 8) & 0xFF;
*pointer++ = (myqsample >> 0) & 0xFF;
}
+#ifdef MICSAMPLES
+ if (micsamples_ptr >= 0 && micsamples_ptr < 48000) {
+ ssample=micsamples[micsamples_ptr];
+ *pointer++ = (ssample >> 8) & 0xFF;
+ *pointer++ = (ssample ) & 0xFF;
+ micsamples_rate++;
+ if (micsamples_rate == 1 << rate) {
+ micsamples_rate=0;
+ micsamples_ptr++;
+ }
+ } else {
+ *pointer++ = 0;
+ *pointer++ = 0;
+ }
+#else
// Microphone samples: silence
pointer += 2;
+#endif
rxptr++; if (rxptr >= RTXLEN) rxptr=0;
noiseIQpt++; if (noiseIQpt == LENNOISE) noiseIQpt=rand() / NOISEDIV;
pt2000++; if (pt2000 == len2000) pt2000=0;
return;
}
-void audio_write (short l, short r)
+void audio_write (int16_t l, int16_t r)
{
PaError err;
if (playback_handle != NULL) {
return;
}
-void audio_write(short left_sample,short right_sample) {
+void audio_write(int16_t left_sample,int16_t right_sample) {
snd_pcm_sframes_t delay;
int error;
long trim;
if (state) {
// This is for aborting CAT CW messages if a key is hit.
cw_key_hit = 1;
- // do PTT already here, unless TX mode was activated before
- if (running && !cwvox && !mox) {
- g_idle_add(ext_mox_update, (gpointer)(long) 1);
- }
- cwvox=(int) cw_keyer_hang_time;
}
if (left) {
// left paddle hit or released
sem_wait(&cw_event);
#endif
+ // swallow any cw_events posted during the last "cw hang" time.
+ if (!kcwl && !kcwr) continue;
+
+ if (!mox) {
+ g_idle_add(ext_mox_update, (gpointer)(long) 1);
+ // Wait for mox, that is, wait for WDSP shutting down the RX and
+ // firing up the TX. This induces a small delay when hitting the key for
+ // the first time, but excludes that the first dot is swallowed.
+ // Note: if out-of-band, mox will never come, therefore
+ // give up after 100 msec.
+ i=100;
+ while (!mox && i-- > 0) usleep(1000);
+ cwvox=(int) cw_keyer_hang_time;
+ }
+
key_state = CHECK;
clock_gettime(CLOCK_MONOTONIC, &loop_delay);
while (key_state != EXITLOOP || cwvox > 0) {
+ //
+ // if key_state == EXITLOOP and cwvox == 0, then
+ // just leave the while-loop without removing MOX
+ //
// re-trigger VOX if *not* busy-spinning
if (cwvox > 0 && key_state != EXITLOOP && key_state != CHECK) cwvox=(int) cw_keyer_hang_time;
#include "discovery.h"
#include "new_protocol.h"
#include "old_protocol.h"
+#include "frequency.h" // for canTransmit
#include "ext.h"
struct utsname unameData;
return NULL;
}
+//
+// handler for key press events.
+// SpaceBar presses toggle MOX, everything else downstream
+// code to switch mox copied from mox_cb() in toolbar.c,
+// but added the correct return values.
+//
+gboolean keypress_cb(GtkWidget *widget, GdkEventKey *event, gpointer data) {
+
+ if (event->keyval == GDK_KEY_space && radio != NULL) {
+ if(getTune()==1) {
+ setTune(0);
+ }
+ if(getMox()==1) {
+ setMox(0);
+ } else if(canTransmit() || tx_out_of_band) {
+ setMox(1);
+ } else {
+ transmitter_set_out_of_band(transmitter);
+ }
+ g_idle_add(ext_vfo_update,NULL);
+ return TRUE;
+ }
+ return FALSE;
+}
+
gboolean main_delete (GtkWidget *widget) {
if(radio!=NULL) {
#ifdef GPIO
g_signal_connect (top_window, "delete-event", G_CALLBACK (main_delete), NULL);
//g_signal_connect (top_window,"draw", G_CALLBACK (main_draw_cb), NULL);
+ //
+ // We want to use the space-bar as an alternative to go to TX
+ //
+ gtk_widget_add_events(top_window, GDK_KEY_PRESS_MASK);
+ g_signal_connect(top_window, "key_press_event", G_CALLBACK(keypress_cb), NULL);
+
+
//fprintf(stderr,"create fixed container\n");
//fixed=gtk_fixed_new();
//gtk_container_add(GTK_CONTAINER(top_window), fixed);
//void* new_discover_receive_thread(void* arg) {
gpointer new_discover_receive_thread(gpointer data) {
struct sockaddr_in addr;
- // DL1YCF change from int to socklen_t
socklen_t len;
unsigned char buffer[2048];
int bytes_read;
}
fprintf(stderr,"new_discover: exiting new_discover_receive_thread\n");
g_thread_exit(NULL);
- // DL1YCF added return statement to make compiler happy.
return NULL;
}
static int running;
#ifdef __APPLE__
-//
-//DL1YCF:
-//Mac OS does not have sem_init for un-named semaphores,
-//we have to use named semaphores created with sem_open.
-//As a side effect, we consistently have to use sem_t
-//pointers instead of sem_t variables
-//
sem_t *response_sem;
#else
sem_t response_sem;
#define NET_BUFFER_SIZE 2048
// Network buffers
static struct sockaddr_in addr;
-// DL1YCF next line: changed int to socklen_t
static socklen_t length;
//static unsigned char buffer[NET_BUFFER_SIZE];
static unsigned char *iq_buffer[MAX_RECEIVERS];
rc=sem_init(&iq_sem_buffer[ddc], 0, 0);
#endif
iq_thread_id[ddc] = g_thread_new( "iq thread", iq_thread, (gpointer)(long)i);
- //DL1YCF: g_thread new always returns a value, upon failure the program aborts
- // so the next four lines have been deactivated.
- //if( ! iq_thread_id ) {
- // fprintf(stderr,"g_thread_new failed for iq_thread: rx=%d\n",i);
- // exit( -1 );
- //}
fprintf(stderr, "iq_thread: rx=%d ddc=%d thread=%p\n",i, ddc, iq_thread_id);
}
}
rx->iq_sequence++;
-// DL1YCF: changed semicolon at end of next line to a plus sign
timestamp=((long long)(buffer[4]&0xFF)<<56)+((long long)(buffer[5]&0xFF)<<48)+((long long)(buffer[6]&0xFF)<<40)+((long long)(buffer[7]&0xFF)<<32)+
((long long)(buffer[8]&0xFF)<<24)+((long long)(buffer[9]&0xFF)<<16)+((long long)(buffer[10]&0xFF)<<8)+(long long)(buffer[11]&0xFF);
bitspersample=((buffer[12]&0xFF)<<8)+(buffer[13]&0xFF);
sequence=((mic_line_buffer[0]&0xFF)<<24)+((mic_line_buffer[1]&0xFF)<<16)+((mic_line_buffer[2]&0xFF)<<8)+(mic_line_buffer[3]&0xFF);
b=4;
for(i=0;i<MIC_SAMPLES;i++) {
- // DL1YCF: changed this to two statements such that the order of pointer increments
- // becomes clearly defined.
sample=(short)(mic_line_buffer[b++]<<8);
sample |= (short) (mic_line_buffer[b++]&0xFF);
#ifdef FREEDV
b=0;
for(i=0;i<MIC_SAMPLES;i++) {
if(le) {
- // DL1YCF: changed this to two statements such that the order of pointer increments
- // becomes clearly defined.
sample = (short)(buffer[b++]&0xFF);
sample |= (short) (buffer[b++]<<8);
} else {
- // DL1YCF: changed this to two statements such that the order of pointer increments
- // becomes clearly defined.
sample = (short)(buffer[b++]<<8);
sample |= (short) (buffer[b++]&0xFF);
}
// }
// }
}
- // DL1YCF: added return statement to make compiler happy.
return NULL;
}
block_sequence++;
}
- // DL1YCF added return statement to make compiler happy.
return NULL;
}
}
fprintf(stderr,"discovery: exiting discover_receive_thread\n");
g_thread_exit(NULL);
- // DL1YCF added return statement to make compiler happy.
return NULL;
}
double right_sample_double_rx;
double left_sample_double_tx;
double right_sample_double_tx;
- int nreceivers;
int id=active_receiver->id;
int tx_vfo=split?VFO_B:VFO_A;
+ int nreceivers;
+
+#ifdef PURESIGNAL
+ // DL1YCF:
+ // for PureSignal, the number of receivers needed is hard-coded below.
+ // we need at least 3 (for RX), and up to 5 for Orion2 boards, since
+ // the TX DAC channel is hard-wired to RX5.
+ nreceivers=3;
+ if (device == DEVICE_HERMES) nreceivers=4;
+ if (device == DEVICE_ANGELIA || device == DEVICE_ORION || device == DEVICE_ORION2) nreceivers=5;
+#else
+#if defined(RADIOBERRY) || defined(PI_SDR)
+ nreceivers = receivers;
+#else
+ nreceivers=RECEIVERS;
+#endif
+#endif
+
if(buffer[b++]==SYNC && buffer[b++]==SYNC && buffer[b++]==SYNC) {
// extract control bytes
control_in[0]=buffer[b++];
break;
}
-
-#ifdef PURESIGNAL
- // DL1YCF:
- // for PureSignal, the number of receivers needed is hard-coded below.
- // we need at least 3 (for RX), and up to 5 for Orion2 boards, since
- // the TX DAC channel is hard-wired to RX5.
- nreceivers=3;
- if (device == DEVICE_HERMES) nreceivers=4;
- if (device == DEVICE_ANGELIA || device == DEVICE_ORION || device == DEVICE_ORION2) nreceivers=5;
-#else
- #if defined(RADIOBERRY) || defined(PI_SDR)
- nreceivers = receivers;
- #else
- nreceivers=RECEIVERS;
- #endif
-#endif
-
int iq_samples=(512-8)/((nreceivers*6)+2);
for(i=0;i<iq_samples;i++) {
// avoid pointer increments in logical-or constructs, as the sequence
// is undefined
if(le) {
- // DL1YCF: changed this to two statements such that the order of pointer increments
- // becomes clearly defined.
sample = (short) (buffer[b++]&0xFF);
sample |= (short) (buffer[b++]<<8);
} else {
- // DL1YCF: changed this to two statements such that the order of pointer increments
- // becomes clearly defined.
sample = (short)(buffer[b++]<<8);
sample |= (short) (buffer[b++]&0xFF);
}
BAND *band;
int nreceivers;
+#ifdef PURESIGNAL
+ // DL1YCF:
+ // for PureSignal, the number of receivers needed is hard-coded below.
+ // we need at least 3 (for RX), and up to 5 for Orion2 boards, since
+ // the TX DAC channel is hard-wired to RX5.
+ nreceivers=3;
+ if (device == DEVICE_HERMES) nreceivers=4;
+ if (device == DEVICE_ANGELIA || device == DEVICE_ORION || device == DEVICE_ORION2) nreceivers=5;
+#else
+#if defined(RADIOBERRY) || defined(PI_SDR)
+ nreceivers = receivers;
+#else
+ nreceivers=RECEIVERS;
+#endif
+#endif
+
output_buffer[SYNC0]=SYNC;
output_buffer[SYNC1]=SYNC;
output_buffer[SYNC2]=SYNC;
//
// Upon TX, we might have to activate a different RX path for the
// attenuated feedback signal. Use feedback_antenna == 0, if
- // the feedback signal is routed automatically/internally (e.g.
- // ANAN-7000DLE, when using the internal feedback path).
+ // the feedback signal is routed automatically/internally
//
if (isTransmitting() && transmitter->puresignal) i=receiver[PS_RX_FEEDBACK]->feedback_antenna;
#endif
switch(i) {
- case 0: // ANT 1
- case 1: // ANT 2
- case 2: // ANT 3
- break;
case 3: // Alex: RX2 IN, ANAN: EXT1, ANAN7000: EXT
output_buffer[C3]|=0xC0;
break;
case 4: // Alex: RX1 IN, ANAN: EXT2, ANAN7000: RX BYPASS
output_buffer[C3]|=0xA0;
break;
- case 5: // XVTR
- output_buffer[C3]|=0xE0;
- break;
default:
break;
}
// TODO - add Alex TX relay, duplex, receivers Mercury board frequency
output_buffer[C4]=0x04; // duplex
-#ifdef PURESIGNAL
- // DL1YCF: see comment on "nreceivers" above. The number is reduced by 1 here
- nreceivers=2;
- if (device == DEVICE_HERMES) nreceivers=3;
- if (device == DEVICE_ANGELIA || device == DEVICE_ORION || device == DEVICE_ORION2) nreceivers=4;
-#else
- #ifdef RADIOBERRY
- nreceivers = receivers-1;
- #else
- nreceivers=RECEIVERS-1;
- #endif
-#endif
// 0 ... 7 maps on 1 ... 8 receivers
- output_buffer[C4]|=nreceivers<<3;
+ output_buffer[C4]|=(nreceivers-1)<<3;
if(isTransmitting()) {
switch(transmitter->alex_antenna) {
}
} else {
//
- // metis_offset !=8: send "command" C&C packets in round-robin
- // using the value of "command" from 1 to 10,
- // and "command==2" packets are repeated for each RX
+ // metis_offset !=8: send the other C&C packets in round-robin
+ // RX frequency commands are repeated for each RX
switch(command) {
case 1: // tx frequency
output_buffer[C0]=0x02;
output_buffer[C4]=txFrequency;
break;
case 2: // rx frequency
-#ifdef PURESIGNAL
- // DL1YCF: see comment on "nreceivers" above.
- nreceivers=3;
- if (device == DEVICE_HERMES) nreceivers=4;
- if (device == DEVICE_ANGELIA || device == DEVICE_ORION || device == DEVICE_ORION2) nreceivers=5;
-#else
- #ifdef RADIOBERRY
- nreceivers = receivers;
- #else
- nreceivers=RECEIVERS;
- #endif
-#endif
-
if(current_rx<nreceivers) {
output_buffer[C0]=0x04+(current_rx*2);
#ifdef PURESIGNAL
if(receiver[0]->alex_antenna==5) { // XVTR
output_buffer[C2]=0x02; // Alex2 XVTR enable
}
+#ifdef PURESIGNAL
+ if(transmitter->puresignal) {
+ output_buffer[C2]|=0x40; // Synchronize RX5 andh TX frequency on transmit
+ }
+#endif
output_buffer[C3]=0x00; // Alex2 filters
output_buffer[C4]=0x00; // Alex2 filters
break;
col++;
GtkWidget *ps_ant_auto=gtk_radio_button_new_with_label(NULL,"AUTO");
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ps_ant_auto),
+ (receiver[PS_RX_FEEDBACK]->feedback_antenna!=3) && (receiver[PS_RX_FEEDBACK]->feedback_antenna!=4));
gtk_widget_show(ps_ant_auto);
gtk_grid_attach(GTK_GRID(grid), ps_ant_auto, col, row, 1, 1);
g_signal_connect(ps_ant_auto,"toggled", G_CALLBACK(ps_ant_cb), (gpointer) (long) 0);
col++;
GtkWidget *ps_ant_ext1=gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(ps_ant_auto),"EXT1");
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ps_ant_ext1), receiver[PS_RX_FEEDBACK]->feedback_antenna==3);
gtk_widget_show(ps_ant_ext1);
gtk_grid_attach(GTK_GRID(grid), ps_ant_ext1, col, row, 1, 1);
g_signal_connect(ps_ant_ext1,"toggled", G_CALLBACK(ps_ant_cb), (gpointer) (long) 3);
col++;
GtkWidget *ps_ant_ext2=gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(ps_ant_auto),"EXT2");
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ps_ant_ext2), receiver[PS_RX_FEEDBACK]->feedback_antenna==4);
gtk_widget_show(ps_ant_ext2);
gtk_grid_attach(GTK_GRID(grid), ps_ant_ext2, col, row, 1, 1);
g_signal_connect(ps_ant_ext2,"toggled", G_CALLBACK(ps_ant_cb), (gpointer) (long) 4);
for(i=0;i<receivers;i++) {
#ifdef PURESIGNAL
// When using PURESIGNAL, delivery of RX samples
- // comes to an abrupt stop (since they are "fed"
- // to pscc()) upon a TX-RX transition.
- // Therefore, wait for all receivers to complete
+ // to WDSP via fexchange0() comes to an abrupt stop
+ // since they go through add_ps_iq_samples()
+ // rather than add_iq_samples().
+ // Therefore, wait for *all* receivers to complete
// their slew-down before going TX.
SetChannelState(receiver[i]->id,0,1);
#else
}
void setMox(int state) {
+ vox_cancel(); // remove time-out
+ if (vox) { // if VOX active, turn it off
+ rxtx(0);
+ vox=0;
+ }
if(mox!=state) {
- if(vox_enabled && vox) {
- vox_cancel();
- } else {
- rxtx(state);
- }
+ rxtx(state);
mox=state;
}
}
return mox;
}
-void setVox(int state) {
- if(vox!=state && !tune) {
+void vox_changed(int state) {
+ if(vox!=state && !tune && !mox) {
rxtx(state);
- vox=state;
}
- g_idle_add(ext_vfo_update,(gpointer)NULL);
-}
-
-void vox_changed(int state) {
- setVox(state);
+ vox=state;
}
void setTune(int state) {
int i;
+ // if state==tune, this function is a no-op
+
if(tune!=state) {
- if(vox_enabled && vox) {
- vox_cancel();
+ vox_cancel();
+ if (vox || mox) {
+ rxtx(0);
+ vox=0;
+ mox=0;
}
if(state) {
if(full_tune) {
for(i=0;i<receivers;i++) {
#ifdef PURESIGNAL
// When using PURESIGNAL, delivery of RX samples
- // comes to an abrupt stop (since they are "fed"
- // to pscc()) upon a TX-RX transition.
- // Therefore, wait for all receivers to complete
+ // to WDSP via fexchange0() comes to an abrupt stop
+ // since they go through add_ps_iq_samples()
+ // rather than add_iq_samples().
+ // Therefore, wait for *all* receivers to complete
// their slew-down before going TX.
SetChannelState(receiver[i]->id,0,1);
#else
return tune;
}
-// DL1YCF: because of the new CW algorithm,
-// this function is no longer used
-void radio_cw_setup() {
- int mode=vfo[VFO_A].mode;;
- if(split) {
- mode=vfo[VFO_B].mode;
- }
-
- // 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);
-}
-
-// DL1YCF: because of the new CW algorithm,
-// this function is no longer used
-void radio_cw_key(int state) {
- SetTXAPostGenRun(transmitter->id,state);
-}
-
int isTransmitting() {
return mox | vox | tune;
}
level=(int)(actual_volts*255.0);
-#ifdef __APPLE__
-#ifdef PURESIGNAL
-//
-// DL1YCF: I do not know exactly why: if the drive level
-// is set to zero while PS is active, the program
-// reproducably crashes when the drive is set from 1 Watt
-// to 0 Watt, possibly a division by zero or the evaluation
-// of a logarithm within WDSP.
-// QuickAndDirty Fix: use "1" as minimum drive level
-// which corresponds to a fraction of a Watt.
-//
- if (level < 1) level=1;
-#endif
-#endif
-
//fprintf(stderr,"calcLevel: %f calib=%f level=%d\n",d, gbb, level);
return level;
}
extern int getMox();
extern void setTune(int state);
extern int getTune();
-extern void setVox(int state);
extern void vox_changed(int state);
extern double getDrive();
extern void setDrive(double d);
extern void set_filter_type(int filter_type);
extern void set_filter_size(int filter_size);
-extern void radio_cw_setup();
-
-extern void radio_cw_key(int state);
-
#ifdef FREEDV
extern void set_freedv(int state);
#endif
int rigctlGetFilterLow();
int rigctlGetFilterHigh();
-// DL1YCF changed next to function to void
void rigctlSetFilterLow(int val);
void rigctlSetFilterHigh(int val);
int new_level;
// Wait throttle time
usleep(RIGCTL_TIMER_DELAY);
rigctl_timer = 0;
- // DL1YCF added return statement to make compiler happy.
return NULL;
}
g_idle_add(ext_mox_update ,(gpointer)1);
// have to wait until it is really there
// Note that if out-of-band, we would wait
- // forever here, so allow at most 500 msec
- i=10;
- while (!mox && (i--) > 0) usleep(50000L);
+ // forever here, so allow at most 100 msec
+ i=100;
+ while (!mox && i-- > 0) usleep(1000L);
// still no MOX? --> silently discard CW character and give up
if (!mox) {
CAT_cw_is_active=0;
if(agc_resp == 0) {
active_receiver->agc = AGC_OFF;
- } else if((agc_resp >0 && agc_resp <= 5) || (agc_resp == 84)) { // DL1YCF: added () to improve readability
+ } else if((agc_resp >0 && agc_resp <= 5) || (agc_resp == 84)) {
active_receiver->agc = AGC_FAST;
// fprintf(stderr,"GT command FAST\n");
- } else if((agc_resp >6 && agc_resp <= 10) || (agc_resp == 2*84)) { // DL1YCF: added () to improve readability
+ } else if((agc_resp >6 && agc_resp <= 10) || (agc_resp == 2*84)) {
active_receiver->agc = AGC_MEDIUM;
// fprintf(stderr,"GT command MED\n");
- } else if((agc_resp >11 && agc_resp <= 15) || (agc_resp == 3*84)) { // DL1YCF: added () to improve readability
+ } else if((agc_resp >11 && agc_resp <= 15) || (agc_resp == 3*84)) {
active_receiver->agc = AGC_SLOW;
//fprintf(stderr,"GT command SLOW\n");
- } else if((agc_resp >16 && agc_resp <= 20) || (agc_resp == 4*84)) { // DL1YCF: added () to improve readability
+ } else if((agc_resp >16 && agc_resp <= 20) || (agc_resp == 4*84)) {
active_receiver->agc = AGC_LONG;
// fprintf(stderr,"GT command LONG\n");
}
// Instead, we offer these checkboxes in either case and must rely on the user
// not playing around with features that are not there.
//
+ // NOTE: Preamps are not present on most current HPSDR models, and ALEX attenuators
+ // are not present e.g. in ANAN-7000.
+ //
if (filter_board != CHARLY25) {
switch(protocol) {
case ORIGINAL_PROTOCOL:
static gboolean step_select_cb (GtkWidget *widget, gpointer data) {
step=steps[(uintptr_t)data];
vfo_update();
- // DL1YCF added return statement to make the compiler happy.
- // however I am unsure about the correct return value.
- // I would have coded this as a void function.
return FALSE;
}
// if "MON" button is active (tx->feedback is TRUE),
// then obtain spectrum pixels from PS_RX_FEEDBACK,
// that is, display the (attenuated) TX signal from the "antenna"
+ //
+ // POSSIBLE MISMATCH OF SAMPLE RATES:
+ // TX sample rate is fixed 48 kHz, but RX sample rate can be
+ // 2*, 4*, or even 8* larger. In this case, the spectrum shown
+ // here is squeezed, so we have to extend the pixels.
+ // So the feedback spectrum is not nice to look at with 192000 Hz
+ // sample rate (low-res), but at least it is correct.
+ // For the sake of saving CPU cycles, we do not interpolate.
+ //
+ // This correction is applied her for the V1 protocol only, because
+ // there might be non-integer ratios using the new protocol.
+ //
if(tx->puresignal && tx->feedback) {
RECEIVER *rx_feedback=receiver[PS_RX_FEEDBACK];
GetPixels(rx_feedback->id,0,rx_feedback->pixel_samples,&rc);
- memcpy(tx->pixel_samples,rx_feedback->pixel_samples,sizeof(float)*tx->pixels);
+ if (protocol == ORIGINAL_PROTOCOL && (active_receiver->sample_rate != 48000)) {
+ int ratio = active_receiver->sample_rate / 48000;
+ int width = tx->pixels / ratio; // number of pixels to copy from the feedback spectrum
+ int start = (tx->pixels - width) >> 1; // Copy from start ... (end-1)
+ int end = start + width;
+ int i;
+ float *tfp=tx->pixel_samples;
+ float *rfp=rx_feedback->pixel_samples+start;
+ switch (ratio) {
+ case 8:
+ for (i=start; i < end; i++) {
+ *tfp++ = *rfp;
+ *tfp++ = *rfp;
+ *tfp++ = *rfp;
+ *tfp++ = *rfp;
+ *tfp++ = *rfp;
+ *tfp++ = *rfp;
+ *tfp++ = *rfp;
+ *tfp++ = *rfp++;
+ }
+ break;
+ case 4:
+ for (i=start; i < end; i++) {
+ *tfp++ = *rfp;
+ *tfp++ = *rfp;
+ *tfp++ = *rfp;
+ *tfp++ = *rfp++;
+ }
+ break;
+ case 2:
+ for (i=start; i < end; i++) {
+ *tfp++ = *rfp;
+ *tfp++ = *rfp++;
+ }
+ break;
+ }
+ } else {
+ // TX and feedback sample rates are equal -- just copy
+ memcpy(tx->pixel_samples,rx_feedback->pixel_samples,sizeof(float)*tx->pixels);
+ }
} else {
#endif
GetPixels(tx->id,0,tx->pixel_samples,&rc);
int i,s;
//
-// silence TX audio if not transmitting, if tuning, or
-// when doing CW. Note: CW side tone added later on by a
-// separate mechanism.
+// silence TX audio if tuning, or when doing CW.
+// (in order not to fire VOX)
//
- if (tune || !isTransmitting() || mode==modeCWL || mode==modeCWU) {
+ if (tune || mode==modeCWL || mode==modeCWU) {
mic_sample_double=0.0;
} else {
mic_sample_double=(double)mic_sample/32768.0;
} else {
vfo_move(-step);
}
- // DL1YCF added return statement to make the compiler happy.
- // however I am unsure about the correct return value.
- // I would have coded this as a void function.
return FALSE;
}
}
}
vfo_update();
- // DL1YCF added return statement to make the compiler happy.
- // however I am unsure about the correct return value.
- // I would have coded this as a void function.
return FALSE;
}
#include "vfo.h"
#include "ext.h"
-static guint vox_timeout;
+static guint vox_timeout=0;
static double peak=0.0;
static int vox_timeout_cb(gpointer data) {
- //setVox(0);
+ //
+ // First set vox_timeout to zero indicating no "hanging" timeout
+ // then, remove VOX and update display
+ //
+ vox_timeout=0;
g_idle_add(ext_vox_changed,(gpointer)0);
g_idle_add(ext_vfo_update,NULL);
return FALSE;
void update_vox(TRANSMITTER *tx) {
// calculate peak microphone input
// assumes it is interleaved left and right channel with length samples
- int previous_vox=vox;
int i;
double sample;
peak=0.0;
}
if(vox_enabled) {
- if(peak>vox_threshold) {
- if(previous_vox) {
+ if(peak > vox_threshold) {
+ // we use the value of vox_timeout to determine whether
+ // the time-out is "hanging". We cannot use the value of vox
+ // since this may be set with a delay, and we MUST NOT miss
+ // a "hanging" timeout. Note that if a time-out fires, vox_timeout
+ // is set to zero.
+ if(vox_timeout) {
g_source_remove(vox_timeout);
} else {
+ //
+ // no hanging time-out, assume that we just fired VOX
g_idle_add(ext_vox_changed,(gpointer)1);
+ g_idle_add(ext_vfo_update,NULL);
}
+ // re-init "vox hang" time
vox_timeout=g_timeout_add((int)vox_hang,vox_timeout_cb,NULL);
}
- if(vox!=previous_vox) {
- g_idle_add(ext_vfo_update,NULL);
- }
+ // if peak is not above threshold, do nothing (this shall be done later in the timeout event
}
}
+//
+// If no vox time-out is hanging, this function is a no-op
+//
void vox_cancel() {
- if(vox) {
+ if(vox_timeout) {
g_source_remove(vox_timeout);
- //setVox(0);
- g_idle_add(ext_vox_changed,(gpointer)0);
- g_idle_add(ext_vfo_update,NULL);
}
}
display_height=gtk_widget_get_allocated_height (widget);
rx->pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, display_width, display_height);
- // DL1YCF changed to uchar
unsigned char *pixels = gdk_pixbuf_get_pixels (rx->pixbuf);
memset(pixels, 0, display_width*display_height*3);
float *samples;
if(rx->pixbuf) {
- // DL1YCF changed to uchar
unsigned char *pixels = gdk_pixbuf_get_pixels (rx->pixbuf);
int width=gdk_pixbuf_get_width(rx->pixbuf);
float sample;
int average=0;
- // DL1YCF changed to uchar
unsigned char *p;
p=pixels;
samples=rx->pixel_samples;