From 0a3b736fa514384eb6c05c67c17368993ac39b3b Mon Sep 17 00:00:00 2001 From: c vw <dl1ycf@darc.de> Date: Fri, 8 Jun 2018 12:13:55 +0200 Subject: [PATCH] Port to MacOS, Compiler warnings removed, thus found many errors. Includes two fixes for STEMlab/HAMlab. --- MacOS/Info.plist | 16 ++ MacOS/PkgInfo | 1 + MacOS/hpsdr.icns | Bin 0 -> 98616 bytes MacOS/hpsdr.png | Bin 0 -> 15116 bytes MacOS/pihpsdr.sh | 14 ++ Makefile | 101 +++++++++- README.MacOS | 141 ++++++++++++++ agc_menu.c | 4 + audio.c | 8 +- audio_waterfall.c | 9 +- band_menu.c | 4 + bandstack_menu.c | 4 + dsp_menu.c | 3 +- equalizer_menu.c | 12 ++ error_handler.c | 4 + ext.c | 10 + filter_menu.c | 8 + freqent_menu.c | 2 + iambic.c | 17 ++ main.c | 45 +++-- mode_menu.c | 4 + new_discovery.c | 5 +- new_protocol.c | 165 +++++++++++++++- new_protocol.h | 4 + new_protocol_programmer.c | 16 +- old_discovery.c | 6 +- old_protocol.c | 122 ++++++++++-- portaudio.c | 396 ++++++++++++++++++++++++++++++++++++++ radio.c | 32 ++- receiver.c | 3 +- receiver.h | 11 +- rigctl.c | 35 ++-- rx_menu.c | 15 +- stemlab_discovery.c | 203 +++++++++++++++++++ step_menu.c | 4 + store_menu.c | 8 + transmitter.c | 2 + tx_menu.c | 4 + tx_panadapter.c | 7 +- vfo.c | 4 + vfo_menu.c | 4 + vox_menu.c | 2 + waterfall.c | 9 +- 43 files changed, 1389 insertions(+), 75 deletions(-) create mode 100644 MacOS/Info.plist create mode 100644 MacOS/PkgInfo create mode 100644 MacOS/hpsdr.icns create mode 100755 MacOS/hpsdr.png create mode 100755 MacOS/pihpsdr.sh create mode 100644 README.MacOS create mode 100644 portaudio.c diff --git a/MacOS/Info.plist b/MacOS/Info.plist new file mode 100644 index 0000000..9bca968 --- /dev/null +++ b/MacOS/Info.plist @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleIdentifier</key> + <string>piHPSDR</string> + <key>CFBundleExecutable</key> + <string>pihpsdr</string> + <key>CFBundleIconFile</key> + <string>hpsdr.icns</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleSignature</key> + <string>BNDL</string> +</dict> +</plist> diff --git a/MacOS/PkgInfo b/MacOS/PkgInfo new file mode 100644 index 0000000..610abe0 --- /dev/null +++ b/MacOS/PkgInfo @@ -0,0 +1 @@ +APPLpihp diff --git a/MacOS/hpsdr.icns b/MacOS/hpsdr.icns new file mode 100644 index 0000000000000000000000000000000000000000..4d1f6a8c7412f4cecca910ba3a38841e5e6fd9ee GIT binary patch literal 98616 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 U7?>&97mLN;qe7!0FhoND0Jy5qo&W#< literal 0 HcmV?d00001 diff --git a/MacOS/hpsdr.png b/MacOS/hpsdr.png new file mode 100755 index 0000000000000000000000000000000000000000..ea96fd7a497da5a35c56bef1c6e4636163882aed GIT binary patch literal 15116 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 literal 0 HcmV?d00001 diff --git a/MacOS/pihpsdr.sh b/MacOS/pihpsdr.sh new file mode 100755 index 0000000..314c755 --- /dev/null +++ b/MacOS/pihpsdr.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +# +# Launch the exe file from a shell. +# This is the only way to make a GTK app bundle +# It still relies on a working GTK installation +# +# A full-fledged wrapper here would set dozens of +# environment variables. +# +this=`dirname $0` +cd $this/../Resources + +exec $this/pihpsdr-bin diff --git a/Makefile b/Makefile index a27c99e..0b89ea6 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,15 @@ GIT_DATE := $(firstword $(shell git --no-pager show --date=short --format="%ai" --name-only)) GIT_VERSION := $(shell git describe --abbrev=0 --tags) +#DL1YCF: this uses PortAudio rather than ALSA +PORTAUDIO_INCLUDE=PORTAUDIO + +#DL1YCF: un-commment this line for activate work-around some RedPitaty HPSDR bugs +STEMLAB_FIX_OPTION=-DSTEMLAB_FIX + +#DL1YCF: un-commment if you need librt (probably always, except on MacOS) +#LIBRT= -lrt + # uncomment the line below to include GPIO #GPIO_INCLUDE=GPIO @@ -27,18 +36,31 @@ GIT_VERSION := $(shell git describe --abbrev=0 --tags) # uncomment the line to below include support local CW keyer #LOCALCW_INCLUDE=LOCALCW -# uncomment the line below to include support for STEMlab discovery +# uncomment the line below to include support for STEMlab discovery (does not work on MacOS) #STEMLAB_DISCOVERY=STEMLAB_DISCOVERY +# uncomment the line below to include support for stripped-down STEMlab discovery that works on MacOS +STEMLAB_DISCOVERY=STEMLAB_DISCOVERY_MAC + #uncomment the line below for the platform being compiled on -UNAME_N=raspberrypi +UNAME_N=MacOS +#UNAME_N=raspberrypi #UNAME_N=odroid #UNAME_N=up #UNAME_N=pine64 #UNAME_N=jetsen CC=gcc + +ifeq ($(UNAME_N),MacOS) +# +# This is only necessary for "make app", since the "patched" +# library names are longer +# +LINK=gcc -headerpad_max_install_names +else LINK=gcc +endif # uncomment the line below for various debug facilities #DEBUG_OPTION=-D DEBUG @@ -164,6 +186,17 @@ ifeq ($(I2C_INCLUDE),I2C) I2C_OBJS=i2c.o endif +# +# STEMLAB_DISCOVERY_MAC depends on curl but not on avahi +# +ifeq ($(STEMLAB_DISCOVERY), STEMLAB_DISCOVERY_MAC) +STEMLAB_OPTIONS=-D STEMLAB_DISCOVERY `pkg-config --cflags libcurl` +STEMLAB_LIBS=`pkg-config --libs libcurl` +STEMLAB_SOURCES=stemlab_discovery.c +STEMLAB_HEADERS=stemlab_discovery.h +STEMLAB_OBJS=stemlab_discovery.o +endif + ifeq ($(STEMLAB_DISCOVERY), STEMLAB_DISCOVERY) STEMLAB_OPTIONS=-D STEMLAB_DISCOVERY \ `pkg-config --cflags avahi-gobject` \ @@ -177,12 +210,23 @@ endif GTKINCLUDES=`pkg-config --cflags gtk+-3.0` GTKLIBS=`pkg-config --libs gtk+-3.0` +ifeq ($(PORTAUDIO_INCLUDE), PORTAUDIO) +PORTAUDIO_OPTIONS=-DPORTAUDIO +AUDIO_LIBS=-lportaudio +else AUDIO_LIBS=-lasound #AUDIO_LIBS=-lsoundio +endif + +OPTIONS=-g -Wno-deprecated-declarations $(PURESIGNAL_OPTIONS) $(REMOTE_OPTIONS) $(RADIOBERRY_OPTIONS) \ + $(USBOZY_OPTIONS) $(I2C_OPTIONS) $(GPIO_OPTIONS) $(LIMESDR_OPTIONS) $(FREEDV_OPTIONS) \ + $(LOCALCW_OPTIONS) $(PSK_OPTIONS) $(STEMLAB_OPTIONS) $(STEMLAB_FIX_OPTION) \ + $(PORTAUDIO_OPTIONS) \ + -D GIT_DATE='"$(GIT_DATE)"' -D GIT_VERSION='"$(GIT_VERSION)"' $(DEBUG_OPTION) -O3 -OPTIONS=-g -Wno-deprecated-declarations $(PURESIGNAL_OPTIONS) $(REMOTE_OPTIONS) $(USBOZY_OPTIONS) $(I2C_OPTIONS) $(GPIO_OPTIONS) $(LIMESDR_OPTIONS) $(FREEDV_OPTIONS) $(LOCALCW_OPTIONS) $(RADIOBERRY_OPTIONS) $(PSK_OPTIONS) $(STEMLAB_OPTIONS) -D GIT_DATE='"$(GIT_DATE)"' -D GIT_VERSION='"$(GIT_VERSION)"' $(DEBUG_OPTION) -O3 +LIBS= $(LIBRT) -lm -lwdsp -lpthread $(AUDIO_LIBS) $(USBOZY_LIBS) $(PSKLIBS) $(GTKLIBS) $(GPIO_LIBS) \ + $(SOAPYSDRLIBS) $(FREEDVLIBS) $(STEMLAB_LIBS) -LIBS=-lrt -lm -lwdsp -lpthread $(AUDIO_LIBS) $(USBOZY_LIBS) $(PSKLIBS) $(GTKLIBS) $(GPIO_LIBS) $(SOAPYSDRLIBS) $(FREEDVLIBS) $(STEMLAB_LIBS) INCLUDES=$(GTKINCLUDES) COMPILE=$(CC) $(OPTIONS) $(INCLUDES) @@ -213,6 +257,7 @@ dsp_menu.c \ pa_menu.c \ cw_menu.c \ oc_menu.c \ +portaudio.c \ xvtr_menu.c \ equalizer_menu.c \ step_menu.c \ @@ -398,10 +443,11 @@ store_menu.o \ memory.o \ led.o \ ext.o \ -error_handler.o +error_handler.o \ +portaudio.o $(PROGRAM): $(OBJS) $(REMOTE_OBJS) $(USBOZY_OBJS) $(LIMESDR_OBJS) $(FREEDV_OBJS) $(LOCALCW_OBJS) $(I2C_OBJS) $(GPIO_OBJS) $(PSK_OBJS) $(PURESIGNAL_OBJS) $(STEMLAB_OBJS) - $(LINK) -o $(PROGRAM) $(OBJS) $(REMOTE_OBJS) $(USBOZY_OBJS) $(I2C_OBJS) $(GPIO_OBJS) $(LIMESDR_OBJS) $(FREEDV_OBJS) $(LOCALCW_OBJS) $(PSK_OBJS) $(LIBS) $(PURESIGNAL_OBJS) $(STEMLAB_OBJS) + $(LINK) -o $(PROGRAM) $(OBJS) $(REMOTE_OBJS) $(USBOZY_OBJS) $(I2C_OBJS) $(GPIO_OBJS) $(LIMESDR_OBJS) $(FREEDV_OBJS) $(LOCALCW_OBJS) $(PSK_OBJS) $(PURESIGNAL_OBJS) $(STEMLAB_OBJS) $(LIBS) all: prebuild $(PROGRAM) $(HEADERS) $(REMOTE_HEADERS) $(USBOZY_HEADERS) $(LIMESDR_HEADERS) $(FREEDV_HEADERS) $(LOCALCW_HEADERS) $(I2C_HEADERS) $(GPIO_HEADERS) $(PSK_HEADERS) $(PURESIGNAL_HEADERS) $(STEMLAB_HEADERS) $(SOURCES) $(REMOTE_SOURCES) $(USBOZY_SOURCES) $(LIMESDR_SOURCES) $(FREEDV_SOURCES) $(I2C_SOURCES) $(GPIO_SOURCES) $(PSK_SOURCES) $(PURESIGNAL_SOURCES) $(STEMLAB_SOURCES) @@ -411,6 +457,7 @@ prebuild: clean: -rm -f *.o -rm -f $(PROGRAM) + -rm -rf $(PROGRAM).app install: $(PROGRAM) cp $(PROGRAM) /usr/local/bin @@ -420,3 +467,45 @@ release: $(PROGRAM) cd release; tar cvf pihpsdr.tar pihpsdr cd release; tar cvf pihpsdr-$(GIT_VERSION).tar pihpsdr +############################################################################# +# +# This is for MacOS "app" creation ONLY +# +# Note: Note that we need a wrapper script to start the program, and +# that it requires a working GTK installation on the Mac. +# The program will not work if the +# libgtk, libgdk, libglib, libgobj, libgio libraries +# are copied to the Frameworks dir and "activated", because +# this stuff depends on tons of other files in /usr/local. +# +# We bundle the "app" with the other libraries such as WDSP, +# portaudio, fftw etc. such that the "app" runs on Macs which +# do not have them. But it is *very* hard to do this with GTK. +# +# ATTENTION +# ========= +# Upon starting a freshly built application in an "app" bundle, +# it will do the FFT calculations required for wdspWisdom, since +# this is then stored *within* the app bundle. +# +############################################################################# +app: pihpsdr + @rm -rf pihpsdr.app + @mkdir -p pihpsdr.app/Contents/MacOS + @mkdir -p pihpsdr.app/Contents/Frameworks + @mkdir -p pihpsdr.app/Contents/Resources + @cp pihpsdr pihpsdr.app/Contents/MacOS/pihpsdr-bin + @cp MacOS/PkgInfo pihpsdr.app/Contents + @cp MacOS/Info.plist pihpsdr.app/Contents + @cp MacOS/hpsdr.icns pihpsdr.app/Contents/Resources/hpsdr.icns + @cp MacOS/pihpsdr.sh pihpsdr.app/Contents/MacOS/pihpsdr + @cp MacOS/hpsdr.png pihpsdr.app/Contents/Resources + @for lib in `otool -L pihpsdr.app/Contents/MacOS/pihpsdr-bin | grep dylib | sed -e "s/ (.*//" | grep -Ev "/(usr/lib|System)" | grep -Ev /libg `; do \ + libfn="`basename $$lib`"; \ + cp "$$lib" "pihpsdr.app/Contents/Frameworks/$$libfn"; \ + chmod u+w "pihpsdr.app/Contents/Frameworks/$$libfn"; \ + install_name_tool -id "@executable_path/../Frameworks/$libfn" "pihpsdr.app/Contents/Frameworks/$$libfn"; \ + install_name_tool -change "$$lib" "@executable_path/../Frameworks/$$libfn" pihpsdr.app/Contents/MacOS/pihpsdr-bin; \ + done +############################################################################# + diff --git a/README.MacOS b/README.MacOS new file mode 100644 index 0000000..a43b6a6 --- /dev/null +++ b/README.MacOS @@ -0,0 +1,141 @@ +=============================================================== += = += piHPSDR on the Macintosh. = += = += Port done by DL1YCF Christoph van Wullen. = += = +=============================================================== + + + +============================ +NOTES ON CHANGES IN THE CODE +============================ + +To make piHPSDR work on MacOS, I had to do two major things: + +a) Semaphores: MacOS does not have sem_t variables, only + sem_t pointers that must not be dereferences. Therefore + it has no working sem_init. + On MacOS one must use sem_open instead and use sem_t + pointers instead of sem_t variable throughout. This is + not recommended for LINUX, since named semaphores stay + alive even when the program terminates (on MacOS, all + semaphores are automatically destroyed). Therefore + *every* declaration of sem_t variable and *every* + call to semaphore functions had to be modified. Everything + is coded as + +#ifdef __APPLE__ + use sem_t pointers +#else + usem_t variables +#endif + + NOTE this change also applies to WDSP. + +b) Audio: MacOS does not have ALSA, therefore an additional + file portaudio.c is provided that is a functional duplicate + of audio.c. The whole file audio.c is not "protected with + +#ifndef PORTAUDIO +everything in audio.c +#endif + + and the new file portaudio.c consequently reads + +#ifdef PORTAUDIO +everything in portaudio.c +#endif + + such that one can link and compile both files. As an additional benefit, + the PortAudio module also offeres a two-tone generator as TX "mic" input. + +c) Only relevant for STEMLAB/HAMLAB: the "special" code that starts the + HPSDR application on the RedPitaya board via a browser interface relies + on AVAHI to detect the RedPitaya board. This does not work on MacOS + since we do not have AVAHI. However, libcurl is available on MacOS. + Therefore I provide a stripped-down version in the file stemlab_discovery.c + which assumes that the RedPitaya board is accessible by a fixed + IP address. This address is read from $HOME/.rp.inet and set to 192.168.1.3 + if this file could not be read. + If your STEMlab/HAMlab is then there, the list of applications is obtained + and the HPSDR application with highest priority is started, the priority + defined through the following list (first line = highest priority) + + hamlab_sdr_transceiver_hpsdr + stemlab_sdr_transceiver_hpsdr + sdr_transceiver_hpsdr + sdr_receiver_hpsdr + +============= +PREREQUISITES +============= + +Since Audio and GUI are OpenSource and not Apple-specific, one needs some +third-party libraries to link the program. Most recommended is to use +"homebrew". You will need portaudio and gtk+3 for piHPSDR, and +fftw3 for wdsp (which you have to compile separately). Note that packages +such as gtk+3 depend on other packages, but these are automatically installed +by homebrew. The Makefile itself relies on pkg-config to determine the +compile and link flags for certain external software. + +Of course, you need the Xcode command line tools (make, gcc, and friends). + +Before you compile piHPSDR, you need to compile wdsp. Using the Mac-Makefile +there, "make install" will put libwdsp.dylib in /usr/local/lib. This is needed +by piHPSDR. + +=================== +COMPILE and INSTALL +=================== + + +That's easy. Just adjust the Makefile according to the instructions found there +and type "make". In my case (I have a HAMLAB RedPitaya-based SDR box), I need +the following options in the Makefile (and have all others commented out): + +PORTAUDIO_INCLUDE=PORTAUDIO +STEMLAB_FIX_OPTION=-DSTEMLAB_FIX +STEMLAB_DISCOVERY=STEMLAB_DISCOVERY_MAC +UNAME_N=MacOS + +The first one activates the PortAudio code and disables the Linux ALSA +code, the second option activates some work-arounds around some RedPitaya +HPSDR application bugs (this is also required if you are NOT using MacOS), +and the third option activates code to start the HPSDR program on a RedPitaya +before the actual discovery stuff takes place. + +Note: never un-comment the lines containing LIBRT, GPI_INCLUDE, I2C_INCLUDE, +SX1509_INCLUDE, or LOCALCW_INCLUDE. This software/hardware is not present on a Mac. + +As a result of "make", you get an executable file "pihpsdr" which you +can start from the terminal. With "make app" you can make a click-able +MacOS application bundle. However note that this bundle is not self-contained: +it needs a working gtk+3 environment. To put all this into an app bundle +would create an app file hundreds of MByte long: you need all those pixbuf +loaders etc. etc. etc. installed. + +What we do is to include portaudio, WDSP, fftw3 and some other libs into the bundle. + + +=========================== +piHPSDR and wsjtx or fldigi +=========================== + +a) CAT control: use hamlib, and choose "OpenHPSDR piHPSDR" radio model and + e.g. port number 19090. Then, activate rigctl in the piHPSDR menu, choosing + the same port number there. + +b) Audio: here you need the "SoundFlower" free-software program for MacOS, that + provides virtual audio cables. I have stripped down this somewhat, it now provides + two stero devices (named VAC A and VAC B) and has less overhead, since audio data + is no longer processed (no volume control) but simply moved: after all, this is what + a "cable" is supposed to do. + + For example, you can Choose "VAC A" as the RX output device and "VAC B" as the + TX input (mic) device in piHPSDR, and choose "VAC B" as the output device and "VAC A" + as the input device in fldigi or wsjt-x. + The modified version of SoundFlower is available on my github account, github.com/dl1ycf, + with the name MacOSVirtualAudioCable. + diff --git a/agc_menu.c b/agc_menu.c index f41a5a3..3c9262d 100644 --- a/agc_menu.c +++ b/agc_menu.c @@ -60,6 +60,10 @@ static gboolean agc_select_cb (GtkWidget *widget, gpointer data) { //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; } void agc_menu(GtkWidget *parent) { diff --git a/audio.c b/audio.c index 923685a..f6a7439 100644 --- a/audio.c +++ b/audio.c @@ -17,6 +17,11 @@ * */ +// +// DL1YCF: If PortAudio is used instead of ALSO (e.g. on MacOS), +// this file is not used (and replaced by portaudio.c). + +#ifndef PORTAUDIO #include <gtk/gtk.h> @@ -527,4 +532,5 @@ fprintf(stderr,"output_device: %s\n",device_id); n++; } snd_device_name_free_hint(hints); -} \ No newline at end of file +} +#endif diff --git a/audio_waterfall.c b/audio_waterfall.c index 48ed0b9..cbf606f 100644 --- a/audio_waterfall.c +++ b/audio_waterfall.c @@ -78,7 +78,8 @@ waterfall_configure_event_cb (GtkWidget *widget, 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); - char *pixels = gdk_pixbuf_get_pixels (pixbuf); + // DL1YCF changed to uchar * + unsigned char *pixels = gdk_pixbuf_get_pixels (pixbuf); memset(pixels, 0, width*height*3); @@ -135,7 +136,8 @@ void audio_waterfall_update() { int i; if(pixbuf) { - char *pixels = gdk_pixbuf_get_pixels (pixbuf); + // DL1YCF changed to uchar * + unsigned char *pixels = gdk_pixbuf_get_pixels (pixbuf); int width=gdk_pixbuf_get_width(pixbuf); int height=gdk_pixbuf_get_height(pixbuf); @@ -147,7 +149,8 @@ void audio_waterfall_update() { memmove(&pixels[rowstride*(header+1)],&pixels[rowstride*header],(height-(header+1))*rowstride); float sample; - char *p; + // DL1YCF changed to uchar * + unsigned char *p; int average=0; p=&pixels[rowstride*header]; for(i=0;i<width;i++) { diff --git a/band_menu.c b/band_menu.c index ff020c2..c82a3e8 100644 --- a/band_menu.c +++ b/band_menu.c @@ -64,6 +64,10 @@ gboolean band_select_cb (GtkWidget *widget, gpointer data) { 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; } void band_menu(GtkWidget *parent) { diff --git a/bandstack_menu.c b/bandstack_menu.c index d5a1b4f..aa05ff0 100644 --- a/bandstack_menu.c +++ b/bandstack_menu.c @@ -63,6 +63,10 @@ static gboolean bandstack_select_cb (GtkWidget *widget, gpointer data) { 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; } void bandstack_menu(GtkWidget *parent) { diff --git a/dsp_menu.c b/dsp_menu.c index 09ce553..73ea8c7 100644 --- a/dsp_menu.c +++ b/dsp_menu.c @@ -70,7 +70,8 @@ static void pre_post_agc_cb(GtkWidget *widget, gpointer data) { } static void nr2_gain_cb(GtkWidget *widget, gpointer data) { - active_receiver->nr2_gain_method==(uintptr_t)data; +// DL1YCF changed == to = + active_receiver->nr2_gain_method=(uintptr_t)data; SetRXAEMNRgainMethod(active_receiver->id, active_receiver->nr2_gain_method); } diff --git a/equalizer_menu.c b/equalizer_menu.c index 514e77d..325c249 100644 --- a/equalizer_menu.c +++ b/equalizer_menu.c @@ -70,6 +70,10 @@ static gboolean tx_rb_cb (GtkWidget *widget, GdkEventButton *event, gpointer dat 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; } static gboolean rx_rb_cb (GtkWidget *widget, GdkEventButton *event, gpointer data) { @@ -82,6 +86,10 @@ static gboolean rx_rb_cb (GtkWidget *widget, GdkEventButton *event, gpointer dat 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; } static gboolean enable_cb (GtkWidget *widget, GdkEventButton *event, gpointer data) { @@ -92,6 +100,10 @@ static gboolean enable_cb (GtkWidget *widget, GdkEventButton *event, gpointer da 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; } static void value_changed_cb (GtkWidget *widget, gpointer data) { diff --git a/error_handler.c b/error_handler.c index 5c7313e..800b04d 100644 --- a/error_handler.c +++ b/error_handler.c @@ -22,6 +22,10 @@ int show_error(void *data) { 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; } void error_handler(char *text,char *err) { diff --git a/ext.c b/ext.c index 071736b..c5ba619 100644 --- a/ext.c +++ b/ext.c @@ -44,6 +44,10 @@ int ext_discovery(void *data) { int ext_set_frequency(void *data) { setFrequency(*(long long *)data); free(data); + // DL1YCF added return statement + // this one is CRITICAL to avoid free() being called + // repeatedly on the same pointer + return 0; } int ext_vfo_update(void *data) { @@ -67,14 +71,20 @@ int ext_band_update(void *data) { int ext_mode_update(void *data) { start_mode(); + // DL1YCF added return statement + return 0; } int ext_filter_update(void *data) { start_filter(); + // DL1YCF added return statement + return 0; } int ext_noise_update(void *data) { start_noise(); + // DL1YCF added return statement + return 0; } int ext_ptt_update(void *data) { diff --git a/filter_menu.c b/filter_menu.c index aee2b68..092d890 100644 --- a/filter_menu.c +++ b/filter_menu.c @@ -67,6 +67,10 @@ static gboolean filter_select_cb (GtkWidget *widget, gpointer data) { set_button_text_color(last_filter,"black"); last_filter=widget; set_button_text_color(last_filter,"orange"); + // 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 gboolean deviation_select_cb (GtkWidget *widget, gpointer data) { @@ -87,6 +91,10 @@ static gboolean deviation_select_cb (GtkWidget *widget, gpointer data) { 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; } static void var_spin_low_cb (GtkWidget *widget, gpointer data) { diff --git a/freqent_menu.c b/freqent_menu.c index 8028ad0..0370ff7 100644 --- a/freqent_menu.c +++ b/freqent_menu.c @@ -146,6 +146,8 @@ static gboolean freqent_select_cb (GtkWidget *widget, gpointer data) { } } vfo_update(); + // DL1YCF added return statement + return FALSE; } static GtkWidget *last_mode; diff --git a/iambic.c b/iambic.c index 51026b5..71f2502 100644 --- a/iambic.c +++ b/iambic.c @@ -116,7 +116,11 @@ static int kcwr = 0; int *kdot; int *kdash; static int running = 0; +#ifdef __APPLE__ +static sem_t *cw_event; +#else static sem_t cw_event; +#endif int keyer_out = 0; @@ -160,7 +164,11 @@ void keyer_event(int gpio, int level) { kcwr = state; if (state || cw_keyer_mode == KEYER_STRAIGHT) { +#ifdef __APPLE__ + sem_post(cw_event); +#else sem_post(&cw_event); +#endif } } #endif @@ -197,7 +205,11 @@ static void* keyer_thread(void *arg) { fprintf(stderr,"keyer_thread state running= %d\n", running); while(running) { +#ifdef __APPLE__ + sem_wait(cw_event); +#else sem_wait(&cw_event); +#endif key_state = CHECK; @@ -390,7 +402,12 @@ int keyer_init() { softToneCreate(SIDETONE_GPIO); } +#ifdef __APPLE__ + cw_event=sem_open("CW", O_CREAT, 0700, 0); + rc = (cw_event == SEM_FAILED); +#else rc = sem_init(&cw_event, 0, 0); +#endif rc |= pthread_create(&keyer_thread_id, NULL, keyer_thread, NULL); running = 1; if(rc < 0) { diff --git a/main.c b/main.c index b17a210..4e3bcbc 100644 --- a/main.c +++ b/main.c @@ -17,6 +17,13 @@ * */ +// DL1YCF +// Define maximum window size. +// Original values 800 and 480, but if the screen is large, why not using it? + +#define MAX_DISPLAY_WIDTH 1020 +#define MAX_DISPLAY_HEIGHT 700 + #include <gtk/gtk.h> #include <gdk/gdk.h> #include <math.h> @@ -58,7 +65,11 @@ gint full_screen=1; static GtkWidget *discovery_dialog; +#ifdef __APPLE__ +static sem_t *wisdom_sem; +#else static sem_t wisdom_sem; +#endif static GdkCursor *cursor_arrow; static GdkCursor *cursor_watch; @@ -91,7 +102,12 @@ static void* wisdom_thread(void *arg) { fprintf(stderr,"Creating wisdom file: %s\n", (char *)arg); status_text("Creating FFTW Wisdom file ..."); WDSPwisdom ((char *)arg); +#ifdef __APPLE__ + sem_post(wisdom_sem); +#else sem_post(&wisdom_sem); +#endif + return NULL; } gboolean main_delete (GtkWidget *widget) { @@ -138,9 +154,18 @@ static int init(void *data) { strcpy(&wisdom_file[strlen(wisdom_file)],"wdspWisdom"); status_text("Checking FFTW Wisdom file ..."); if(access(wisdom_file,F_OK)<0) { +#ifdef __APPLE__ + int rc; + wisdom_sem=sem_open("WISDOM", O_CREAT, 0700, 0); +#else int rc=sem_init(&wisdom_sem, 0, 0); +#endif rc=pthread_create(&wisdom_thread_id, NULL, wisdom_thread, (void *)wisdom_directory); +#ifdef __APPLE__ + while(sem_trywait(wisdom_sem)<0) { +#else while(sem_trywait(&wisdom_sem)<0) { +#endif status_text(wisdom_get_status()); while (gtk_events_pending ()) gtk_main_iteration (); @@ -178,21 +203,11 @@ static void activate_pihpsdr(GtkApplication *app, gpointer data) { display_height=gdk_screen_get_height(screen); fprintf(stderr,"width=%d height=%d\n", display_width, display_height); - if(display_width>800 || display_height>480) { -/* - if(display_width>1600) { - display_width=1600; - } else { - display_width=800; - } - if(display_height>960) { - display_height=960; - } else { - display_height=480; - } -*/ - display_width=800; - display_height=480; + + // DL1YCF: use define'd constants here + if(display_width>MAX_DISPLAY_WIDTH || display_height>MAX_DISPLAY_HEIGHT) { + display_width=MAX_DISPLAY_WIDTH; + display_height=MAX_DISPLAY_HEIGHT; full_screen=0; } diff --git a/mode_menu.c b/mode_menu.c index b2c02bb..d40eee3 100644 --- a/mode_menu.c +++ b/mode_menu.c @@ -64,6 +64,10 @@ static gboolean mode_select_cb (GtkWidget *widget, gpointer data) { last_mode=widget; set_button_text_color(last_mode,"orange"); vfo_mode_changed(m); + // 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; } void mode_menu(GtkWidget *parent) { diff --git a/new_discovery.c b/new_discovery.c index 8ab5f88..634d99d 100644 --- a/new_discovery.c +++ b/new_discovery.c @@ -185,7 +185,8 @@ void new_discover(struct ifaddrs* iface) { //void* new_discover_receive_thread(void* arg) { gpointer new_discover_receive_thread(gpointer data) { struct sockaddr_in addr; - int len; + // DL1YCF change from int to socklen_t + socklen_t len; unsigned char buffer[2048]; int bytes_read; struct timeval tv; @@ -275,4 +276,6 @@ gpointer new_discover_receive_thread(gpointer data) { } fprintf(stderr,"new_discover: exiting new_discover_receive_thread\n"); g_thread_exit(NULL); + // DL1YCF added return statement to make compiler happy. + return NULL; } diff --git a/new_protocol.c b/new_protocol.c index 78da6c8..93d84a9 100644 --- a/new_protocol.c +++ b/new_protocol.c @@ -73,7 +73,18 @@ int data_socket=-1; 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; +#endif static struct sockaddr_in base_addr; static int base_addr_length; @@ -137,17 +148,37 @@ static int response; //static sem_t send_general_sem; //static int send_general=0; +#ifdef __APPLE__ +static sem_t *command_response_sem_ready; +static sem_t *command_response_sem_buffer; +#else static sem_t command_response_sem_ready; static sem_t command_response_sem_buffer; +#endif static GThread *command_response_thread_id; +#ifdef __APPLE__ +static sem_t *high_priority_sem_ready; +static sem_t *high_priority_sem_buffer; +#else static sem_t high_priority_sem_ready; static sem_t high_priority_sem_buffer; +#endif static GThread *high_priority_thread_id; +#ifdef __APPLE__ +static sem_t *mic_line_sem_ready; +static sem_t *mic_line_sem_buffer; +#else static sem_t mic_line_sem_ready; static sem_t mic_line_sem_buffer; +#endif static GThread *mic_line_thread_id; +#ifdef __APPLE__ +static sem_t *iq_sem_ready[MAX_RECEIVERS]; +static sem_t *iq_sem_buffer[MAX_RECEIVERS]; +#else static sem_t iq_sem_ready[MAX_RECEIVERS]; static sem_t iq_sem_buffer[MAX_RECEIVERS]; +#endif static GThread *iq_thread_id[MAX_RECEIVERS]; static int samples[MAX_RECEIVERS]; @@ -169,7 +200,8 @@ static int psk_resample=6; // convert from 48000 to 8000 #define NET_BUFFER_SIZE 2048 // Network buffers static struct sockaddr_in addr; -static int length; +// 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]; static unsigned char *command_response_buffer; @@ -296,28 +328,47 @@ void new_protocol_init(int pixels) { new_protocol_calc_buffers(); #endif +#ifdef __APPLE__ + response_sem=sem_open("RESPONSE", O_CREAT, 0700, 0); +#else rc=sem_init(&response_sem, 0, 0); +#endif //rc=sem_init(&send_high_priority_sem, 0, 1); //rc=sem_init(&send_general_sem, 0, 1); +#ifdef __APPLE__ + command_response_sem_ready=sem_open("COMMRESREADY", O_CREAT, 0700, 0); + command_response_sem_buffer=sem_open("COMMRESBUF", O_CREAT, 0700, 0); +#else rc=sem_init(&command_response_sem_ready, 0, 0); rc=sem_init(&command_response_sem_buffer, 0, 0); +#endif command_response_thread_id = g_thread_new( "command_response thread",command_response_thread, NULL); if( ! command_response_thread_id ) { fprintf(stderr,"g_thread_new failed on command_response_thread\n"); exit( -1 ); } fprintf(stderr, "command_response_thread: id=%p\n",command_response_thread_id); +#ifdef __APPLE__ + high_priority_sem_ready=sem_open("HIGHREADY", O_CREAT, 0700, 0); + high_priority_sem_buffer=sem_open("HIGHBUF", O_CREAT, 0700, 0); +#else rc=sem_init(&high_priority_sem_ready, 0, 0); rc=sem_init(&high_priority_sem_buffer, 0, 0); +#endif high_priority_thread_id = g_thread_new( "high_priority thread", high_priority_thread, NULL); if( ! high_priority_thread_id ) { fprintf(stderr,"g_thread_new failed on high_priority_thread\n"); exit( -1 ); } fprintf(stderr, "high_priority_thread: id=%p\n",high_priority_thread_id); +#ifdef __APPLE__ + mic_line_sem_ready=sem_open("MICREADY", O_CREAT, 0700, 0); + mic_line_sem_buffer=sem_open("MICBUF", O_CREAT, 0700, 0); +#else rc=sem_init(&mic_line_sem_ready, 0, 0); rc=sem_init(&mic_line_sem_buffer, 0, 0); +#endif mic_line_thread_id = g_thread_new( "mic_line thread", mic_line_thread, NULL); if( ! mic_line_thread_id ) { fprintf(stderr,"g_thread_new failed on mic_line_thread\n"); @@ -326,23 +377,45 @@ void new_protocol_init(int pixels) { fprintf(stderr, "mic_line_thread: id=%p\n",mic_line_thread_id); for(i=0;i<RECEIVERS;i++) { +#ifdef __APPLE__ + char sname[12]; +#endif ddc=receiver[i]->ddc; +#ifdef __APPLE__ + sprintf(sname,"IQREADY%03d", ddc); + iq_sem_ready[ddc]=sem_open(sname, O_CREAT, 0700, 0); + sprintf(sname,"IQBUF%03d", ddc); + iq_sem_buffer[ddc]=sem_open(sname, O_CREAT, 0700, 0); +#else rc=sem_init(&iq_sem_ready[ddc], 0, 0); rc=sem_init(&iq_sem_buffer[ddc], 0, 0); +#endif iq_thread_id[ddc] = g_thread_new( "iq thread", iq_thread, (gpointer)(long)i); - if( ! iq_thread_id ) { - fprintf(stderr,"g_thread_new failed for iq_thread: rx=%d\n",i); - exit( -1 ); - } + //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); } #ifdef PURESIGNAL // for PS the two feedback streams are synced on the one DDC if(device!=NEW_DEVICE_HERMES) { +#ifdef __APPLE__ + char sname[12]; +#endif ddc=receiver[PS_TX_FEEDBACK]->ddc; +#ifdef __APPLE__ + sprintf(sname,"IQREADY%03d", ddc); + iq_sem_ready[ddc]=sem_open(sname, O_CREAT, 0700, 0); + sprintf(sname,"IQBUF%03d", ddc); + iq_sem_buffer[ddc]=sem_open(sname, O_CREAT, 0700, 0); +#else rc=sem_init(&iq_sem_ready[ddc], 0, 0); rc=sem_init(&iq_sem_buffer[ddc], 0, 0); +#endif iq_thread_id[ddc] = g_thread_new( "ps iq thread", ps_iq_thread, (gpointer)(long)PS_TX_FEEDBACK); if( ! iq_thread_id ) { fprintf(stderr,"g_thread_new failed for ps_iq_thread: rx=%d\n",PS_TX_FEEDBACK); @@ -1144,28 +1217,60 @@ fprintf(stderr,"new_protocol_thread: high_priority_addr setup for port %d\n",HIG if(ddc>=MAX_RECEIVERS) { fprintf(stderr,"unexpected iq data from ddc %d\n",ddc); } else { +#ifdef __APPLE__ + sem_wait(iq_sem_ready[ddc]); +#else sem_wait(&iq_sem_ready[ddc]); +#endif iq_buffer[ddc]=buffer; +#ifdef __APPLE__ + sem_post(iq_sem_buffer[ddc]); +#else sem_post(&iq_sem_buffer[ddc]); +#endif } break; case COMMAND_RESPONCE_TO_HOST_PORT: +#ifdef __APPLE__ + sem_wait(command_response_sem_ready); +#else sem_wait(&command_response_sem_ready); +#endif command_response_buffer=buffer; +#ifdef __APPLE__ + sem_post(command_response_sem_buffer); +#else sem_post(&command_response_sem_buffer); +#endif //process_command_response(); break; case HIGH_PRIORITY_TO_HOST_PORT: +#ifdef __APPLE__ + sem_wait(high_priority_sem_ready); +#else sem_wait(&high_priority_sem_ready); +#endif high_priority_buffer=buffer; +#ifdef __APPLE__ + sem_post(high_priority_sem_buffer); +#else sem_post(&high_priority_sem_buffer); +#endif //process_high_priority(); break; case MIC_LINE_TO_HOST_PORT: +#ifdef __APPLE__ + sem_wait(mic_line_sem_ready); +#else sem_wait(&mic_line_sem_ready); +#endif mic_line_buffer=buffer; mic_bytes_read=bytesread; +#ifdef __APPLE__ + sem_post(mic_line_sem_buffer); +#else sem_post(&mic_line_sem_buffer); +#endif break; default: fprintf(stderr,"new_protocol_thread: Unknown port %d\n",sourceport); @@ -1175,13 +1280,19 @@ fprintf(stderr,"new_protocol_thread: Unknown port %d\n",sourceport); } close(data_socket); + return NULL; } static gpointer command_response_thread(gpointer data) { while(1) { fprintf(stderr,"command_response_thread\n"); +#ifdef __APPLE__ + sem_post(command_response_sem_ready); + sem_wait(command_response_sem_buffer); +#else sem_post(&command_response_sem_ready); sem_wait(&command_response_sem_buffer); +#endif process_command_response(); free(command_response_buffer); } @@ -1190,8 +1301,13 @@ fprintf(stderr,"command_response_thread\n"); static gpointer high_priority_thread(gpointer data) { fprintf(stderr,"high_priority_thread\n"); while(1) { +#ifdef __APPLE__ + sem_post(high_priority_sem_ready); + sem_wait(high_priority_sem_buffer); +#else sem_post(&high_priority_sem_ready); sem_wait(&high_priority_sem_buffer); +#endif process_high_priority(); free(high_priority_buffer); } @@ -1200,8 +1316,13 @@ fprintf(stderr,"high_priority_thread\n"); static gpointer mic_line_thread(gpointer data) { fprintf(stderr,"mic_line_thread\n"); while(1) { +#ifdef __APPLE__ + sem_post(mic_line_sem_ready); + sem_wait(mic_line_sem_buffer); +#else sem_post(&mic_line_sem_ready); sem_wait(&mic_line_sem_buffer); +#endif if(!transmitter->local_microphone) { process_mic_data(mic_bytes_read); } @@ -1214,8 +1335,13 @@ static gpointer iq_thread(gpointer data) { int ddc=receiver[rx]->ddc; fprintf(stderr,"iq_thread: rx=%d ddc=%d\n",rx,ddc); while(1) { +#ifdef __APPLE__ + sem_post(iq_sem_ready[ddc]); + sem_wait(iq_sem_buffer[ddc]); +#else sem_post(&iq_sem_ready[ddc]); sem_wait(&iq_sem_buffer[ddc]); +#endif process_iq_data(receiver[rx]); free(iq_buffer[ddc]); } @@ -1227,8 +1353,13 @@ static gpointer ps_iq_thread(gpointer data) { int ddc=receiver[rx]->ddc; fprintf(stderr,"ps_iq_thread: rx=%d ddc=%d\n",rx,ddc); while(1) { +#ifdef __APPLE__ + sem_post(iq_sem_ready[ddc]); + sem_wait(iq_sem_buffer[ddc]); +#else sem_post(&iq_sem_ready[ddc]); sem_wait(&iq_sem_buffer[ddc]); +#endif process_ps_iq_data(receiver[rx]); free(iq_buffer[ddc]); } @@ -1257,7 +1388,8 @@ static void process_iq_data(RECEIVER *rx) { } rx->iq_sequence++; - 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); +// 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); samplesperframe=((buffer[14]&0xFF)<<8)+(buffer[15]&0xFF); @@ -1378,7 +1510,11 @@ static void process_command_response() { response_sequence=((command_response_buffer[0]&0xFF)<<24)+((command_response_buffer[1]&0xFF)<<16)+((command_response_buffer[2]&0xFF)<<8)+(command_response_buffer[3]&0xFF); response=command_response_buffer[4]&0xFF; fprintf(stderr,"response_sequence=%ld response=%d\n",response_sequence,response); +#ifdef __APPLE__ + sem_post(response_sem); +#else sem_post(&response_sem); +#endif } static void process_high_priority(unsigned char *buffer) { @@ -1424,7 +1560,10 @@ static void process_mic_data(int bytes) { 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++) { - sample=(short)((mic_line_buffer[b++]<<8) | (mic_line_buffer[b++]&0xFF)); + // 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 if(active_receiver->freedv) { add_freedv_mic_sample(transmitter,sample); @@ -1445,9 +1584,15 @@ void new_protocol_process_local_mic(unsigned char *buffer,int le) { b=0; for(i=0;i<MIC_SAMPLES;i++) { if(le) { - sample = (short)((buffer[b++]&0xFF) | (buffer[b++]<<8)); + // 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 { - sample = (short)((buffer[b++]<<8) | (buffer[b++]&0xFF)); + // 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); } #ifdef FREEDV if(active_receiver->freedv) { @@ -1533,4 +1678,6 @@ fprintf(stderr,"new_protocol_timer_thread\n"); // } // } } + // DL1YCF: added return statement to make compiler happy. + return NULL; } diff --git a/new_protocol.h b/new_protocol.h index fa09067..659c721 100644 --- a/new_protocol.h +++ b/new_protocol.h @@ -50,7 +50,11 @@ #define MIC_SAMPLES 64 extern int data_socket; +#ifdef __APPLE__ +extern sem_t *response_sem; +#else extern sem_t response_sem; +#endif /* extern long response_sequence; diff --git a/new_protocol_programmer.c b/new_protocol_programmer.c index ea4daf8..939ef04 100644 --- a/new_protocol_programmer.c +++ b/new_protocol_programmer.c @@ -172,7 +172,11 @@ void *programmer_thread(void *arg) { // wait for the response to the erase command clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec+=120; // wait for 30 seconds +#ifdef __APPLE__ + result=sem_trywait(response_sem); +#else result=sem_timedwait(&response_sem,&ts); +#endif if(result==-1) { if(errno == ETIMEDOUT) { fprintf(stderr,"timedout waiting for response for erase (start)\n"); @@ -190,7 +194,11 @@ void *programmer_thread(void *arg) { // wait for the erase to complete clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec+=120; // wait for 30 seconds +#ifdef __APPLE__ + result=sem_trywait(response_sem); +#else result=sem_timedwait(&response_sem,&ts); +#endif if(result==-1) { if(errno == ETIMEDOUT) { fprintf(stderr,"timedout waiting for response for erase (complete)\n"); @@ -211,7 +219,11 @@ void *programmer_thread(void *arg) { programmer_send_block(b); clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec+=5; // wait for 5 seconds +#ifdef __APPLE__ + result=sem_trywait(response_sem); +#else result=sem_timedwait(&response_sem,&ts); +#endif if(result==-1) { if(errno == ETIMEDOUT) { fprintf(stderr,"timedout waiting for response for sent block\n"); @@ -227,5 +239,7 @@ void *programmer_thread(void *arg) { } block_sequence++; } - + + // DL1YCF added return statement to make compiler happy. + return NULL; } diff --git a/old_discovery.c b/old_discovery.c index 57ea09e..e47c465 100644 --- a/old_discovery.c +++ b/old_discovery.c @@ -133,7 +133,8 @@ static void discover(struct ifaddrs* iface) { //static void *discover_receive_thread(void* arg) { static gpointer discover_receive_thread(gpointer data) { struct sockaddr_in addr; - int len; + // DL1YCF changed int to socklen_t + socklen_t len; unsigned char buffer[2048]; int bytes_read; struct timeval tv; @@ -183,6 +184,7 @@ fprintf(stderr,"discover_receive_thread\n"); #else strcpy(discovered[devices].name,"Hermes Lite"); #endif + break; case DEVICE_ORION2: strcpy(discovered[devices].name,"Orion 2"); @@ -222,6 +224,8 @@ fprintf(stderr,"discover_receive_thread\n"); } fprintf(stderr,"discovery: exiting discover_receive_thread\n"); g_thread_exit(NULL); + // DL1YCF added return statement to make compiler happy. + return NULL; } void old_discovery() { diff --git a/old_protocol.c b/old_protocol.c index 3459bec..8b2aa12 100644 --- a/old_protocol.c +++ b/old_protocol.c @@ -131,6 +131,9 @@ static double phase=0.0; static int running; static long ep4_sequence; +// DL1YCF added this variable for lost-package-check +static long last_seq_num=0; + static int current_rx=0; static int samples=0; @@ -161,7 +164,8 @@ static int command=1; static GThread *receive_thread_id; static void start_receive_thread(); static gpointer receive_thread(gpointer arg); -static void process_ozy_input_buffer(char *buffer); +// DL1YCF changed buffer to uchar* +static void process_ozy_input_buffer(unsigned char *buffer); static void process_bandscope_buffer(char *buffer); void ozy_send_buffer(); @@ -169,9 +173,11 @@ static unsigned char metis_buffer[1032]; static long send_sequence=-1; static int metis_offset=8; -static int metis_write(unsigned char ep,char* buffer,int length); +// DL1YCF changed buffer to uchar* +static int metis_write(unsigned char ep,unsigned char* buffer,int length); static void metis_start_stop(int command); -static void metis_send_buffer(char* buffer,int length); +// DL1YCF changed buffer to uchar* +static void metis_send_buffer(unsigned char* buffer,int length); static void metis_restart(); #define COMMON_MERCURY_FREQUENCY 0x80 @@ -374,7 +380,8 @@ static void start_receive_thread() { static gpointer receive_thread(gpointer arg) { struct sockaddr_in addr; - int length; + // DL1YCF changed from int to socklen_t + socklen_t length; unsigned char buffer[2048]; int bytes_read; int ep; @@ -414,6 +421,11 @@ static gpointer receive_thread(gpointer arg) { // get the sequence number sequence=((buffer[4]&0xFF)<<24)+((buffer[5]&0xFF)<<16)+((buffer[6]&0xFF)<<8)+(buffer[7]&0xFF); + // DL1YCF: added check on lost packets + if (sequence != last_seq_num+1) { + fprintf(stderr,"SEQ ERROR: last %ld, recvd %ld\n", last_seq_num, sequence); + } + last_seq_num=sequence; switch(ep) { case 6: // EP6 // process the data @@ -453,9 +465,12 @@ static gpointer receive_thread(gpointer arg) { break; } } + // DL1YCF added return statement to make compiler happy. + return NULL; } -static void process_ozy_input_buffer(char *buffer) { +// Dl1YCF changed buffer to uchar* +static void process_ozy_input_buffer(unsigned char *buffer) { int i,j; int r; int b=0; @@ -702,10 +717,18 @@ void old_protocol_process_local_mic(unsigned char *buffer,int le) { // always 48000 samples per second b=0; for(i=0;i<720;i++) { + // avoid pointer increments in logical-or constructs, as the sequence + // is undefined if(le) { - sample = (short)((buffer[b++]&0xFF) | (buffer[b++]<<8)); + // 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 { - sample = (short)((buffer[b++]<<8) | (buffer[b++]&0xFF)); + // 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); } #ifdef FREEDV if(active_receiver->freedv) { @@ -724,7 +747,10 @@ static void process_bandscope_buffer(char *buffer) { } */ - +#ifdef PROTOCOL_DEBUG +// DL1YCF Debug: save last values and log changes +static unsigned char last_c1[20], last_c2[20], last_c3[20], last_c4[20], last_mox; +#endif void ozy_send_buffer() { @@ -986,7 +1012,25 @@ void ozy_send_buffer() { { BAND *band=band_get_current_band(); int power=0; +#ifdef STEMLAB_FIX + // + // DL1YCF: + // On my HAMlab RedPitaya-based SDR transceiver, CW is generated on-board the RP. + // However, while in CW mode, DriveLevel changes do not become effective. + // If the CW paddle is hit, the new PTT state is sent to piHPSDR, then the TX drive + // is sent the next time "command 3" is performed, but this often is too late and + // CW is generated with zero DriveLevel. + // Therefore, when in CW mode, send the TX drive level also when receiving. + // + if(split) { + mode=vfo[1].mode; + } else { + mode=vfo[0].mode; + } + if(isTransmitting() || (mode == modeCWU) || (mode == modeCWL)) { +#else if(isTransmitting()) { +#endif if(tune && !transmitter->tune_use_drive) { power=(int)((double)transmitter->drive_level/100.0*(double)transmitter->tune_percent); } else { @@ -1166,6 +1210,37 @@ void ozy_send_buffer() { } } +#ifdef PROTOCOL_DEBUG +// +// DL1YCF debug: +// look for changed parameters and log them +// This is great for debugging protocol problems, +// such as the HAMlab CW error fixed above, so I +// leave it here deactivated +// + int ind = output_buffer[C0] >> 1; + if (last_c1[ind] != output_buffer[C1]) { + fprintf(stderr, "C0=%x Old C1=%x New C1=%x\n", 2*ind,last_c1[ind], output_buffer[C1]); + last_c1[ind]=output_buffer[C1]; + } + if (last_c2[ind] != output_buffer[C2]) { + fprintf(stderr, "C0=%x Old C2=%x New C2=%x\n", 2*ind,last_c2[ind], output_buffer[C2]); + last_c2[ind]=output_buffer[C2]; + } + if (last_c3[ind] != output_buffer[C3]) { + fprintf(stderr, "C0=%x Old C3=%x New C3=%x\n", 2*ind,last_c3[ind], output_buffer[C3]); + last_c3[ind]=output_buffer[C3]; + } + if (last_c4[ind] != output_buffer[C4]) { + fprintf(stderr, "C0=%x Old C4=%x New C4=%x\n", 2*ind,last_c4[ind], output_buffer[C4]); + last_c4[ind]=output_buffer[C4]; + } + if ((output_buffer[C0] & 1) != last_mox) { + fprintf(stderr, "Last Mox=%d New Mox=%d\n", last_mox, output_buffer[C0] & 1); + last_mox=output_buffer[C0] & 1; + } +#endif + #ifdef USBOZY // // if we have a USB interfaced Ozy device: @@ -1215,7 +1290,8 @@ static int ozyusb_write(char* buffer,int length) } #endif -static int metis_write(unsigned char ep,char* buffer,int length) { +// DL1YCF change buffer to uchar* +static int metis_write(unsigned char ep,unsigned char* buffer,int length) { int i; // copy the buffer over @@ -1248,11 +1324,33 @@ static int metis_write(unsigned char ep,char* buffer,int length) { static void metis_restart() { // reset metis frame - metis_offset==8; + // DL1YCF change == to = in the next line + metis_offset=8; // reset current rx current_rx=0; +#ifdef STEMLAB_FIX + // DL1YCF: + // My RedPitaya HPSDR "clone" won't start up + // if too many commands are sent here. Note these + // packets are only there for sync-ing in the clock + // source etc. + // Note that always two 512-byte OZY buffers are + // combined into one METIS packet. + // + command=1; // ship out a "C0=0" and a "set tx" command + ozy_send_buffer(); + ozy_send_buffer(); + command=2; // ship out a "C0=0" and a "set rx" command for RX1 + ozy_send_buffer(); + ozy_send_buffer(); + + // DL1YCF: reset for the next commands + current_rx=0; + command=1; +#else + // DL1YCF this is the original code, which does not do what it pretends .... // send commands twice command=1; do { @@ -1262,6 +1360,7 @@ static void metis_restart() { do { ozy_send_buffer(); } while (command!=1); +#endif sleep(1); @@ -1293,7 +1392,8 @@ static void metis_start_stop(int command) { #endif } -static void metis_send_buffer(char* buffer,int length) { +// DL1YCF changedbuffer to uchar * +static void metis_send_buffer(unsigned char* buffer,int length) { if(sendto(data_socket,buffer,length,0,(struct sockaddr*)&data_addr,data_addr_length)!=length) { perror("sendto socket failed for metis_send_data\n"); } diff --git a/portaudio.c b/portaudio.c new file mode 100644 index 0000000..69f77fc --- /dev/null +++ b/portaudio.c @@ -0,0 +1,396 @@ +#ifdef PORTAUDIO +// +// DL1YCF: if PortAudio is NOT used, this file is empty, and audio.c +// is used instead. +// Here we also implement two (hopefully useful) functions: +// - a dummy two-tone 'Microphone' device +// + +#include <gtk/gtk.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <pthread.h> +#include <sched.h> +#include <semaphore.h> + +#include "new_protocol.h" +#include "old_protocol.h" +#ifdef RADIOBERRY +#include "radioberry.h" +#endif +#include "radio.h" +#include "receiver.h" +#include "portaudio.h" +#include "math.h" // for sintab, two-tone generator + +static PaStream *record_handle=NULL; + +#define MAXDEVICES 12 + +const char *input_devices[MAXDEVICES]; +const char *output_devices[MAXDEVICES]; + +int n_input_devices=0; +int n_output_devices=0; + +static int in_device_no[MAXDEVICES]; +static int out_device_no[MAXDEVICES]; + +static unsigned char *mic_buffer=NULL; +static int mic_buffer_size; +static int audio_buffer_size=256; + +// +// Dummy Two-tone input device +// +static int TwoTone=0; +#define lentab 480 +static float sintab[lentab]; +static int tonept; +#define twopi 6.2831853071795864769252867665590 +#define factab (twopi/480) + + +// +// AUDIO_GET_CARDS +// +// This inits PortAudio and looks for suitable input and output channels +// +void audio_get_cards() +{ + int i, numDevices; + const PaDeviceInfo *deviceInfo; + PaStreamParameters inputParameters, outputParameters; + + PaError err; + + // generate sine tab + for (i=0; i< lentab; i++) sintab[i] = 0.35*(sin(7*i*factab)+sin(19*i*factab)); + + err = Pa_Initialize(); + if( err != paNoError ) + { + fprintf(stderr, "PORTAUDIO ERROR: Pa_Initialize: %s\n", Pa_GetErrorText(err)); + return; + } + numDevices = Pa_GetDeviceCount(); + if( numDevices < 0 ) return; + + n_input_devices=0; + n_output_devices=0; + + for( i=0; i<numDevices; i++ ) + { + deviceInfo = Pa_GetDeviceInfo( i ); + + inputParameters.device = i; + inputParameters.channelCount = 1; + inputParameters.sampleFormat = paFloat32; + inputParameters.suggestedLatency = 0; /* ignored by Pa_IsFormatSupported() */ + inputParameters.hostApiSpecificStreamInfo = NULL; + if (Pa_IsFormatSupported(&inputParameters, NULL, 48000.0) == paFormatIsSupported) { + // duplicate the first suitable device, this will become + // a dummy two-tone generator + if (n_input_devices == 0) { + input_devices[n_input_devices]="TwoTone"; + in_device_no[n_input_devices++] =i; + } + if (n_input_devices < MAXDEVICES) { + input_devices[n_input_devices]=deviceInfo->name; + in_device_no[n_input_devices++] =i; + } + fprintf(stderr,"PORTAUDIO INPUT DEVICE, No=%d, Name=%s\n", i, deviceInfo->name); + } + + outputParameters.device = i; + outputParameters.channelCount = 1; + outputParameters.sampleFormat = paFloat32; + outputParameters.suggestedLatency = 0; /* ignored by Pa_IsFormatSupported() */ + outputParameters.hostApiSpecificStreamInfo = NULL; + if (Pa_IsFormatSupported(NULL, &outputParameters, 48000.0) == paFormatIsSupported) { + if (n_output_devices < MAXDEVICES) { + output_devices[n_output_devices]=deviceInfo->name; + out_device_no[n_output_devices++] =i; + } + fprintf(stderr,"PORTAUDIO OUTPUT DEVICE, No=%d, Name=%s\n", i, deviceInfo->name); + } + } +} + +// +// AUDIO_OPEN_INPUT +// +// open a PA stream that connects to the TX microphone +// The PA callback function then sends the data to the transmitter +// + +int pa_mic_cb(const void*, void*, unsigned long, const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags, void*); +unsigned char *micbuffer = NULL; + +int audio_open_input() +{ + PaError err; + PaStreamParameters inputParameters, outputParameters; + long framesPerBuffer; + int i; + + fprintf(stderr,"audio_open_input: %d\n",transmitter->input_device); + if(transmitter->input_device<0 || transmitter->input_device>=n_input_devices) { + transmitter->input_device=0; + return -1; + } + + switch(protocol) { + case ORIGINAL_PROTOCOL: + framesPerBuffer = 720; + break; + case NEW_PROTOCOL: + framesPerBuffer = 64; + break; +#ifdef RADIOBERRY + case RADIOBERRY_PROTOCOL: + framesPerBuffer = 1024; + break; +#endif + default: + break; + } + + bzero( &inputParameters, sizeof( inputParameters ) ); //not necessary if you are filling in all the fields + inputParameters.channelCount = 1; + inputParameters.device = in_device_no[transmitter->input_device]; + inputParameters.hostApiSpecificStreamInfo = NULL; + inputParameters.sampleFormat = paFloat32; + inputParameters.suggestedLatency = Pa_GetDeviceInfo(in_device_no[transmitter->input_device])->defaultLowInputLatency ; + inputParameters.hostApiSpecificStreamInfo = NULL; //See you specific host's API docs for info on using this field + + err = Pa_OpenStream(&record_handle, &inputParameters, NULL, 48000.0, framesPerBuffer, paNoFlag, pa_mic_cb, NULL); + if (err != paNoError) { + fprintf(stderr, "PORTAUDIO ERROR: AOI open stream: %s\n",Pa_GetErrorText(err)); + return -1; + } + + err = Pa_StartStream(record_handle); + if (err != paNoError) { + fprintf(stderr, "PORTAUDIO ERROR: AOI start stream:%s\n",Pa_GetErrorText(err)); + return -1; + } + mic_buffer=(unsigned char *)malloc(2*framesPerBuffer); + mic_buffer_size=framesPerBuffer; + TwoTone=0; + if (transmitter->input_device == 0) { + tonept=0; + TwoTone=1; + } + return 0; +} + +// +// PortAudio call-back function for Audio input +// +int pa_mic_cb(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userdata) +{ + const float *in = (float *)inputBuffer; + int i; + short isample; + unsigned char *p; + +// +// Convert input buffer in paFloat32 into a sequence of 16-bit +// values in the mic buffer +// + if (mic_buffer == NULL) return paAbort; + + p=mic_buffer; + if (in == NULL || framesPerBuffer != mic_buffer_size) { + for (i=0; i<mic_buffer_size; i++) { + *p++ = 0; + *p++ = 0; + } + } else if (TwoTone == 0) { + for (i=0; i<framesPerBuffer; i++) { + isample=(short) (in[i]*32768.0); + *p++ = (isample & 0xFF); // LittleEndian + *p++ = (isample >> 8)& 0xFF; + } + } else { + for (i=0; i<framesPerBuffer; i++) { + isample=(short) (sintab[tonept++]*32768.0); + if (tonept == lentab) tonept=0; + *p++ = (isample & 0xFF); // LittleEndian + *p++ = (isample >> 8)& 0xFF; + } + } +// +// Call routine to send mic buffer +// + switch(protocol) { + case ORIGINAL_PROTOCOL: + old_protocol_process_local_mic(mic_buffer,1); + break; + case NEW_PROTOCOL: + new_protocol_process_local_mic(mic_buffer,1); + break; +#ifdef RADIOBERRY + case RADIOBERRY_PROTOCOL: + radioberry_protocol_process_local_mic(mic_buffer,1); + break; +#endif + default: + break; + } + return paContinue; +} + +// +// AUDIO_OPEN_OUTPUT +// +// open a PA stream for data from one of the RX +// +int audio_open_output(RECEIVER *rx) +{ + PaError err; + PaStreamParameters outputParameters; + long framesPerBuffer=(long) audio_buffer_size; + + int padev = out_device_no[rx->audio_device]; + fprintf(stderr,"audio_open_output: %d PADEV=%d\n",rx->audio_device,padev); + if(rx->audio_device<0 || rx->audio_device>=n_output_devices) { + rx->audio_device=-1; + return -1; + } + bzero( &outputParameters, sizeof( outputParameters ) ); //not necessary if you are filling in all the fields + outputParameters.channelCount = 1; // Always MONO + outputParameters.device = padev; + outputParameters.hostApiSpecificStreamInfo = NULL; + outputParameters.sampleFormat = paFloat32; + outputParameters.suggestedLatency = Pa_GetDeviceInfo(padev)->defaultLowOutputLatency ; + outputParameters.hostApiSpecificStreamInfo = NULL; //See you specific host's API docs for info on using this field + + // Try using AudioWrite without a call-back function + + rx->playback_buffer=malloc(audio_buffer_size*sizeof(float)); + rx->playback_offset=0; + err = Pa_OpenStream(&(rx->playback_handle), NULL, &outputParameters, 48000.0, framesPerBuffer, paNoFlag, NULL, NULL); + if (err != paNoError) { + fprintf(stderr,"PORTAUDIO ERROR: AOO open stream: %s\n",Pa_GetErrorText(err)); + rx->playback_handle = NULL; + if (rx->playback_buffer) free(rx->playback_buffer); + rx->playback_buffer = NULL; + return -1; + } + + err = Pa_StartStream(rx->playback_handle); + if (err != paNoError) { + fprintf(stderr,"PORTAUDIO ERROR: AOO start stream:%s\n",Pa_GetErrorText(err)); + rx->playback_handle=NULL; + if (rx->playback_buffer) free(rx->playback_buffer); + rx->playback_buffer = NULL; + return -1; + } + // Write one buffer to avoid under-flow errors + // (this gives us 5 msec to pass before we have to call audio_write the first time) + bzero(rx->playback_buffer, (size_t) audio_buffer_size*sizeof(float)); + err=Pa_WriteStream(rx->playback_handle, (void *) rx->playback_buffer, (unsigned long) audio_buffer_size); + return 0; +} + +// +// AUDIO_CLOSE_INPUT +// +// close a TX microphone stream +// +void audio_close_input() +{ + PaError err; + + fprintf(stderr,"AudioCloseInput: %d\n", transmitter->input_device); + + if(record_handle!=NULL) { + err = Pa_StopStream(record_handle); + if (err != paNoError) { + fprintf(stderr,"PORTAUDIO ERROR: ACI stop stream: %s\n",Pa_GetErrorText(err)); + } + err = Pa_CloseStream(record_handle); + if (err != paNoError) { + fprintf(stderr,"PORTAUDIO ERROR: ACI close stream: %s\n",Pa_GetErrorText(err)); + } + record_handle=NULL; + } + if(mic_buffer!=NULL) { + free(mic_buffer); + mic_buffer=NULL; + } +} + +// +// AUDIO_CLOSE_OUTPUT +// +// shut down the stream connected with audio from one of the RX +// +void audio_close_output(RECEIVER *rx) { + PaError err; + + fprintf(stderr,"AudioCloseOutput: %d\n", rx->audio_device); + +// free the buffer first, this then indicates to audio_write to do nothing + if(rx->playback_buffer!=NULL) { + free(rx->playback_buffer); + rx->playback_buffer=NULL; + } + + if(rx->playback_handle!=NULL) { + err = Pa_StopStream(rx->playback_handle); + if (err != paNoError) { + fprintf(stderr,"PORTAUDIO ERROR: ACO stop stream: %s\n",Pa_GetErrorText(err)); + } + err = Pa_CloseStream(rx->playback_handle); + if (err != paNoError) { + fprintf(stderr,"PORTAUDIO ERROR: ACO close stream: %s\n",Pa_GetErrorText(err)); + } + rx->playback_handle=NULL; + } +} + +// +// AUDIO_WRITE +// +// send RX audio data to a PA output stream +// we have to store the data such that the PA callback function +// can access it. +// +static int apt=0; +int audio_write (RECEIVER *rx, short r, short l) +{ + PaError err; + + if (rx->playback_handle != NULL && rx->playback_buffer != NULL) { + rx->playback_buffer[rx->playback_offset++] = (r + l) *0.000015259; // 65536 --> 1.0 + if (rx->playback_offset == audio_buffer_size) { + rx->playback_offset=0; + err=Pa_WriteStream(rx->playback_handle, (void *) rx->playback_buffer, (unsigned long) audio_buffer_size); + //if (err != paNoError) { + // fprintf(stderr,"PORTAUDIO ERROR: write stream dev=%d: %s\n",out_device_no[rx->audio_device],Pa_GetErrorText(err)); + // return -1; + // } + } + } + return 0; +} + +// +// CW audio write +// This is a dummy here because I think it is not correctly implemented in audio.c +// +void cw_audio_write(double sample) { +} + +#endif diff --git a/radio.c b/radio.c index 658c8f5..512687b 100644 --- a/radio.c +++ b/radio.c @@ -121,7 +121,11 @@ static gint save_timer_id; DISCOVERED *radio=NULL; char property_path[128]; +#ifdef __APPLE__ +sem_t *property_sem; +#else sem_t property_sem; +#endif RECEIVER *receiver[MAX_RECEIVERS]; RECEIVER *active_receiver; @@ -317,7 +321,8 @@ void reconfigure_radio() { } else { gtk_fixed_move(GTK_FIXED(fixed),sliders,0,y); } - gtk_widget_show_all(sliders); + gtk_widget_show_all(sliders); // DL1YCF this shows both C25 and Alex ATT/Preamp sliders + att_type_changed(); // DL1YCF added here to hide the âwrongâ ones. } else { if(sliders!=NULL) { gtk_container_remove(GTK_CONTAINER(fixed),sliders); @@ -352,12 +357,21 @@ void start_radio() { gdk_window_set_cursor(gtk_widget_get_window(top_window),gdk_cursor_new(GDK_WATCH)); int rc; +#ifdef __APPLE__ + property_sem=sem_open("PROPERTY", O_CREAT, 0700, 0); + rc=(property_sem == SEM_FAILED); +#else rc=sem_init(&property_sem, 0, 0); +#endif if(rc!=0) { fprintf(stderr,"start_radio: sem_init failed for property_sem: %d\n", rc); exit(-1); } +#ifdef __APPLE__ + sem_post(property_sem); +#else sem_post(&property_sem); +#endif char text[256]; //for(i=0;i<devices;i++) { @@ -1062,7 +1076,11 @@ void radioRestoreState() { char *value; fprintf(stderr,"radioRestoreState: %s\n",property_path); +#ifdef __APPLE__ + sem_wait(property_sem); +#else sem_wait(&property_sem); +#endif loadProperties(property_path); value=getProperty("region"); @@ -1264,14 +1282,22 @@ fprintf(stderr,"radioRestoreState: %s\n",property_path); value=getProperty("rx2_gain_slider"); if(value) rx_gain_slider[1]=atoi(value); +#ifdef __APPLE__ + sem_post(property_sem); +#else sem_post(&property_sem); +#endif } void radioSaveState() { int i; char value[80]; +#ifdef __APPLE__ + sem_wait(property_sem); +#else sem_wait(&property_sem); +#endif sprintf(value,"%d",region); setProperty("region",value); sprintf(value,"%d",buffer_size); @@ -1457,7 +1483,11 @@ void radioSaveState() { setProperty("rigctl_port_base",value); saveProperties(property_path); +#ifdef __APPLE__ + sem_post(property_sem); +#else sem_post(&property_sem); +#endif } void calculate_display_average(RECEIVER *rx) { diff --git a/receiver.c b/receiver.c index 2ea9f1d..15c7f9c 100644 --- a/receiver.c +++ b/receiver.c @@ -131,7 +131,8 @@ gboolean receiver_motion_notify_event(GtkWidget *widget, GdkEventMotion *event, &x, &y, &state); - if((state & GDK_BUTTON1_MASK == GDK_BUTTON1_MASK) || pressed) { + // DL1YCF added a pair of () to fix an error + if(((state & GDK_BUTTON1_MASK) == GDK_BUTTON1_MASK) || pressed) { int moved=last_x-x; vfo_move((long long)((float)moved*rx->hz_per_pixel)); last_x=x; diff --git a/receiver.h b/receiver.h index 3bd59c4..39bf084 100644 --- a/receiver.h +++ b/receiver.h @@ -20,7 +20,11 @@ #define _RECEIVER_H #include <gtk/gtk.h> +#ifdef PORTAUDIO +#include "portaudio.h" +#else #include <alsa/asoundlib.h> +#endif #define AUDIO_BUFFER_SIZE 260 @@ -106,9 +110,14 @@ typedef struct _receiver { int local_audio; int mute_when_not_active; int audio_device; +#ifdef PORTAUDIO + PaStream *playback_handle; + float *playback_buffer; +#else snd_pcm_t *playback_handle; - int playback_offset; unsigned char *playback_buffer; +#endif + int playback_offset; int low_latency; int squelch_enable; diff --git a/rigctl.c b/rigctl.c index 6216fb3..c121392 100644 --- a/rigctl.c +++ b/rigctl.c @@ -82,8 +82,9 @@ int connect_cnt = 0; int rigctlGetFilterLow(); int rigctlGetFilterHigh(); -int rigctlSetFilterLow(int val); -int rigctlSetFilterHigh(int val); +// DL1YCF changed next to function to void +void rigctlSetFilterLow(int val); +void rigctlSetFilterHigh(int val); int new_level; int active_transmitter = 0; int rigctl_busy = 0; // Used to tell rigctl_menu that launch has already occured @@ -122,7 +123,8 @@ static int rigctl_timer = 0; typedef struct _client { int socket; - int address_length; + // Dl1YCF change from int to socklen_t + socklen_t address_length; struct sockaddr_in address; GThread *thread_id; } CLIENT; @@ -197,6 +199,8 @@ static gpointer set_rigctl_timer (gpointer data) { // Wait throttle time usleep(RIGCTL_TIMER_DELAY); rigctl_timer = 0; + // DL1YCF added return statement to make compiler happy. + return NULL; } // @@ -1930,16 +1934,16 @@ void parse_cmd ( char * cmd_input,int len,int client_sock) { if(agc_resp == 0) { active_receiver->agc = AGC_OFF; - } else if(agc_resp >0 && agc_resp <= 5 || (agc_resp == 84)) { + } else if((agc_resp >0 && agc_resp <= 5) || (agc_resp == 84)) { // DL1YCF: added () to improve readability active_receiver->agc = AGC_FAST; // fprintf(stderr,"GT command FAST\n"); - } else if(agc_resp >6 && agc_resp <= 10 || (agc_resp == 2*84)) { + } else if((agc_resp >6 && agc_resp <= 10) || (agc_resp == 2*84)) { // DL1YCF: added () to improve readability active_receiver->agc = AGC_MEDIUM; // fprintf(stderr,"GT command MED\n"); - } else if(agc_resp >11 && agc_resp <= 15 || (agc_resp == 3*84)) { + } else if((agc_resp >11 && agc_resp <= 15) || (agc_resp == 3*84)) { // DL1YCF: added () to improve readability active_receiver->agc = AGC_SLOW; //fprintf(stderr,"GT command SLOW\n"); - } else if(agc_resp >16 && agc_resp <= 20 || (agc_resp == 4*84)) { + } else if((agc_resp >16 && agc_resp <= 20) || (agc_resp == 4*84)) { // DL1YCF: added () to improve readability active_receiver->agc = AGC_LONG; // fprintf(stderr,"GT command LONG\n"); } @@ -2019,8 +2023,8 @@ void parse_cmd ( char * cmd_input,int len,int client_sock) { send_resp(client_sock,"IS00000;"); } } - else if((strcmp(cmd_str,"KS")==0) && (zzid_flag == 0) || - (strcmp(cmd_str,"CS")==0) && (zzid_flag==1)) { + else if(((strcmp(cmd_str,"KS")==0) && (zzid_flag == 0)) || // Dl1YCF added () to improve readablity + ((strcmp(cmd_str,"CS")==0) && (zzid_flag==1))) { // Dl1YCF added () to improve readablity // TS-2000 - KS - Set/Reads keying speed 0-060 max // PiHPSDR - ZZCS - Sets/Reads Keying speed if(len <=2) { @@ -3053,8 +3057,8 @@ void parse_cmd ( char * cmd_input,int len,int client_sock) { send_resp(client_sock,"?;"); } } - else if((strcmp(cmd_str,"SD")==0) && (zzid_flag == 0) || - (strcmp(cmd_str,"CD")==0) && (zzid_flag ==1)) { + else if(((strcmp(cmd_str,"SD")==0) && (zzid_flag == 0)) || // Dl1YCF added () to improve readablity + ((strcmp(cmd_str,"CD")==0) && (zzid_flag ==1))) { // Dl1YCF added () to improve readablity // PiHPSDR - ZZCD - Set/Read CW Keyer Hang Time // TS-2000 - SD - Set/Read Break In Delay // @@ -3123,7 +3127,8 @@ void parse_cmd ( char * cmd_input,int len,int client_sock) { // Determine how high above 127 we are..making a range of 114 from S0 to S9+60db // 5 is a fugdge factor that shouldn't be there - but seems to get us to S9=SM015 - level = abs(127+(level + (double) adc_attenuation[receiver[r]->adc]))+5; + // DL1YCF replaced abs by fabs, and changed 127 to floating point constant + level = fabs(127.0+(level + (double) adc_attenuation[receiver[r]->adc]))+5; // Clip the value just in case if(cmd_input[2] == '0') { @@ -3839,10 +3844,10 @@ int rigctlGetMode() { } } - -int rigctlSetFilterLow(int val){ +// Changed these two functions to void +void rigctlSetFilterLow(int val){ }; -int rigctlSetFilterHigh(int val){ +void rigctlSetFilterHigh(int val){ }; void set_freqB(long long new_freqB) { diff --git a/rx_menu.c b/rx_menu.c index 704de48..3e83bb1 100644 --- a/rx_menu.c +++ b/rx_menu.c @@ -113,12 +113,19 @@ static void mute_radio_cb(GtkWidget *widget, gpointer data) { active_receiver->mute_radio=gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)); } +// +// DL1YCF: +// possible the device has been changed: +// call audo_close_output with old device, audio_open_output with new one +// static void local_output_changed_cb(GtkWidget *widget, gpointer data) { - active_receiver->audio_device=(int)(long)data; -fprintf(stderr,"local_output_changed rx=%d to %d\n",active_receiver->id,active_receiver->audio_device); +//active_receiver->audio_device=(int)(long)data; + int newdev = (int)(long)data; +fprintf(stderr,"local_output_changed rx=%d from %d to %d\n",active_receiver->id,active_receiver->audio_device,newdev); if(active_receiver->local_audio) { - audio_close_output(active_receiver); - if(audio_open_output(active_receiver)==0) { + audio_close_output(active_receiver); // audio_close with OLD device + active_receiver->audio_device=newdev; // update rx to NEW device + if(audio_open_output(active_receiver)==0) { // audio_open with NEW device active_receiver->local_audio=1; } else { active_receiver->local_audio=0; diff --git a/stemlab_discovery.c b/stemlab_discovery.c index 1927884..e3ceb7d 100644 --- a/stemlab_discovery.c +++ b/stemlab_discovery.c @@ -17,6 +17,208 @@ * */ +#ifdef __APPLE__ + + +// +// MacOS has no vahi, but it does have libcurl. +// Therefore we try to start the SDR app on the RedPitaya +// assuming is has the (fixed) ip address which can be +// read from $HOME/.rp.inet, if this does not succeed it +// defaults to 192.168.1.3. +// +// So, on MacOS, just configure your STEMLAB/HAMLAB to this +// fixed IP address and you need not open a browser to start +// SDR *before* you can use piHPSDR. +// +// Sure it's not perfect, but it makes life much easier for me. +// +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <curl/curl.h> +#include <glib.h> + +extern void status_text(const char *); + +static const char *appid = NULL; + +// +// Extract the list of apps from the JSON answer +// +static size_t app_list_callback(void *buffer, size_t size, size_t nmemb, void *data) { + const gchar *needle; + + needle="\"sdr_receiver_hpsdr\""; + if (g_strstr_len(buffer, size*nmemb, needle) != NULL) { + appid="sdr_receiver_hpsdr"; + } + + needle="\"sdr_transceiver_hpsdr\""; + if (g_strstr_len(buffer, size*nmemb, needle) != NULL) { + appid="sdr_transceiver_hpsdr"; + } + + needle="\"stemlab_sdr_transceiver_hpsdr\""; + if (g_strstr_len(buffer, size*nmemb, needle) != NULL) { + appid="stemlab_sdr_transceiver_hpsdr"; + } + + needle="\"hamlab_sdr_transceiver_hpsdr\""; + if (g_strstr_len(buffer, size*nmemb, needle) != NULL) { + appid="hamlab_sdr_transceiver_hpsdr"; + } + + if (appid) fprintf(stderr,"RedPitay WEB application to start: %s\n", appid); + return size * nmemb; +} + +void stemlab_discovery() { + // this one is used "as the last resort", if nothing else is found. + size_t len; + char inet[20]; + char txt[150]; + CURL *curl_handle; + CURLcode curl_error; + FILE *fpin; + char *p; + + fprintf(stderr,"Stripped-down STEMLAB/HAMLAB discovery...\n"); +// +// Try to read inet addr from $HOME/.rp.inet, otherwise take 192.168.1.3 +// + strcpy(inet,"192,168.1.3"); + p=getenv("HOME"); + if (p) { + strncpy(txt,p, (size_t) 100); // way less than size of txt + } else { + strcpy(txt,"."); + } + strcat(txt,"/.rp.inet"); + fprintf(stderr,"Trying to read inet addr from file=%s\n", txt); + fpin=fopen(txt, "r"); + if (fpin) { + len=100; + p=txt; + len=getline(&p, &len, fpin); + // not txt now contains the trailing newline character + while (*p != 0) { + if (*p == '\n') *p = 0; + p++; + } + if (len < 20) strcpy(inet,txt); + } + fclose(fpin); + fprintf(stderr,"STEMLAB: using inet addr %s\n", inet); +// +// Do a HEAD request (poor curl's ping) to see whether the device is on-line +// allow a 15 sec time-out + status_text("Looking for a STEMLAB web server ..."); + curl_handle = curl_easy_init(); + if (curl_handle == NULL) { + fprintf(stderr, "stemlab_start: Failed to create cURL handle\n"); + return; + } + sprintf(txt,"http://%s",inet); + curl_error = curl_easy_setopt(curl_handle, CURLOPT_URL, txt); + curl_error = curl_easy_setopt(curl_handle, CURLOPT_NOBODY, (long) 1); + curl_error = curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, (long) 15); + curl_error = curl_easy_perform(curl_handle); + curl_easy_cleanup(curl_handle); + if (curl_error == CURLE_OPERATION_TIMEDOUT) { + sprintf(txt,"No response from web server at %s", inet); + status_text(txt); + fprintf(stderr,"%s\n",txt); + } + if (curl_error != CURLE_OK) { + fprintf(stderr, "STEMLAB ping error: %s\n", curl_easy_strerror(curl_error)); + return; + } + +// +//obtain a list of apps, and choose the right one by looking for the following +//target strings (in that order). Whatever is found first, is started. Then, we rely +//on the original discovery() to discover the device. +// +//hamlab_sdr_transceiver_hpsdr +//stemlab_sdr_transceiver_hpsdr +//sdr_transceiver_hpsdr +//sdr_receiver_hpsdr +// + curl_handle = curl_easy_init(); + if (curl_handle == NULL) { + fprintf(stderr, "stemlab_start: Failed to create cURL handle\n"); + return; + } + sprintf(txt,"http://%s/bazaar?apps=", inet); + curl_error = curl_easy_setopt(curl_handle, CURLOPT_URL, txt); + curl_error = curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, (long) 60); + curl_error = curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, app_list_callback); + curl_error = curl_easy_perform(curl_handle); + curl_easy_cleanup(curl_handle); + if (curl_error == CURLE_OPERATION_TIMEDOUT) { + status_text("No Response from RedPitaya in 60 secs"); + fprintf(stderr,"60-sec TimeOut met when trying to get list of HPSDR apps from RedPitaya\n"); + } + if (curl_error != CURLE_OK) { + fprintf(stderr, "STEMLAB app-list error: %s\n", curl_easy_strerror(curl_error)); + return; + } + +// +// Now we actually start the hpsdr application +// Actually, try to stop it first, then re-start it. +// + if (appid) { + curl_handle = curl_easy_init(); + if (curl_handle == NULL) { + fprintf(stderr, "stemlab_start: Failed to create cURL handle\n"); + return; + } + sprintf(txt,"http://%s/bazaar?stop=%s",inet,appid); + curl_error = curl_easy_setopt(curl_handle, CURLOPT_URL, txt); + curl_error = curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, (long) 60); + curl_error = curl_easy_perform(curl_handle); + if (curl_error == CURLE_OPERATION_TIMEDOUT) { + fprintf(stderr,"60-sec TimeOut met when trying to stop HPSDR app on RedPitaya\n"); + } + if (curl_error != CURLE_OK) { + fprintf(stderr, "STEMLAB app-start error: %s\n", curl_easy_strerror(curl_error)); + } + curl_handle = curl_easy_init(); + if (curl_handle == NULL) { + fprintf(stderr, "stemlab_start: Failed to create cURL handle\n"); + return; + } + sprintf(txt,"http://%s/bazaar?start=%s",inet,appid); + curl_error = curl_easy_setopt(curl_handle, CURLOPT_URL, txt); + curl_error = curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, (long) 60); + curl_error = curl_easy_perform(curl_handle); + if (curl_error == CURLE_OPERATION_TIMEDOUT) { + fprintf(stderr,"60-sec TimeOut met when trying to start HPSDR app on RedPitaya\n"); + } + if (curl_error != CURLE_OK) { + fprintf(stderr, "STEMLAB app-start error: %s\n", curl_easy_strerror(curl_error)); + } + + } + // Whether or net we have successfully started the HPSDR application on the RedPitaya, + // we now return to the regular HPSDR protocol handling code that will eventually detect + // the "board". If this code does not work, you have to open a browser and start the HPSDR + // application manually. +} + +// dummy function +void stemlab_cleanup() { +} + +// dummy function, never called +void stemlab_start_app() { +} + +#else + #include <errno.h> #include <ifaddrs.h> #include <net/if.h> @@ -324,3 +526,4 @@ void stemlab_cleanup(void) { curl_global_cleanup(); } } +#endif diff --git a/step_menu.c b/step_menu.c index d1d0a87..233a5b0 100644 --- a/step_menu.c +++ b/step_menu.c @@ -52,6 +52,10 @@ static gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer user_d 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; } void step_menu(GtkWidget *parent) { diff --git a/store_menu.c b/store_menu.c index 80f3871..2bb38a9 100644 --- a/store_menu.c +++ b/store_menu.c @@ -83,6 +83,10 @@ static gboolean store_select_cb (GtkWidget *widget, gpointer data) { // Save in the file now.. memSaveState(); + // 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 gboolean recall_select_cb (GtkWidget *widget, gpointer data) { @@ -107,6 +111,10 @@ static gboolean recall_select_cb (GtkWidget *widget, gpointer data) { vfo_mode_changed(mem[index].mode); g_idle_add(ext_vfo_update,NULL); + // 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; } void store_menu(GtkWidget *parent) { diff --git a/transmitter.c b/transmitter.c index d2c12ec..aeac250 100644 --- a/transmitter.c +++ b/transmitter.c @@ -63,6 +63,8 @@ static int waterfall_resample=8; int key = 0; +// DL1YCF added next line. +extern void cw_audio_write(double sample); static gint update_out_of_band(gpointer data) { TRANSMITTER *tx=(TRANSMITTER *)data; tx->out_of_band=0; diff --git a/tx_menu.c b/tx_menu.c index a746fe1..7e08fc9 100644 --- a/tx_menu.c +++ b/tx_menu.c @@ -160,6 +160,10 @@ static void local_input_changed_cb(GtkWidget *widget, gpointer data) { static gboolean emp_cb (GtkWidget *widget, gpointer data) { pre_emphasize=gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)); tx_set_pre_emphasize(transmitter,pre_emphasize); + // 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 tune_value_changed_cb(GtkWidget *widget, gpointer data) { diff --git a/tx_panadapter.c b/tx_panadapter.c index eaf6e8f..43f6e5c 100644 --- a/tx_panadapter.c +++ b/tx_panadapter.c @@ -144,7 +144,8 @@ tx_panadapter_motion_notify_event_cb (GtkWidget *widget, &x, &y, &state); - if((state & GDK_BUTTON1_MASK == GDK_BUTTON1_MASK) || pressed) { + // DL1YCF: added a pair of () to fix an error + if(((state & GDK_BUTTON1_MASK) == GDK_BUTTON1_MASK) || pressed) { int moved=last_x-x; vfo_move((long long)((float)moved*hz_per_pixel)); last_x=x; @@ -164,6 +165,10 @@ tx_panadapter_scroll_event_cb (GtkWidget *widget, } 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; } void tx_panadapter_update(TRANSMITTER *tx) { diff --git a/vfo.c b/vfo.c index 61262d8..a279a3a 100644 --- a/vfo.c +++ b/vfo.c @@ -513,6 +513,10 @@ vfo_scroll_event_cb (GtkWidget *widget, } 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; } diff --git a/vfo_menu.c b/vfo_menu.c index e2356ea..e47936d 100644 --- a/vfo_menu.c +++ b/vfo_menu.c @@ -161,6 +161,10 @@ static gboolean freqent_select_cb (GtkWidget *widget, gpointer 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; } static void rit_cb(GtkComboBox *widget,gpointer data) { diff --git a/vox_menu.c b/vox_menu.c index 460ac43..e14d5f2 100644 --- a/vox_menu.c +++ b/vox_menu.c @@ -91,6 +91,8 @@ static gpointer level_thread(gpointer arg) { g_idle_add(level_update,NULL); usleep(100000); // 100ms } + // DL1YCF added return statement to make compilers happy. + return NULL; } static void cleanup() { diff --git a/waterfall.c b/waterfall.c index b673f80..bccebcc 100644 --- a/waterfall.c +++ b/waterfall.c @@ -63,7 +63,8 @@ waterfall_configure_event_cb (GtkWidget *widget, display_height=gtk_widget_get_allocated_height (widget); rx->pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, display_width, display_height); - char *pixels = gdk_pixbuf_get_pixels (rx->pixbuf); + // DL1YCF changed to uchar + unsigned char *pixels = gdk_pixbuf_get_pixels (rx->pixbuf); memset(pixels, 0, display_width*display_height*3); @@ -122,7 +123,8 @@ void waterfall_update(RECEIVER *rx) { float *samples; if(rx->pixbuf) { - char *pixels = gdk_pixbuf_get_pixels (rx->pixbuf); + // DL1YCF changed to uchar + unsigned char *pixels = gdk_pixbuf_get_pixels (rx->pixbuf); int width=gdk_pixbuf_get_width(rx->pixbuf); int height=gdk_pixbuf_get_height(rx->pixbuf); @@ -169,7 +171,8 @@ void waterfall_update(RECEIVER *rx) { float sample; int average=0; - char *p; + // DL1YCF changed to uchar + unsigned char *p; p=pixels; samples=rx->pixel_samples; for(i=0;i<width;i++) { -- 2.45.2