From 52adb351dc46d8dd2eab549c5bc33bd69cea5a32 Mon Sep 17 00:00:00 2001 From: John Melton G0ORX Date: Sat, 14 Mar 2020 13:43:11 +0000 Subject: [PATCH] Added ZOOM and PAN capability. Added external PTT GPIO input. --- configure.c | 52 ++++++++++- discovery.c | 83 +++++++++++------ display_menu.c | 15 +++- encoder_menu.c | 6 +- ext.c | 80 +++++++++++++---- ext.h | 55 ++++++++---- gpio.c | 222 ++++++++++++++++++++++++++++++++++------------ gpio.h | 119 ++++++++++++++----------- i2c.c | 64 +++---------- midi.h | 6 +- midi2.c | 6 +- midi3.c | 45 +++++++++- new_protocol.c | 1 + old_protocol.c | 1 + radio.c | 80 +++++++++++++++-- radio.h | 3 + receiver.c | 153 +++++++++++++++++++++----------- receiver.h | 11 ++- rx_panadapter.c | 156 +++++++++++++++++++++++++++----- sliders.c | 26 ++---- sliders.h | 25 ++++++ soapy_discovery.c | 11 +++ soapy_protocol.c | 86 ++++++++++++++---- soapy_protocol.h | 2 +- vfo.c | 60 +++++++++---- waterfall.c | 4 +- zoompan.c | 211 +++++++++++++++++++++++++++++++++++++++++++ zoompan.h | 31 +++++++ 28 files changed, 1236 insertions(+), 378 deletions(-) create mode 100644 zoompan.c create mode 100644 zoompan.h diff --git a/configure.c b/configure.c index c9c496d..b3deb42 100644 --- a/configure.c +++ b/configure.c @@ -139,6 +139,13 @@ static GtkWidget *b_enable_cwlr; static GtkWidget *b_cw_active_low; #endif +#ifdef PTT +static GtkWidget *ptt_label; +static GtkWidget *ptt; +static GtkWidget *b_enable_ptt; +static GtkWidget *b_ptt_active_low; +#endif + static gboolean save_cb (GtkWidget *widget, GdkEventButton *event, gpointer data) { if(dialog!=NULL) { ENABLE_VFO_ENCODER=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(b_enable_vfo_encoder))?1:0; @@ -164,7 +171,7 @@ static gboolean save_cb (GtkWidget *widget, GdkEventButton *event, gpointer data ENABLE_E4_PULLUP=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(b_enable_E4_pullup))?1:0; E4_FUNCTION=gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(E4_sw)); - if(controller==CONTROLLER2_V1 || controller==CONTROLLER2_V2 || controller==CUSTOM_CONTROLLER) { + if(controller==CONTROLLER2_V1 || controller==CONTROLLER2_V2) { ENABLE_E5_ENCODER=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(b_enable_E5_encoder))?1:0; E5_ENCODER_A=gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(E5_a)); E5_ENCODER_B=gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(E5_b)); @@ -215,6 +222,12 @@ static gboolean save_cb (GtkWidget *widget, GdkEventButton *event, gpointer data SIDETONE_GPIO=gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(cws)); #endif +#ifdef PTT + ENABLE_PTT_GPIO=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(b_enable_ptt))?1:0; + PTT_ACTIVE_LOW=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(b_ptt_active_low))?1:0; + PTT_GPIO=gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(ptt)); +#endif + gpio_save_state(); gtk_widget_destroy(dialog); } @@ -258,7 +271,9 @@ void configure_gpio(GtkWidget *parent) { // Encoders GtkWidget *grid1=gtk_grid_new(); - gtk_grid_set_column_spacing (GTK_GRID(grid1),10); + gtk_grid_set_column_homogeneous(GTK_GRID(grid1),FALSE); + gtk_grid_set_row_homogeneous(GTK_GRID(grid1),TRUE); + gtk_grid_set_column_spacing (GTK_GRID(grid1),5); row=0; b_enable_vfo_encoder=gtk_check_button_new_with_label("Enable VFO"); @@ -410,7 +425,7 @@ void configure_gpio(GtkWidget *parent) { row++; - if(controller==CONTROLLER2_V1 || controller==CONTROLLER2_V2 || controller==CUSTOM_CONTROLLER) { + if(controller==CONTROLLER2_V1 || controller==CONTROLLER2_V2) { b_enable_E5_encoder=gtk_check_button_new_with_label("Enable E5"); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (b_enable_E5_encoder), ENABLE_E5_ENCODER); gtk_widget_show(b_enable_E5_encoder); @@ -701,6 +716,37 @@ void configure_gpio(GtkWidget *parent) { gtk_notebook_append_page(GTK_NOTEBOOK(notebook),grid3,gtk_label_new("CW")); #endif +#ifdef PTT + GtkWidget *grid4=gtk_grid_new(); + gtk_grid_set_column_spacing (GTK_GRID(grid4),10); + row=0; + + b_enable_ptt=gtk_check_button_new_with_label("PTT Enable"); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (b_enable_ptt), ENABLE_PTT_GPIO); + gtk_widget_show(b_enable_ptt); + gtk_grid_attach(GTK_GRID(grid4),b_enable_ptt,0,row,1,1); + + row++; + + ptt_label=gtk_label_new("PTT GPIO:"); + gtk_widget_show(ptt_label); + gtk_grid_attach(GTK_GRID(grid4),ptt_label,0,row,1,1); + + ptt=gtk_spin_button_new_with_range (0.0,100.0,1.0); + gtk_spin_button_set_value (GTK_SPIN_BUTTON(ptt),CWR_BUTTON); + gtk_widget_show(ptt); + gtk_grid_attach(GTK_GRID(grid4),ptt,1,row,1,1); + + row++; + + b_ptt_active_low=gtk_check_button_new_with_label("PTT active-low"); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (b_ptt_active_low), PTT_ACTIVE_LOW); + gtk_widget_show(b_ptt_active_low); + gtk_grid_attach(GTK_GRID(grid4),b_ptt_active_low,0,row,1,1); + + gtk_notebook_append_page(GTK_NOTEBOOK(notebook),grid4,gtk_label_new("PTT")); +#endif + gtk_grid_attach(GTK_GRID(grid0),notebook,0,1,6,1); gtk_container_add(GTK_CONTAINER(content),grid0); diff --git a/discovery.c b/discovery.c index 5252ff0..8b3a870 100644 --- a/discovery.c +++ b/discovery.c @@ -67,6 +67,12 @@ GtkWidget *tcpaddr; static char ipaddr_tcp_buf[IPADDR_LEN] = "10.10.10.10"; char *ipaddr_tcp = &ipaddr_tcp_buf[0]; +#ifdef SERVER +GtkWidget *connect_addr_entry; +static char connect_addr_buffer[30]="0.0.0.0:50000"; +char *connect_addr = &connect_addr_buffer[0]; +#endif + static gboolean delete_event_cb(GtkWidget *widget, GdkEvent *event, gpointer data) { _exit(0); } @@ -256,6 +262,15 @@ static gboolean tcp_cb (GtkWidget *widget, GdkEventButton *event, gpointer data) return TRUE; } +#ifdef SERVER +static gboolean connect_cb (GtkWidget *widget, GdkEventButton *event, gpointer data) { + // connect to remote host running piHPSDR + strncpy(connect_addr, gtk_entry_get_text(GTK_ENTRY(connect_addr_entry)), 30); +g_print("connect_cb: %s\n",connect_addr); + return TRUE; +} +#endif + void discovery() { //fprintf(stderr,"discovery\n"); @@ -401,11 +416,11 @@ void discovery() { //gtk_grid_set_column_homogeneous(GTK_GRID(grid),TRUE); gtk_grid_set_row_spacing (GTK_GRID(grid),10); - int i; + int row; char version[16]; char text[256]; - for(i=0;iprotocol,d->name); sprintf(version,"v%d.%d", d->software_version/10, @@ -460,12 +475,12 @@ fprintf(stderr,"%p Protocol=%d name=%s\n",d,d->protocol,d->name); gtk_widget_override_font(label, pango_font_description_from_string("Sans 11")); gtk_widget_set_halign (label, GTK_ALIGN_START); gtk_widget_show(label); - gtk_grid_attach(GTK_GRID(grid),label,0,i,3,1); + gtk_grid_attach(GTK_GRID(grid),label,0,row,3,1); GtkWidget *start_button=gtk_button_new_with_label("Start"); gtk_widget_override_font(start_button, pango_font_description_from_string("Sans 16")); gtk_widget_show(start_button); - gtk_grid_attach(GTK_GRID(grid),start_button,3,i,1,1); + gtk_grid_attach(GTK_GRID(grid),start_button,3,row,1,1); g_signal_connect(start_button,"button_press_event",G_CALLBACK(start_cb),(gpointer)d); // if not available then cannot start it @@ -492,37 +507,37 @@ fprintf(stderr,"%p Protocol=%d name=%s\n",d,d->protocol,d->name); gtk_button_set_label(GTK_BUTTON(start_button), "Not installed"); gtk_widget_set_sensitive(start_button, FALSE); } else { - apps_combobox[i] = gtk_combo_box_text_new(); - gtk_widget_override_font(apps_combobox[i], pango_font_description_from_string("Sans 11")); + apps_combobox[row] = gtk_combo_box_text_new(); + gtk_widget_override_font(apps_combobox[row], pango_font_description_from_string("Sans 11")); // We want the default selection priority for the STEMlab app to be // RP-Trx > HAMlab-Trx > Pavel-Trx > Pavel-Rx, so we add in decreasing order and // always set the newly added entry to be active. if ((d->software_version & STEMLAB_PAVEL_RX) != 0) { - gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(apps_combobox[i]), + gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(apps_combobox[row]), "sdr_receiver_hpsdr", "Pavel-Rx"); - gtk_combo_box_set_active_id(GTK_COMBO_BOX(apps_combobox[i]), + gtk_combo_box_set_active_id(GTK_COMBO_BOX(apps_combobox[row]), "sdr_receiver_hpsdr"); } if ((d->software_version & STEMLAB_PAVEL_TRX) != 0) { - gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(apps_combobox[i]), + gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(apps_combobox[row]), "sdr_transceiver_hpsdr", "Pavel-Trx"); - gtk_combo_box_set_active_id(GTK_COMBO_BOX(apps_combobox[i]), + gtk_combo_box_set_active_id(GTK_COMBO_BOX(apps_combobox[row]), "sdr_transceiver_hpsdr"); } if ((d->software_version & HAMLAB_RP_TRX) != 0) { - gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(apps_combobox[i]), + gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(apps_combobox[row]), "hamlab_sdr_transceiver_hpsdr", "HAMlab-Trx"); - gtk_combo_box_set_active_id(GTK_COMBO_BOX(apps_combobox[i]), + gtk_combo_box_set_active_id(GTK_COMBO_BOX(apps_combobox[row]), "hamlab_sdr_transceiver_hpsdr"); } if ((d->software_version & STEMLAB_RP_TRX) != 0) { - gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(apps_combobox[i]), + gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(apps_combobox[row]), "stemlab_sdr_transceiver_hpsdr", "STEMlab-Trx"); - gtk_combo_box_set_active_id(GTK_COMBO_BOX(apps_combobox[i]), + gtk_combo_box_set_active_id(GTK_COMBO_BOX(apps_combobox[row]), "stemlab_sdr_transceiver_hpsdr"); } - gtk_widget_show(apps_combobox[i]); - gtk_grid_attach(GTK_GRID(grid), apps_combobox[i], 4, i, 1, 1); + gtk_widget_show(apps_combobox[row]); + gtk_grid_attach(GTK_GRID(grid), apps_combobox[row], 4, i, 1, 1); } } #endif @@ -538,8 +553,7 @@ fprintf(stderr,"%p Protocol=%d name=%s\n",d,d->protocol,d->name); gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(gpio),NULL,"Controller1"); gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(gpio),NULL,"Controller2 V1"); gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(gpio),NULL,"Controller2 V2"); - gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(gpio),NULL,"Custom"); - gtk_grid_attach(GTK_GRID(grid),gpio,0,i,1,1); + gtk_grid_attach(GTK_GRID(grid),gpio,0,row,1,1); gtk_combo_box_set_active(GTK_COMBO_BOX(gpio),controller); g_signal_connect(gpio,"changed",G_CALLBACK(gpio_changed_cb),NULL); @@ -547,38 +561,53 @@ fprintf(stderr,"%p Protocol=%d name=%s\n",d,d->protocol,d->name); GtkWidget *discover_b=gtk_button_new_with_label("Discover"); g_signal_connect (discover_b, "button-press-event", G_CALLBACK(discover_cb), NULL); - gtk_grid_attach(GTK_GRID(grid),discover_b,1,i,1,1); + gtk_grid_attach(GTK_GRID(grid),discover_b,1,row,1,1); GtkWidget *protocols_b=gtk_button_new_with_label("Protocols"); g_signal_connect (protocols_b, "button-press-event", G_CALLBACK(protocols_cb), NULL); - gtk_grid_attach(GTK_GRID(grid),protocols_b,2,i,1,1); + gtk_grid_attach(GTK_GRID(grid),protocols_b,2,row,1,1); #ifdef MIDI GtkWidget *midi_b=gtk_button_new_with_label("ImportMIDI"); g_signal_connect (midi_b, "button-press-event", G_CALLBACK(midi_cb), NULL); - gtk_grid_attach(GTK_GRID(grid),midi_b,3,i,1,1); + gtk_grid_attach(GTK_GRID(grid),midi_b,3,row,1,1); #endif - i++; + row++; #ifdef GPIO GtkWidget *gpio_b=gtk_button_new_with_label("Configure GPIO"); g_signal_connect (gpio_b, "button-press-event", G_CALLBACK(gpio_cb), NULL); - gtk_grid_attach(GTK_GRID(grid),gpio_b,0,i,1,1); + gtk_grid_attach(GTK_GRID(grid),gpio_b,0,row,1,1); #endif GtkWidget *tcp_b=gtk_button_new_with_label("Use new TCP Addr:"); g_signal_connect (tcp_b, "button-press-event", G_CALLBACK(tcp_cb), NULL); - gtk_grid_attach(GTK_GRID(grid),tcp_b,1,i,1,1); + gtk_grid_attach(GTK_GRID(grid),tcp_b,1,row,1,1); tcpaddr=gtk_entry_new(); gtk_entry_set_max_length(GTK_ENTRY(tcpaddr), 20); - gtk_grid_attach(GTK_GRID(grid),tcpaddr,2,i,1,1); + gtk_grid_attach(GTK_GRID(grid),tcpaddr,2,row,1,1); gtk_entry_set_text(GTK_ENTRY(tcpaddr), ipaddr_tcp); GtkWidget *exit_b=gtk_button_new_with_label("Exit"); g_signal_connect (exit_b, "button-press-event", G_CALLBACK(exit_cb), NULL); - gtk_grid_attach(GTK_GRID(grid),exit_b,3,i,1,1); + gtk_grid_attach(GTK_GRID(grid),exit_b,3,row,1,1); + +#ifdef SERVER + row++; + + GtkWidget *connect_b=gtk_button_new_with_label("Connect (Addr:Port)"); + g_signal_connect (connect_b, "button-press-event", G_CALLBACK(connect_cb), NULL); + gtk_grid_attach(GTK_GRID(grid),connect_b,0,row,1,1); + + connect_addr_entry=gtk_entry_new(); + gtk_entry_set_max_length(GTK_ENTRY(connect_addr_entry), 30); + gtk_grid_attach(GTK_GRID(grid),connect_addr_entry,1,row,1,1); + gtk_entry_set_text(GTK_ENTRY(connect_addr_entry), connect_addr); + +#endif + gtk_container_add (GTK_CONTAINER (content), grid); gtk_widget_show_all(discovery_dialog); diff --git a/display_menu.c b/display_menu.c index 7155bb2..995ff72 100644 --- a/display_menu.c +++ b/display_menu.c @@ -116,6 +116,11 @@ static void display_waterfall_cb(GtkWidget *widget, gpointer data) { reconfigure_radio(); } +static void display_zoompan_cb(GtkWidget *widget, gpointer data) { + display_zoompan=display_zoompan==1?0:1; + reconfigure_radio(); +} + static void display_sliders_cb(GtkWidget *widget, gpointer data) { display_sliders=display_sliders==1?0:1; reconfigure_radio(); @@ -395,7 +400,7 @@ void display_menu(GtkWidget *parent) { row++; row++; row++; - col=1; + col=0; GtkWidget *b_display_waterfall=gtk_check_button_new_with_label("Display Waterfall"); @@ -407,6 +412,14 @@ void display_menu(GtkWidget *parent) { col++; + GtkWidget *b_display_zoompan=gtk_check_button_new_with_label("Display Zoom/Pan"); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (b_display_zoompan), display_zoompan); + gtk_widget_show(b_display_zoompan); + gtk_grid_attach(GTK_GRID(grid),b_display_zoompan,col,row,1,1); + g_signal_connect(b_display_zoompan,"toggled",G_CALLBACK(display_zoompan_cb),(gpointer *)NULL); + + col++; + GtkWidget *b_display_sliders=gtk_check_button_new_with_label("Display Sliders"); //gtk_widget_override_font(b_display_sliders, pango_font_description_from_string("Arial 18")); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (b_display_sliders), display_sliders); diff --git a/encoder_menu.c b/encoder_menu.c index da4f775..bde18bf 100644 --- a/encoder_menu.c +++ b/encoder_menu.c @@ -624,10 +624,10 @@ void encoder_menu(GtkWidget *parent) { GtkWidget *grid=gtk_grid_new(); - gtk_grid_set_column_homogeneous(GTK_GRID(grid),TRUE); + gtk_grid_set_column_homogeneous(GTK_GRID(grid),FALSE); gtk_grid_set_row_homogeneous(GTK_GRID(grid),TRUE); - gtk_grid_set_column_spacing (GTK_GRID(grid),5); - gtk_grid_set_row_spacing (GTK_GRID(grid),5); + gtk_grid_set_column_spacing (GTK_GRID(grid),2); + gtk_grid_set_row_spacing (GTK_GRID(grid),2); GtkWidget *close_b=gtk_button_new_with_label("Close"); g_signal_connect (close_b, "pressed", G_CALLBACK(close_cb), NULL); diff --git a/ext.c b/ext.c index 484be9d..619271c 100644 --- a/ext.c +++ b/ext.c @@ -43,6 +43,7 @@ #include "noise_menu.h" #include "wdsp.h" #include "ext.h" +#include "zoompan.h" // The following calls functions can be called usig g_idle_add @@ -139,7 +140,7 @@ int ext_noise_update(void *data) { } int ext_mox_update(void *data) { -//g_print("ext_mox_update: %d\n",GPOINTER_TO_INT(data)); +g_print("ext_mox_update: %d\n",GPOINTER_TO_INT(data)); mox_update(GPOINTER_TO_INT(data)); return 0; } @@ -645,34 +646,81 @@ int ext_set_duplex(void *data) { return 0; } -int ext_set_rx_frequency(void *data) { - RX_FREQUENCY *p=(RX_FREQUENCY *)data; - int b = get_band_from_frequency(p->frequency); - if (b != vfo[p->rx].band) { - vfo_band_changed(b); +int ext_remote_command(void *data) { + REMOTE_COMMAND *p=(REMOTE_COMMAND *)data; + switch(p->cmd) { + case RX_FREQ_CMD: + { + int b = get_band_from_frequency(p->data.frequency); + if (b != vfo[p->id].band) { + vfo_band_changed(b); + } + setFrequency(p->data.frequency); + } + break; + case RX_MOVE_CMD: + vfo_move(p->data.frequency,TRUE); + break; + case RX_MOVETO_CMD: + vfo_move_to(p->data.frequency); + break; + case RX_MODE_CMD: + vfo_mode_changed(p->data.mode); + break; + case RX_FILTER_CMD: + vfo_filter_changed(p->data.filter); + break; + case RX_AGC_CMD: + active_receiver->agc=p->data.agc; + set_agc(active_receiver, active_receiver->agc); + vfo_update(); + break; + case RX_NR_CMD: + break; + case RX_NB_CMD: + break; + case RX_SNB_CMD: + break; + case RX_SPLIT_CMD: + break; + case RX_SAT_CMD: + break; + case RX_DUP_CMD: + break; } - setFrequency(p->frequency); g_free(data); return 0; +} +int ext_mute_update(void *data) { + active_receiver->mute_radio=!active_receiver->mute_radio; return 0; } -int ext_set_rx_mode(void *data) { - RX_MODE *p=(RX_MODE *)data; - vfo_mode_changed(p->mode); +int ext_zoom_update(void *data) { + update_zoom((double)GPOINTER_TO_INT(data)); return 0; } -int ext_set_rx_filter(void *data) { - RX_FILTER *p=(RX_FILTER *)data; - vfo_filter_changed(p->filter); +int ext_zoom_set(void *data) { + int pos=GPOINTER_TO_INT(data); + double zoom=((double)pos/(100.0/7.0))+1.0; + if((int)zoom!=active_receiver->zoom) { + set_zoom(active_receiver->id,(double)zoom); + } return 0; } -int ext_mute_update(void *data) { -g_print("ext_mute_update: currently %d\n",active_receiver->mute_radio); - active_receiver->mute_radio=!active_receiver->mute_radio; +int ext_pan_update(void *data) { + update_pan((double)GPOINTER_TO_INT(data)); return 0; } +int ext_pan_set(void *data) { + if(active_receiver->zoom>1) { + int pos=GPOINTER_TO_INT(data); + double pan=(double)((active_receiver->zoom-1)*active_receiver->width)*((double)pos/100.0); + set_pan(active_receiver->id,(double)pan); + } + return 0; +} diff --git a/ext.h b/ext.h index f59aba7..906a099 100644 --- a/ext.h +++ b/ext.h @@ -23,20 +23,37 @@ // Use these calls from within the rigclt daemon, or the GPIO or MIDI stuff // -typedef struct _RX_FREQUENCY { - int rx; - long long frequency; -} RX_FREQUENCY; - -typedef struct _RX_MODE { - int rx; - int mode; -} RX_MODE; - -typedef struct _RX_FILTER { - int rx; - int filter; -} RX_FILTER; +enum { + RX_FREQ_CMD, + RX_MOVE_CMD, + RX_MOVETO_CMD, + RX_MODE_CMD, + RX_FILTER_CMD, + RX_AGC_CMD, + RX_NR_CMD, + RX_NB_CMD, + RX_SNB_CMD, + RX_SPLIT_CMD, + RX_SAT_CMD, + RX_DUP_CMD +}; + +typedef struct _REMOTE_COMMAND { + int id; + int cmd; + union { + long long frequency; + int mode; + int filter; + int agc; + int nr; + int nb; + int snb; + int split; + int sat; + int dup; + } data; +} REMOTE_COMMAND; extern int ext_discovery(void *data); extern int ext_vfo_update(void *data); @@ -130,7 +147,11 @@ int ext_update_noise(void *data); int ext_start_ps(void *data); #endif -int ext_set_rx_frequency(void *data); -int ext_set_rx_mode(void *data); -int ext_set_rx_filter(void *data); int ext_mute_update(void *data); + +int ext_zoom_update(void *data); +int ext_zoom_set(void *data); +int ext_pan_update(void *data); +int ext_pan_set(void *data); + +int ext_remote_command(void *data); diff --git a/gpio.c b/gpio.c index 57fd76a..efd9e64 100644 --- a/gpio.c +++ b/gpio.c @@ -56,6 +56,7 @@ #ifdef LOCALCW #include "iambic.h" #endif +#include "zoompan.h" // debounce settle time in ms #define DEFAULT_SETTLE_TIME 50 @@ -131,6 +132,12 @@ int ENABLE_CW_BUTTONS=1; int CW_ACTIVE_LOW=1; #endif +#ifdef PTT +int ENABLE_PTT_GPIO=1; +int PTT_GPIO=15; +int PTT_ACTIVE_LOW=1; +#endif + int vfoEncoderPos; int vfoFunction; @@ -190,87 +197,93 @@ static int running=0; char *encoder_string[ENCODER_ACTIONS] = { "NO ACTION", "AF GAIN", - "RF GAIN", - "AGC GAIN", - "IF WIDTH", - "IF SHIFT", "AF GAIN RX1", - "RF GAIN RX1", "AF GAIN RX2", - "RF GAIN RX2", + "AGC GAIN", "AGC GAIN RX1", "AGC GAIN RX2", - "IF WIDTH RX1", - "IF WIDTH RX2", + "ATTENUATION/RX GAIN", + "COMP", + "CW FREQUENCY", + "CW SPEED", + "DIVERSITY GAIN", + "DIVERSITY GAIN (coarse)", + "DIVERSITY GAIN (fine)", + "DIVERSITY PHASE", + "DIVERSITY PHASE (coarse)", + "DIVERSITY PHASE (fine)", + "DRIVE", + "IF SHIFT", "IF SHIFT RX1", "IF SHIFT RX2", - "ATTENUATION/RX GAIN", + "IF WIDTH", + "IF WIDTH RX1", + "IF WIDTH RX2", "MIC GAIN", - "DRIVE", - "TUNE DRIVE", - "RIT", - "RIT RX1", - "RIT RX2", - "XIT", - "CW SPEED", - "CW FREQUENCY", + "PAN", "PANADAPTER HIGH", "PANADAPTER LOW", "PANADAPTER STEP", - "WATERFALL HIGH", - "WATERFALL LOW", + "RF GAIN", + "RF GAIN RX1", + "RF GAIN RX2", + "RIT", + "RIT RX1", + "RIT RX2", "SQUELCH", "SQUELCH RX1", "SQUELCH RX2", - "COMP", - "DIVERSITY GAIN", - "DIVERSITY GAIN (coarse)", - "DIVERSITY GAIN (fine)", - "DIVERSITY PHASE", - "DIVERSITY PHASE (coarse)", - "DIVERSITY PHASE (fine)" + "TUNE DRIVE", + "WATERFALL HIGH", + "WATERFALL LOW", + "XIT", + "ZOOM", }; char *sw_string[SWITCH_ACTIONS] = { "", - "TUNE", + "A TO B", + "A SWAP B", + "AGC", + "B TO A", + "BAND -", + "BAND +", + "BSTACK -", + "BSTACK +", + "CTUN", + "DIV", + "FILTER -", + "FILTER +", + "FUNCTION", + "LOCK", + "MENU BAND", + "MENU BSTACK", + "MENU DIV", + "MENU FILTER", + "MENU FREQUENCY", + "MENU MEMORY", + "MENU MODE", + "MENU PS", + "MODE -", + "MODE +", "MOX", - "PS", - "TWO TONE", - "NR", + "MUTE", "NB", - "SNB", + "NR", + "PAN -", + "PAN +", + "PS", "RIT", "RIT CL", + "SAT", + "SNB", + "SPLIT", + "TUNE", + "TWO TONE", "XIT", "XIT CL", - "BAND +", - "BAND -", - "BSTACK +", - "BSTACK -", - "MODE +", - "MODE -", - "FILTER +", - "FILTER -", - "A TO B", - "B TO A", - "A SWAP B", - "LOCK", - "CTUN", - "AGC", - "SPLIT", - "DIV", - "SAT", - "BAND", - "BSTACK", - "MODE", - "FILTER", - "FREQUENCY", - "MEMORY", - "DIV MENU", - "PS MENU", - "FUNCTION", - "MUTE" + "ZOOM -", + "ZOOM +", }; int *sw_action=NULL; @@ -365,6 +378,20 @@ static int vfo_function_released(void *data) { return 0; } +#ifdef PTT +static int ptt_pressed(void *data) { +g_print("ptt_pressed\n"); + if(can_transmit) g_idle_add(ext_mox_update,GINT_TO_POINTER(1)); + return 0; +} + +static int ptt_released(void *data) { +g_print("ptt_released\n"); + if(can_transmit) g_idle_add(ext_mox_update,GINT_TO_POINTER(0)); + return 0; +} +#endif + static int e_function_pressed(void *data) { int action=(int)data; g_print("e_function_pressed: %d\n",action); @@ -487,6 +514,18 @@ g_print("e_function_pressed: %d\n",action); case MUTE: g_idle_add(ext_mute_update,NULL); break; + case PAN_MINUS: + g_idle_add(ext_pan_update,GINT_TO_POINTER(-100)); + break; + case PAN_PLUS: + g_idle_add(ext_pan_update,GINT_TO_POINTER(100)); + break; + case ZOOM_MINUS: + g_idle_add(ext_zoom_update,GINT_TO_POINTER(-1)); + break; + case ZOOM_PLUS: + g_idle_add(ext_zoom_update,GINT_TO_POINTER(1)); + break; } return 0; } @@ -819,6 +858,28 @@ static void pI2CInterrupt() { } } +#ifdef PTT +static int ptt_level=1; +static unsigned long ptt_debounce=0; + +static void pttAlert() { + int t=millis(); + if(millis() B - AF_GAIN, // AFGAIN: AF gain + MIDI_AF_GAIN, // AFGAIN: AF gain AGCATTACK, // AGCATTACK: AGC ATTACK (cycle fast/med/slow etc.) MIDI_AGC, // AGCVAL: AGC level ATT, // ATT: Step attenuator or Programmable attenuator @@ -80,6 +80,7 @@ enum MIDIaction { MIDI_MUTE, // MUTE: toggle mute on/off MIDI_NB, // NOISEBLANKER: cycle through NoiseBlanker states (none, NB, NB2) MIDI_NR, // NOISEREDUCTION: cycle through NoiseReduction states (none, NR, NR2) + MIDI_PAN, // PAN: change panning of panadater/waterfall when zoomed PAN_HIGH, // PANHIGH: "high" value of current panadapter PAN_LOW, // PANLOW: "low" value of current panadapter PRE, // PREAMP: preamp on/off @@ -103,6 +104,9 @@ enum MIDIaction { VOXLEVEL, // VOXLEVEL: adjust VOX threshold MIDI_XIT_CLEAR, // XITCLEAR: clear XIT value XIT_VAL, // XITVAL: change XIT value + MIDI_ZOOM, // ZOOM: change zoom factor + ZOOM_UP, // ZOOMUP: change zoom factor + ZOOM_DOWN, // ZOOMDOWN: change zoom factor }; // diff --git a/midi2.c b/midi2.c index b152b3a..94b23e9 100644 --- a/midi2.c +++ b/midi2.c @@ -102,7 +102,7 @@ static struct { const char *str; } ActionTable[] = { { VFO_A2B, "A2B"}, - { AF_GAIN, "AFGAIN"}, + { MIDI_AF_GAIN, "AFGAIN"}, { AGCATTACK, "AGCATTACK"}, { MIDI_AGC, "AGCVAL"}, { ATT, "ATT"}, @@ -133,6 +133,7 @@ static struct { { MIDI_MUTE, "MUTE"}, { MIDI_NB, "NOISEBLANKER"}, { MIDI_NR, "NOISEREDUCTION"}, + { MIDI_PAN, "PAN"}, { PAN_HIGH, "PANHIGH"}, { PAN_LOW, "PANLOW"}, { PRE, "PREAMP"}, @@ -156,6 +157,9 @@ static struct { { VOXLEVEL, "VOXLEVEL"}, { MIDI_XIT_CLEAR, "XITCLEAR"}, { XIT_VAL, "XITVAL"}, + { MIDI_ZOOM, "ZOOM"}, + { ZOOM_UP, "ZOOMUP"}, + { ZOOM_DOWN, "ZOOMDOWN"}, { ACTION_NONE, "NONE"} }; diff --git a/midi3.c b/midi3.c index 776c1ce..ff92e31 100644 --- a/midi3.c +++ b/midi3.c @@ -44,7 +44,7 @@ void DoTheMidi(enum MIDIaction action, enum MIDItype type, int val) { } break; /////////////////////////////////////////////////////////// "AFGAIN" - case AF_GAIN: // knob or wheel supported + case MIDI_AF_GAIN: // knob or wheel supported switch (type) { case MIDI_KNOB: active_receiver->volume = 0.01*val; @@ -470,6 +470,17 @@ void DoTheMidi(enum MIDIaction action, enum MIDItype type, int val) { g_idle_add(ext_vfo_update, NULL); } break; + /////////////////////////////////////////////////////////// "PAN" + case MIDI_PAN: // wheel and knob + switch (type) { + case MIDI_WHEEL: + g_idle_add(ext_pan_update,GINT_TO_POINTER(val)); + break; + case MIDI_KNOB: + g_idle_add(ext_pan_set,GINT_TO_POINTER(val)); + break; + } + break; /////////////////////////////////////////////////////////// "PANHIGH" case PAN_HIGH: // wheel or knob switch (type) { @@ -806,6 +817,38 @@ void DoTheMidi(enum MIDIaction action, enum MIDItype type, int val) { g_idle_add(ext_vfo_update, NULL); } break; + /////////////////////////////////////////////////////////// "ZOOM" + case MIDI_ZOOM: // wheel and knob + switch (type) { + case MIDI_WHEEL: +g_print("MIDI_ZOOM: MIDI_WHEEL: val=%d\n",val); + g_idle_add(ext_zoom_update,GINT_TO_POINTER(val)); + break; + case MIDI_KNOB: +g_print("MIDI_ZOOM: MIDI_KNOB: val=%d\n",val); + g_idle_add(ext_zoom_set,GINT_TO_POINTER(val)); + break; + } + break; + /////////////////////////////////////////////////////////// "ZOOMDOWN" + /////////////////////////////////////////////////////////// "ZOOMUP" + case ZOOM_UP: // key + case ZOOM_DOWN: // key + switch (type) { + case MIDI_KEY: + new = (action == ZOOM_UP) ? 1 : -1; + g_idle_add(ext_zoom_update,GINT_TO_POINTER(new)); + break; + case MIDI_WHEEL: + new = (val > 0) ? 1 : -1; + g_idle_add(ext_zoom_update,GINT_TO_POINTER(new)); + break; + default: + // do nothing + // we should not come here anyway + break; + } + break; case ACTION_NONE: // No error message, this is the "official" action for un-used controller buttons. diff --git a/new_protocol.c b/new_protocol.c index 979ff06..3aef1bd 100644 --- a/new_protocol.c +++ b/new_protocol.c @@ -1600,6 +1600,7 @@ static gpointer iq_thread(gpointer data) { if(ddc_sequence[ddc] !=sequence) { g_print("DDC %d sequence error: expected %ld got %ld\n",ddc,ddc_sequence[ddc],sequence); ddc_sequence[ddc]=sequence; + sequence_errors++; } ddc_sequence[ddc]++; // diff --git a/old_protocol.c b/old_protocol.c index 82fc028..b08120d 100644 --- a/old_protocol.c +++ b/old_protocol.c @@ -532,6 +532,7 @@ static gpointer receive_thread(gpointer arg) { // and is no error condition if (sequence != 0 && sequence != last_seq_num+1) { g_print("SEQ ERROR: last %ld, recvd %ld\n", (long) last_seq_num, (long) sequence); + sequence_errors++; } last_seq_num=sequence; switch(ep) { diff --git a/radio.c b/radio.c index 893c30b..81a7310 100644 --- a/radio.c +++ b/radio.c @@ -60,6 +60,7 @@ #include "rx_panadapter.h" #include "tx_panadapter.h" #include "waterfall.h" +#include "zoompan.h" #include "sliders.h" #include "toolbar.h" #include "rigctl.h" @@ -70,6 +71,9 @@ #ifdef MIDI #include "midi.h" #endif +#ifdef SERVER +#include "hpsdr_server.h" +#endif #define min(x,y) (xsample_rate:192000,display_width); switch (protocol) { case NEW_PROTOCOL: - pk = 0.2899; - break; + pk = 0.2899; + break; case ORIGINAL_PROTOCOL: switch (device) { - case DEVICE_HERMES_LITE2: - pk = 0.2300; + case DEVICE_HERMES_LITE2: + pk = 0.2300; break; - default: - pk = 0.4067; + default: + pk = 0.4067; break; } } @@ -1033,12 +1069,26 @@ void start_radio() { audio_waterfall=audio_waterfall_init(200,100); gtk_fixed_put(GTK_FIXED(fixed),audio_waterfall,0,VFO_HEIGHT+20); #endif - + + gboolean init_gpio=FALSE; +#ifdef LOCALCW + init_gpio=TRUE; +#endif +#ifdef PTT + init_gpio=TRUE; +#endif +#ifdef GPIO + init_gpio=TRUE; +#endif + + if(init_gpio) { #ifdef GPIO if(gpio_init()<0) { g_print("GPIO failed to initialize\n"); } #endif + } + #ifdef LOCALCW // init local keyer if enabled if (cw_keyer_internal == 0) { @@ -1061,6 +1111,12 @@ void start_radio() { #endif } + if(display_zoompan) { + zoompan = zoompan_init(display_width,ZOOMPAN_HEIGHT); + gtk_fixed_put(GTK_FIXED(fixed),zoompan,0,y); + y+=ZOOMPAN_HEIGHT; + } + if(display_sliders) { //g_print("create sliders\n"); sliders = sliders_init(display_width,SLIDERS_HEIGHT); @@ -1162,6 +1218,10 @@ void start_radio() { MIDIstartup(); #endif +#ifdef SERVER + create_hpsdr_server(); +#endif + } void disable_rigctl() { @@ -1227,7 +1287,7 @@ void radio_change_sample_rate(int rate) { break; #ifdef SOAPYSDR case SOAPYSDR_PROTOCOL: - soapy_protocol_change_sample_rate(receiver[0],rate); + soapy_protocol_change_sample_rate(receiver[0]); soapy_protocol_set_mic_sample_rate(rate); break; #endif @@ -1755,6 +1815,8 @@ g_print("radioRestoreState: %s\n",property_path); if(value) panadapter_high=atoi(value); value=getProperty("panadapter_low"); if(value) panadapter_low=atoi(value); + value=getProperty("display_zoompan"); + if(value) display_zoompan=atoi(value); value=getProperty("display_sliders"); if(value) display_sliders=atoi(value); value=getProperty("display_toolbar"); @@ -2005,6 +2067,8 @@ g_print("radioSaveState: %s\n",property_path); setProperty("panadapter_high",value); sprintf(value,"%d",panadapter_low); setProperty("panadapter_low",value); + sprintf(value,"%d",display_zoompan); + setProperty("display_zoompan",value); sprintf(value,"%d",display_sliders); setProperty("display_sliders",value); sprintf(value,"%d",display_toolbar); diff --git a/radio.h b/radio.h index 818d991..53aa27a 100644 --- a/radio.h +++ b/radio.h @@ -151,6 +151,7 @@ extern double display_average_time; //extern int display_waterfall; +extern int display_zoompan; extern int display_sliders; extern int display_toolbar; @@ -292,6 +293,8 @@ extern int have_rx_gain; // TRUE on HermesLite/RadioBerry extern int rx_gain_calibration; // position of the RX gain slider that // corresponds to zero amplification/attenuation +extern int sequence_errors; + extern void radio_stop(); extern void reconfigure_radio(); extern void start_radio(); diff --git a/receiver.c b/receiver.c index 73bc152..107174a 100644 --- a/receiver.c +++ b/receiver.c @@ -41,6 +41,7 @@ #include "vfo.h" #include "meter.h" #include "rx_panadapter.h" +#include "zoompan.h" #include "sliders.h" #include "waterfall.h" #include "new_protocol.h" @@ -91,15 +92,14 @@ gboolean receiver_button_release_event(GtkWidget *widget, GdkEventButton *event, making_active=FALSE; g_idle_add(menu_active_receiver_changed,NULL); g_idle_add(ext_vfo_update,NULL); + g_idle_add(zoompan_active_receiver_changed,NULL); g_idle_add(sliders_active_receiver_changed,NULL); if(event->button==3) { g_idle_add(ext_start_rx,NULL); } - g_print("receiver: %d adc=%d attenuation=%d rx_gain_calibration=%d\n",rx->id,rx->adc,adc_attenuation[rx->adc],rx_gain_calibration); + ///g_print("receiver: %d adc=%d attenuation=%d rx_gain_calibration=%d\n",rx->id,rx->adc,adc_attenuation[rx->adc],rx_gain_calibration); } else { - //int display_width=gtk_widget_get_allocated_width (rx->panadapter); - //int display_height=gtk_widget_get_allocated_height (rx->panadapter); if(pressed) { int x=(int)event->x; if (event->button == 1) { @@ -308,6 +308,12 @@ void receiver_save_state(RECEIVER *rx) { sprintf(value,"%f",rx->squelch); setProperty(name,value); + sprintf(name,"receiver.%d.zoom",rx->id); + sprintf(value,"%d",rx->zoom); + setProperty(name,value); + sprintf(name,"receiver.%d.pan",rx->id); + sprintf(value,"%d",rx->pan); + setProperty(name,value); } void receiver_restore_state(RECEIVER *rx) { @@ -488,6 +494,12 @@ fprintf(stderr,"receiver_restore_state: id=%d\n",rx->id); value=getProperty(name); if(value) rx->squelch=atof(value); + sprintf(name,"receiver.%d.zoom",rx->id); + value=getProperty(name); + if(value) rx->zoom=atoi(value); + sprintf(name,"receiver.%d.pan",rx->id); + value=getProperty(name); + if(value) rx->pan=atoi(value); } void reconfigure_receiver(RECEIVER *rx,int height) { @@ -552,21 +564,23 @@ static gint update_display(gpointer data) { //fprintf(stderr,"update_display: %d displaying=%d\n",rx->id,rx->displaying); if(rx->displaying) { - GetPixels(rx->id,0,rx->pixel_samples,&rc); - if(rc) { - if(rx->display_panadapter) { - rx_panadapter_update(rx); - } - if(rx->display_waterfall) { - waterfall_update(rx); - } + if(rx->pixels>0) { + GetPixels(rx->id,0,rx->pixel_samples,&rc); + if(rc) { + if(rx->display_panadapter) { + rx_panadapter_update(rx); + } + if(rx->display_waterfall) { + waterfall_update(rx); + } } - - if(active_receiver==rx) { - double m=GetRXAMeter(rx->id,smeter)+meter_calibration; - meter_update(rx,SMETER,m,0.0,0.0,0.0); + + if(active_receiver==rx) { + double m=GetRXAMeter(rx->id,smeter)+meter_calibration; + meter_update(rx,SMETER,m,0.0,0.0,0.0); + } + return TRUE; } - return TRUE; } return FALSE; } @@ -677,7 +691,7 @@ static void init_analyzer(RECEIVER *rx) { overlap = (int)max(0.0, ceil(fft_size - (double)rx->sample_rate / (double)rx->fps)); - fprintf(stderr,"SetAnalyzer id=%d buffer_size=%d overlap=%d\n",rx->id,rx->buffer_size,overlap); + //g_print("SetAnalyzer id=%d buffer_size=%d overlap=%d\n",rx->id,rx->buffer_size,overlap); SetAnalyzer(rx->id, @@ -893,17 +907,9 @@ fprintf(stderr,"create_receiver: id=%d buffer_size=%d fft_size=%d pixels=%d fps= fprintf(stderr,"create_receiver: id=%d default adc=%d\n",rx->id, rx->adc); #ifdef SOAPYSDR if(radio->device==SOAPYSDR_USB_DEVICE) { -/* - if(strcmp(radio->name,"lime")==0) { - rx->sample_rate=384000; - } else if(strcmp(radio->name,"rtlsdr")==0) { - rx->sample_rate=384000; - } else { - rx->sample_rate=384000; - } -*/ rx->sample_rate=radio->info.soapy.sample_rate; - rx->resample_step=1; + rx->resampler=NULL; + rx->resample_buffer=NULL; } else { #endif rx->sample_rate=48000; @@ -912,23 +918,12 @@ fprintf(stderr,"create_receiver: id=%d default adc=%d\n",rx->id, rx->adc); #endif rx->buffer_size=buffer_size; rx->fft_size=fft_size; - rx->pixels=pixels; rx->fps=fps; rx->update_timer_id=-1; - -// rx->dds_offset=0; -// rx->rit=0; - rx->width=width; rx->height=height; - // allocate buffers - rx->iq_input_buffer=g_new(double,2*rx->buffer_size); - rx->audio_buffer_size=480; - rx->audio_sequence=0L; - rx->pixel_samples=g_new(float,pixels); - rx->samples=0; rx->displaying=0; rx->display_panadapter=1; @@ -992,13 +987,20 @@ fprintf(stderr,"create_receiver: id=%d default adc=%d\n",rx->id, rx->adc); rx->mute_radio=0; + rx->fexchange_errors=0; + + rx->zoom=1; + rx->pan=0; + receiver_restore_state(rx); -#ifdef SOAPYSDR - rx->resample_step=radio->info.soapy.sample_rate/rx->sample_rate; -#else - rx->resample_step=1; -#endif + // allocate buffers + rx->iq_input_buffer=g_new(double,2*rx->buffer_size); + rx->audio_buffer_size=480; + rx->audio_sequence=0L; + rx->pixels=pixels*rx->zoom; + rx->pixel_samples=g_new(float,rx->pixels); + fprintf(stderr,"create_receiver (after restore): rx=%p id=%d audio_buffer_size=%d local_audio=%d\n",rx,rx->id,rx->audio_buffer_size,rx->local_audio); //rx->audio_buffer=g_new(guchar,rx->audio_buffer_size); @@ -1008,7 +1010,7 @@ fprintf(stderr,"create_receiver (after restore): rx=%p id=%d audio_buffer_size=% fprintf(stderr,"create_receiver: id=%d output_samples=%d\n",rx->id,rx->output_samples); - rx->hz_per_pixel=(double)rx->sample_rate/(double)rx->width; + rx->hz_per_pixel=(double)rx->sample_rate/(double)rx->pixels; // setup wdsp for this receiver @@ -1069,7 +1071,6 @@ fprintf(stderr,"RXASetMP %d\n",rx->low_latency); } receiver_mode_changed(rx); - //receiver_frequency_changed(rx); int result; XCreateAnalyzer(rx->id, &result, 262144, 1, 1, ""); @@ -1143,13 +1144,10 @@ g_print("receiver_change_sample_rate: id=%d rate=%d scale=%d buffer_size=%d outp SetInputSamplerate(rx->id, sample_rate); SetEXTANBSamplerate (rx->id, sample_rate); SetEXTNOBSamplerate (rx->id, sample_rate); -#ifdef SOAPYSDR if(protocol==SOAPYSDR_PROTOCOL) { - rx->resample_step=radio_sample_rate/rx->sample_rate; -g_print("receiver_change_sample_rate: resample_step=%d\n",rx->resample_step); + soapy_protocol_change_sample_rate(rx); soapy_protocol_set_mic_sample_rate(rx->sample_rate); } -#endif SetChannelState(rx->id,1,0); @@ -1162,9 +1160,29 @@ void receiver_frequency_changed(RECEIVER *rx) { int id=rx->id; if(vfo[id].ctun) { + + long long frequency=vfo[id].frequency; + long long half=(long long)rx->sample_rate/2LL; + long long rx_low=vfo[id].ctun_frequency+rx->filter_low; + long long rx_high=vfo[id].ctun_frequency+rx->filter_high; + + if(rx->zoom>1) { + long long min_display=frequency-half+(long long)((double)rx->pan*rx->hz_per_pixel); + long long max_display=min_display+(long long)((double)rx->width*rx->hz_per_pixel); + if(rx_low<=min_display) { + rx->pan=rx->pan-(rx->width/2); + if(rx->pan<0) rx->pan=0; + set_pan(id,rx->pan); + } else if(rx_high>=max_display) { + rx->pan=rx->pan+(rx->width/2); + if(rx->pan>(rx->pixels-rx->width)) rx->pan=rx->pixels-rx->width; + set_pan(id,rx->pan); + } + } + vfo[id].offset=vfo[id].ctun_frequency-vfo[id].frequency; if(vfo[id].rit_enabled) { - vfo[id].offset+=vfo[id].rit; + vfo[id].offset+=vfo[id].rit; } set_offset(rx,vfo[id].offset); } else { @@ -1318,7 +1336,8 @@ void full_rx_buffer(RECEIVER *rx) { fexchange0(rx->id, rx->iq_input_buffer, rx->audio_output_buffer, &error); if(error!=0) { - fprintf(stderr,"full_rx_buffer: id=%d fexchange0: error=%d\n",rx->id,error); + //fprintf(stderr,"full_rx_buffer: id=%d fexchange0: error=%d\n",rx->id,error); + rx->fexchange_errors++; } if(rx->displaying) { @@ -1355,3 +1374,35 @@ void add_div_iq_samples(RECEIVER *rx, double i0, double q0, double i1, double q1 rx->samples=0; } } + +void receiver_change_zoom(RECEIVER *rx,double zoom) { + g_mutex_lock(&rx->mutex); + if(rx->pixel_samples!=NULL) { + g_free(rx->pixel_samples); + } + rx->pixels=rx->width*(int)zoom; + rx->pixel_samples=g_new(float,rx->pixels); + rx->hz_per_pixel=(double)rx->sample_rate/(double)rx->pixels; + if(zoom==0) { + rx->pan=0; + } else { + if(vfo[rx->id].ctun) { + long long min_frequency=vfo[rx->id].frequency-(long long)(rx->sample_rate/2); + rx->pan=((vfo[rx->id].ctun_frequency-min_frequency)/rx->hz_per_pixel)-(rx->width/2); + if(rx->pan<0) rx->pan=0; + if(rx->pan>(rx->pixels-rx->width)) rx->pan=rx->pixels-rx->width; + } else { + rx->pan=(rx->pixels/2)-(rx->width/2); + } + } + rx->zoom=(int)zoom; + init_analyzer(rx); + g_mutex_unlock(&rx->mutex); +} + +void receiver_change_pan(RECEIVER *rx,double pan) { + g_mutex_lock(&rx->mutex); + rx->pan=(int)pan; + g_mutex_unlock(&rx->mutex); +} + diff --git a/receiver.h b/receiver.h index 220fec1..a0b5be9 100644 --- a/receiver.h +++ b/receiver.h @@ -137,7 +137,14 @@ typedef struct _receiver { gint mute_radio; gdouble *buffer; - gint resample_step; + void *resampler; + gdouble *resample_buffer; + gint resample_buffer_size; + + gint fexchange_errors; + + gint zoom; + gint pan; gint x; gint y; @@ -151,6 +158,8 @@ extern void receiver_frequency_changed(RECEIVER *rx); extern void receiver_mode_changed(RECEIVER *rx); extern void receiver_filter_changed(RECEIVER *rx); extern void receiver_vfo_changed(RECEIVER *rx); +extern void receiver_change_zoom(RECEIVER *rx,double zoom); +extern void receiver_change_pan(RECEIVER *rx,double pan); extern void set_mode(RECEIVER* rx,int m); extern void set_filter(RECEIVER *rx,int low,int high); diff --git a/rx_panadapter.c b/rx_panadapter.c index 37e47a8..0b18a05 100644 --- a/rx_panadapter.c +++ b/rx_panadapter.c @@ -48,6 +48,9 @@ static gfloat filter_left; static gfloat filter_right; static gfloat cw_frequency; +static gint sequence_error_count=0; +static gint fexchange_error_count=0; + /* Create a new surface of the appropriate size to store our scribbles */ static gboolean panadapter_configure_event_cb (GtkWidget *widget, @@ -146,8 +149,8 @@ void rx_panadapter_update(RECEIVER *rx) { } BAND *band=band_get_band(vfoband); - long half=(long)rx->sample_rate/2L; - double vfofreq=(double) display_width * 0.5; + long long half=(long long)rx->sample_rate/2LL; + double vfofreq=((double) rx->pixels * 0.5)-(double)rx->pan; // // There are two options here in CW mode, depending on cw_is_on_vfo_freq. @@ -171,8 +174,8 @@ void rx_panadapter_update(RECEIVER *rx) { vfofreq -= (double) cw_keyer_sidetone_frequency / HzPerPixel; } } - long long min_display=frequency-half; - long long max_display=frequency+half; + long long min_display=frequency-half+(long long)((double)rx->pan*HzPerPixel); + long long max_display=min_display+(long long)((double)rx->width*HzPerPixel); if(vfoband==band60) { for(i=0;ifilter_low+offset)/HzPerPixel); - filter_right=(double)display_width*0.5 +(((double)rx->filter_high+offset)/HzPerPixel); + //filter_left =(double)display_width*0.5 +(((double)rx->filter_low+offset)/HzPerPixel); + //filter_right=(double)display_width*0.5 +(((double)rx->filter_high+offset)/HzPerPixel); + filter_left =((double)rx->pixels*0.5)-(double)rx->pan +(((double)rx->filter_low+offset)/HzPerPixel); + filter_right=((double)rx->pixels*0.5)-(double)rx->pan +(((double)rx->filter_high+offset)/HzPerPixel); cairo_rectangle(cr, filter_left, 0.0, filter_right-filter_left, (double)display_height); cairo_fill(cr); @@ -251,32 +247,120 @@ void rx_panadapter_update(RECEIVER *rx) { switch(rx->sample_rate) { case 48000: divisor=5000L; + switch(rx->zoom) { + case 2: + case 3: + case 4: + divisor=2000L; + break; + case 5: + case 6: + case 7: + case 8: + divisor=1000L; + break; + } break; case 96000: case 100000: divisor=10000L; + switch(rx->zoom) { + case 2: + case 3: + case 4: + divisor=5000L; + break; + case 5: + case 6: + divisor=2000L; + break; + case 7: + case 8: + divisor=1000L; + break; + } break; case 192000: divisor=20000L; + switch(rx->zoom) { + case 2: + case 3: + divisor=10000L; + break; + case 4: + case 5: + case 6: + divisor=5000L; + break; + case 7: + case 8: + divisor=2000L; + break; + } break; case 384000: divisor=50000L; + switch(rx->zoom) { + case 2: + case 3: + divisor=25000L; + break; + case 4: + case 5: + case 6: + divisor=10000L; + break; + case 7: + case 8: + divisor=5000L; + break; + } break; case 768000: divisor=100000L; + switch(rx->zoom) { + case 2: + case 3: + divisor=50000L; + break; + case 4: + case 5: + case 6: + divisor=25000L; + break; + case 7: + case 8: + divisor=20000L; + break; + } break; case 1048576: case 1536000: case 2097152: divisor=200000L; + switch(rx->zoom) { + case 2: + case 3: + divisor=100000L; + break; + case 4: + case 5: + case 6: + divisor=50000L; + break; + case 7: + case 8: + divisor=20000L; + break; + } break; } for(i=0;ipan)); if (f > 0) { if ((f % divisor) < (long) HzPerPixel) { cairo_set_line_width(cr, 1.0); - //cairo_move_to(cr,(double)i,0.0); + //cairo_move_to(cr,(double)x,0.0); cairo_move_to(cr,(double)i,10.0); cairo_line_to(cr,(double)i,(double)display_height); @@ -382,12 +466,12 @@ void rx_panadapter_update(RECEIVER *rx) { // signal double s1,s2; - samples[0]=-200.0; - samples[display_width-1]=-200.0; + samples[rx->pan]=-200.0; + samples[display_width-1+rx->pan]=-200.0; if(have_rx_gain) { - s1=(double)samples[0]+rx_gain_calibration-adc_attenuation[rx->adc]; + s1=(double)samples[rx->pan]+rx_gain_calibration-adc_attenuation[rx->adc]; } else { - s1=(double)samples[0]+(double)adc_attenuation[rx->adc]; + s1=(double)samples[rx->pan]+(double)adc_attenuation[rx->adc]; } if (filter_board == ALEX && rx->adc == 0) s1 += (double)(10*rx->alex_attenuation); if (filter_board == CHARLY25) { @@ -406,9 +490,9 @@ void rx_panadapter_update(RECEIVER *rx) { cairo_move_to(cr, 0.0, s1); for(i=1;iadc]; + s2=(double)samples[i+rx->pan]+rx_gain_calibration-adc_attenuation[rx->adc]; } else { - s2=(double)samples[i]+(double)adc_attenuation[rx->adc]; + s2=(double)samples[i+rx->pan]+(double)adc_attenuation[rx->adc]; } if (filter_board == ALEX && rx->adc == 0) s2 += (double)(10*rx->alex_attenuation); if (filter_board == CHARLY25) { @@ -469,6 +553,32 @@ void rx_panadapter_update(RECEIVER *rx) { } #endif + if(sequence_errors!=0) { + cairo_move_to(cr,100,20); + cairo_set_source_rgb(cr,1.0,0.0,0.0); + cairo_set_font_size(cr,12); + cairo_show_text(cr, "Sequence Error"); + sequence_error_count++; + // show for 1 second + if(sequence_error_count==2*rx->fps) { + sequence_errors=0; + sequence_error_count=0; + } + } + + if(rx->fexchange_errors!=0) { + cairo_move_to(cr,100,30); + cairo_set_source_rgb(cr,1.0,0.0,0.0); + cairo_set_font_size(cr,12); + cairo_show_text(cr, "fexchange Error"); + fexchange_error_count++; + // show for 1 second + if(fexchange_error_count==2*rx->fps) { + rx->fexchange_errors=0; + fexchange_error_count=0; + } + } + cairo_destroy (cr); gtk_widget_queue_draw (rx->panadapter); diff --git a/sliders.c b/sliders.c index b3639e3..a8308b2 100644 --- a/sliders.c +++ b/sliders.c @@ -62,27 +62,11 @@ static int height; static GtkWidget *sliders; -enum { - NO_FUNCTION=0, - AF_GAIN, - RF_GAIN, - MIC_GAIN, - LINEIN_GAIN, - AGC_GAIN, - DRIVE, - ATTENUATION, - SQUELCH, - COMP, - FILTER_WIDTH, - FILTER_SHIFT, - DIVERSITY_GAIN, - DIVERSITY_PHASE, -}; - -static gint scale_timer; -static int scale_status=NO_FUNCTION; -static int scale_rx=0; -static GtkWidget *scale_dialog; +gint scale_timer; +int scale_status=NO_FUNCTION; +int scale_rx=0; +GtkWidget *scale_dialog; + static GtkWidget *af_gain_label; static GtkWidget *af_gain_scale; static GtkWidget *rf_gain_label; diff --git a/sliders.h b/sliders.h index f303db1..7c981c7 100644 --- a/sliders.h +++ b/sliders.h @@ -22,6 +22,31 @@ #include "receiver.h" #include "transmitter.h" +enum { + NO_FUNCTION=0, + AF_GAIN, + RF_GAIN, + MIC_GAIN, + LINEIN_GAIN, + AGC_GAIN, + DRIVE, + ATTENUATION, + SQUELCH, + COMP, + FILTER_WIDTH, + FILTER_SHIFT, + DIVERSITY_GAIN, + DIVERSITY_PHASE, + ZOOM, + PAN +}; + +extern gint scale_timer; +extern gint scale_status; +extern gint scale_rx; +extern GtkWidget *scale_dialog; +int scale_timeout_cb(gpointer data); + extern void att_type_changed(void); extern void update_att_preamp(void); diff --git a/soapy_discovery.c b/soapy_discovery.c index db472d9..630d34d 100644 --- a/soapy_discovery.c +++ b/soapy_discovery.c @@ -117,6 +117,17 @@ static void get_info(char *driver) { } fprintf(stderr,"\n"); free(rx_rates); + + if(strcmp(driver,"lime")==0) { + sample_rate=768000; + } else if(strcmp(driver,"rtlsdr")==0) { + sample_rate=1024000; + } else if(strcmp(driver,"plutosdr")==0) { + sample_rate=2048000; + } else { + sample_rate=1024000; + } + fprintf(stderr,"sample_rate selected %d\n",sample_rate); SoapySDRRange *tx_rates=SoapySDRDevice_getSampleRateRange(sdr, SOAPY_SDR_TX, 1, &tx_rates_length); diff --git a/soapy_protocol.c b/soapy_protocol.c index 6a988e6..95fca28 100644 --- a/soapy_protocol.c +++ b/soapy_protocol.c @@ -57,7 +57,6 @@ static double bandwidth=2500000.0; static SoapySDRDevice *soapy_device; static SoapySDRStream *rx_stream; static SoapySDRStream *tx_stream; -static int soapy_rx_sample_rate; static int max_samples; static int samples=0; @@ -86,28 +85,50 @@ SoapySDRDevice *get_soapy_device() { void soapy_protocol_set_mic_sample_rate(int rate) { mic_sample_divisor=rate/48000; +g_print("soapy_protocol_set_mic_sample_rate: rate=%d mic_sample_divisor=%d\n",rate,mic_sample_divisor); } -void soapy_protocol_change_sample_rate(RECEIVER *rx,int rate) { +void soapy_protocol_change_sample_rate(RECEIVER *rx) { +g_print("soapy_protocol_change_sample_rate: %d\n",rx->sample_rate); + if(rx->sample_rate==radio_sample_rate) { + if(rx->resample_buffer!=NULL) { + g_free(rx->resample_buffer); + rx->resample_buffer=NULL; + rx->resample_buffer_size=0; + } + if(rx->resampler!=NULL) { + destroy_resample(rx->resampler); + rx->resampler=NULL; + } + } else { + if(rx->resample_buffer!=NULL) { + g_free(rx->resample_buffer); + rx->resample_buffer=NULL; + } + if(rx->resampler!=NULL) { + destroy_resample(rx->resampler); + rx->resampler=NULL; + } + rx->resample_buffer_size=2*max_samples/(radio_sample_rate/rx->sample_rate); + rx->resample_buffer=g_new(double,rx->resample_buffer_size); + rx->resampler=create_resample (1,max_samples,rx->buffer,rx->resample_buffer,radio_sample_rate,rx->sample_rate,0.0,0,1.0); + +g_print("soapy_protocol_change_sample_rate: buffer=%p buffer_size=%d resampler=%p\n",rx->resample_buffer,rx->resample_buffer_size,rx->resampler); + } + } void soapy_protocol_create_receiver(RECEIVER *rx) { int rc; - soapy_rx_sample_rate=rx->sample_rate; - if(rx->sample_rate!=radio_sample_rate) { - soapy_rx_sample_rate=radio_sample_rate; - } - mic_sample_divisor=soapy_rx_sample_rate/48000; + mic_sample_divisor=rx->sample_rate/48000; -fprintf(stderr,"soapy_protocol_create_receiver: setting samplerate=%f adc=%d mic_sample_divisor=%d\n",(double)soapy_rx_sample_rate,rx->adc,mic_sample_divisor); - rc=SoapySDRDevice_setSampleRate(soapy_device,SOAPY_SDR_RX,rx->adc,(double)soapy_rx_sample_rate); +fprintf(stderr,"soapy_protocol_create_receiver: setting samplerate=%f adc=%d mic_sample_divisor=%d\n",(double)radio_sample_rate,rx->adc,mic_sample_divisor); + rc=SoapySDRDevice_setSampleRate(soapy_device,SOAPY_SDR_RX,rx->adc,(double)radio_sample_rate); if(rc!=0) { - fprintf(stderr,"soapy_protocol_create_receiver: SoapySDRDevice_setSampleRate(%f) failed: %s\n",(double)soapy_rx_sample_rate,SoapySDR_errToStr(rc)); + fprintf(stderr,"soapy_protocol_create_receiver: SoapySDRDevice_setSampleRate(%f) failed: %s\n",(double)radio_sample_rate,SoapySDR_errToStr(rc)); } - - size_t channel=rx->adc; #if defined(SOAPY_SDR_API_VERSION) && (SOAPY_SDR_API_VERSION < 0x00080000) fprintf(stderr,"soapy_protocol_create_receiver: SoapySDRDevice_setupStream(version<0x00080000): channel=%ld\n",channel); @@ -130,8 +151,26 @@ fprintf(stderr,"soapy_protocol_create_receiver: SoapySDRDevice_setupStream(versi if(max_samples>(2*rx->fft_size)) { max_samples=2*rx->fft_size; } + if(max_samples>=4096) { + max_samples=4096; + } else if(max_samples>=2048) { + max_samples=2048; + } else { + max_samples=1024; + } rx->buffer=g_new(double,max_samples*2); + if(rx->sample_rate==radio_sample_rate) { + rx->resample_buffer=NULL; + rx->resampler=NULL; + rx->resample_buffer_size=0; + } else { + rx->resample_buffer_size=2*max_samples/(radio_sample_rate/rx->sample_rate); + rx->resample_buffer=g_new(double,rx->resample_buffer_size); + rx->resampler=create_resample (1,max_samples,rx->buffer,rx->resample_buffer,radio_sample_rate,rx->sample_rate,0.0,0,1.0); + } + + fprintf(stderr,"soapy_protocol_create_receiver: max_samples=%d buffer=%p\n",max_samples,rx->buffer); } @@ -278,10 +317,13 @@ fprintf(stderr,"soapy_protocol: receive_thread\n"); rx->buffer[i*2]=(double)buffer[i*2]; rx->buffer[(i*2)+1]=(double)buffer[(i*2)+1]; } - if(rx->sample_rate!=radio_sample_rate) { - for(int i=0;iresample_step) { - isample=rx->buffer[i*2]; - qsample=rx->buffer[(i*2)+1]; + + + if(rx->resampler!=NULL) { + int samples=xresample(rx->resampler); + for(i=0;iresample_buffer[i*2]; + qsample=rx->resample_buffer[(i*2)+1]; if(iqswap) { add_iq_samples(rx,qsample,isample); } else { @@ -290,7 +332,11 @@ fprintf(stderr,"soapy_protocol: receive_thread\n"); if(can_transmit) { mic_samples++; if(mic_samples>=mic_sample_divisor) { // reduce to 48000 - fsample = transmitter->local_microphone ? audio_get_next_mic_sample() : (float)0.0; + if(transmitter!=NULL) { + fsample = transmitter->local_microphone ? audio_get_next_mic_sample() : 0.0F; + } else { + fsample=0.0F; + } add_mic_sample(transmitter,fsample); mic_samples=0; } @@ -308,7 +354,11 @@ fprintf(stderr,"soapy_protocol: receive_thread\n"); if(can_transmit) { mic_samples++; if(mic_samples>=mic_sample_divisor) { // reduce to 48000 - fsample = transmitter->local_microphone ? audio_get_next_mic_sample() : (float)0.0; + if(transmitter!=NULL) { + fsample = transmitter->local_microphone ? audio_get_next_mic_sample() : 0.0F; + } else { + fsample=0.0F; + } add_mic_sample(transmitter,fsample); mic_samples=0; } diff --git a/soapy_protocol.h b/soapy_protocol.h index 5060891..541dbf9 100644 --- a/soapy_protocol.h +++ b/soapy_protocol.h @@ -35,7 +35,7 @@ void soapy_protocol_set_lna_gain(RECEIVER *rx,int gain); void soapy_protocol_set_gain(RECEIVER *rx,double gain); void soapy_protocol_set_gain_element(RECEIVER *rx,char *name,int gain); int soapy_protocol_get_gain_element(RECEIVER *rx,char *name); -void soapy_protocol_change_sample_rate(RECEIVER *rx,int rate); +void soapy_protocol_change_sample_rate(RECEIVER *rx); gboolean soapy_protocol_get_automatic_gain(RECEIVER *rx); void soapy_protocol_set_automatic_gain(RECEIVER *rx,gboolean mode); void soapy_protocol_create_transmitter(TRANSMITTER *tx); diff --git a/vfo.c b/vfo.c index 8ba8a38..c8811e7 100644 --- a/vfo.c +++ b/vfo.c @@ -533,6 +533,20 @@ void vfo_step(int steps) { if(!locked) { if(vfo[id].ctun) { + // don't let ctun go beyond end of passband + long long frequency=vfo[id].frequency; + long long rx_low=((vfo[id].ctun_frequency/step + steps)*step)+active_receiver->filter_low; + long long rx_high=((vfo[id].ctun_frequency/step + steps)*step)+active_receiver->filter_high; + long long half=(long long)active_receiver->sample_rate/2LL; + long long min_freq=frequency-half; + long long max_freq=frequency+half; + + if(rx_low<=min_freq) { + return; + } else if(rx_high>=max_freq) { + return; + } + delta=vfo[id].ctun_frequency; vfo[id].ctun_frequency=(vfo[id].ctun_frequency/step + steps)*step; delta=vfo[id].ctun_frequency - delta; @@ -639,6 +653,20 @@ void vfo_move(long long hz,int round) { if(!locked) { if(vfo[id].ctun) { + // don't let ctun go beyond end of passband + long long frequency=vfo[id].frequency; + long long rx_low=vfo[id].ctun_frequency+hz+active_receiver->filter_low; + long long rx_high=vfo[id].ctun_frequency+hz+active_receiver->filter_high; + long long half=(long long)active_receiver->sample_rate/2LL; + long long min_freq=frequency-half; + long long max_freq=frequency+half; + + if(rx_low<=min_freq) { + return; + } else if(rx_high>=max_freq) { + return; + } + delta=vfo[id].ctun_frequency; vfo[id].ctun_frequency=vfo[id].ctun_frequency+hz; if(round && (vfo[id].mode!=modeCWL && vfo[id].mode!=modeCWU)) { @@ -689,7 +717,7 @@ void vfo_move(long long hz,int round) { } void vfo_move_to(long long hz) { - // hz is the offset from the min frequency + // hz is the offset from the min displayed frequency int id=active_receiver->id; long long offset=hz; long long half=(long long)(active_receiver->sample_rate/2); @@ -701,7 +729,7 @@ void vfo_move_to(long long hz) { if(vfo[id].mode!=modeCWL && vfo[id].mode!=modeCWU) { offset=(hz/step)*step; } - f=(vfo[id].frequency-half)+offset; + f=(vfo[id].frequency-half)+offset+((double)active_receiver->pan*active_receiver->hz_per_pixel); if(!locked) { if(vfo[id].ctun) { @@ -894,8 +922,7 @@ void vfo_update() { #ifdef PURESIGNAL if(can_transmit) { - //cairo_move_to(cr, 180, 15); - cairo_move_to(cr, 55, 50); + cairo_move_to(cr, 130, 50); if(transmitter->puresignal) { cairo_set_source_rgb(cr, 1.0, 1.0, 0.0); } else { @@ -905,7 +932,16 @@ void vfo_update() { cairo_show_text(cr, "PS"); } #endif - + + cairo_move_to(cr, 55, 50); + if(active_receiver->zoom>1) { + cairo_set_source_rgb(cr, 1.0, 1.0, 0.0); + } else { + cairo_set_source_rgb(cr, 0.7, 0.7, 0.7); + } + cairo_set_font_size(cr, 12); + sprintf(temp_text,"Zoom x%d",active_receiver->zoom); + cairo_show_text(cr, temp_text); if(vfo[id].rit_enabled==0) { cairo_set_source_rgb(cr, 0.7, 0.7, 0.7); @@ -913,7 +949,6 @@ void vfo_update() { cairo_set_source_rgb(cr, 0.0, 1.0, 0.0); } sprintf(temp_text,"RIT: %lldHz",vfo[id].rit); - //cairo_move_to(cr, 210, 15); cairo_move_to(cr, 170, 15); cairo_set_font_size(cr, 12); cairo_show_text(cr, temp_text); @@ -975,7 +1010,6 @@ void vfo_update() { } cairo_show_text(cr, "SNB"); - //cairo_move_to(cr, 300, 50); cairo_move_to(cr, 270, 50); switch(active_receiver->agc) { case AGC_OFF: @@ -1005,7 +1039,6 @@ void vfo_update() { // we should display the compressor (level) // if(can_transmit) { - //cairo_move_to(cr, 400, 50); cairo_move_to(cr, 330, 50); if (transmitter->compressor) { sprintf(temp_text,"CMPR %d dB",(int) transmitter->compressor_level); @@ -1030,17 +1063,10 @@ void vfo_update() { s++; } sprintf(temp_text,"Step %s",step_labels[s]); - //cairo_move_to(cr, 300, 15); cairo_move_to(cr, 400, 15); cairo_set_source_rgb(cr, 1.0, 1.0, 0.0); cairo_show_text(cr, temp_text); -/* - cairo_move_to(cr, (my_width/4)*3, 50); - cairo_show_text(cr, getFrequencyInfo(af)); -*/ - - //cairo_move_to(cr, 400, 15); cairo_move_to(cr, 430, 50); if(vfo[id].ctun) { cairo_set_source_rgb(cr, 1.0, 1.0, 0.0); @@ -1049,7 +1075,6 @@ void vfo_update() { } cairo_show_text(cr, "CTUN"); - //cairo_move_to(cr, 450, 15); cairo_move_to(cr, 470, 50); if(cat_control>0) { cairo_set_source_rgb(cr, 1.0, 1.0, 0.0); @@ -1076,7 +1101,6 @@ void vfo_update() { } cairo_show_text(cr, "Locked"); - //cairo_move_to(cr, 55, 50); cairo_move_to(cr, 260, 18); if(split) { cairo_set_source_rgb(cr, 1.0, 0.0, 0.0); @@ -1085,7 +1109,6 @@ void vfo_update() { } cairo_show_text(cr, "Split"); - //cairo_move_to(cr, 95, 50); cairo_move_to(cr, 260, 28); if(sat_mode!=SAT_NONE) { cairo_set_source_rgb(cr, 1.0, 0.0, 0.0); @@ -1105,7 +1128,6 @@ void vfo_update() { cairo_set_source_rgb(cr, 0.7, 0.7, 0.7); } sprintf(temp_text,"DUP"); - //cairo_move_to(cr, 130, 50); cairo_move_to(cr, 260, 38); cairo_set_font_size(cr, 12); cairo_show_text(cr, temp_text); diff --git a/waterfall.c b/waterfall.c index 376d114..8166d08 100644 --- a/waterfall.c +++ b/waterfall.c @@ -173,9 +173,9 @@ void waterfall_update(RECEIVER *rx) { samples=rx->pixel_samples; for(i=0;iadc]); + sample=samples[i+rx->pan]+(float)(rx_gain_calibration-adc_attenuation[rx->adc]); } else { - sample=samples[i]+(float)adc_attenuation[rx->adc]; + sample=samples[i+rx->pan]+(float)adc_attenuation[rx->adc]; } average+=(int)sample; if(sample<(float)rx->waterfall_low) { diff --git a/zoompan.c b/zoompan.c new file mode 100644 index 0000000..2aee381 --- /dev/null +++ b/zoompan.c @@ -0,0 +1,211 @@ +/* Copyright (C) +* 2015 - John Melton, G0ORX/N6LYT +* +* 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. +* +*/ + +// +// DL1YCF: +// uncomment the #define line following, then you will get +// a "TX compression" slider with an enabling checkbox +// in the bottom right of the sliders area, instead of the +// sequelch slider and checkbox. +// This option can also be passed to the compiler with "-D" +// and thus be activated through the Makefile. +// +//#define COMPRESSION_SLIDER_INSTEAD_OF_SQUELCH 1 +// + +#include +#include +#include +#include +#include + +#include "main.h" +#include "receiver.h" +#include "radio.h" +#include "vfo.h" +#include "sliders.h" +#include "zoompan.h" + +static int width; +static int height; + +static GtkWidget *zoompan; +static GtkWidget *zoom_label; +static GtkWidget *zoom_scale; +static GtkWidget *pan_label; +static GtkWidget *pan_scale; + +static GdkRGBA white; +static GdkRGBA gray; + +int zoompan_active_receiver_changed(void *data) { + if(display_zoompan) { + gtk_range_set_value(GTK_RANGE(zoom_scale),active_receiver->zoom); + gtk_range_set_range(GTK_RANGE(pan_scale),0.0,(double)(active_receiver->pixels-active_receiver->width)); + gtk_range_set_value (GTK_RANGE(pan_scale),active_receiver->pan); + if(active_receiver->zoom == 1) { + gtk_widget_set_sensitive(pan_scale, FALSE); + } + } + return FALSE; +} + +static void zoom_value_changed_cb(GtkWidget *widget, gpointer data) { + receiver_change_zoom(active_receiver,gtk_range_get_value(GTK_RANGE(zoom_scale))); + gtk_range_set_range(GTK_RANGE(pan_scale),0.0,(double)(active_receiver->pixels-active_receiver->width)); + gtk_range_set_value (GTK_RANGE(pan_scale),active_receiver->pan); + if(active_receiver->zoom == 1) { + gtk_widget_set_sensitive(pan_scale, FALSE); + } else { + gtk_widget_set_sensitive(pan_scale, TRUE); + } + vfo_update(); +} + +void set_zoom(int rx,double value) { + receiver[rx]->zoom=value; + if(display_zoompan) { + gtk_range_set_value (GTK_RANGE(zoom_scale),receiver[rx]->zoom); + } else { + if(scale_status!=ZOOM || scale_rx!=rx) { + if(scale_status!=NO_FUNCTION) { + g_source_remove(scale_timer); + gtk_widget_destroy(scale_dialog); + scale_status=NO_FUNCTION; + } + } + if(scale_status==NO_FUNCTION) { + scale_status=ZOOM; + scale_rx=rx; + char title[64]; + sprintf(title,"Zoom RX %d",rx); + scale_dialog=gtk_dialog_new_with_buttons(title,GTK_WINDOW(top_window),GTK_DIALOG_DESTROY_WITH_PARENT,NULL,NULL); + GtkWidget *content=gtk_dialog_get_content_area(GTK_DIALOG(scale_dialog)); + zoom_scale=gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL,1.0, MAX_ZOOM, 1.00); + gtk_widget_set_size_request (zoom_scale, 400, 30); + gtk_range_set_value (GTK_RANGE(zoom_scale),receiver[rx]->zoom); + gtk_widget_show(zoom_scale); + gtk_container_add(GTK_CONTAINER(content),zoom_scale); + scale_timer=g_timeout_add(2000,scale_timeout_cb,NULL); + gtk_dialog_run(GTK_DIALOG(scale_dialog)); + } else { + g_source_remove(scale_timer); + gtk_range_set_value (GTK_RANGE(zoom_scale),receiver[rx]->zoom); + scale_timer=g_timeout_add(2000,scale_timeout_cb,NULL); + } + } + vfo_update(); +} + +void update_zoom(double zoom) { + int z=active_receiver->zoom+(int)zoom; + if(z>MAX_ZOOM) z=MAX_ZOOM; + if(z<1) z=1; + set_zoom(active_receiver->id,z); +} + +static void pan_value_changed_cb(GtkWidget *widget, gpointer data) { + receiver_change_pan(active_receiver,gtk_range_get_value(GTK_RANGE(pan_scale))); +} + +void set_pan(int rx,double value) { + receiver[rx]->pan=(int)value; + if(display_zoompan) { + gtk_range_set_value (GTK_RANGE(pan_scale),receiver[rx]->pan); + } else { + if(scale_status!=PAN || scale_rx!=rx) { + if(scale_status!=NO_FUNCTION) { + g_source_remove(scale_timer); + gtk_widget_destroy(scale_dialog); + scale_status=NO_FUNCTION; + } + } + if(scale_status==NO_FUNCTION) { + scale_status=PAN; + scale_rx=rx; + char title[64]; + sprintf(title,"Pan RX %d",rx); + scale_dialog=gtk_dialog_new_with_buttons(title,GTK_WINDOW(top_window),GTK_DIALOG_DESTROY_WITH_PARENT,NULL,NULL); + GtkWidget *content=gtk_dialog_get_content_area(GTK_DIALOG(scale_dialog)); + pan_scale=gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL,0.0, active_receiver->zoom==1?active_receiver->pixels:active_receiver->pixels-active_receiver->width, 1.00); + gtk_widget_set_size_request (pan_scale, 400, 30); + gtk_range_set_value (GTK_RANGE(pan_scale),receiver[rx]->pan); + gtk_widget_show(pan_scale); + gtk_container_add(GTK_CONTAINER(content),pan_scale); + scale_timer=g_timeout_add(2000,scale_timeout_cb,NULL); + gtk_dialog_run(GTK_DIALOG(scale_dialog)); + } else { + g_source_remove(scale_timer); + gtk_range_set_value (GTK_RANGE(pan_scale),receiver[rx]->pan); + scale_timer=g_timeout_add(2000,scale_timeout_cb,NULL); + } + } +} + +void update_pan(double pan) { + int p=active_receiver->pan+(int)pan; + if(p<0) p=0; + if(p>(active_receiver->pixels-active_receiver->width)) p=active_receiver->pixels-active_receiver->width; + set_pan(active_receiver->id,p); +} + +GtkWidget *zoompan_init(int my_width, int my_height) { + width=my_width; + height=my_height; + +fprintf(stderr,"zoompan_init: width=%d height=%d\n", width,height); + + zoompan=gtk_grid_new(); + gtk_widget_set_size_request (zoompan, width, height); + gtk_grid_set_row_homogeneous(GTK_GRID(zoompan), FALSE); + gtk_grid_set_column_homogeneous(GTK_GRID(zoompan),TRUE); + + zoom_label=gtk_label_new("Zoom"); + gtk_widget_override_font(zoom_label, pango_font_description_from_string("Sans 10")); + gtk_widget_show(zoom_label); + gtk_grid_attach(GTK_GRID(zoompan),zoom_label,0,0,1,1); + + zoom_scale=gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL,1.0,MAX_ZOOM,1.00); + gtk_widget_override_font(zoom_scale, pango_font_description_from_string("Sans 10")); + gtk_range_set_increments (GTK_RANGE(zoom_scale),1.0,1.0); + gtk_range_set_value (GTK_RANGE(zoom_scale),active_receiver->zoom); + gtk_widget_show(zoom_scale); + gtk_grid_attach(GTK_GRID(zoompan),zoom_scale,1,0,2,1); + g_signal_connect(G_OBJECT(zoom_scale),"value_changed",G_CALLBACK(zoom_value_changed_cb),NULL); + + pan_label=gtk_label_new("Pan:"); + gtk_widget_override_font(pan_label, pango_font_description_from_string("Sans 10")); + gtk_widget_show(pan_label); + gtk_grid_attach(GTK_GRID(zoompan),pan_label,3,0,1,1); + + pan_scale=gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL,0.0,active_receiver->zoom==1?active_receiver->pixels:active_receiver->pixels-active_receiver->width,1.0); + gtk_widget_override_font(pan_scale, pango_font_description_from_string("Sans 10")); + gtk_scale_set_draw_value (GTK_SCALE(pan_scale), FALSE); + gtk_range_set_increments (GTK_RANGE(pan_scale),10.0,10.0); + gtk_range_set_value (GTK_RANGE(pan_scale),active_receiver->pan); + gtk_widget_show(pan_scale); + gtk_grid_attach(GTK_GRID(zoompan),pan_scale,4,0,5,1); + g_signal_connect(G_OBJECT(pan_scale),"value_changed",G_CALLBACK(pan_value_changed_cb),NULL); + + if(active_receiver->zoom == 1) { + gtk_widget_set_sensitive(pan_scale, FALSE); + } + + return zoompan; +} diff --git a/zoompan.h b/zoompan.h new file mode 100644 index 0000000..833d6f7 --- /dev/null +++ b/zoompan.h @@ -0,0 +1,31 @@ +/* Copyright (C) +* 2015 - John Melton, G0ORX/N6LYT +* +* 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. +* +*/ + +#ifndef _ZOOMPAN_H +#define _ZOOMPAN_H + +#define MAX_ZOOM 8 + +extern GtkWidget *zoompan_init(int my_width, int my_height); +extern int zoompan_active_receiver_changed(void *data); + +extern void update_pan(double pan); +extern void update_zoom(double zoom); + +extern void set_pan(int rx,double value); +extern void set_zoom(int rx,double value); +#endif -- 2.45.2