From 8e34ef51308ed5628e275f8ef29894eb926b6f1b Mon Sep 17 00:00:00 2001
From: John Melton G0ORX <john.d.melton@googlemail.com>
Date: Thu, 25 Feb 2021 16:32:34 +0000
Subject: [PATCH] One Makefile.  Added MIDI configuration menu.

---
 Makefile          |   98 ++--
 Makefile.mac      |  514 -------------------
 adc.h             |    2 +-
 alsa_midi.c       |  138 +++--
 band_menu.c       |    2 +
 dac.h             |    2 +-
 discovered.h      |    5 +
 discovery.c       |    8 +-
 display_menu.c    |   36 +-
 ext.c             |   48 ++
 ext.h             |    4 +
 gpio.c            |  137 ++++-
 gpio.h            |   14 +
 i2c.c             |    2 +
 iambic.c          |    3 +-
 mac_midi.c        |  120 ++++-
 main.h            |    3 +-
 midi.h            |   77 ++-
 midi2.c           |  237 ++++++---
 midi3.c           |  176 ++++++-
 midi_menu.c       | 1222 +++++++++++++++++++++++++++++++++++++++++++++
 midi_menu.h       |   25 +
 new_menu.c        |   30 +-
 new_protocol.c    |   22 +-
 portaudio.c       |    3 +-
 radio.c           |  223 +++++----
 radio.h           |   27 +-
 radio_menu.c      |  130 ++---
 receiver.c        |    2 +
 receiver.h        |    1 -
 sliders.c         |    4 +-
 soapy_discovery.c |  132 +++--
 soapy_protocol.c  |  122 +++--
 soapy_protocol.h  |    4 +-
 switch_menu.c     |    2 +-
 tx_menu.c         |    2 +
 vfo.c             |   15 +-
 vfo.h             |    3 +
 38 files changed, 2578 insertions(+), 1017 deletions(-)
 delete mode 100644 Makefile.mac
 create mode 100644 midi_menu.c
 create mode 100644 midi_menu.h

diff --git a/Makefile b/Makefile
index 7c41f2c..742ecf5 100644
--- a/Makefile
+++ b/Makefile
@@ -1,12 +1,10 @@
+# get the OS Name
+UNAME_S := $(shell uname -s)
+
 # Get git commit version and date
 GIT_DATE := $(firstword $(shell git --no-pager show --date=short --format="%ai" --name-only))
 GIT_VERSION := $(shell git describe --abbrev=0 --tags)
 
-ISSUE := $(shell cat /etc/issue.net)
-ifneq ($(filter %Raspbian,$(ISSUE)),)
-	OSFLAG=-D RASPBIAN
-endif
-
 # uncomment the following line to force 480x320 screen
 #SMALL_SCREEN_OPTIONS=-D SMALL_SCREEN
 
@@ -28,7 +26,7 @@ PURESIGNAL_INCLUDE=PURESIGNAL
 #LOCALCW_INCLUDE=LOCALCW
 
 # uncomment the line below for SoapySDR
-#SOAPYSDR_INCLUDE=SOAPYSDR
+SOAPYSDR_INCLUDE=SOAPYSDR
 
 # uncomment the line to below include support for sx1509 i2c expander
 #SX1509_INCLUDE=sx1509
@@ -40,7 +38,7 @@ PURESIGNAL_INCLUDE=PURESIGNAL
 #STEMLAB_DISCOVERY=STEMLAB_DISCOVERY_NOAVAHI
 
 # uncomment the line below to include MIDI support
-#MIDI_INCLUDE=MIDI
+MIDI_INCLUDE=MIDI
 
 # uncomment the line below for various debug facilities
 #DEBUG_OPTION=-D DEBUG
@@ -55,11 +53,18 @@ LINK=gcc
 
 ifeq ($(MIDI_INCLUDE),MIDI)
 MIDI_OPTIONS=-D MIDI
-MIDI_SOURCES= alsa_midi.c midi2.c midi3.c
-MIDI_HEADERS= midi.h
-MIDI_OBJS= alsa_midi.o midi2.o midi3.o
+MIDI_HEADERS= midi.h midi_menu.h
+ifeq ($(UNAME_S), Darwin)
+MIDI_SOURCES= mac_midi.c midi2.c midi3.c
+MIDI_OBJS= mac_midi.o midi2.o midi3.o midi_menu.o
+MIDI_LIBS= -framework CoreMIDI -framework Foundation
+endif
+ifeq ($(UNAME_S), Linux)
+MIDI_SOURCES= alsa_midi.c midi2.c midi3.c midi_menu.c
+MIDI_OBJS= alsa_midi.o midi2.o midi3.o midi_menu.o
 MIDI_LIBS= -lasound
 endif
+endif
 
 ifeq ($(PURESIGNAL_INCLUDE),PURESIGNAL)
 PURESIGNAL_OPTIONS=-D PURESIGNAL
@@ -123,6 +128,10 @@ PTT_OPTIONS=-D PTT
 endif
 
 ifeq ($(GPIO_INCLUDE),GPIO)
+GPIOD_VERSION=$(shell pkg-config --modversion libgpiod)
+ifeq ($(GPIOD_VERSION),1.2)
+GPIOD_OPTIONS=-D OLD_GPIOD
+endif
 GPIO_OPTIONS=-D GPIO
 GPIO_LIBS=-lgpiod -li2c
 GPIO_SOURCES= \
@@ -182,18 +191,33 @@ endif
 GTKINCLUDES=`pkg-config --cflags gtk+-3.0`
 GTKLIBS=`pkg-config --libs gtk+-3.0`
 
+ifeq ($(UNAME_S), Linux)
 AUDIO_LIBS=-lpulse-simple -lpulse -lpulse-mainloop-glib
-//AUDIO_LIBS=-lasound
+AUDIO_SOURCES=pulseaudio.c
+AUDIO_OBJS=pulseaudio.o
+endif
+ifeq ($(UNAME_S), Darwin)
+AUDIO_OPTIONS=-DPORTAUDIO
+AUDIO_LIBS=-lportaudio
+AUDIO_SOURCES=portaudio.c
+AUDIO_OBJS=portaudio.o
+endif
 
 CFLAGS=	-g -Wno-deprecated-declarations -O3
 OPTIONS=$(SMALL_SCREEN_OPTIONS) $(MIDI_OPTIONS) $(PURESIGNAL_OPTIONS) $(REMOTE_OPTIONS) $(USBOZY_OPTIONS) \
-	$(GPIO_OPTIONS) $(SOAPYSDR_OPTIONS) $(LOCALCW_OPTIONS) \
+	$(GPIO_OPTIONS) $(GPIOD_OPTIONS) $(SOAPYSDR_OPTIONS) $(LOCALCW_OPTIONS) \
 	$(STEMLAB_OPTIONS) \
         $(PTT_OPTIONS) \
 	$(SERVER_OPTIONS) \
-	-D GIT_DATE='"$(GIT_DATE)"' -D GIT_VERSION='"$(GIT_VERSION)"' $(DEBUG_OPTION) $(OSFLAG)
+	$(AUDIO_OPTIONS) \
+	-D GIT_DATE='"$(GIT_DATE)"' -D GIT_VERSION='"$(GIT_VERSION)"' $(DEBUG_OPTION)
+
+
+ifeq ($(UNAME_S), Linux)
+RT_OPTION=-lrt
+endif
 
-LIBS=-lrt -lm -lwdsp -lpthread $(AUDIO_LIBS) $(USBOZY_LIBS) $(GTKLIBS) $(GPIO_LIBS) $(SOAPYSDRLIBS) $(STEMLAB_LIBS) $(MIDI_LIBS)
+LIBS=$(RT_OPTION) -lm -lwdsp -lpthread $(AUDIO_LIBS) $(USBOZY_LIBS) $(GTKLIBS) $(GPIO_LIBS) $(SOAPYSDRLIBS) $(STEMLAB_LIBS) $(MIDI_LIBS)
 INCLUDES=$(GTKINCLUDES)
 
 COMPILE=$(CC) $(CFLAGS) $(OPTIONS) $(INCLUDES)
@@ -204,7 +228,6 @@ COMPILE=$(CC) $(CFLAGS) $(OPTIONS) $(INCLUDES)
 PROGRAM=pihpsdr
 
 SOURCES= \
-pulseaudio.c \
 band.c \
 discovered.c \
 discovery.c \
@@ -270,11 +293,16 @@ error_handler.c \
 cwramp.c \
 protocols.c \
 css.c \
-actions.c
+actions.c \
+configure.c \
+i2c.c \
+gpio.c \
+encoder_menu.c \
+switch_menu.c
+
 
 
 HEADERS= \
-pulseaudio.h \
 agc.h \
 alex.h \
 band.h \
@@ -341,11 +369,16 @@ ext.h \
 error_handler.h \
 protocols.h \
 css.h \
-actions.h
+actions.h \
+configure.h \
+i2c.h \
+gpio.h \
+encoder_menu.h \
+switch_menu.h
+
 
 
 OBJS= \
-pulseaudio.o \
 band.o \
 discovered.o \
 discovery.o \
@@ -411,21 +444,26 @@ error_handler.o \
 cwramp.o \
 protocols.o \
 css.o \
-actions.o
-
-$(PROGRAM):  $(OBJS) $(REMOTE_OBJS) $(USBOZY_OBJS) $(SOAPYSDR_OBJS) \
-		$(LOCALCW_OBJS) $(GPIO_OBJS) $(PURESIGNAL_OBJS) \
+actions.o \
+configure.o \
+i2c.o \
+gpio.o \
+encoder_menu.o \
+switch_menu.o
+
+$(PROGRAM):  $(OBJS) $(AUDIO_OBJS) $(REMOTE_OBJS) $(USBOZY_OBJS) $(SOAPYSDR_OBJS) \
+		$(LOCALCW_OBJS) $(PURESIGNAL_OBJS) \
 		$(MIDI_OBJS) $(STEMLAB_OBJS) $(SERVER_OBJS)
-	$(LINK) -o $(PROGRAM) $(OBJS) $(REMOTE_OBJS) $(USBOZY_OBJS) $(GPIO_OBJS) \
+	$(LINK) -o $(PROGRAM) $(OBJS) $(AUDIO_OBJS) $(REMOTE_OBJS) $(USBOZY_OBJS) \
 		$(SOAPYSDR_OBJS) $(LOCALCW_OBJS) $(PURESIGNAL_OBJS) \
 		$(MIDI_OBJS) $(STEMLAB_OBJS) $(SERVER_OBJS) $(LIBS)
 
 .PHONY:	all
-all:	prebuild  $(PROGRAM) $(HEADERS) $(USBOZY_HEADERS) $(SOAPYSDR_HEADERS) \
-	$(LOCALCW_HEADERS) $(GPIO_HEADERS) \
+all:	prebuild  $(PROGRAM) $(HEADERS) $(AUDIO_HEADERS) $(USBOZY_HEADERS) $(SOAPYSDR_HEADERS) \
+	$(LOCALCW_HEADERS) \
 	$(PURESIGNAL_HEADERS) $(MIDI_HEADERS) $(STEMLAB_HEADERS) $(SERVER_HEADERS)\
-	$(SOURCES) \
-	$(USBOZY_SOURCES) $(SOAPYSDR_SOURCES) $(LOCALCW_SOURCE) $(GPIO_SOURCES) \
+	$(AUDIO_SOURCES) $(SOURCES) \
+	$(USBOZY_SOURCES) $(SOAPYSDR_SOURCES) $(LOCALCW_SOURCE)
 	$(PURESIGNAL_SOURCES) $(MIDI_SOURCES) $(STEMLAB_SOURCES) $(SERVER_SOURCES)
 
 .PHONY:	prebuild
@@ -443,8 +481,8 @@ CPPINCLUDES:=$(shell echo $(INCLUDES) | sed -e "s/-pthread / /" )
 
 .PHONY:	cppcheck
 cppcheck:
-	cppcheck $(CPPOPTIONS) $(OPTIONS) $(CPPINCLUDES) $(SOURCES) $(REMOTE_SOURCES) \
-	$(USBOZY_SOURCES) $(SOAPYSDR_SOURCES) $(GPIO_SOURCES) \
+	cppcheck $(CPPOPTIONS) $(OPTIONS) $(CPPINCLUDES) $(AUDIO_SOURCES) $(SOURCES) $(REMOTE_SOURCES) \
+	$(USBOZY_SOURCES) $(SOAPYSDR_SOURCES) \
 	$(PURESIGNAL_SOURCES) $(MIDI_SOURCES) $(STEMLAB_SOURCES) $(LOCALCW_SOURCES) \
 	$(SERVER_SOURCES)
 
diff --git a/Makefile.mac b/Makefile.mac
deleted file mode 100644
index 6d6fdec..0000000
--- a/Makefile.mac
+++ /dev/null
@@ -1,514 +0,0 @@
-# Get git commit version and date
-GIT_DATE := $(firstword $(shell git --no-pager show --date=short --format="%ai" --name-only))
-GIT_VERSION := $(shell git describe --abbrev=0 --tags)
-
-# uncomment the line below to include USB Ozy support
-# USBOZY_INCLUDE=USBOZY
-
-# uncomment the line below to include Pure Signal support
-PURESIGNAL_INCLUDE=PURESIGNAL
-
-# uncomment the line to below include support local CW keyer
-LOCALCW_INCLUDE=LOCALCW
-
-# uncomment the line below for SoapySDR
-#SOAPYSDR_INCLUDE=SOAPYSDR
-
-# uncomment the line to below include support for sx1509 i2c expander
-#SX1509_INCLUDE=sx1509
-
-# uncomment the line below to include support for STEMlab discovery (WITH AVAHI)
-#STEMLAB_DISCOVERY=STEMLAB_DISCOVERY
-
-# uncomment the line below to include support for STEMlab discovery (WITHOUT AVAHI)
-STEMLAB_DISCOVERY=STEMLAB_DISCOVERY_NOAVAHI
-
-# uncomment the line below to include MIDI support
-MIDI_INCLUDE=MIDI
-
-# very early code not included yet
-#SERVER_INCLUDE=SERVER
-
-# uncomment the line below for various debug facilities
-#DEBUG_OPTION=-D DEBUG
-
-CC=gcc
-LINK=gcc
-
-ifeq ($(CONTROLLER2_V2_INCLUDE),CONTROLLER2_V2)
-CONTROLLER2_OPTIONS=-D CONTROLLER2_V2
-endif
-ifeq ($(CONTROLLER2_V1_INCLUDE),CONTROLLER2_V1)
-CONTROLLER2_OPTIONS=-D CONTROLLER2_V1
-endif
-
-ifeq ($(MIDI_INCLUDE),MIDI)
-MIDI_OPTIONS=-D MIDI
-MIDI_SOURCES= mac_midi.c midi2.c midi3.c
-MIDI_HEADERS= midi.h
-MIDI_OBJS= mac_midi.o midi2.o midi3.o
-MIDI_LIBS= -framework CoreMIDI -framework Foundation
-endif
-
-ifeq ($(PURESIGNAL_INCLUDE),PURESIGNAL)
-PURESIGNAL_OPTIONS=-D PURESIGNAL
-PURESIGNAL_SOURCES= \
-ps_menu.c
-PURESIGNAL_HEADERS= \
-ps_menu.h
-PURESIGNAL_OBJS= \
-ps_menu.o
-endif
-
-ifeq ($(REMOTE_INCLUDE),REMOTE)
-REMOTE_OPTIONS=-D REMOTE
-REMOTE_SOURCES= \
-remote_radio.c \
-remote_receiver.c
-REMOTE_HEADERS= \
-remote_radio.h \
-remote_receiver.h
-REMOTE_OBJS= \
-remote_radio.o \
-remote_receiver.o
-endif
-
-ifeq ($(USBOZY_INCLUDE),USBOZY)
-USBOZY_OPTIONS=-D USBOZY
-USBOZY_LIBS=-lusb-1.0
-USBOZY_SOURCES= \
-ozyio.c
-USBOZY_HEADERS= \
-ozyio.h
-USBOZY_OBJS= \
-ozyio.o
-endif
-
-ifeq ($(SOAPYSDR_INCLUDE),SOAPYSDR)
-SOAPYSDR_OPTIONS=-D SOAPYSDR
-SOAPYSDRLIBS=-lSoapySDR
-SOAPYSDR_SOURCES= \
-soapy_discovery.c \
-soapy_protocol.c
-SOAPYSDR_HEADERS= \
-soapy_discovery.h \
-soapy_protocol.h
-SOAPYSDR_OBJS= \
-soapy_discovery.o \
-soapy_protocol.o
-endif
-
-ifeq ($(LOCALCW_INCLUDE),LOCALCW)
-LOCALCW_OPTIONS=-D LOCALCW
-LOCALCW_SOURCES= iambic.c
-LOCALCW_HEADERS= iambic.h
-LOCALCW_OBJS   = iambic.o
-endif
-
-#
-# We have two versions of STEMLAB_DISCOVERY here,
-# the second one has to be used
-# if you do not have the avahi (devel-) libraries
-# on your system.
-#
-ifeq ($(STEMLAB_DISCOVERY), STEMLAB_DISCOVERY)
-STEMLAB_OPTIONS=-D STEMLAB_DISCOVERY \
-  `pkg-config --cflags avahi-gobject` \
-  `pkg-config --cflags libcurl`
-STEMLAB_LIBS=`pkg-config --libs avahi-gobject` `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_NOAVAHI)
-STEMLAB_OPTIONS=-D STEMLAB_DISCOVERY -D NO_AVAHI `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 ($(SERVER_INCLUDE), SERVER)
-SERVER_OPTIONS=-D SERVER
-SERVER_SOURCES= \
-hpsdr_server.c
-SERVER_HEADERS= \
-hpsdr_server.h
-SERVER_OBJS= \
-hpsdr_server.o
-endif
-
-GTKINCLUDES=`pkg-config --cflags gtk+-3.0`
-GTKLIBS=`pkg-config --libs gtk+-3.0`
-
-AUDIO_OPTIONS=-DPORTAUDIO
-AUDIO_LIBS=-lportaudio -O3
-
-CFLAGS=	-g -Wno-deprecated-declarations
-OPTIONS=$(MIDI_OPTIONS) $(PURESIGNAL_OPTIONS) $(REMOTE_OPTIONS) $(USBOZY_OPTIONS) \
-	$(SOAPYSDR_OPTIONS) $(LOCALCW_OPTIONS) \
-	$(STEMLAB_OPTIONS) \
-        $(CONTROLLER2_OPTIONS) $(AUDIO_OPTIONS) \
-	-D GIT_DATE='"$(GIT_DATE)"' -D GIT_VERSION='"$(GIT_VERSION)"' $(DEBUG_OPTION)
-
-LIBS=     -lm -lwdsp -lpthread $(AUDIO_LIBS) $(USBOZY_LIBS) $(GTKLIBS) $(SOAPYSDRLIBS) $(STEMLAB_LIBS) $(MIDI_LIBS)
-INCLUDES=$(GTKINCLUDES)
-
-COMPILE=$(CC) $(CFLAGS) $(OPTIONS) $(INCLUDES)
-
-.c.o:
-	$(COMPILE) -c -o $@ $<
-
-PROGRAM=pihpsdr
-
-SOURCES= \
-audio.c \
-band.c \
-configure.c \
-discovered.c \
-discovery.c \
-filter.c \
-main.c \
-new_menu.c \
-about_menu.c \
-exit_menu.c \
-radio_menu.c \
-rx_menu.c \
-ant_menu.c \
-display_menu.c \
-dsp_menu.c \
-pa_menu.c \
-cw_menu.c \
-oc_menu.c \
-portaudio.c \
-xvtr_menu.c \
-equalizer_menu.c \
-step_menu.c \
-meter_menu.c \
-band_menu.c \
-bandstack_menu.c \
-mode_menu.c \
-filter_menu.c \
-noise_menu.c \
-agc_menu.c \
-vox_menu.c \
-fft_menu.c \
-diversity_menu.c \
-tx_menu.c \
-vfo_menu.c \
-test_menu.c \
-meter.c \
-mode.c \
-old_discovery.c \
-new_discovery.c \
-old_protocol.c \
-new_protocol.c \
-new_protocol_programmer.c \
-rx_panadapter.c \
-tx_panadapter.c \
-property.c \
-radio.c \
-receiver.c \
-rigctl.c \
-rigctl_menu.c \
-toolbar.c \
-transmitter.c \
-zoompan.c \
-sliders.c \
-version.c \
-vfo.c \
-waterfall.c \
-button_text.c \
-vox.c \
-update.c \
-store.c \
-store_menu.c \
-memory.c \
-led.c \
-ext.c \
-error_handler.c \
-cwramp.c \
-protocols.c
-
-
-HEADERS= \
-audio.h \
-agc.h \
-alex.h \
-band.h \
-configure.h \
-bandstack.h \
-channel.h \
-discovered.h \
-discovery.h \
-filter.h \
-new_menu.h \
-about_menu.h \
-rx_menu.h \
-exit_menu.h \
-radio_menu.h \
-ant_menu.h \
-display_menu.h \
-dsp_menu.h \
-pa_menu.h \
-cw_menu.h \
-oc_menu.h \
-xvtr_menu.h \
-equalizer_menu.h \
-step_menu.h \
-meter_menu.h \
-band_menu.h \
-bandstack_menu.h \
-mode_menu.h \
-filter_menu.h \
-noise_menu.h \
-agc_menu.h \
-vox_menu.h \
-fft_menu.h \
-diversity_menu.h \
-tx_menu.h \
-vfo_menu.h \
-test_menu.h \
-meter.h \
-mode.h \
-old_discovery.h \
-new_discovery.h \
-old_protocol.h \
-new_protocol.h \
-rx_panadapter.h \
-tx_panadapter.h \
-property.h \
-radio.h \
-receiver.h \
-rigctl.h \
-rigctl_menu.h \
-toolbar.h \
-transmitter.h \
-zoompan.h \
-sliders.h \
-version.h \
-vfo.h \
-waterfall.h \
-button_text.h \
-vox.h \
-update.h \
-store.h \
-store_menu.h \
-memory.h \
-led.h \
-ext.h \
-error_handler.h \
-protocols.h
-
-
-OBJS= \
-audio.o \
-portaudio.o \
-band.o \
-configure.o \
-discovered.o \
-discovery.o \
-filter.o \
-version.o \
-main.o \
-new_menu.o \
-about_menu.o \
-rx_menu.o \
-exit_menu.o \
-radio_menu.o \
-ant_menu.o \
-display_menu.o \
-dsp_menu.o \
-pa_menu.o \
-cw_menu.o \
-oc_menu.o \
-xvtr_menu.o \
-equalizer_menu.o \
-step_menu.o \
-meter_menu.o \
-band_menu.o \
-bandstack_menu.o \
-mode_menu.o \
-filter_menu.o \
-noise_menu.o \
-agc_menu.o \
-vox_menu.o \
-fft_menu.o \
-diversity_menu.o \
-tx_menu.o \
-vfo_menu.o \
-test_menu.o \
-meter.o \
-mode.o \
-old_discovery.o \
-new_discovery.o \
-old_protocol.o \
-new_protocol.o \
-new_protocol_programmer.o \
-rx_panadapter.o \
-tx_panadapter.o \
-property.o \
-radio.o \
-receiver.o \
-rigctl.o \
-rigctl_menu.o \
-toolbar.o \
-transmitter.o \
-zoompan.o \
-sliders.o \
-vfo.o \
-waterfall.o \
-button_text.o \
-vox.o \
-update.o \
-store.o \
-store_menu.o \
-memory.o \
-led.o \
-ext.o \
-error_handler.o \
-cwramp.o \
-protocols.o
-
-$(PROGRAM):  $(OBJS) $(REMOTE_OBJS) $(USBOZY_OBJS) $(SOAPYSDR_OBJS) \
-		$(LOCALCW_OBJS) $(PURESIGNAL_OBJS) \
-		$(MIDI_OBJS) $(STEMLAB_OBJS)
-	$(LINK) -o $(PROGRAM) $(OBJS) $(REMOTE_OBJS) $(USBOZY_OBJS) \
-		$(SOAPYSDR_OBJS) $(LOCALCW_OBJS) $(PURESIGNAL_OBJS) \
-		$(MIDI_OBJS) $(STEMLAB_OBJS) $(LIBS)
-
-all:	prebuild  $(PROGRAM) $(HEADERS) $(REMOTE_HEADERS) $(USBOZY_HEADERS) $(SOAPYSDR_HEADERS) \
-	$(LOCALCW_HEADERS) \
-	$(PURESIGNAL_HEADERS) $(MIDI_HEADERS) $(STEMLAB_HEADERS) $(SOURCES) $(REMOTE_SOURCES) \
-	$(USBOZY_SOURCES) $(SOAPYSDR_SOURCES) \
-	$(PURESIGNAL_SOURCES) $(MIDI_SOURCES) $(STEMLAB_SOURCES) $(LOCALCW_SOURCES)
-
-prebuild:
-	rm -f version.o
-
-#
-# On some platforms, INCLUDES contains "-pthread"  (from a pkg-config output)
-# which is not a valid cppcheck option
-# Therefore, correct this here. Furthermore, we can add additional options to CPP
-# in the variable CPPOPTIONS
-#
-CPPOPTIONS= --enable=all --suppress=shadowVariable --suppress=variableScope -D__APPLE__
-CPPINCLUDES:=$(shell echo $(INCLUDES) | sed -e "s/-pthread / /" )
-
-.PHONY:	cppcheck
-cppcheck:
-	cppcheck $(CPPOPTIONS) $(OPTIONS) $(CPPINCLUDES) $(SOURCES) $(REMOTE_SOURCES) \
-	$(USBOZY_SOURCES) $(SOAPYSDR_SOURCES) \
-	$(PURESIGNAL_SOURCES) $(MIDI_SOURCES) $(STEMLAB_SOURCES) $(LOCALCW_SOURCES)
-
-clean:
-	-rm -f *.o
-	-rm -f $(PROGRAM) hpsdrsim
-	-rm -rf $(PROGRAM).app
-
-install: $(PROGRAM)
-	cp $(PROGRAM) /usr/local/bin
-
-release: $(PROGRAM)
-	cp $(PROGRAM) release/pihpsdr
-	cd release; tar cvf pihpsdr.tar pihpsdr
-	cd release; tar cvf pihpsdr-$(GIT_VERSION).tar pihpsdr
-
-nocontroller: clean controller1 $(PROGRAM)
-	cp $(PROGRAM) release/pihpsdr
-	cd release; tar cvf pihpsdr-nocontroller.$(GIT_VERSION).tar pihpsdr
-
-controller1: clean $(PROGRAM)
-	cp $(PROGRAM) release/pihpsdr
-	cd release; tar cvf pihpsdr-controller1.$(GIT_VERSION).tar pihpsdr
-
-controller2v1: clean $(PROGRAM)
-	cp $(PROGRAM) release/pihpsdr
-	cd release; tar cvf pihpsdr-controller2-v1.$(GIT_VERSION).tar pihpsdr
-
-controller2v2: clean $(PROGRAM)
-	cp $(PROGRAM) release/pihpsdr
-	cd release; tar cvf pihpsdr-controller2-v2.$(GIT_VERSION).tar pihpsdr
-
-
-#############################################################################
-#
-# hpsdrsim is a cool program that emulates an SDR board with UDP and TCP
-# facilities. It even feeds back the TX signal and distorts it, so that
-# you can test PURESIGNAL.
-# This feature only works if the sample rate is 48000
-#
-#############################################################################
-
-hpsdrsim.o:     hpsdrsim.c  hpsdrsim.h
-	$(CC) -c -O -DPORTAUDIO hpsdrsim.c
-        
-newhpsdrsim.o:	newhpsdrsim.c hpsdrsim.h
-	$(CC) -c -O newhpsdrsim.c
-
-hpsdrsim:       hpsdrsim.o newhpsdrsim.o
-	$(LINK) -o hpsdrsim hpsdrsim.o newhpsdrsim.o -lportaudio -lm -lpthread
-
-#############################################################################
-#
-# 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.
-#
-#       piHPSDR working dir
-#       ===================
-#
-#       The piHPSDR working directory is
-#	$HOME -> Application Support -> piHPSDR
-#
-#       That is the directory where the WDSP wisdom file (created upon first
-#       start of piHPSDR) but also the radio settings and the midi.props file
-#       are stored.
-#
-#############################################################################
-app:	$(OBJS) $(REMOTE_OBJS) $(USBOZY_OBJS) \
-		$(LOCALCW_OBJS) \
-		$(PURESIGNAL_OBJS) $(MIDI_OBJS) $(STEMLAB_OBJS)
-	$(LINK) -headerpad_max_install_names -o $(PROGRAM) $(OBJS) $(REMOTE_OBJS) \
-		$(USBOZY_OBJS)  \
-		$(LOCALCW_OBJS) $(PURESIGNAL_OBJS) \
-		$(MIDI_OBJS) $(STEMLAB_OBJS) $(LIBS)
-	@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  | grep -Ev pango | grep -Ev cairo`; 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
-	@# once more to install libraries on which libs just copied depend (such as wdsp -> fftw)
-	@for file in pihpsdr.app/Contents/Frameworks/*.dylib; do \
-	  for lib in `otool -L pihpsdr.app/Contents/Frameworks/*.dylib | grep -v pihpsdr.app | grep -v "executable_path" | grep dylib | sed -e "s/ (.*//" | grep -Ev "/(usr/lib|System)" | grep -Ev /libg  | grep -Ev pango | grep -Ev cairo`; 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" $$file; \
-	  done; \
-	done
-#############################################################################
-
diff --git a/adc.h b/adc.h
index c1ab51d..8c0be6c 100644
--- a/adc.h
+++ b/adc.h
@@ -66,7 +66,7 @@ typedef struct _adc {
   gint attenuation;
   gboolean enable_step_attenuation;
 #ifdef SOAPYSDR
-  gint *rx_gain;
+  gdouble gain;
   gboolean agc;
 #endif
 } ADC;
diff --git a/alsa_midi.c b/alsa_midi.c
index 89dc8e5..eb4d0af 100644
--- a/alsa_midi.c
+++ b/alsa_midi.c
@@ -18,7 +18,11 @@
  *       program amidi.c in alsautils.
  */
 
+#include <gtk/gtk.h>
+
 #include "midi.h"
+#include "midi_menu.h"
+#include "alsa_midi.h"
 
 #ifndef __APPLE__
 
@@ -28,6 +32,10 @@
 static pthread_t midi_thread_id;
 static void* midi_thread(void *);
 
+MIDI_DEVICE midi_devices[MAX_MIDI_DEVICES];
+int n_midi_devices;
+int running;
+
 static char portname[64];
 
 static enum {
@@ -43,9 +51,17 @@ static enum {
 	CMD_PITCH,
 } command;
 
+static gboolean configure=FALSE;
+
+static snd_rawmidi_t *input;
+
+void configure_midi_device(gboolean state) {
+  configure=state;
+}
+
 static void *midi_thread(void *arg) {
     int ret;
-    snd_rawmidi_t *input;
+    MIDI_DEVICE *midi_device=(MIDI_DEVICE *)arg;
     int npfds;
     struct pollfd *pfds;
     unsigned char buf[32];
@@ -54,22 +70,28 @@ static void *midi_thread(void *arg) {
     int i;
     int chan,arg1,arg2;
 
-    if ((ret = snd_rawmidi_open(&input, NULL, portname, SND_RAWMIDI_NONBLOCK)) < 0) {
-        fprintf(stderr,"cannot open port \"%s\": %s\n", portname, snd_strerror(ret));
+    /*
+    if ((ret = snd_rawmidi_open(&input, NULL, midi_device->port, SND_RAWMIDI_NONBLOCK)) < 0) {
+        fprintf(stderr,"cannot open port \"%s\": %s\n", midi_device->port, snd_strerror(ret));
         return NULL;
     }
+    */
+
+    running=1;
+
     snd_rawmidi_read(input, NULL, 0); /* trigger reading */
 
     npfds = snd_rawmidi_poll_descriptors_count(input);
     pfds = alloca(npfds * sizeof(struct pollfd));
     snd_rawmidi_poll_descriptors(input, pfds, npfds);
-    for (;;) {
+    while (running) {
 	ret = poll(pfds, npfds, 250);
 	if (ret < 0) {
             fprintf(stderr,"poll failed: %s\n", strerror(errno));
 	    // Do not give up, but also do not fire too rapidly
 	    usleep(250000);
 	}
+	if(!running) continue;
 	if (ret <= 0) continue;  // nothing arrived, do next poll()
 	if ((ret = snd_rawmidi_poll_descriptors_revents(input, pfds, npfds, &revents)) < 0) {
             fprintf(stderr,"cannot get poll events: %s\n", snd_strerror(errno));
@@ -129,19 +151,39 @@ static void *midi_thread(void *arg) {
 			   // messages with velocity == 0 when releasing
 			   // a push-button
 			   if (arg2 == 0) {
-			     NewMidiEvent(MIDI_NOTE, chan, arg1, 0);
+                             if(configure) {
+			       NewMidiConfigureEvent(MIDI_NOTE, chan, arg1, 0);
+                             } else {
+			       NewMidiEvent(MIDI_NOTE, chan, arg1, 0);
+			     }
 			   } else {
-			     NewMidiEvent(MIDI_NOTE, chan, arg1, 1);
+                             if(configure) {
+			       NewMidiConfigureEvent(MIDI_NOTE, chan, arg1, 1);
+                             } else {
+			       NewMidiEvent(MIDI_NOTE, chan, arg1, 1);
+			     }
 			   }
 			   break;
 			case CMD_NOTEOFF:
-			   NewMidiEvent(MIDI_NOTE, chan, arg1, 0);
+                           if(configure) {
+			     NewMidiConfigureEvent(MIDI_NOTE, chan, arg1, 0);
+                           } else {
+			     NewMidiEvent(MIDI_NOTE, chan, arg1, 0);
+			   }
 			   break;
 			case CMD_CTRL:
-			   NewMidiEvent(MIDI_CTRL, chan, arg1, arg2);
+                           if(configure) {
+			     NewMidiConfigureEvent(MIDI_CTRL, chan, arg1, arg2);
+                           } else {
+			     NewMidiEvent(MIDI_CTRL, chan, arg1, arg2);
+			   }
 			   break;
 			case CMD_PITCH:
-			   NewMidiEvent(MIDI_PITCH, chan, 0, arg1+128*arg2);
+                           if(configure) {
+			     NewMidiConfigureEvent(MIDI_PITCH, chan, 0, arg1+128*arg2);
+                           } else {
+			     NewMidiEvent(MIDI_PITCH, chan, 0, arg1+128*arg2);
+			   }
 			   break;
                     }
 		    state=STATE_SKIP;
@@ -151,9 +193,52 @@ static void *midi_thread(void *arg) {
     }
 }
 
-void register_midi_device(char *myname) {
+int register_midi_device(char *myname) {
+    int i;
+    int ret=0;
+
+    configure=FALSE;
+
+    for(i=0;i<n_midi_devices;i++) {
+        if(strcmp(myname,midi_devices[i].name)==0) {
+	    strcpy(portname,midi_devices[i].port);
+            if ((ret = snd_rawmidi_open(&input, NULL, midi_devices[i].port, SND_RAWMIDI_NONBLOCK)) < 0) {
+               g_print("%s: cannot open port \"%s\": %s\n", __FUNCTION__, midi_devices[i].port, snd_strerror(ret));
+               break;
+            }
+
+            g_print("%s: %s Opened\n",__FUNCTION__,myname);
+
+            ret = pthread_create(&midi_thread_id, NULL, midi_thread, &midi_devices[i]);
+            if (ret < 0) {
+                g_print("%s: Failed to create MIDI read thread\n",__FUNCTION__);
+            }
+	    break;
+	}
+    }
+    if(i>=n_midi_devices) {
+        g_print("%s: Cannot find MIDI device: %s\n",__FUNCTION__,myname);
+	ret=-1;
+    }
+}
+
+void close_midi_device() {
+    int ret;
+
+    g_print("%s\n",__FUNCTION__);
+    if(input!=NULL) {
+      g_print("%s: snd_rawmidi_close\n",__FUNCTION__);
+      running=0;
+      if((ret = snd_rawmidi_close(input)) < 0) {
+        g_print("%s: cannot close port: %s\n",__FUNCTION__, snd_strerror(ret));
+      }
+      input=NULL;
+      usleep(250000L);
+    }
+}
+
+void get_midi_devices() {
 
-    int mylen=strlen(myname);
     snd_ctl_t *ctl;
     snd_rawmidi_info_t *info;
     int card, device, subs, sub, ret;
@@ -161,13 +246,14 @@ void register_midi_device(char *myname) {
     int found=0;
     char name[64];
 
+    n_midi_devices=0;
     card=-1;
     if ((ret = snd_card_next(&card)) < 0) {
         fprintf(stderr,"cannot determine card number: %s\n", snd_strerror(ret));
         return;
     }
     while (card >= 0) {
-	fprintf(stderr,"Found Sound Card=%d\n",card);
+	//fprintf(stderr,"Found Sound Card=%d\n",card);
 	sprintf(name,"hw:%d", card);
         if ((ret = snd_ctl_open(&ctl, name, 0)) < 0) {
                 fprintf(stderr,"cannot open control for card %d: %s\n", card, snd_strerror(ret));
@@ -181,7 +267,7 @@ void register_midi_device(char *myname) {
                 break;
             }
 	    if (device < 0) break;
-	    fprintf(stderr,"Found Device=%d on Card=%d\n", device, card);
+	    //fprintf(stderr,"Found Device=%d on Card=%d\n", device, card);
 	    // found sub-device
 	    snd_rawmidi_info_alloca(&info);
             snd_rawmidi_info_set_device(info, device);
@@ -192,7 +278,7 @@ void register_midi_device(char *myname) {
             } else {
                 subs = 0;
 	    }
-	    fprintf(stderr,"Number of MIDI input devices: %d\n", subs);
+	    //fprintf(stderr,"Number of MIDI input devices: %d\n", subs);
 	    if (!subs) break;
 	    // subs: number of sub-devices to device on card
             for (sub = 0; sub < subs; ++sub) {
@@ -216,12 +302,13 @@ void register_midi_device(char *myname) {
 		    sprintf(portname,"hw:%d,%d,%d", card, device, sub);
 		    devnam=subnam;
 		}
-		if (!strncmp(myname, devnam, mylen)) {
-		    found=1;
-		    fprintf(stderr,"MIDI device %s selected (PortName=%s)\n", devnam, portname);
-		} else {
-                    fprintf(stderr,"MIDI device found BUT NOT SELECTED: %s\n", devnam);
-		}
+
+		midi_devices[n_midi_devices].name=g_new(gchar,strlen(devnam)+1);
+		strcpy(midi_devices[n_midi_devices].name,devnam);
+
+		midi_devices[n_midi_devices].port=g_new(gchar,strlen(portname)+1);
+                strcpy(midi_devices[n_midi_devices].port,portname);
+		n_midi_devices++;
 	    }
 	}
 	snd_ctl_close(ctl);
@@ -231,14 +318,9 @@ void register_midi_device(char *myname) {
             break;
         }
     }
-    if (!found) {
-	fprintf(stderr,"MIDI device %s NOT FOUND!\n", myname);
-        return;
-    }
-    // Found our MIDI input device. Spawn off a thread reading data
-    ret = pthread_create(&midi_thread_id, NULL, midi_thread, NULL);
-    if (ret < 0) {
-	fprintf(stderr,"Failed to create MIDI read thread\n");
+
+    for(int i=0;i<n_midi_devices;i++) {
+        g_print("%s: %d: %s %s\n",__FUNCTION__,i,midi_devices[i].name,midi_devices[i].port);
     }
 }
 #endif
diff --git a/band_menu.c b/band_menu.c
index eb2102d..7bbb472 100644
--- a/band_menu.c
+++ b/band_menu.c
@@ -32,7 +32,9 @@
 #include "receiver.h"
 #include "vfo.h"
 #include "button_text.h"
+#ifdef CLIENT_SERVER
 #include "client_server.h"
+#endif
 
 static GtkWidget *parent_window=NULL;
 
diff --git a/dac.h b/dac.h
index 5bd9ad4..a08135f 100644
--- a/dac.h
+++ b/dac.h
@@ -22,7 +22,7 @@
 
 typedef struct _dac {
   gint antenna;
-  gint *tx_gain;
+  gdouble gain;
 } DAC;
 
 #endif
diff --git a/discovered.h b/discovered.h
index dc71bfc..91e7b0b 100644
--- a/discovered.h
+++ b/discovered.h
@@ -117,6 +117,7 @@ struct _DISCOVERED {
         char hardware_key[64];
         char driver_key[64];
         int rtlsdr_count;
+        int sdrplay_count;
         int sample_rate;
         size_t rx_channels;
         size_t rx_gains;
@@ -132,6 +133,10 @@ struct _DISCOVERED {
         SoapySDRRange *tx_range;
         size_t tx_antennas;
         char **tx_antenna;
+	size_t sensors;
+        char **sensor;
+        gboolean has_temp;
+        char address[64];
       } soapy;
 #endif
     } info;
diff --git a/discovery.c b/discovery.c
index 32ffdb0..c3a69bd 100644
--- a/discovery.c
+++ b/discovery.c
@@ -434,8 +434,9 @@ fprintf(stderr,"%p Protocol=%d name=%s\n",d,d->protocol,d->name);
             break;
 #ifdef SOAPYSDR
           case SOAPYSDR_PROTOCOL:
-            sprintf(text,"%s (Protocol SOAPY_SDR %s) on USB",d->name,d->info.soapy.version);
+            sprintf(text,"%s (Protocol SOAPY_SDR %s) on %s",d->name,d->info.soapy.version,d->info.soapy.address);
             break;
+
 #endif
 #ifdef STEMLAB_DISCOVERY
           case STEMLAB_PROTOCOL:
@@ -571,7 +572,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,"Controller I2C");
+    //gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(gpio),NULL,"Controller I2C");
     gtk_grid_attach(GTK_GRID(grid),gpio,0,row,1,1);
 
     gtk_combo_box_set_active(GTK_COMBO_BOX(gpio),controller);
@@ -586,12 +587,13 @@ fprintf(stderr,"%p Protocol=%d name=%s\n",d,d->protocol,d->name);
     g_signal_connect (protocols_b, "button-press-event", G_CALLBACK(protocols_cb), NULL);
     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,row,1,1);
 #endif
-
+*/
     row++;
 
 #ifdef GPIO
diff --git a/display_menu.c b/display_menu.c
index 0dba23b..38aab90 100644
--- a/display_menu.c
+++ b/display_menu.c
@@ -440,25 +440,23 @@ void display_menu(GtkWidget *parent) {
 
   col++;
 
-  if(controller!=CONTROLLER_I2C) {
-    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);
-    gtk_widget_show(b_display_sliders);
-    gtk_grid_attach(GTK_GRID(grid),b_display_sliders,col,row,1,1);
-    g_signal_connect(b_display_sliders,"toggled",G_CALLBACK(display_sliders_cb),(gpointer *)NULL);
-  
-    col++;
-  
-    GtkWidget *b_display_toolbar=gtk_check_button_new_with_label("Display Toolbar");
-    //gtk_widget_override_font(b_display_toolbar, pango_font_description_from_string("Arial 18"));
-    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (b_display_toolbar), display_toolbar);
-    gtk_widget_show(b_display_toolbar);
-    gtk_grid_attach(GTK_GRID(grid),b_display_toolbar,col,row,1,1);
-    g_signal_connect(b_display_toolbar,"toggled",G_CALLBACK(display_toolbar_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);
+  gtk_widget_show(b_display_sliders);
+  gtk_grid_attach(GTK_GRID(grid),b_display_sliders,col,row,1,1);
+  g_signal_connect(b_display_sliders,"toggled",G_CALLBACK(display_sliders_cb),(gpointer *)NULL);
+
+  col++;
+
+  GtkWidget *b_display_toolbar=gtk_check_button_new_with_label("Display Toolbar");
+  //gtk_widget_override_font(b_display_toolbar, pango_font_description_from_string("Arial 18"));
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (b_display_toolbar), display_toolbar);
+  gtk_widget_show(b_display_toolbar);
+  gtk_grid_attach(GTK_GRID(grid),b_display_toolbar,col,row,1,1);
+  g_signal_connect(b_display_toolbar,"toggled",G_CALLBACK(display_toolbar_cb),(gpointer *)NULL);
+
+  col++;
 
   GtkWidget *b_display_sequence_errors=gtk_check_button_new_with_label("Display Seq Errs");
   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (b_display_sequence_errors), display_sequence_errors);
diff --git a/ext.c b/ext.c
index fc57b0b..6ee09b2 100644
--- a/ext.c
+++ b/ext.c
@@ -53,6 +53,47 @@
 
 // The following calls functions can be called usig g_idle_add
 
+int ext_menu_filter(void *data) {
+  start_filter();
+  return 0;
+}
+
+int ext_menu_mode(void *data) {
+  start_mode();
+  return 0;
+}
+
+int ext_num_pad(void *data) {
+  gint val=GPOINTER_TO_INT(data);
+  RECEIVER *rx=active_receiver;
+  if(!vfo[rx->id].entering_frequency) {
+    vfo[rx->id].entered_frequency=0;
+    vfo[rx->id].entering_frequency=TRUE;
+  }
+  switch(val) {
+    case -1: // clear
+      vfo[rx->id].entered_frequency=0;
+      vfo[rx->id].entering_frequency=FALSE;
+      break;
+    case -2: // enter
+      if(vfo[rx->id].entered_frequency!=0) {
+        vfo[rx->id].frequency=vfo[rx->id].entered_frequency;
+	if(vfo[rx->id].ctun) {
+          vfo[rx->id].ctun=FALSE;
+          vfo[rx->id].offset=0;
+          vfo[rx->id].ctun_frequency=vfo[rx->id].frequency;
+	}
+      }
+      vfo[rx->id].entering_frequency=FALSE;
+      break;
+    default:
+      vfo[rx->id].entered_frequency=(vfo[rx->id].entered_frequency*10)+val;
+      break;
+  }
+  vfo_update(rx);
+  return 0;
+}
+
 int ext_vfo_mode_changed(void * data)
 {
   int mode=GPOINTER_TO_INT(data);
@@ -428,6 +469,13 @@ void band_plus(int id) {
   }
 }
 
+int ext_band_select(void *data) {
+  int b=GPOINTER_TO_INT(data);
+  g_print("%s: %d\n",__FUNCTION__,b);
+  vfo_band_changed(active_receiver->id,b);
+  return 0;
+}
+
 int ext_band_plus(void *data) {
   band_plus(active_receiver->id);
   return 0;
diff --git a/ext.h b/ext.h
index 51f1bcc..647f1fc 100644
--- a/ext.h
+++ b/ext.h
@@ -31,6 +31,9 @@ extern int ext_remote_command(void *data);
 extern int ext_receiver_remote_update_display(void *data);
 #endif
 
+extern int ext_menu_filter(void *data);
+extern int ext_menu_mode(void *data);
+extern int ext_num_pad(void *data);
 extern void local_set_frequency(int v,long long f);
 extern int ext_discovery(void *data);
 extern int ext_vfo_update(void *data);
@@ -55,6 +58,7 @@ extern int ext_nr_update(void *data);
 extern int ext_nb_update(void *data);
 extern int ext_snb_update(void *data);
 extern int ext_anf_update(void *data);
+extern int ext_band_select(void *data);
 extern void band_plus(int id);
 extern int ext_band_plus(void *data);
 extern void band_minus(int id);
diff --git a/gpio.c b/gpio.c
index ca48856..5674cca 100644
--- a/gpio.c
+++ b/gpio.c
@@ -1,6 +1,6 @@
 /* Copyright (C)
 * 2020 - 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
@@ -33,10 +33,12 @@
 #include <poll.h>
 #include <sched.h>
 
+#ifdef GPIO
 #include <gpiod.h>
 #include <linux/i2c-dev.h>
 #include <i2c/smbus.h>
 #include <sys/ioctl.h>
+#endif
 
 #include "band.h"
 #include "channel.h"
@@ -60,10 +62,28 @@
 #include "ext.h"
 #include "sliders.h"
 #include "new_protocol.h"
+#include "zoompan.h"
 #ifdef LOCALCW
 #include "iambic.h"
+
+//
+// Broadcom pins #9, 10, 11 are not used
+// by Controller1 and Controller2_V1
+// (and keep #2,3 reserved for I2C extensions)
+//
+int CWL_BUTTON=9;
+int CWR_BUTTON=11;
+int SIDETONE_GPIO=10;
+int ENABLE_GPIO_SIDETONE=0;
+int ENABLE_CW_BUTTONS=1;
+int CW_ACTIVE_LOW=1;
+#endif
+
+#ifdef PTT
+int ENABLE_PTT_GPIO=1;
+int PTT_GPIO=14;
+int PTT_ACTIVE_LOW=1;
 #endif
-#include "zoompan.h"
 
 enum {
   TOP_ENCODER,
@@ -75,12 +95,13 @@ enum {
   B
 };
 
+#ifdef GPIO
 char *consumer="pihpsdr";
 
 char *gpio_device="/dev/gpiochip0";
 
 static struct gpiod_chip *chip=NULL;
-//static struct gpiod_line *line=NULL;
+#endif
 
 static GMutex encoder_mutex;
 static GThread *monitor_thread_id;
@@ -514,6 +535,7 @@ int process_function_switch(void *data) {
   return 0;
 }
 
+#ifdef GPIO
 static unsigned long switch_debounce;
 
 static void process_encoder(int e,int l,int addr,int val) {
@@ -560,8 +582,21 @@ static void process_edge(int offset,int value) {
   gint i;
   gint t;
   gboolean found;
-  // check encoders
+
   found=FALSE;
+#ifdef LOCALCW
+  if(ENABLE_CW_BUTTONS) {
+    if(offset==CWL_BUTTON) {
+      keyer_event(1, CW_ACTIVE_LOW ? (value==PRESSED) : value);
+      found=TRUE;
+    } else if(offset==CWR_BUTTON) {
+      keyer_event(1, CW_ACTIVE_LOW ? (value==PRESSED) : value);
+      found=TRUE;
+    }
+  }
+  if(found) return;
+#endif
+  // check encoders
   for(i=0;i<MAX_ENCODERS;i++) {
     if(encoders[i].bottom_encoder_enabled && encoders[i].bottom_encoder_address_a==offset) {
       //g_print("%s: found %d encoder %d bottom A\n",__FUNCTION__,offset,i);
@@ -635,6 +670,7 @@ static int interrupt_cb(int event_type, unsigned int line, const struct timespec
   }
   return GPIOD_CTXLESS_EVENT_CB_RET_OK;
 }
+#endif
 
 void gpio_set_defaults(int ctrlr) {
   int i;
@@ -656,10 +692,6 @@ void gpio_set_defaults(int ctrlr) {
       encoders=encoders_controller2_v2;
       switches=switches_controller2_v2;
       break;
-    case CONTROLLER_I2C:
-      encoders=encoders_no_controller;
-      switches=switches_no_controller;
-      break;
   }
 }
 
@@ -887,6 +919,7 @@ void gpio_save_actions() {
   }
 }
 
+#ifdef GPIO
 static gpointer monitor_thread(gpointer arg) {
   struct timespec t;
 
@@ -925,7 +958,7 @@ static int setup_line(struct gpiod_chip *chip, int offset, gboolean pullup) {
 
   config.consumer=consumer;
   config.request_type=GPIOD_LINE_REQUEST_DIRECTION_INPUT | GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES;
-#ifdef RASPBIAN
+#ifdef OLD_GPIOD
   config.flags=pullup?GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW:0;
 #else
   config.flags=pullup?GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP:GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN;
@@ -943,9 +976,38 @@ static int setup_line(struct gpiod_chip *chip, int offset, gboolean pullup) {
   return 0;
 }
 
+static int setup_output_line(struct gpiod_chip *chip, int offset, int _initial_value) {
+  int ret;
+  struct gpiod_line_request_config config;
+
+  g_print("%s: %d\n",__FUNCTION__,offset);
+  struct gpiod_line *line=gpiod_chip_get_line(chip, offset);
+  if (!line) {
+    g_print("%s: get line %d failed: %s\n",__FUNCTION__,offset,g_strerror(errno));
+    return -1;
+  }
+
+  config.consumer=consumer;
+  config.request_type=GPIOD_LINE_REQUEST_DIRECTION_OUTPUT;
+  ret=gpiod_line_request(line,&config,1);
+  if (ret<0) {
+    g_print("%s: line %d gpiod_line_request failed: %s\n",__FUNCTION__,offset,g_strerror(errno));
+    return ret;
+  }
+
+  // write initial value
+  
+
+  gpiod_line_release(line);
+
+  return 0;
+}
+#endif
+
 int gpio_init() {
   int ret=0;
 
+#ifdef GPIO
   initialiseEpoch();
   switch_debounce=millis();
 
@@ -1008,37 +1070,79 @@ int gpio_init() {
     }
   }
 
-  if(controller!=NO_CONTROLLER && controller!=CONTROLLER_I2C) {
+#ifdef LOCALCW
+  g_print("%s: ENABLE_CW_BUTTONS=%d  CWL_BUTTON=%d CWR_BUTTON=%d\n", __FUNCTION__, ENABLE_CW_BUTTONS, CWL_BUTTON, CWR_BUTTON);
+  if(ENABLE_CW_BUTTONS) {
+    if((ret=setup_line(chip,CWL_BUTTON,CW_ACTIVE_LOW==1))<0) {
+      goto err;
+    }
+    monitor_lines[lines]=CWL_BUTTON;
+    lines++;
+    if((ret=setup_line(chip,CWR_BUTTON,CW_ACTIVE_LOW==1))<0) {
+      goto err;
+    }
+    monitor_lines[lines]=CWR_BUTTON;
+    lines++;
+
+  }
+  if (ENABLE_GPIO_SIDETONE) {
+//
+//  use this pin as an output pin and
+//  set its value to LOW
+//
+    if((ret=setup_output_line(chip,SIDETONE_GPIO,0))<0) {
+      goto err;
+    }
+  }
+#endif
+
+  if(controller!=NO_CONTROLLER
+#ifdef LOCALCW
+    || ENABLE_CW_BUTTONS
+#endif
+    ) {
     monitor_thread_id = g_thread_new( "gpiod monitor", monitor_thread, NULL);
     if(!monitor_thread_id ) {
       g_print("%s: g_thread_new failed for monitor_thread\n",__FUNCTION__);
     }
 
-    rotary_encoder_thread_id = g_thread_new( "encoders", rotary_encoder_thread, NULL);
-    if(!rotary_encoder_thread_id ) {
-      g_print("%s: g_thread_new failed on rotary_encoder_thread\n",__FUNCTION__);
-      exit( -1 );
+    if(controller!=NO_CONTROLLER) {
+      rotary_encoder_thread_id = g_thread_new( "encoders", rotary_encoder_thread, NULL);
+      if(!rotary_encoder_thread_id ) {
+        g_print("%s: g_thread_new failed on rotary_encoder_thread\n",__FUNCTION__);
+        exit( -1 );
+      }
+      g_print("%s: rotary_encoder_thread: id=%p\n",__FUNCTION__,rotary_encoder_thread_id);
     }
-    g_print("%s: rotary_encoder_thread: id=%p\n",__FUNCTION__,rotary_encoder_thread_id);
   }
-
+#endif
   return 0;
 
 err:
 g_print("%s: err\n",__FUNCTION__);
+#ifdef GPIO
   if(chip!=NULL) {
     gpiod_chip_close(chip);
     chip=NULL;
   }
+#endif
   return ret;
 }
 
 void gpio_close() {
+#ifdef GPIO
   if(chip!=NULL) gpiod_chip_close(chip);
+#endif
 }
 
 #ifdef LOCALCW
 void gpio_cw_sidetone_set(int level) {
+  int rc;
+  if (ENABLE_GPIO_SIDETONE) {
+    if((rc=gpiod_ctxless_set_value_ext(gpio_device,SIDETONE_GPIO,level,FALSE,consumer,NULL,NULL,0))<0) {
+      g_print("%s: err=%d\n",__FUNCTION__,rc);
+    }
+  }
 }
 
 int  gpio_left_cw_key() {
@@ -1048,6 +1152,7 @@ int  gpio_right_cw_key() {
 }
 
 int  gpio_cw_sidetone_enabled() {
+  return ENABLE_GPIO_SIDETONE;
 }
 
 #endif
diff --git a/gpio.h b/gpio.h
index 822acec..5ef78df 100644
--- a/gpio.h
+++ b/gpio.h
@@ -75,4 +75,18 @@ extern void gpio_save_state();
 extern void gpio_save_actions();
 extern int gpio_init();
 extern void gpio_close();
+
+#ifdef LOCALCW
+extern int CWL_BUTTON;
+extern int CWR_BUTTON;
+extern int SIDETONE_GPIO;
+extern int ENABLE_GPIO_SIDETONE;
+extern int ENABLE_CW_BUTTONS;
+extern int CW_ACTIVE_LOW;
+extern void gpio_cw_sidetone_set(int level);
+extern int  gpio_left_cw_key();
+extern int  gpio_right_cw_key();
+extern int  gpio_cw_sidetone_enabled();
+#endif
+
 #endif
diff --git a/i2c.c b/i2c.c
index b17659b..a0566a8 100644
--- a/i2c.c
+++ b/i2c.c
@@ -1,3 +1,4 @@
+#ifdef GPIO
 #include <string.h>
 #include <unistd.h>
 #include <errno.h>
@@ -306,3 +307,4 @@ void i2c_init() {
   } while(flags!=0);
   
 }
+#endif
diff --git a/iambic.c b/iambic.c
index 58a20cb..6f32453 100644
--- a/iambic.c
+++ b/iambic.c
@@ -177,6 +177,7 @@
  **************************************************************************************************************
  */
 
+#include <gtk/gtk.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -191,7 +192,7 @@
 #include <time.h>
 #include <sys/mman.h>
 
-#ifdef GPIO
+#ifdef LOCALCW
 #include "gpio.h"
 #endif
 #include "radio.h"
diff --git a/mac_midi.c b/mac_midi.c
index d52603d..898527f 100644
--- a/mac_midi.c
+++ b/mac_midi.c
@@ -27,10 +27,30 @@
  *
  */
 
+#include <gtk/gtk.h>
+#include "discovered.h"
+#include "receiver.h"
+#include "transmitter.h"
+#include "receiver.h"
+#include "adc.h"
+#include "dac.h"
+#include "radio.h"
 #include "midi.h"
+#include "midi_menu.h"
 
 #ifdef __APPLE__
 
+typedef struct _midi_device {
+  char *name;
+  char *port;
+} MIDI_DEVICE;
+
+#define MAX_MIDI_DEVICES 10
+
+MIDI_DEVICE midi_devices[MAX_MIDI_DEVICES];
+int n_midi_devices;
+
+
 /*
  * For MacOS, things are easy:
  * The OS takes care of everything, we only have to register a callback
@@ -43,6 +63,10 @@
 #include <CoreAudio/HostTime.h>
 #include <CoreAudio/CoreAudio.h>
 
+MIDI_DEVICE midi_devices[MAX_MIDI_DEVICES];
+int n_midi_devices;
+int running;
+
 //
 // MIDI callback function
 // called by MacOSX when data from the specified MIDI device arrives.
@@ -69,6 +93,8 @@ static enum {
         CMD_PITCH,
 } command;
 
+static gboolean configure=FALSE;
+
 static void ReadMIDIdevice(const MIDIPacketList *pktlist, void *refCon, void *connRefCon) {
     int i,j,byte,chan,arg1,arg2;
     MIDIPacket *packet = (MIDIPacket *)pktlist->packet;
@@ -119,19 +145,39 @@ static void ReadMIDIdevice(const MIDIPacketList *pktlist, void *refCon, void *co
                            // messages with velocity == 0 when releasing
                            // a push-button.
                            if (arg2 == 0) {
-                             NewMidiEvent(MIDI_NOTE, chan, arg1, 0);
+                             if(configure) {
+                               NewMidiConfigureEvent(MIDI_NOTE, chan, arg1, 0);
+                             } else {
+                               NewMidiEvent(MIDI_NOTE, chan, arg1, 0);
+                             }
                            } else {
-                             NewMidiEvent(MIDI_NOTE, chan, arg1, 1);
+                             if(configure) {
+                               NewMidiConfigureEvent(MIDI_NOTE, chan, arg1, 1);
+                             } else {
+                               NewMidiEvent(MIDI_NOTE, chan, arg1, 1);
+                             }
                            }
                            break;
                         case CMD_NOTEOFF:
-                           NewMidiEvent(MIDI_NOTE, chan, arg1, 0);
+                           if(configure) {
+                             NewMidiConfigureEvent(MIDI_NOTE, chan, arg1, 0);
+                           } else {
+                             NewMidiEvent(MIDI_NOTE, chan, arg1, 0);
+                           }
                            break;
                         case CMD_CTRL:
-                           NewMidiEvent(MIDI_CTRL, chan, arg1, arg2);
+                           if(configure) {
+                             NewMidiConfigureEvent(MIDI_CTRL, chan, arg1, arg2);
+                           } else {
+                             NewMidiEvent(MIDI_CTRL, chan, arg1, arg2);
+                           }
                            break;
                         case CMD_PITCH:
-                           NewMidiEvent(MIDI_PITCH, chan, 0, arg1+128*arg2);
+                           if(configure) {
+                             NewMidiConfigureEvent(MIDI_PITCH, chan, 0, arg1+128*arg2);
+                           } else {
+                             NewMidiEvent(MIDI_PITCH, chan, 0, arg1+128*arg2);
+                           }
                            break;
                     }
                     state=STATE_SKIP;
@@ -142,35 +188,31 @@ static void ReadMIDIdevice(const MIDIPacketList *pktlist, void *refCon, void *co
     } // j-loop through the list of packets
 }
 
+void close_midi_device() {
+    fprintf(stderr,"%s\n",__FUNCTION__);
+}
 
-void register_midi_device(char *myname) {
-    unsigned long nDevices;
+int register_midi_device(char *myname) {
     int i;
     CFStringRef pname;
     char name[100];
     int FoundMIDIref=-1;
     int mylen=strlen(myname);
+    int ret;
 
+    configure=false;
 
+    g_print("%s: %s\n",__FUNCTION__,myname);
 //
 // Go through the list of MIDI devices and
 // look whether the one we are looking for is there
 //
-
-    nDevices=MIDIGetNumberOfSources();
-    for (i=0; i<nDevices; i++) {
-	MIDIEndpointRef dev = MIDIGetSource(i);
-	if (dev != 0) {
-	    MIDIObjectGetStringProperty(dev, kMIDIPropertyName, &pname);
-	    CFStringGetCString(pname, name, sizeof(name), 0);
-	    CFRelease(pname);
-	    fprintf(stderr,"MIDI device found: >>>%s<<<\n", name);
-	    if (!strncmp(name, myname, mylen)) {
-		FoundMIDIref=i;
-		fprintf(stderr,"MIDI device found and selected: >>>%s<<<\n", name);
-	    } else {
-		fprintf(stderr,"MIDI device found BUT NOT SELECTED: >>>%s<<<\n", name);
-	    }
+    for (i=0; i<n_midi_devices; i++) {
+        if(!strncmp(midi_devices[i].name, myname, mylen)) {
+	    FoundMIDIref=i;
+	    fprintf(stderr,"MIDI device found and selected: >>>%s<<<\n", name);
+	} else {
+	    fprintf(stderr,"MIDI device found BUT NOT SELECTED: >>>%s<<<\n", name);
 	}
     }
 
@@ -185,6 +227,40 @@ void register_midi_device(char *myname) {
         MIDIClientCreate(CFSTR("piHPSDR"),NULL,NULL, &client);
         MIDIInputPortCreate(client, CFSTR("FromMIDI"), ReadMIDIdevice, NULL, &myMIDIport);
         MIDIPortConnectSource(myMIDIport,MIDIGetSource(FoundMIDIref), NULL);
+        ret=0;
+    } else {
+        ret=-1;
+    }
+
+    return ret;
+}
+
+void get_midi_devices() {
+    int n;
+    int i;
+    CFStringRef pname;
+    char name[100];
+    int FoundMIDIref=-1;
+
+    n=MIDIGetNumberOfSources();
+    n_midi_devices=0;
+    for (i=0; i<n; i++) {
+        MIDIEndpointRef dev = MIDIGetSource(i);
+        if (dev != 0) {
+            MIDIObjectGetStringProperty(dev, kMIDIPropertyName, &pname);
+            CFStringGetCString(pname, name, sizeof(name), 0);
+            CFRelease(pname);
+            g_print("%s: %s\n",__FUNCTION__,name);
+            midi_devices[n_midi_devices].name=g_new(gchar,strlen(name)+1);
+            strcpy(midi_devices[n_midi_devices].name,name);
+            n_midi_devices++;
+        }
     }
+    g_print("%s: devices=%d\n",__FUNCTION__,n_midi_devices);
 }
+
+void configure_midi_device(gboolean state) {
+  configure=state;
+}
+
 #endif
diff --git a/main.h b/main.h
index 5c9fb6d..bf1e33f 100644
--- a/main.h
+++ b/main.h
@@ -27,8 +27,7 @@ enum {
   NO_CONTROLLER,
   CONTROLLER1,
   CONTROLLER2_V1,
-  CONTROLLER2_V2,
-  CONTROLLER_I2C
+  CONTROLLER2_V2
 };
 
 extern gint controller;
diff --git a/midi.h b/midi.h
index bfe6230..5c66621 100644
--- a/midi.h
+++ b/midi.h
@@ -42,6 +42,8 @@
  * must generate MIDI events on different channels
  */
 
+#ifndef _MIDI_H
+#define _MIDI_H
 //
 // MIDIaction encodes the "action" to be taken in Layer3
 // (sorted alphabetically by the keyword)
@@ -55,13 +57,35 @@ enum MIDIaction {
   ANF,			// ANF:			toggel ANF on/off
   ATT,			// ATT:			Step attenuator or Programmable attenuator
   VFO_B2A,		// B2A:			VFO B -> A
-  BAND_DOWN,		// BANDDOWN:		cycle through bands downwards
-  BAND_UP,		// BANDUP:		cycle through bands upwards
+  MIDI_BAND_10,         // BAND10
+  MIDI_BAND_12,         // BAND12
+  MIDI_BAND_1240,       // BAND1240
+  MIDI_BAND_144,        // BAND144
+  MIDI_BAND_15,         // BAND15
+  MIDI_BAND_160,        // BAND160
+  MIDI_BAND_17,         // BAND17
+  MIDI_BAND_20,         // BAND20
+  MIDI_BAND_220,        // BAND220
+  MIDI_BAND_2300,       // BAND2300
+  MIDI_BAND_30,         // BAND30
+  MIDI_BAND_3400,       // BAND3400
+  MIDI_BAND_40,         // BAND40
+  MIDI_BAND_430,        // BAND430
+  MIDI_BAND_6,          // BAND6
+  MIDI_BAND_60,         // BAND60
+  MIDI_BAND_70,         // BAND70
+  MIDI_BAND_80,         // BAND80
+  MIDI_BAND_902,        // BAND902
+  MIDI_BAND_AIR,        // BANDAIR
+  BAND_DOWN,            // BANDDOWN
+  MIDI_BAND_GEN,        // BANDGEN
+  BAND_UP,              // BANDUP
+  MIDI_BAND_WWV,        // BANDWWV
   COMPRESS,		// COMPRESS:		TX compressor value
   MIDI_CTUN,		// CTUN:		toggle CTUN on/off
   VFO,			// CURRVFO:		change VFO frequency
-  CWL,			// CWL:			Left paddle pressed (use with ONOFF)
-  CWR,			// CWR:			Right paddle pressed (use with ONOFF)
+  CWLEFT,		// CWL:			Left paddle pressed (use with ONOFF)
+  CWRIGHT,		// CWR:			Right paddle pressed (use with ONOFF)
   CWSPEED,		// CWSPEED:		Set speed of (iambic) CW keyer
   DIV_COARSEGAIN,	// DIVCOARSEGAIN:	change DIVERSITY gain in large increments
   DIV_COARSEPHASE,	// DIVPHASE:		change DIVERSITY phase in large increments
@@ -73,6 +97,8 @@ enum MIDIaction {
   MIDI_DUP,		// DUP:			toggle duplex on/off
   FILTER_DOWN,		// FILTERDOWN:		cycle through filters downwards
   FILTER_UP,		// FILTERUP:		cycle through filters upwards
+  MENU_FILTER,
+  MENU_MODE,
   MIDI_LOCK,		// LOCK:		lock VFOs, disable frequency changes
   MIC_VOLUME,		// MICGAIN:		MIC gain
   MODE_DOWN,		// MODEDOWN:		cycle through modes downwards
@@ -81,6 +107,18 @@ 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)
+  NUMPAD_0,		// NUMPAD0
+  NUMPAD_1,		// NUMPAD1
+  NUMPAD_2,		// NUMPAD2
+  NUMPAD_3,		// NUMPAD3
+  NUMPAD_4,		// NUMPAD4
+  NUMPAD_5,		// NUMPAD5
+  NUMPAD_6,		// NUMPAD6
+  NUMPAD_7,		// NUMPAD7
+  NUMPAD_8,		// NUMPAD8
+  NUMPAD_9,		// NUMPAD9
+  NUMPAD_CL,		// NUMPADCL
+  NUMPAD_ENTER,		// NUMPADENTER
   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
@@ -136,11 +174,14 @@ enum MIDIaction {
 
 enum MIDItype {
  TYPE_NONE=0,
- MIDI_KEY,          // Button (press event)
- MIDI_KNOB,         // Knob   (value between 0 and 100)
- MIDI_WHEEL         // Wheel  (direction and speed)
+ MIDI_KEY=1,          // Button (press event)
+ MIDI_KNOB=2,         // Knob   (value between 0 and 100)
+ MIDI_WHEEL=4         // Wheel  (direction and speed)
 };
 
+extern gchar *midi_types[];
+extern gchar *midi_events[];
+
 //
 // MIDIevent encodes the actual MIDI event "seen" in Layer-1 and
 // passed to Layer-2. MIDI_NOTE events end up as MIDI_KEY and
@@ -158,6 +199,14 @@ enum MIDIevent {
 // Data structure for Layer-2
 //
 
+typedef struct _action_table {
+  enum MIDIaction action;
+  const char *str;
+  enum MIDItype type;
+} ACTION_TABLE;
+
+extern ACTION_TABLE ActionTable[];
+
 //
 // There is linked list of all specified MIDI events for a given "Note" value,
 // which contains the defined actions for all MIDI_NOTE and MIDI_CTRL events
@@ -199,17 +248,23 @@ struct desc {
    struct desc       *next;       // Next defined action for a controller/key with that note value (NULL for end of list)
 };
 
-struct cmdtable{
+struct cmdtable {
    struct desc *desc[128];    // description for Note On/Off and ControllerChange
    struct desc *pitch;        // description for PitchChanges
 };
 
+extern struct cmdtable MidiCommandsTable;
+
+extern int midi_debug;
+
 //
 // Layer-1 entry point, called once for all the MIDI devices
 // that have been defined. This is called upon startup by
 // Layer-2 through the function MIDIstartup.
 //
-void register_midi_device(char *name);
+int register_midi_device(char *name);
+void close_midi_device();
+void configure_midi_device(gboolean state);
 
 //
 // Layer-2 entry point (called by Layer1)
@@ -222,7 +277,8 @@ void register_midi_device(char *name);
 // for each device description that was successfully read.
 
 void NewMidiEvent(enum MIDIevent event, int channel, int note, int val);
-void MIDIstartup();
+int MIDIstartup(char *filename);
+int MIDIstop();
 
 //
 // Layer-3 entry point (called by Layer2). In Layer-3, all the pihpsdr
@@ -232,3 +288,4 @@ void MIDIstartup();
 //
 
 void DoTheMidi(enum MIDIaction code, enum MIDItype type, int val);
+#endif
diff --git a/midi2.c b/midi2.c
index 7683721..f0c5175 100644
--- a/midi2.c
+++ b/midi2.c
@@ -8,12 +8,21 @@
  */
 
 #include <gtk/gtk.h>
+
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
 #include <time.h>
+#include "receiver.h"
+#include "discovered.h"
+#include "adc.h"
+#include "dac.h"
+#include "transmitter.h"
+#include "radio.h"
+#include "main.h"
 #include "midi.h"
 
+
 struct cmdtable MidiCommandsTable;
 
 void NewMidiEvent(enum MIDIevent event, int channel, int note, int val) {
@@ -24,15 +33,15 @@ void NewMidiEvent(enum MIDIevent event, int channel, int note, int val) {
     static struct timespec tp, last_wheel_tp={0,0};
     long delta;
 
-//g_print("%s:EVENT=%d CHAN=%d NOTE=%d VAL=%d\n",__FUNCTION__,event,channel,note,val);
+//g_print("%s: EVENT=%d CHAN=%d NOTE=%d VAL=%d\n",__FUNCTION__,event,channel,note,val);
     if (event == MIDI_PITCH) {
 	desc=MidiCommandsTable.pitch;
     } else {
 	desc=MidiCommandsTable.desc[note];
     }
-//fprintf(stderr,"MIDI:init DESC=%p\n",desc);
+//g_print("%s: init DESC=%p\n",__FUNCTION__,desc);
     while (desc) {
-//fprintf(stderr,"DESC=%p next=%p CHAN=%d EVENT=%d\n", desc,desc->next,desc->channel,desc->event);
+//g_print("%s: DESC=%p next=%p CHAN=%d EVENT=%d\n",__FUNCTION__,desc,desc->next,desc->channel,desc->event);
 	if ((desc->channel == channel || desc->channel == -1) && (desc->event == event)) {
 	    // Found matching entry
 	    switch (desc->event) {
@@ -56,12 +65,15 @@ void NewMidiEvent(enum MIDIevent event, int channel, int note, int val) {
 			}
 			// translate value to direction
 			new=0;
+			new=val-64;
+			/*
 			if ((val >= desc->vfl1) && (val <= desc->vfl2)) new=-100;
 			if ((val >= desc-> fl1) && (val <= desc-> fl2)) new=-10;
 			if ((val >= desc->lft1) && (val <= desc->lft2)) new=-1;
 			if ((val >= desc->rgt1) && (val <= desc->rgt2)) new= 1;
 			if ((val >= desc-> fr1) && (val <= desc-> fr2)) new= 10;
 			if ((val >= desc->vfr1) && (val <= desc->vfr2)) new= 100;
+			*/
 //			fprintf(stderr,"WHEEL: val=%d new=%d thrs=%d/%d, %d/%d, %d/%d, %d/%d, %d/%d, %d/%d\n",
 //                                  val, new, desc->vfl1, desc->vfl2, desc->fl1, desc->fl2, desc->lft1, desc->lft2,
 //				          desc->rgt1, desc->rgt2, desc->fr1, desc->fr2, desc->vfr1, desc->vfr2);
@@ -86,12 +98,15 @@ void NewMidiEvent(enum MIDIevent event, int channel, int note, int val) {
     }
     if (!desc) {
       // Nothing found. This is nothing to worry about, but log the key to stderr
-      if (event == MIDI_PITCH) fprintf(stderr, "Unassigned PitchBend Value=%d\n", val);
-      if (event == MIDI_NOTE ) fprintf(stderr, "Unassigned Key Note=%d Val=%d\n", note, val);
-      if (event == MIDI_CTRL ) fprintf(stderr, "Unassigned Controller Ctl=%d Val=%d\n", note, val);
+        if (event == MIDI_PITCH) g_print("%s: Unassigned PitchBend Value=%d\n",__FUNCTION__, val);
+        if (event == MIDI_NOTE ) g_print("%s: Unassigned Key Note=%d Val=%d\n",__FUNCTION__, note, val);
+        if (event == MIDI_CTRL ) g_print("%s: Unassigned Controller Ctl=%d Val=%d\n",__FUNCTION__, note, val);
     }
 }
 
+gchar *midi_types[] = {"NONE","KEY","KNOB/SLIDER","*INVALID*","WHEEL"};
+gchar *midi_events[] = {"NONE","NOTE","CTRL","PITCH"};
+
 /*
  * This data structre connects names as used in the midi.props file with
  * our MIDIaction enum values.
@@ -100,72 +115,113 @@ void NewMidiEvent(enum MIDIevent event, int channel, int note, int val) {
  * a match for "VFO" when the key word is "VFOA".
  */
 
+/*
 static struct {
   enum MIDIaction action;
   const char *str;
+  enum MIDItype type;
 } ActionTable[] = {
-	{ VFO_A2B,		"A2B"},
-        { MIDI_AF_GAIN,      	"AFGAIN"},
-	{ AGCATTACK,   		"AGCATTACK"},
-        { MIDI_AGC,     	"AGCVAL"},
-        { ANF,     		"ANF"},
-        { ATT,          	"ATT"},
-	{ VFO_B2A,		"B2A"},
-        { BAND_DOWN,    	"BANDDOWN"},
-        { BAND_UP,      	"BANDUP"},
-        { COMPRESS,     	"COMPRESS"},
-	{ MIDI_CTUN,  		"CTUN"},
-	{ VFO,			"CURRVFO"},
-	{ CWL,			"CWL"},
-	{ CWR,			"CWR"},
-	{ CWSPEED,		"CWSPEED"},
-	{ DIV_COARSEGAIN,	"DIVCOARSEGAIN"},
-	{ DIV_COARSEPHASE,	"DIVCOARSEPHASE"},
-	{ DIV_FINEGAIN,		"DIVFINEGAIN"},
-	{ DIV_FINEPHASE,	"DIVFINEPHASE"},
-	{ DIV_GAIN,		"DIVGAIN"},
-	{ DIV_PHASE,		"DIVPHASE"},
-	{ DIV_TOGGLE,		"DIVTOGGLE"},
-	{ MIDI_DUP,  		"DUP"},
-        { FILTER_DOWN,  	"FILTERDOWN"},
-        { FILTER_UP,    	"FILTERUP"},
-	{ MIDI_LOCK,    	"LOCK"},
-        { MIC_VOLUME,   	"MICGAIN"},
-	{ MODE_DOWN,		"MODEDOWN"},
-	{ MODE_UP,		"MODEUP"},
-        { MIDI_MOX,     	"MOX"},
-	{ MIDI_MUTE,		"MUTE"},
-	{ MIDI_NB,    		"NOISEBLANKER"},
-	{ MIDI_NR,    		"NOISEREDUCTION"},
-        { MIDI_PAN,		"PAN"},
-        { PAN_HIGH,     	"PANHIGH"},
-        { PAN_LOW,      	"PANLOW"},
-        { PRE,          	"PREAMP"},
-	{ MIDI_PS,    		"PURESIGNAL"},
-	{ MIDI_RF_GAIN, 	"RFGAIN"},
-        { TX_DRIVE,     	"RFPOWER"},
-	{ MIDI_RIT_CLEAR,	"RITCLEAR"},
-	{ RIT_STEP, 		"RITSTEP"},
-        { RIT_TOGGLE,   	"RITTOGGLE"},
-        { RIT_VAL,      	"RITVAL"},
-        { MIDI_SAT,     	"SAT"},
-        { SNB, 		    	"SNB"},
-	{ MIDI_SPLIT,  		"SPLIT"},
-	{ SWAP_RX,		"SWAPRX"},
-	{ SWAP_VFO,		"SWAPVFO"},
-        { MIDI_TUNE,    	"TUNE"},
-        { VFOA,         	"VFOA"},
-        { VFOB,         	"VFOB"},
-	{ VFO_STEP_UP,  	"VFOSTEPUP"},
-	{ VFO_STEP_DOWN,	"VFOSTEPDOWN"},
-	{ VOX,   		"VOX"},
-	{ VOXLEVEL,   		"VOXLEVEL"},
-	{ MIDI_XIT_CLEAR,  	"XITCLEAR"},
-	{ XIT_VAL,  		"XITVAL"},
-	{ MIDI_ZOOM,		"ZOOM"},
-	{ ZOOM_UP,		"ZOOMUP"},
-	{ ZOOM_DOWN,		"ZOOMDOWN"},
-        { ACTION_NONE,  	"NONE"}
+*/
+ACTION_TABLE ActionTable[] = {
+	{ ACTION_NONE,		"NONE",		TYPE_NONE},
+	{ VFO_A2B,		"A2B",		MIDI_KEY},
+        { MIDI_AF_GAIN,      	"AFGAIN",	MIDI_KNOB|MIDI_WHEEL},
+	{ AGCATTACK,   		"AGCATTACK",	MIDI_KEY},
+        { MIDI_AGC,     	"AGCVAL",	MIDI_KNOB|MIDI_WHEEL},
+        { ANF,     		"ANF",		MIDI_KEY},
+        { ATT,          	"ATT",		MIDI_KNOB|MIDI_WHEEL},
+	{ VFO_B2A,		"B2A",		MIDI_KEY},
+	{ MIDI_BAND_10,         "BAND10",	MIDI_KEY},
+        { MIDI_BAND_12,         "BAND12",	MIDI_KEY},
+        { MIDI_BAND_1240,       "BAND1240",	MIDI_KEY},
+        { MIDI_BAND_144,        "BAND144",	MIDI_KEY},
+        { MIDI_BAND_15,         "BAND15",	MIDI_KEY},
+        { MIDI_BAND_160,        "BAND160",	MIDI_KEY},
+        { MIDI_BAND_17,         "BAND17",	MIDI_KEY},
+        { MIDI_BAND_20,         "BAND20",	MIDI_KEY},
+        { MIDI_BAND_220,        "BAND220",	MIDI_KEY},
+        { MIDI_BAND_2300,       "BAND2300",	MIDI_KEY},
+        { MIDI_BAND_30,         "BAND30",	MIDI_KEY},
+        { MIDI_BAND_3400,       "BAND3400",	MIDI_KEY},
+        { MIDI_BAND_40,         "BAND40",	MIDI_KEY},
+        { MIDI_BAND_430,        "BAND430",	MIDI_KEY},
+        { MIDI_BAND_6,          "BAND6",	MIDI_KEY},
+        { MIDI_BAND_60,         "BAND60",	MIDI_KEY},
+        { MIDI_BAND_70,         "BAND70",	MIDI_KEY},
+        { MIDI_BAND_80,         "BAND80",	MIDI_KEY},
+        { MIDI_BAND_902,        "BAND902",	MIDI_KEY},
+        { MIDI_BAND_AIR,        "BANDAIR",	MIDI_KEY},
+        { BAND_DOWN,            "BANDDOWN",	MIDI_KEY},
+        { MIDI_BAND_GEN,        "BANDGEN",	MIDI_KEY},
+        { BAND_UP,              "BANDUP",	MIDI_KEY},
+        { MIDI_BAND_WWV,        "BANDWWV",	MIDI_KEY},
+        { COMPRESS,     	"COMPRESS",	MIDI_KEY},
+	{ MIDI_CTUN,  		"CTUN",		MIDI_KEY},
+	{ VFO,			"CURRVFO",	MIDI_WHEEL},
+	{ CWLEFT,		"CWL",		MIDI_KEY},
+	{ CWRIGHT,		"CWR",		MIDI_KEY},
+	{ CWSPEED,		"CWSPEED",	MIDI_KNOB|MIDI_WHEEL},
+	{ DIV_COARSEGAIN,	"DIVCOARSEGAIN",	MIDI_KNOB|MIDI_WHEEL},
+	{ DIV_COARSEPHASE,	"DIVCOARSEPHASE",	MIDI_KNOB|MIDI_WHEEL},
+	{ DIV_FINEGAIN,		"DIVFINEGAIN",	MIDI_KNOB|MIDI_WHEEL},
+	{ DIV_FINEPHASE,	"DIVFINEPHASE",	MIDI_KNOB|MIDI_WHEEL},
+	{ DIV_GAIN,		"DIVGAIN",	MIDI_KNOB|MIDI_WHEEL},
+	{ DIV_PHASE,		"DIVPHASE",	MIDI_KNOB|MIDI_WHEEL},
+	{ DIV_TOGGLE,		"DIVTOGGLE",	MIDI_KEY},
+	{ MIDI_DUP,  		"DUP",		MIDI_KEY},
+        { FILTER_DOWN,  	"FILTERDOWN",	MIDI_KEY},
+        { FILTER_UP,    	"FILTERUP",	MIDI_KEY},
+	{ MENU_FILTER,		"MENU_FILTER",	MIDI_KEY},
+	{ MENU_MODE,		"MENU_MODE",	MIDI_KEY},
+	{ MIDI_LOCK,    	"LOCK",		MIDI_KEY},
+        { MIC_VOLUME,   	"MICGAIN",	MIDI_KNOB|MIDI_WHEEL},
+	{ MODE_DOWN,		"MODEDOWN",	MIDI_KEY|MIDI_KNOB|MIDI_WHEEL},
+	{ MODE_UP,		"MODEUP",	MIDI_KEY|MIDI_KNOB|MIDI_WHEEL},
+        { MIDI_MOX,     	"MOX",	MIDI_KEY},
+	{ MIDI_MUTE,		"MUTE",	MIDI_KEY},
+	{ MIDI_NB,    		"NOISEBLANKER",	MIDI_KEY},
+	{ MIDI_NR,    		"NOISEREDUCTION",	MIDI_KEY},
+	{ NUMPAD_0,		"NUMPAD0",	MIDI_KEY},
+	{ NUMPAD_1,		"NUMPAD1",	MIDI_KEY},
+	{ NUMPAD_2,		"NUMPAD2",	MIDI_KEY},
+	{ NUMPAD_3,		"NUMPAD3",	MIDI_KEY},
+	{ NUMPAD_4,		"NUMPAD4",	MIDI_KEY},
+	{ NUMPAD_5,		"NUMPAD5",	MIDI_KEY},
+	{ NUMPAD_6,		"NUMPAD6",	MIDI_KEY},
+	{ NUMPAD_7,		"NUMPAD7",	MIDI_KEY},
+	{ NUMPAD_8,		"NUMPAD8",	MIDI_KEY},
+	{ NUMPAD_9,		"NUMPAD9",	MIDI_KEY},
+	{ NUMPAD_CL,		"NUMPADCL",	MIDI_KEY},
+	{ NUMPAD_ENTER,		"NUMPADENTER",	MIDI_KEY},
+        { MIDI_PAN,		"PAN",	MIDI_KNOB|MIDI_WHEEL},
+        { PAN_HIGH,     	"PANHIGH",	MIDI_KNOB|MIDI_WHEEL},
+        { PAN_LOW,      	"PANLOW",	MIDI_KNOB|MIDI_WHEEL},
+        { PRE,          	"PREAMP",	MIDI_KEY},
+	{ MIDI_PS,    		"PURESIGNAL",	MIDI_KEY},
+	{ MIDI_RF_GAIN, 	"RFGAIN",	MIDI_KNOB|MIDI_WHEEL},
+        { TX_DRIVE,     	"RFPOWER",	MIDI_KNOB|MIDI_WHEEL},
+	{ MIDI_RIT_CLEAR,	"RITCLEAR",	MIDI_KEY},
+	{ RIT_STEP, 		"RITSTEP",	MIDI_KNOB|MIDI_WHEEL},
+        { RIT_TOGGLE,   	"RITTOGGLE",	MIDI_KEY},
+        { RIT_VAL,      	"RITVAL",	MIDI_KNOB|MIDI_WHEEL},
+        { MIDI_SAT,     	"SAT",	MIDI_KEY},
+        { SNB, 		    	"SNB",	MIDI_KEY},
+	{ MIDI_SPLIT,  		"SPLIT",	MIDI_KEY},
+	{ SWAP_RX,		"SWAPRX",	MIDI_KEY},
+	{ SWAP_VFO,		"SWAPVFO",	MIDI_KEY},
+        { MIDI_TUNE,    	"TUNE",	MIDI_KEY},
+        { VFOA,         	"VFOA",	MIDI_WHEEL},
+        { VFOB,         	"VFOB",	MIDI_WHEEL},
+	{ VFO_STEP_UP,  	"VFOSTEPUP",	MIDI_KEY},
+	{ VFO_STEP_DOWN,	"VFOSTEPDOWN",	MIDI_KEY},
+	{ VOX,   		"VOX",	MIDI_KEY},
+	{ VOXLEVEL,   		"VOXLEVEL",	MIDI_KNOB|MIDI_WHEEL},
+	{ MIDI_XIT_CLEAR,  	"XITCLEAR",	MIDI_KEY},
+	{ XIT_VAL,  		"XITVAL",	MIDI_KNOB|MIDI_WHEEL},
+	{ MIDI_ZOOM,		"ZOOM",	MIDI_KNOB|MIDI_WHEEL},
+	{ ZOOM_UP,		"ZOOMUP",	MIDI_KEY},
+	{ ZOOM_DOWN,		"ZOOMDOWN",	MIDI_KEY},
+        { ACTION_NONE,  	"NONE",	TYPE_NONE}
 };
 
 /*
@@ -182,12 +238,19 @@ static enum MIDIaction keyword2action(char *s) {
     return ACTION_NONE;
 }
 
+
+int MIDIstop() {
+  midi_enabled=FALSE;
+  close_midi_device();
+  return 0;
+}
+
 /*
- * Here we read in a MIDI description file "midi.def" and fill the MidiCommandsTable
+ * Here we read in a MIDI description file and fill the MidiCommandsTable
  * data structure
  */
 
-void MIDIstartup() {
+int MIDIstartup(char *filename) {
     FILE *fpin;
     char zeile[255];
     char *cp,*cq;
@@ -205,8 +268,14 @@ void MIDIstartup() {
     for (i=0; i<128; i++) MidiCommandsTable.desc[i]=NULL;
     MidiCommandsTable.pitch=NULL;
 
-    fpin=fopen("midi.props", "r");
-    if (!fpin) return;
+    g_print("%s: %s\n",__FUNCTION__,filename);
+    fpin=fopen(filename, "r");
+
+    g_print("%s: fpin=%p\n",__FUNCTION__,fpin);
+    if (!fpin) {
+      g_print("%s: failed to open MIDI device\n",__FUNCTION__);
+      return -1;
+    }
 
     for (;;) {
       if (fgets(zeile, 255, fpin) == NULL) break;
@@ -231,7 +300,7 @@ void MIDIstartup() {
 	cp++;
       }
       
-//fprintf(stderr,"\nMIDI:INP:%s\n",zeile);
+g_print("\n%s:INP:%s\n",__FUNCTION__,zeile);
 
       if ((cp = strstr(zeile, "DEVICE="))) {
         // Delete comments and trailing blanks
@@ -241,7 +310,7 @@ void MIDIstartup() {
 	while (cq > cp+7 && (*cq == ' ' || *cq == '\t')) cq--;
 	*(cq+1)=0;
 //fprintf(stderr,"MIDI:REG:>>>%s<<<\n",cp+7);
-	register_midi_device(cp+7);
+	int result=register_midi_device(cp+7);
         continue; // nothing more in this line
       }
       chan=-1;  // default: any channel
@@ -262,18 +331,18 @@ void MIDIstartup() {
         sscanf(cp+4, "%d", &key);
         event=MIDI_NOTE;
 	type=MIDI_KEY;
-//fprintf(stderr,"MIDI:KEY:%d\n", key);
+g_print("%s: MIDI:KEY:%d\n",__FUNCTION__, key);
       }
       if ((cp = strstr(zeile, "CTRL="))) {
         sscanf(cp+5, "%d", &key);
 	event=MIDI_CTRL;
 	type=MIDI_KNOB;
-//fprintf(stderr,"MIDI:CTL:%d\n", key);
+g_print("%s: MIDI:CTL:%d\n",__FUNCTION__, key);
       }
       if ((cp = strstr(zeile, "PITCH "))) {
         event=MIDI_PITCH;
 	type=MIDI_KNOB;
-//fprintf(stderr,"MIDI:PITCH\n");
+g_print("%s: MIDI:PITCH\n",__FUNCTION__);
       }
       //
       // If event is still undefined, skip line
@@ -293,20 +362,20 @@ void MIDIstartup() {
         sscanf(cp+5, "%d", &chan);
 	chan--;
         if (chan<0 || chan>15) chan=-1;
-//fprintf(stderr,"MIDI:CHA:%d\n",chan);
+g_print("%s:CHAN:%d\n",__FUNCTION__,chan);
       }
       if ((cp = strstr(zeile, "WHEEL")) && (type == MIDI_KNOB)) {
 	// change type from MIDI_KNOB to MIDI_WHEEL
         type=MIDI_WHEEL;
-//fprintf(stderr,"MIDI:WHEEL\n");
+g_print("%s:WHEEL\n",__FUNCTION__);
       }
       if ((cp = strstr(zeile, "ONOFF"))) {
         onoff=1;
-//fprintf(stderr,"MIDI:ONOFF\n");
+g_print("%s:ONOFF\n",__FUNCTION__);
       }
       if ((cp = strstr(zeile, "DELAY="))) {
         sscanf(cp+6, "%d", &delay);
-//fprintf(stderr,"MIDI:DELAY:%d\n",delay);
+g_print("%s:DELAY:%d\n",__FUNCTION__,delay);
       }
       if ((cp = strstr(zeile, "THR="))) {
         sscanf(cp+4, "%d %d %d %d %d %d %d %d %d %d %d %d",
@@ -319,7 +388,7 @@ void MIDIstartup() {
         while (*cq != 0 && *cq != '\n' && *cq != ' ' && *cq != '\t') cq++;
 	*cq=0;
         action=keyword2action(cp+7);
-//fprintf(stderr,"MIDI:ACTION:%s (%d)\n",cp+7, action);
+g_print("%s: MIDI:ACTION:%s (%d)\n",__FUNCTION__,cp+7, action);
       }
       //
       // All data for a descriptor has been read. Construct it!
@@ -359,7 +428,7 @@ void MIDIstartup() {
 	}
       }
       if (event == MIDI_KEY || event == MIDI_CTRL) {
-//fprintf(stderr,"MIDI:TAB:Insert desc=%p in CMDS[%d] table\n",desc,key);
+g_print("%s:TAB:Insert desc=%p in CMDS[%d] table\n",__FUNCTION__,desc,key);
 	dp = MidiCommandsTable.desc[key];
 	if (dp == NULL) {
 	  MidiCommandsTable.desc[key]=desc;
@@ -369,4 +438,6 @@ void MIDIstartup() {
 	}
       }
     }
+
+    return 0;
 }
diff --git a/midi3.c b/midi3.c
index 3a096aa..212f3c6 100644
--- a/midi3.c
+++ b/midi3.c
@@ -148,6 +148,166 @@ void DoTheMidi(enum MIDIaction action, enum MIDItype type, int val) {
 	      g_idle_add(ext_vfo_b_to_a, NULL);
 	    }
 	    break;
+	/////////////////////////////////////////////////////////// "NUMPADxx"
+	case NUMPAD_0:
+	    g_idle_add(ext_num_pad,GINT_TO_POINTER(0));
+            break;
+	case NUMPAD_1:
+	    g_idle_add(ext_num_pad,GINT_TO_POINTER(1));
+            break;
+	case NUMPAD_2:
+	    g_idle_add(ext_num_pad,GINT_TO_POINTER(2));
+            break;
+	case NUMPAD_3:
+	    g_idle_add(ext_num_pad,GINT_TO_POINTER(3));
+            break;
+	case NUMPAD_4:
+	    g_idle_add(ext_num_pad,GINT_TO_POINTER(4));
+            break;
+	case NUMPAD_5:
+	    g_idle_add(ext_num_pad,GINT_TO_POINTER(5));
+            break;
+	case NUMPAD_6:
+	    g_idle_add(ext_num_pad,GINT_TO_POINTER(6));
+            break;
+	case NUMPAD_7:
+	    g_idle_add(ext_num_pad,GINT_TO_POINTER(7));
+            break;
+	case NUMPAD_8:
+	    g_idle_add(ext_num_pad,GINT_TO_POINTER(8));
+            break;
+	case NUMPAD_9:
+	    g_idle_add(ext_num_pad,GINT_TO_POINTER(9));
+            break;
+	case NUMPAD_CL:
+	    g_idle_add(ext_num_pad,GINT_TO_POINTER(-1));
+            break;
+	case NUMPAD_ENTER:
+	    g_idle_add(ext_num_pad,GINT_TO_POINTER(-2));
+            break;
+	    break;
+
+	/////////////////////////////////////////////////////////// "BANDxxx"
+        case MIDI_BAND_10:
+            if (type == MIDI_KEY) {
+              g_idle_add(ext_band_select, GINT_TO_POINTER(band10));
+            }
+            break;
+        case MIDI_BAND_12:
+            if (type == MIDI_KEY) {
+              g_idle_add(ext_band_select, GINT_TO_POINTER(band12));
+            }
+            break;
+#ifdef SOAPYSDR
+        case MIDI_BAND_1240:
+            if (type == MIDI_KEY) {
+              g_idle_add(ext_band_select, GINT_TO_POINTER(band1240));
+            }
+            break;
+        case MIDI_BAND_144:
+            if (type == MIDI_KEY) {
+              g_idle_add(ext_band_select, GINT_TO_POINTER(band144));
+            }
+            break;
+#endif
+        case MIDI_BAND_15:
+            if (type == MIDI_KEY) {
+              g_idle_add(ext_band_select, GINT_TO_POINTER(band15));
+            }
+            break;
+        case MIDI_BAND_160:
+            if (type == MIDI_KEY) {
+              g_idle_add(ext_band_select, GINT_TO_POINTER(band160));
+            }
+            break;
+        case MIDI_BAND_17:
+            if (type == MIDI_KEY) {
+              g_idle_add(ext_band_select, GINT_TO_POINTER(band17));
+            }
+            break;
+        case MIDI_BAND_20:
+            if (type == MIDI_KEY) {
+              g_idle_add(ext_band_select, GINT_TO_POINTER(band20));
+            }
+            break;
+#ifdef SOAPYSDR
+        case MIDI_BAND_220:
+            if (type == MIDI_KEY) {
+              g_idle_add(ext_band_select, GINT_TO_POINTER(band220));
+            }
+            break;
+        case MIDI_BAND_2300:
+            if (type == MIDI_KEY) {
+              g_idle_add(ext_band_select, GINT_TO_POINTER(band2300));
+            }
+            break;
+#endif
+        case MIDI_BAND_30:
+            if (type == MIDI_KEY) {
+              g_idle_add(ext_band_select, GINT_TO_POINTER(band30));
+            }
+            break;
+#ifdef SOAPYSDR
+        case MIDI_BAND_3400:
+            if (type == MIDI_KEY) {
+              g_idle_add(ext_band_select, GINT_TO_POINTER(band3400));
+            }
+            break;
+        case MIDI_BAND_70:
+            if (type == MIDI_KEY) {
+              g_idle_add(ext_band_select, GINT_TO_POINTER(band70));
+            }
+            break;
+#endif
+        case MIDI_BAND_40:
+            if (type == MIDI_KEY) {
+              g_idle_add(ext_band_select, GINT_TO_POINTER(band40));
+            }
+            break;
+#ifdef SOAPYSDR
+        case MIDI_BAND_430:
+            if (type == MIDI_KEY) {
+              g_idle_add(ext_band_select, GINT_TO_POINTER(band430));
+            }
+            break;
+#endif
+        case MIDI_BAND_6:
+            if (type == MIDI_KEY) {
+              g_idle_add(ext_band_select, GINT_TO_POINTER(band6));
+            }
+            break;
+        case MIDI_BAND_60:
+            if (type == MIDI_KEY) {
+              g_idle_add(ext_band_select, GINT_TO_POINTER(band60));
+            }
+            break;
+        case MIDI_BAND_80:
+            if (type == MIDI_KEY) {
+              g_idle_add(ext_band_select, GINT_TO_POINTER(band80));
+            }
+            break;
+#ifdef SOAPYSDR
+        case MIDI_BAND_902:
+            if (type == MIDI_KEY) {
+              g_idle_add(ext_band_select, GINT_TO_POINTER(band902));
+            }
+            break;
+        case MIDI_BAND_AIR:
+            if (type == MIDI_KEY) {
+              g_idle_add(ext_band_select, GINT_TO_POINTER(bandAIR));
+            }
+            break;
+#endif
+        case MIDI_BAND_GEN:
+            if (type == MIDI_KEY) {
+              g_idle_add(ext_band_select, GINT_TO_POINTER(bandGen));
+            }
+            break;
+        case MIDI_BAND_WWV:
+            if (type == MIDI_KEY) {
+              g_idle_add(ext_band_select, GINT_TO_POINTER(bandWWV));
+            }
+            break;
 	/////////////////////////////////////////////////////////// "BANDDOWN"
 	/////////////////////////////////////////////////////////// "BANDUP"
 	case BAND_DOWN:
@@ -218,15 +378,15 @@ void DoTheMidi(enum MIDIaction action, enum MIDItype type, int val) {
 	    break;
 	/////////////////////////////////////////////////////////// "CWL"
 	/////////////////////////////////////////////////////////// "CWR"
-	case CWL: // only key
-	case CWR: // only key
+	case CWLEFT: // only key
+	case CWRIGHT: // only key
 #ifdef LOCALCW
 	    if (type == MIDI_KEY) {
-		new=(action == CWL);
+		new=(action == CWLEFT);
 		keyer_event(new,val);
 	    }
 #else
-	    g_print("%s: %s:%d\n",__FUNCTION__,action==CWL?"CWL":"CWR",val);
+	    g_print("%s: %s:%d\n",__FUNCTION__,action==CWLEFT?"CWL":"CWR",val);
 
 #endif
 	    break;
@@ -375,6 +535,14 @@ void DoTheMidi(enum MIDIaction action, enum MIDItype type, int val) {
 	      g_idle_add(ext_vfo_filter_changed, GINT_TO_POINTER(new));
 	    }
 	    break;
+	/////////////////////////////////////////////////////////// "MENU_FILTFILTERER"
+	case MENU_FILTER:
+	    g_idle_add(ext_menu_filter, NULL);
+	    break;
+	/////////////////////////////////////////////////////////// "MENU_MODE"
+	case MENU_MODE:
+	    g_idle_add(ext_menu_mode, NULL);
+	    break;
 	/////////////////////////////////////////////////////////// "LOCK"
 	case MIDI_LOCK: // only key supported
 	    if (type == MIDI_KEY) {
diff --git a/midi_menu.c b/midi_menu.c
new file mode 100644
index 0000000..2a85480
--- /dev/null
+++ b/midi_menu.c
@@ -0,0 +1,1222 @@
+/* Copyright (C)
+* 2020 - 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.
+*
+*/
+
+#include <gtk/gtk.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <termios.h>
+
+#include "discovered.h"
+#include "mode.h"
+#include "filter.h"
+#include "band.h"
+#include "receiver.h"
+#include "transmitter.h"
+#include "receiver.h"
+#include "adc.h"
+#include "dac.h"
+#include "radio.h"
+#include "midi.h"
+#include "alsa_midi.h"
+#include "new_menu.h"
+#include "midi_menu.h"
+#include "property.h"
+
+enum {
+  EVENT_COLUMN,
+  CHANNEL_COLUMN,
+  NOTE_COLUMN,
+  TYPE_COLUMN,
+  ACTION_COLUMN,
+  N_COLUMNS
+};
+
+static GtkWidget *parent_window=NULL;
+static GtkWidget *menu_b=NULL;
+static GtkWidget *dialog=NULL;
+
+static GtkWidget *midi_enable_b;
+
+static GtkListStore *store;
+static GtkWidget *view;
+static GtkWidget *scrolled_window=NULL;
+static gulong selection_signal_id;
+static GtkTreeModel *model;
+static GtkTreeIter iter;
+struct desc *current_cmd;
+
+static GtkWidget *filename;
+
+static GtkWidget *newEvent;
+static GtkWidget *newChannel;
+static GtkWidget *newNote;
+static GtkWidget *newVal;
+static GtkWidget *newType;
+static GtkWidget *newMin;
+static GtkWidget *newMax;
+static GtkWidget *newAction;
+static GtkWidget *configure_b;
+static GtkWidget *add_b;
+static GtkWidget *update_b;
+static GtkWidget *delete_b;
+
+static enum MIDIevent thisEvent=EVENT_NONE;
+static int thisChannel;
+static int thisNote;
+static int thisVal;
+static int thisMin;
+static int thisMax;
+static enum MIDItype thisType;
+static enum MIDIaction thisAction;
+
+gchar *midi_device_name=NULL;
+static gint device_index=-1;
+
+enum {
+  UPDATE_NEW,
+  UPDATE_CURRENT,
+  UPDATE_EXISTING
+};
+
+static int update(void *data);
+static void load_store();
+
+static void cleanup() {
+  if(dialog!=NULL) {
+    gtk_widget_destroy(dialog);
+    dialog=NULL;
+    sub_menu=NULL;
+  }
+}
+
+static gboolean close_cb (GtkWidget *widget, GdkEventButton *event, gpointer data) {
+  cleanup();
+  return TRUE;
+}
+
+static gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
+  cleanup();
+  return FALSE;
+}
+
+static gboolean midi_enable_cb(GtkWidget *widget,gpointer data) {
+  if(midi_enabled) {
+    close_midi_device();
+  }
+  midi_enabled=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON (widget));
+  if(midi_enabled) {
+    if(register_midi_device(midi_device_name)<0) {
+      midi_enabled=FALSE;
+      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (widget), midi_enabled);
+    }
+  }
+  return TRUE;
+}
+
+static void configure_cb(GtkWidget *widget, gpointer data) {
+  gboolean conf=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON (widget));
+  configure_midi_device(conf);
+}
+
+static void device_changed_cb(GtkWidget *widget, gpointer data) {
+  device_index = gtk_combo_box_get_active(GTK_COMBO_BOX(widget));
+  if(midi_device_name!=NULL) {
+    g_free(midi_device_name);
+  }
+  midi_device_name=g_new(gchar,strlen(midi_devices[device_index].name)+1);
+  strcpy(midi_device_name,midi_devices[device_index].name);
+  if(midi_enabled) {
+    close_midi_device();
+    if(register_midi_device(midi_device_name)) {
+      midi_enabled=FALSE;
+      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(midi_enable_b), midi_enabled);
+    }
+  }
+}
+
+static void type_changed_cb(GtkWidget *widget, gpointer data) {
+  int i=1;
+  int j=1;
+
+  // update actions available for the type
+  gchar *type=gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(widget));
+
+  g_print("%s: type=%s action=%d\n",__FUNCTION__,type,thisAction);
+  gtk_combo_box_text_remove_all(GTK_COMBO_BOX_TEXT(newAction));
+  gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(newAction),NULL,ActionTable[0].str);
+  if(type==NULL || strcmp(type,"NONE")==0) {
+    // leave empty
+    gtk_combo_box_set_active (GTK_COMBO_BOX(newAction),0);
+  } else if(strcmp(type,"KEY")==0) {
+    // add all the Key actions
+    while(ActionTable[i].action!=ACTION_NONE) {
+      if(ActionTable[i].type&MIDI_KEY) {
+        gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(newAction),NULL,ActionTable[i].str);
+	if(ActionTable[i].action==thisAction) {
+          gtk_combo_box_set_active(GTK_COMBO_BOX(newAction),j);
+	}
+	j++;
+      }
+      i++;
+    }
+  } else if(strcmp(type,"KNOB/SLIDER")==0) {
+    // add all the Knob actions
+    while(ActionTable[i].action!=ACTION_NONE) {
+      if(ActionTable[i].type&MIDI_KNOB) {
+        gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(newAction),NULL,ActionTable[i].str);
+	if(ActionTable[i].action==thisAction) {
+          gtk_combo_box_set_active (GTK_COMBO_BOX(newAction),j);
+	}
+	j++;
+      }
+      i++;
+    }
+  } else if(strcmp(type,"WHEEL")==0) {
+    // add all the Wheel actions
+    while(ActionTable[i].action!=ACTION_NONE) {
+      if(ActionTable[i].type&MIDI_WHEEL || ActionTable[i].type&MIDI_KNOB) {
+        gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(newAction),NULL,ActionTable[i].str);
+	if(ActionTable[i].action==thisAction) {
+          gtk_combo_box_set_active (GTK_COMBO_BOX(newAction),j);
+	}
+	j++;
+      }
+      i++;
+    }
+  }
+}
+
+static void row_inserted_cb(GtkTreeModel *tree_model,GtkTreePath *path, GtkTreeIter *iter,gpointer user_data) {
+  //g_print("%s\n",__FUNCTION__);
+  gtk_tree_view_set_cursor(GTK_TREE_VIEW(view),path,NULL,FALSE);
+}
+
+
+static void tree_selection_changed_cb (GtkTreeSelection *selection, gpointer data) {
+  char *str_event;
+  char *str_channel;
+  char *str_note;
+  char *str_type;
+  char *str_action;
+
+  //g_print("%s\n",__FUNCTION__);
+  //if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(configure_b))) {
+    if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
+      gtk_tree_model_get(model, &iter, EVENT_COLUMN, &str_event, -1);
+      gtk_tree_model_get(model, &iter, CHANNEL_COLUMN, &str_channel, -1);
+      gtk_tree_model_get(model, &iter, NOTE_COLUMN, &str_note, -1);
+      gtk_tree_model_get(model, &iter, TYPE_COLUMN, &str_type, -1);
+      gtk_tree_model_get(model, &iter, ACTION_COLUMN, &str_action, -1);
+
+      g_print("%s: %s %s %s %s %s\n",__FUNCTION__,str_event,str_channel,str_note,str_type,str_action);
+
+      if(str_event!=NULL && str_channel!=NULL && str_note!=NULL && str_type!=NULL && str_action!=NULL) {
+
+        if(strcmp(str_event,"CTRL")==0) {
+          thisEvent=MIDI_CTRL;
+        } else if(strcmp(str_event,"PITCH")==0) {
+          thisEvent=MIDI_PITCH;
+        } else if(strcmp(str_event,"NOTE")==0) {
+          thisEvent=MIDI_NOTE;
+        } else {
+          thisEvent=EVENT_NONE;
+        }
+        thisChannel=atoi(str_channel);
+        thisNote=atoi(str_note);
+        thisVal=0;
+        thisMin=0;
+        thisMax=0;
+        if(strcmp(str_type,"KEY")==0) {
+          thisType=MIDI_KEY;
+        } else if(strcmp(str_type,"KNOB/SLIDER")==0) {
+          thisType=MIDI_KNOB;
+        } else if(strcmp(str_type,"WHEEL")==0) {
+          thisType=MIDI_WHEEL;
+        } else {
+          thisType=TYPE_NONE;
+        }
+        thisAction=ACTION_NONE;
+        int i=1;
+        while(ActionTable[i].action!=ACTION_NONE) {
+          if(strcmp(ActionTable[i].str,str_action)==0) {
+            thisAction=ActionTable[i].action;
+            break;
+          }
+          i++;
+        }
+        g_idle_add(update,GINT_TO_POINTER(UPDATE_EXISTING));
+      }
+    }
+  //}
+}
+
+static void find_current_cmd() {
+  struct desc *cmd;
+  g_print("%s:\n",__FUNCTION__);
+  cmd=MidiCommandsTable.desc[thisNote];
+  while(cmd!=NULL) {
+    if((cmd->channel==thisChannel || cmd->channel==-1) && cmd->type==thisType && cmd->action==thisAction) {
+      g_print("%s: found cmd %p\n",__FUNCTION__,cmd);
+      break;
+    }
+    cmd=cmd->next;
+  }
+  current_cmd=cmd;
+}
+
+static void clear_cb(GtkWidget *widget,gpointer user_data) {
+  struct desc *cmd;
+  struct desc *next;
+  for(int i=0;i<128;i++) {
+    cmd=MidiCommandsTable.desc[i];
+    while(cmd!=NULL) {
+      next=cmd->next;
+      g_free(cmd);
+      cmd=next;
+    }
+    MidiCommandsTable.desc[i]=NULL;
+  }
+  gtk_list_store_clear(store);
+}
+
+static void save_cb(GtkWidget *widget,gpointer user_data) {
+  GtkWidget *save_dialog;
+  GtkFileChooser *chooser;
+  GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_SAVE;
+  gchar *filename;
+  gint res;
+  struct desc *cmd;
+
+  save_dialog = gtk_file_chooser_dialog_new ("Save File",
+                                      GTK_WINDOW(dialog),
+                                      action,
+                                      "_Cancel",
+                                      GTK_RESPONSE_CANCEL,
+                                      "_Save",
+                                      GTK_RESPONSE_ACCEPT,
+                                      NULL);
+  chooser = GTK_FILE_CHOOSER (save_dialog);
+  gtk_file_chooser_set_do_overwrite_confirmation (chooser, TRUE);
+  if(midi_device_name==NULL) {
+    filename=g_new(gchar,10);
+    sprintf(filename,"midi.midi");
+  } else {
+    filename=g_new(gchar,strlen(midi_device_name)+6);
+    sprintf(filename,"%s.midi",midi_device_name);
+  }
+  gtk_file_chooser_set_current_name(chooser,filename);
+  res = gtk_dialog_run (GTK_DIALOG (save_dialog));
+  if(res==GTK_RESPONSE_ACCEPT) {
+    char *savefilename=gtk_file_chooser_get_filename(chooser);
+    clearProperties();
+    midi_save_state();
+    saveProperties(savefilename);
+    g_free(savefilename);
+  }
+  gtk_widget_destroy(save_dialog);
+  g_free(filename);
+}
+
+static void load_cb(GtkWidget *widget,gpointer user_data) {
+  GtkWidget *load_dialog;
+  GtkFileChooser *chooser;
+  GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
+  gchar *filename;
+  gint res;
+  struct desc *cmd;
+
+  load_dialog = gtk_file_chooser_dialog_new ("Open MIDI File",
+                                      GTK_WINDOW(dialog),
+                                      action,
+                                      "_Cancel",
+                                      GTK_RESPONSE_CANCEL,
+                                      "_Save",
+                                      GTK_RESPONSE_ACCEPT,
+                                      NULL);
+  chooser = GTK_FILE_CHOOSER (load_dialog);
+  if(midi_device_name==NULL) {
+    filename=g_new(gchar,10);
+    sprintf(filename,"midi.midi");
+  } else {
+    filename=g_new(gchar,strlen(midi_device_name)+6);
+    sprintf(filename,"%s.midi",midi_device_name);
+  }
+  gtk_file_chooser_set_current_name(chooser,filename);
+  res = gtk_dialog_run (GTK_DIALOG (load_dialog));
+  if(res==GTK_RESPONSE_ACCEPT) {
+    char *loadfilename=gtk_file_chooser_get_filename(chooser);
+    clear_cb(NULL,NULL);
+    clearProperties();
+    loadProperties(loadfilename);
+    midi_restore_state();
+    load_store();
+    g_free(loadfilename);
+  }
+  gtk_widget_destroy(load_dialog);
+  g_free(filename);
+}
+
+static void load_original_cb(GtkWidget *widget,gpointer user_data) {
+  GtkWidget *load_dialog;
+  GtkFileChooser *chooser;
+  GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
+  gchar *filename;
+  gint res;
+  struct desc *cmd;
+
+  load_dialog = gtk_file_chooser_dialog_new ("Open ORIGINAL MIDI File",
+                                      GTK_WINDOW(dialog),
+                                      action,
+                                      "_Cancel",
+                                      GTK_RESPONSE_CANCEL,
+                                      "_Save",
+                                      GTK_RESPONSE_ACCEPT,
+                                      NULL);
+  chooser = GTK_FILE_CHOOSER (load_dialog);
+  filename=g_new(gchar,strlen(midi_device_name)+6);
+  sprintf(filename,"%s.midi",midi_device_name);
+  gtk_file_chooser_set_current_name(chooser,filename);
+  res = gtk_dialog_run (GTK_DIALOG (load_dialog));
+  if(res==GTK_RESPONSE_ACCEPT) {
+    char *loadfilename=gtk_file_chooser_get_filename(chooser);
+    clear_cb(NULL,NULL);
+    MIDIstartup(loadfilename);
+    load_store();
+    g_free(loadfilename);
+  }
+  gtk_widget_destroy(load_dialog);
+  g_free(filename);
+}
+
+static void add_store(int key,struct desc *cmd) {
+  char str_event[16];
+  char str_channel[16];
+  char str_note[16];
+  char str_type[32];
+  char str_action[32];
+
+  //g_print("%s: key=%d desc=%p\n",__FUNCTION__,key,cmd);
+  switch(cmd->event) {
+    case EVENT_NONE:
+      strcpy(str_event,"NONE");
+      break;
+    case MIDI_NOTE:
+      strcpy(str_event,"NOTE");
+      break;
+    case MIDI_CTRL:
+      strcpy(str_event,"CTRL");
+      break;
+    case MIDI_PITCH:
+      strcpy(str_event,"PITCH");
+      break;
+  }
+  sprintf(str_channel,"%d",cmd->channel);
+  sprintf(str_note,"%d",key);
+  switch(cmd->type) {
+    case TYPE_NONE:
+      strcpy(str_type,"NONE");
+      break;
+    case MIDI_KEY:
+      strcpy(str_type,"KEY");
+      break;
+    case MIDI_KNOB:
+      strcpy(str_type,"KNOB/SLIDER");
+      break;
+    case MIDI_WHEEL:
+      strcpy(str_type,"WHEEL");
+      break;
+  }
+  strcpy(str_action,ActionTable[cmd->action].str);
+  gtk_list_store_prepend(store,&iter);
+  gtk_list_store_set(store,&iter,
+      EVENT_COLUMN,str_event,
+      CHANNEL_COLUMN,str_channel,
+      NOTE_COLUMN,str_note,
+      TYPE_COLUMN,str_type,
+      ACTION_COLUMN,str_action,
+      -1);
+
+  if(scrolled_window!=NULL) {
+    GtkAdjustment *adjustment=gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW(scrolled_window));
+    //g_print("%s: adjustment=%f lower=%f upper=%f\n",__FUNCTION__,gtk_adjustment_get_value(adjustment),gtk_adjustment_get_lower(adjustment),gtk_adjustment_get_upper(adjustment));
+    if(gtk_adjustment_get_value(adjustment)!=0.0) {
+      gtk_adjustment_set_value(adjustment,0.0);
+    }
+  }
+}
+
+static void load_store() {
+  struct desc *cmd;
+  gtk_list_store_clear(store);
+  for(int i=0;i<128;i++) {
+    cmd=MidiCommandsTable.desc[i];
+    while(cmd!=NULL) {
+      add_store(i,cmd);
+      cmd=cmd->next;
+    }
+  }
+}
+
+static void add_cb(GtkButton *widget,gpointer user_data) {
+
+  gchar *str_type=gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(newType));
+  gchar *str_action=gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(newAction));
+;
+
+  gint i;
+  gint type;
+  gint action;
+
+  if(strcmp(str_type,"KEY")==0) {
+    type=MIDI_KEY;
+  } else if(strcmp(str_type,"KNOB/SLIDER")==0) {
+    type=MIDI_KNOB;
+  } else if(strcmp(str_type,"WHEEL")==0) {
+    type=MIDI_WHEEL;
+  } else {
+    type=TYPE_NONE;
+  }
+
+  action=ACTION_NONE;
+  i=1;
+  while(ActionTable[i].action!=ACTION_NONE) {
+    if(strcmp(ActionTable[i].str,str_action)==0) {
+      action=ActionTable[i].action;
+      break;
+    }
+    i++;
+  }
+
+  g_print("%s: type=%s (%d) action=%s (%d)\n",__FUNCTION__,str_type,type,str_action,action);
+
+  struct desc *desc;
+  desc = (struct desc *) malloc(sizeof(struct desc));
+  desc->next = NULL;
+  desc->action = action; // MIDIaction
+  desc->type = type; // MIDItype
+  desc->event = thisEvent; // MIDevent
+  desc->onoff = action==CWLEFT || action==CWRIGHT;
+  desc->delay = 0;
+  desc->vfl1  = -1;
+  desc->vfl2  = -1;
+  desc->fl1   = -1;
+  desc->fl2   = -1;
+  desc->lft1  = -1;
+  desc->lft2  = 63;
+  desc->rgt1  = 64;
+  desc->rgt2  = 128;
+  desc->fr1   = 128;
+  desc->fr2   = 128;
+  desc->vfr1  = 128;
+  desc->vfr2  = 128;
+  desc->channel  = thisChannel;
+
+  gint key=thisNote;
+  if(key<0) key=0;
+  if(key>127) key=0;
+
+
+  if(MidiCommandsTable.desc[key]!=NULL) {
+    desc->next=MidiCommandsTable.desc[key];
+  }
+  MidiCommandsTable.desc[key]=desc;
+    
+  add_store(key,desc);
+
+  gtk_widget_set_sensitive(add_b,FALSE);
+  gtk_widget_set_sensitive(update_b,TRUE);
+  gtk_widget_set_sensitive(delete_b,TRUE);
+
+}
+
+static void update_cb(GtkButton *widget,gpointer user_data) {
+  char str_event[16];
+  char str_channel[16];
+  char str_note[16];
+  int i;
+
+
+  gchar *str_type=gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(newType));
+  gchar *str_action=gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(newAction));
+;
+  //g_print("%s: type=%s action=%s\n",__FUNCTION__,str_type,str_action);
+
+  if(strcmp(str_type,"KEY")==0) {
+    thisType=MIDI_KEY;
+  } else if(strcmp(str_type,"KNOB/SLIDER")==0) {
+    thisType=MIDI_KNOB;
+  } else if(strcmp(str_type,"WHEEL")==0) {
+    thisType=MIDI_WHEEL;
+  } else {
+    thisType=TYPE_NONE;
+  }
+
+  thisAction=ACTION_NONE;
+  i=1;
+  while(ActionTable[i].action!=ACTION_NONE) {
+    if(strcmp(ActionTable[i].str,str_action)==0) {
+      thisAction=ActionTable[i].action;
+      break;
+    }
+    i++;
+  }
+
+  current_cmd->channel=thisChannel;
+  current_cmd->type=thisType;
+  current_cmd->action=thisAction;
+
+  switch(current_cmd->event) {
+    case EVENT_NONE:
+      strcpy(str_event,"NONE");
+      break;
+    case MIDI_NOTE:
+      strcpy(str_event,"NOTE");
+      break;
+    case MIDI_CTRL:
+      strcpy(str_event,"CTRL");
+      break;
+    case MIDI_PITCH:
+      strcpy(str_event,"PITCH");
+      break;
+  }
+  sprintf(str_channel,"%d",current_cmd->channel);
+  sprintf(str_note,"%d",thisNote);
+
+  //g_print("%s: event=%s channel=%s note=%s type=%s action=%s\n",
+  //        __FUNCTION__,str_event,str_channel,str_note,str_type,str_action);
+  gtk_list_store_set(store,&iter,
+      EVENT_COLUMN,str_event,
+      CHANNEL_COLUMN,str_channel,
+      NOTE_COLUMN,str_note,
+      TYPE_COLUMN,str_type,
+      ACTION_COLUMN,str_action,
+      -1);
+}
+
+static void delete_cb(GtkButton *widget,gpointer user_data) {
+  struct desc *previous_cmd;
+  struct desc *next_cmd;
+  GtkTreeIter saved_iter;
+  g_print("%s: thisNote=%d current_cmd=%p\n",__FUNCTION__,thisNote,current_cmd);
+
+  saved_iter=iter;
+
+
+  // remove from MidiCommandsTable
+  if(MidiCommandsTable.desc[thisNote]==current_cmd) {
+    g_print("%s: remove first\n",__FUNCTION__);
+    MidiCommandsTable.desc[thisNote]=current_cmd->next;
+    g_free(current_cmd);
+  } else {
+    previous_cmd=MidiCommandsTable.desc[thisNote];
+    while(previous_cmd->next!=NULL) {
+      next_cmd=previous_cmd->next;
+      if(next_cmd==current_cmd) {
+        g_print("%s: remove next\n",__FUNCTION__);
+	previous_cmd->next=next_cmd->next;
+	g_free(next_cmd);
+	break;
+      }
+      previous_cmd=next_cmd;
+    }
+  }
+
+  // remove from list store
+  gtk_list_store_remove(store,&saved_iter);
+
+  gtk_widget_set_sensitive(add_b,TRUE);
+  gtk_widget_set_sensitive(update_b,FALSE);
+  gtk_widget_set_sensitive(delete_b,FALSE);
+
+}
+
+void midi_menu(GtkWidget *parent) {
+  int i;
+  int col=0;
+  int row=0;
+  GtkCellRenderer *renderer;
+
+  dialog=gtk_dialog_new();
+  gtk_window_set_transient_for(GTK_WINDOW(dialog),GTK_WINDOW(parent_window));
+  char title[64];
+  sprintf(title,"piHPSDR - MIDI");
+  gtk_window_set_title(GTK_WINDOW(dialog),title);
+  g_signal_connect (dialog, "delete_event", G_CALLBACK (delete_event), NULL);
+
+  GdkRGBA color;
+  color.red = 1.0;
+  color.green = 1.0;
+  color.blue = 1.0;
+  color.alpha = 1.0;
+  gtk_widget_override_background_color(dialog,GTK_STATE_FLAG_NORMAL,&color);
+
+  GtkWidget *content=gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+
+  GtkWidget *grid=gtk_grid_new();
+  gtk_grid_set_column_spacing (GTK_GRID(grid),2);
+
+  row=0;
+  col=0;
+
+  get_midi_devices();
+  if(n_midi_devices>0) {
+    GtkWidget *devices_label=gtk_label_new("Select MIDI device: ");
+    gtk_grid_attach(GTK_GRID(grid),devices_label,col,row,3,1);
+    col+=3;
+
+    GtkWidget *devices=gtk_combo_box_text_new();
+    for(int i=0;i<n_midi_devices;i++) {
+      gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(devices),NULL,midi_devices[i].name);
+      if(midi_device_name!=NULL) {
+        if(strcmp(midi_device_name,midi_devices[i].name)==0) {
+          device_index=i;
+        }
+      }
+    }
+    gtk_grid_attach(GTK_GRID(grid),devices,col,row,6,1);
+    gtk_combo_box_set_active(GTK_COMBO_BOX(devices),device_index);
+    g_signal_connect(devices,"changed",G_CALLBACK(device_changed_cb),NULL);
+  } else {
+    GtkWidget *message=gtk_label_new("No MIDI devices found!");
+    gtk_grid_attach(GTK_GRID(grid),message,col,row,1,1);
+  }
+  row++;
+  col=0;
+
+  midi_enable_b=gtk_check_button_new_with_label("MIDI Enable");
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (midi_enable_b), midi_enabled);
+  gtk_grid_attach(GTK_GRID(grid),midi_enable_b,col,row,3,1);
+  g_signal_connect(midi_enable_b,"toggled",G_CALLBACK(midi_enable_cb),NULL);
+
+  row++;
+  col=0;
+
+  GtkWidget *clear_b=gtk_button_new_with_label("Clear");
+  gtk_grid_attach(GTK_GRID(grid),clear_b,col,row,1,1);
+  g_signal_connect(clear_b,"clicked",G_CALLBACK(clear_cb),NULL);
+  col++;
+
+  GtkWidget *save_b=gtk_button_new_with_label("Save");
+  gtk_grid_attach(GTK_GRID(grid),save_b,col,row,1,1);
+  g_signal_connect(save_b,"clicked",G_CALLBACK(save_cb),NULL);
+  col++;
+
+  GtkWidget *load_b=gtk_button_new_with_label("Load");
+  gtk_grid_attach(GTK_GRID(grid),load_b,col,row,1,1);
+  g_signal_connect(load_b,"clicked",G_CALLBACK(load_cb),NULL);
+  col++;
+
+  GtkWidget *load_original_b=gtk_button_new_with_label("Load Original");
+  gtk_grid_attach(GTK_GRID(grid),load_original_b,col,row,1,1);
+  g_signal_connect(load_original_b,"clicked",G_CALLBACK(load_original_cb),NULL);
+  col++;
+
+  row++;
+  col=0;
+
+  configure_b=gtk_check_button_new_with_label("MIDI Configure");
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (configure_b), FALSE);
+  gtk_grid_attach(GTK_GRID(grid),configure_b,col,row,3,1);
+  g_signal_connect(configure_b,"toggled",G_CALLBACK(configure_cb),NULL);
+
+
+  row++;
+  col=0;
+  GtkWidget *label=gtk_label_new("Evt");
+  gtk_grid_attach(GTK_GRID(grid),label,col++,row,1,1);
+  label=gtk_label_new("Ch");
+  gtk_grid_attach(GTK_GRID(grid),label,col++,row,1,1);
+  label=gtk_label_new("Note");
+  gtk_grid_attach(GTK_GRID(grid),label,col++,row,1,1);
+  label=gtk_label_new("Type");
+  gtk_grid_attach(GTK_GRID(grid),label,col++,row,1,1);
+  label=gtk_label_new("Value");
+  gtk_grid_attach(GTK_GRID(grid),label,col++,row,1,1);
+  label=gtk_label_new("Min");
+  gtk_grid_attach(GTK_GRID(grid),label,col++,row,1,1);
+  label=gtk_label_new("Max");
+  gtk_grid_attach(GTK_GRID(grid),label,col++,row,1,1);
+  label=gtk_label_new("Action");
+  gtk_grid_attach(GTK_GRID(grid),label,col++,row,1,1);
+
+
+  row++;
+  col=0;
+  newEvent=gtk_label_new("");
+  gtk_grid_attach(GTK_GRID(grid),newEvent,col++,row,1,1);
+  newChannel=gtk_label_new("");
+  gtk_grid_attach(GTK_GRID(grid),newChannel,col++,row,1,1);
+  newNote=gtk_label_new("");
+  gtk_grid_attach(GTK_GRID(grid),newNote,col++,row,1,1);
+  newType=gtk_combo_box_text_new();
+  gtk_grid_attach(GTK_GRID(grid),newType,col++,row,1,1);
+  g_signal_connect(newType,"changed",G_CALLBACK(type_changed_cb),NULL);
+  newVal=gtk_label_new("");
+  gtk_grid_attach(GTK_GRID(grid),newVal,col++,row,1,1);
+  newMin=gtk_label_new("");
+  gtk_grid_attach(GTK_GRID(grid),newMin,col++,row,1,1);
+  newMax=gtk_label_new("");
+  gtk_grid_attach(GTK_GRID(grid),newMax,col++,row,1,1);
+  newAction=gtk_combo_box_text_new();
+  gtk_combo_box_set_wrap_width(GTK_COMBO_BOX(newAction),5);
+  gtk_grid_attach(GTK_GRID(grid),newAction,col++,row,1,1);
+
+  add_b=gtk_button_new_with_label("Add");
+  g_signal_connect(add_b, "pressed", G_CALLBACK(add_cb),NULL);
+  gtk_grid_attach(GTK_GRID(grid),add_b,col++,row,1,1);
+  gtk_widget_set_sensitive(add_b,FALSE);
+
+
+  update_b=gtk_button_new_with_label("Update");
+  g_signal_connect(update_b, "pressed", G_CALLBACK(update_cb),NULL);
+  gtk_grid_attach(GTK_GRID(grid),update_b,col++,row,1,1);
+  gtk_widget_set_sensitive(update_b,FALSE);
+
+  delete_b=gtk_button_new_with_label("Delete");
+  g_signal_connect(delete_b, "pressed", G_CALLBACK(delete_cb),NULL);
+  gtk_grid_attach(GTK_GRID(grid),delete_b,col++,row,1,1);
+  gtk_widget_set_sensitive(delete_b,FALSE);
+  row++;
+  col=0;
+
+  scrolled_window=gtk_scrolled_window_new (NULL, NULL);
+  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),GTK_POLICY_AUTOMATIC,GTK_POLICY_ALWAYS);
+  gtk_widget_set_size_request(scrolled_window,400,300);
+
+  view=gtk_tree_view_new();
+
+  renderer=gtk_cell_renderer_text_new();
+  gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, "Event", renderer, "text", EVENT_COLUMN, NULL);
+
+  renderer=gtk_cell_renderer_text_new();
+  gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, "CHANNEL", renderer, "text", CHANNEL_COLUMN, NULL);
+
+  renderer=gtk_cell_renderer_text_new();
+  gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, "NOTE", renderer, "text", NOTE_COLUMN, NULL);
+
+  renderer=gtk_cell_renderer_text_new();
+  gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, "TYPE", renderer, "text", TYPE_COLUMN, NULL);
+
+  renderer=gtk_cell_renderer_text_new();
+  gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, "ACTION", renderer, "text", ACTION_COLUMN, NULL);
+
+  store=gtk_list_store_new(N_COLUMNS,G_TYPE_STRING,G_TYPE_STRING,G_TYPE_STRING,G_TYPE_STRING,G_TYPE_STRING,G_TYPE_STRING,G_TYPE_STRING);
+
+  load_store();
+
+  gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(store));
+
+  gtk_container_add(GTK_CONTAINER(scrolled_window),view);
+
+  gtk_grid_attach(GTK_GRID(grid), scrolled_window, col, row, 6, 10);
+
+  model=gtk_tree_view_get_model(GTK_TREE_VIEW(view));
+  g_signal_connect(model,"row-inserted",G_CALLBACK(row_inserted_cb),NULL);
+
+  GtkTreeSelection *selection=gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
+  gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
+
+  selection_signal_id=g_signal_connect(G_OBJECT(selection),"changed",G_CALLBACK(tree_selection_changed_cb),NULL);
+
+  gtk_container_add(GTK_CONTAINER(content),grid);
+  sub_menu=dialog;
+  gtk_widget_show_all(dialog);
+}
+
+static int update(void *data) {
+  int state=GPOINTER_TO_INT(data);
+  gchar text[32];
+  gint i=1;
+
+  switch(state) {
+    case UPDATE_NEW:
+      g_print("%s: UPDATE_NEW\n",__FUNCTION__);
+      switch(thisEvent) {
+        case EVENT_NONE:
+          gtk_label_set_text(GTK_LABEL(newEvent),"NONE");
+          break;
+        case MIDI_NOTE:
+          gtk_label_set_text(GTK_LABEL(newEvent),"NOTE");
+          break;
+        case MIDI_CTRL:
+          gtk_label_set_text(GTK_LABEL(newEvent),"CTRL");
+          break;
+        case MIDI_PITCH:
+          gtk_label_set_text(GTK_LABEL(newEvent),"PITCH");
+          break;
+      }
+      sprintf(text,"%d",thisChannel);
+      gtk_label_set_text(GTK_LABEL(newChannel),text);
+      sprintf(text,"%d",thisNote);
+      gtk_label_set_text(GTK_LABEL(newNote),text);
+      gtk_combo_box_text_remove_all(GTK_COMBO_BOX_TEXT(newType));
+      gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(newType),NULL,"NONE");
+      switch(thisEvent) {
+        case EVENT_NONE:
+          break;
+        case MIDI_NOTE:
+        case MIDI_PITCH:
+          gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(newType),NULL,"KEY");
+          break;
+        case MIDI_CTRL:
+          gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(newType),NULL,"KNOB/SLIDER");
+          gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(newType),NULL,"WHEEL");
+          break;
+      }
+      gtk_combo_box_set_active (GTK_COMBO_BOX(newType),0);
+      gtk_combo_box_text_remove_all(GTK_COMBO_BOX_TEXT(newAction));
+      gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(newAction),NULL,"NONE");
+      gtk_combo_box_set_active (GTK_COMBO_BOX(newAction),0);
+      sprintf(text,"%d",thisVal);
+      gtk_label_set_text(GTK_LABEL(newVal),text);
+      sprintf(text,"%d",thisMin);
+      gtk_label_set_text(GTK_LABEL(newMin),text);
+      sprintf(text,"%d",thisMax);
+      gtk_label_set_text(GTK_LABEL(newMax),text);
+
+      gtk_widget_set_sensitive(add_b,TRUE);
+      gtk_widget_set_sensitive(update_b,FALSE);
+      gtk_widget_set_sensitive(delete_b,FALSE);
+      break;
+
+    case UPDATE_CURRENT:
+      g_print("%s: UPDATE_CURRENT\n",__FUNCTION__);
+      sprintf(text,"%d",thisVal);
+      gtk_label_set_text(GTK_LABEL(newVal),text);
+      sprintf(text,"%d",thisMin);
+      gtk_label_set_text(GTK_LABEL(newMin),text);
+      sprintf(text,"%d",thisMax);
+      gtk_label_set_text(GTK_LABEL(newMax),text);
+      break;
+
+    case UPDATE_EXISTING:
+      g_print("%s: UPDATE_EXISTING\n",__FUNCTION__);
+      switch(thisEvent) {
+        case EVENT_NONE:
+          gtk_label_set_text(GTK_LABEL(newEvent),"NONE");
+          break;
+        case MIDI_NOTE:
+          gtk_label_set_text(GTK_LABEL(newEvent),"NOTE");
+          break;
+        case MIDI_CTRL:
+          gtk_label_set_text(GTK_LABEL(newEvent),"CTRL");
+          break;
+        case MIDI_PITCH:
+          gtk_label_set_text(GTK_LABEL(newEvent),"PITCH");
+          break;
+      }
+      sprintf(text,"%d",thisChannel);
+      gtk_label_set_text(GTK_LABEL(newChannel),text);
+      sprintf(text,"%d",thisNote);
+      gtk_label_set_text(GTK_LABEL(newNote),text);
+      gtk_combo_box_text_remove_all(GTK_COMBO_BOX_TEXT(newType));
+      gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(newType),NULL,"NONE");
+      switch(thisEvent) {
+        case EVENT_NONE:
+	  gtk_combo_box_set_active (GTK_COMBO_BOX(newType),0);
+          break;
+        case MIDI_NOTE:
+        case MIDI_PITCH:
+          gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(newType),NULL,"KEY");
+	  if(thisType==TYPE_NONE) {
+	    gtk_combo_box_set_active (GTK_COMBO_BOX(newType),0);
+	  } else if(thisType==MIDI_KEY) {
+	    gtk_combo_box_set_active (GTK_COMBO_BOX(newType),1);
+	  }
+          break;
+        case MIDI_CTRL:
+          gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(newType),NULL,"KNOB/SLIDER");
+          gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(newType),NULL,"WHEEL");
+	  if(thisType==TYPE_NONE) {
+	    gtk_combo_box_set_active (GTK_COMBO_BOX(newType),0);
+	  } else if(thisType==MIDI_KNOB) {
+	    gtk_combo_box_set_active (GTK_COMBO_BOX(newType),1);
+	  } else if(thisType==MIDI_WHEEL) {
+            gtk_combo_box_set_active (GTK_COMBO_BOX(newType),2);
+          }
+          break;
+      }
+      sprintf(text,"%d",thisVal);
+      gtk_label_set_text(GTK_LABEL(newVal),text);
+      sprintf(text,"%d",thisMin);
+      gtk_label_set_text(GTK_LABEL(newMin),text);
+      sprintf(text,"%d",thisMax);
+      gtk_label_set_text(GTK_LABEL(newMax),text);
+  
+      find_current_cmd();
+      g_print("%s: current_cmd %p\n",__FUNCTION__,current_cmd);
+
+      gtk_widget_set_sensitive(add_b,FALSE);
+      gtk_widget_set_sensitive(update_b,TRUE);
+      gtk_widget_set_sensitive(delete_b,TRUE);
+      break;
+
+  }
+
+  return 0;
+}
+
+void NewMidiConfigureEvent(enum MIDIevent event, int channel, int note, int val) {
+
+  gboolean valid;
+  char *str_event;
+  char *str_channel;
+  char *str_note;
+  char *str_type;
+  char *str_action;
+
+  gint tree_event;
+  gint tree_channel;
+  gint tree_note;
+
+  //g_print("%s: event=%d channel=%d note=%d val=%d\n", __FUNCTION__,event,channel,note,val);
+
+  if(event==thisEvent && channel==thisChannel && note==thisNote) {
+    //g_print("%s: current event\n",__FUNCTION__);
+    thisVal=val;
+    if(val<thisMin) thisMin=val;
+    if(val>thisMax) thisMax=val;
+    g_idle_add(update,GINT_TO_POINTER(UPDATE_CURRENT));
+  } else {
+    //g_print("%s: new or existing event\n",__FUNCTION__);
+    thisEvent=event;
+    thisChannel=channel;
+    thisNote=note;
+    thisVal=val;
+    thisMin=val;
+    thisMax=val;
+    thisType=TYPE_NONE;
+    thisAction=ACTION_NONE;
+
+    // search tree to see if it is existing event
+    valid=gtk_tree_model_get_iter_first(model,&iter);
+    while(valid) {
+      gtk_tree_model_get(model, &iter, EVENT_COLUMN, &str_event, -1);
+      gtk_tree_model_get(model, &iter, CHANNEL_COLUMN, &str_channel, -1);
+      gtk_tree_model_get(model, &iter, NOTE_COLUMN, &str_note, -1);
+      gtk_tree_model_get(model, &iter, TYPE_COLUMN, &str_type, -1);
+      gtk_tree_model_get(model, &iter, ACTION_COLUMN, &str_action, -1);
+
+      //g_print("%s: %s %s %s %s %s\n",__FUNCTION__,str_event,str_channel,str_note,str_type,str_action);
+
+      if(str_event!=NULL && str_channel!=NULL && str_note!=NULL && str_type!=NULL && str_action!=NULL) {
+        if(strcmp(str_event,"CTRL")==0) {
+          tree_event=MIDI_CTRL;
+        } else if(strcmp(str_event,"PITCH")==0) {
+          tree_event=MIDI_PITCH;
+        } else if(strcmp(str_event,"NOTE")==0) {
+          tree_event=MIDI_NOTE;
+        } else {
+          tree_event=EVENT_NONE;
+        }
+        tree_channel=atoi(str_channel);
+        tree_note=atoi(str_note);
+
+	if(thisEvent==tree_event && (thisChannel==tree_channel || tree_channel==-1) && thisNote==tree_note) {
+          thisVal=0;
+          thisMin=0;
+          thisMax=0;
+          if(strcmp(str_type,"KEY")==0) {
+            thisType=MIDI_KEY;
+          } else if(strcmp(str_type,"KNOB/SLIDER")==0) {
+            thisType=MIDI_KNOB;
+          } else if(strcmp(str_type,"WHEEL")==0) {
+            thisType=MIDI_WHEEL;
+          } else {
+            thisType=TYPE_NONE;
+          }
+          thisAction=ACTION_NONE;
+          int i=1;
+          while(ActionTable[i].action!=ACTION_NONE) {
+            if(strcmp(ActionTable[i].str,str_action)==0) {
+              thisAction=ActionTable[i].action;
+              break;
+            }
+            i++;
+          }
+	  gtk_tree_view_set_cursor(GTK_TREE_VIEW(view),gtk_tree_model_get_path(model,&iter),NULL,FALSE);
+          g_idle_add(update,GINT_TO_POINTER(UPDATE_EXISTING));
+          return;
+	}
+      }
+
+      valid=gtk_tree_model_iter_next(model,&iter);
+    }
+    
+    g_idle_add(update,GINT_TO_POINTER(UPDATE_NEW));
+  }
+}
+
+void midi_save_state() {
+  char name[80];
+  char value[80];
+  struct desc *cmd;
+  gint channels;
+
+  if(device_index!=-1) {
+    setProperty("midi_device",midi_devices[device_index].name);
+    for(int i=0;i<128;i++) {
+      channels=0;
+      cmd=MidiCommandsTable.desc[i];
+      while(cmd!=NULL) {
+        //g_print("%s:  channel=%d key=%d event=%s onoff=%d type=%s action=%s\n",__FUNCTION__,cmd->channel,i,midi_events[cmd->event],cmd->onoff,midi_types[cmd->type],ActionTable[cmd->action].str);
+
+        sprintf(name,"midi[%d].channel[%d]",i,channels);
+        sprintf(value,"%d",cmd->channel);
+        setProperty(name,value);
+        sprintf(name,"midi[%d].channel[%d].event",i,cmd->channel);
+        setProperty(name,midi_events[cmd->event]);
+        sprintf(name,"midi[%d].channel[%d].onoff",i,cmd->channel);
+        sprintf(value,"%d",cmd->onoff);
+        setProperty(name,value);
+        sprintf(name,"midi[%d].channel[%d].type",i,cmd->channel);
+        setProperty(name,midi_types[cmd->type]);
+        sprintf(name,"midi[%d].channel[%d].action",i,cmd->channel);
+	sprintf(value,"%s",ActionTable[cmd->action].str);
+        setProperty(name,value);
+        cmd=cmd->next;
+	channels++;
+      }
+
+      if(channels!=0) {
+        sprintf(name,"midi[%d].channels",i);
+        sprintf(value,"%d",channels);
+        setProperty(name,value);
+      }
+
+    }
+  }
+}
+
+void midi_restore_state() {
+  char name[80];
+  char *value;
+  gint channels;
+  gint channel;
+  gint event;
+  gint onoff;
+  gint type;
+  gint action;
+
+  struct desc *cmd;
+
+  get_midi_devices();
+
+  //g_print("%s\n",__FUNCTION__);
+  value=getProperty("midi_device");
+  if(value) {
+    //g_print("%s: device=%s\n",__FUNCTION__,value);
+    midi_device_name=g_new(gchar,strlen(value)+1);
+    strcpy(midi_device_name,value);
+
+    
+    for(int i=0;i<n_midi_devices;i++) {
+      if(strcmp(midi_devices[i].name,value)==0) {
+        device_index=i;
+        g_print("%s: found device at %d\n",__FUNCTION__,i);
+        break;
+      }
+    }
+  }
+
+  for(int i=0;i<128;i++) {
+    sprintf(name,"midi[%d].channels",i);
+    value=getProperty(name);
+    if(value) {
+      channels=atoi(value);
+      for(int c=0;c<channels;c++) {
+        sprintf(name,"midi[%d].channel[%d]",i,c);
+        value=getProperty(name);
+        if(value) {
+	  channel=atoi(value);
+          sprintf(name,"midi[%d].channel[%d].event",i,channel);
+          value=getProperty(name);
+	  event=EVENT_NONE;
+          if(value) {
+            for(int j=0;j<4;j++) {
+	      if(strcmp(value,midi_events[j])==0) {
+		event=j;
+		break;
+              }
+	    }
+	  }
+          sprintf(name,"midi[%d].channel[%d].onoff",i,channel);
+          value=getProperty(name);
+          if(value) onoff=atoi(value);
+          sprintf(name,"midi[%d].channel[%d].type",i,channel);
+          value=getProperty(name);
+	  type=TYPE_NONE;
+          if(value) {
+            for(int j=0;j<5;j++) {
+              if(strcmp(value,midi_types[j])==0) {
+                type=j;
+                break;
+              }
+            }
+	  }
+          sprintf(name,"midi[%d].channel[%d].action",i,channel);
+          value=getProperty(name);
+	  action=ACTION_NONE;
+          if(value) {
+	    int j=1;
+	    while(ActionTable[j].type!=ACTION_NONE) {
+              if(strcmp(value,ActionTable[j].str)==0) {
+                action=ActionTable[j].action;
+		break;
+              }
+	      j++;
+	    }
+	  }
+
+
+	  struct desc *desc;
+          desc = (struct desc *) malloc(sizeof(struct desc));
+          desc->next = NULL;
+          desc->action = action; // MIDIaction
+          desc->type = type; // MIDItype
+          desc->event = event; // MIDevent
+          desc->onoff = onoff;
+          desc->delay = 0;
+          desc->vfl1  = -1;
+          desc->vfl2  = -1;
+          desc->fl1   = -1;
+          desc->fl2   = -1;
+          desc->lft1  = -1;
+          desc->lft2  = 63;
+          desc->rgt1  = 64;
+          desc->rgt2  = 128;
+          desc->fr1   = 128;
+          desc->fr2   = 128;
+          desc->vfr1  = 128;
+          desc->vfr2  = 128;
+          desc->channel  = channel;
+
+	  if(MidiCommandsTable.desc[i]!=NULL) {
+            desc->next=MidiCommandsTable.desc[i];
+          }
+          MidiCommandsTable.desc[i]=desc;
+        }
+      }
+    }
+  }
+
+}
+
diff --git a/midi_menu.h b/midi_menu.h
new file mode 100644
index 0000000..bed7e0a
--- /dev/null
+++ b/midi_menu.h
@@ -0,0 +1,25 @@
+/* Copyright (C)
+* 2021 - 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.
+*
+*/
+
+extern gchar *midi_device_name;
+extern void midi_menu(GtkWidget *parent);
+extern void NewMidiConfigureEvent(enum MIDIevent event, int channel, int note, int val);
+extern void midi_save_state();
+extern void midi_restore_state();
+
diff --git a/new_menu.c b/new_menu.c
index 24a819c..7c5c1b3 100644
--- a/new_menu.c
+++ b/new_menu.c
@@ -58,14 +58,15 @@
 #include "fft_menu.h"
 #include "main.h"
 #include "actions.h"
-#ifdef GPIO
 #include "gpio.h"
-#endif
 #include "old_protocol.h"
 #include "new_protocol.h"
 #ifdef CLIENT_SERVER
 #include "server_menu.h"
 #endif
+#ifdef MIDI
+#include "midi_menu.h"
+#endif
 
 
 static GtkWidget *menu_b=NULL;
@@ -193,6 +194,7 @@ static gboolean encoder_cb (GtkWidget *widget, GdkEventButton *event, gpointer d
   encoder_menu(top_window);
   return TRUE;
 }
+#endif
 
 static gboolean switch_cb (GtkWidget *widget, GdkEventButton *event, gpointer data) {
   cleanup();
@@ -200,8 +202,6 @@ static gboolean switch_cb (GtkWidget *widget, GdkEventButton *event, gpointer da
   return TRUE;
 }
 
-#endif
-
 static gboolean cw_cb (GtkWidget *widget, GdkEventButton *event, gpointer data) {
   cleanup();
   cw_menu(top_window);
@@ -433,6 +433,18 @@ static gboolean server_cb (GtkWidget *widget, GdkEventButton *event, gpointer da
 }
 #endif
 
+#ifdef MIDI
+void start_midi() {
+  cleanup();
+  midi_menu(top_window);
+}
+
+static gboolean midi_cb (GtkWidget *widget, GdkEventButton *event, gpointer data) {
+  start_midi();
+  return TRUE;
+}
+#endif
+
 void new_menu()
 {
   int i;
@@ -580,7 +592,6 @@ void new_menu()
     gtk_grid_attach(GTK_GRID(grid),rigctl_b,(i%5),i/5,1,1);
     i++;
 
-#ifdef GPIO
     switch(controller) {
       case NO_CONTROLLER:
 	{
@@ -594,11 +605,12 @@ void new_menu()
       case CONTROLLER2_V1:
       case CONTROLLER2_V2:
         {
+#ifdef GPIO
         GtkWidget *encoders_b=gtk_button_new_with_label("Encoders");
         g_signal_connect (encoders_b, "button-press-event", G_CALLBACK(encoder_cb), NULL);
         gtk_grid_attach(GTK_GRID(grid),encoders_b,(i%5),i/5,1,1);
         i++;
-
+#endif
         GtkWidget *switches_b=gtk_button_new_with_label("Switches");
         g_signal_connect (switches_b, "button-press-event", G_CALLBACK(switch_cb), NULL);
         gtk_grid_attach(GTK_GRID(grid),switches_b,(i%5),i/5,1,1);
@@ -606,6 +618,12 @@ void new_menu()
         }
         break;
     }
+
+#ifdef MIDI
+    GtkWidget *midi_b=gtk_button_new_with_label("MIDI");
+    g_signal_connect (midi_b, "button-press-event", G_CALLBACK(midi_cb), NULL);
+    gtk_grid_attach(GTK_GRID(grid),midi_b,(i%5),i/5,1,1);
+    i++;
 #endif
 
 //
diff --git a/new_protocol.c b/new_protocol.c
index 40a33e1..dfcb00e 100644
--- a/new_protocol.c
+++ b/new_protocol.c
@@ -75,8 +75,8 @@
 #define RXACTION_PS     2    // deliver 2*119 samples to PS engine
 #define RXACTION_DIV    3    // take 2*119 samples, mix them, deliver to a receiver
 
-static int rxcase[MAX_DDC];
-static int rxid  [MAX_DDC];
+static int rxcase[7/*MAX_DDC*/];
+static int rxid  [7/*MAX_DDC*/];
 
 int data_socket=-1;
 
@@ -109,8 +109,8 @@ static int audio_addr_length;
 static struct sockaddr_in iq_addr;
 static int iq_addr_length;
 
-static struct sockaddr_in data_addr[MAX_DDC];
-static int data_addr_length[MAX_DDC];
+static struct sockaddr_in data_addr[7/*MAX_DDC*/];
+static int data_addr_length[7/*MAX_DDC*/];
 
 static GThread *new_protocol_thread_id;
 static GThread *new_protocol_timer_thread_id;
@@ -119,7 +119,7 @@ static long high_priority_sequence = 0;
 static long general_sequence = 0;
 static long rx_specific_sequence = 0;
 static long tx_specific_sequence = 0;
-static long ddc_sequence[MAX_DDC];
+static long ddc_sequence[7/*MAX_DDC*/];
 
 //static int buffer_size=BUFFER_SIZE;
 //static int fft_size=4096;
@@ -175,13 +175,13 @@ static sem_t mic_line_sem_buffer;
 #endif
 static GThread *mic_line_thread_id;
 #ifdef __APPLE__
-static sem_t *iq_sem_ready[MAX_DDC];
-static sem_t *iq_sem_buffer[MAX_DDC];
+static sem_t *iq_sem_ready[7/*MAX_DDC*/];
+static sem_t *iq_sem_buffer[7/*MAX_DDC*/];
 #else
-static sem_t iq_sem_ready[MAX_DDC];
-static sem_t iq_sem_buffer[MAX_DDC];
+static sem_t iq_sem_ready[7/*MAX_DDC*/];
+static sem_t iq_sem_buffer[7/*MAX_DDC*/];
 #endif
-static GThread *iq_thread_id[MAX_DDC];
+static GThread *iq_thread_id[7/*MAX_DDC*/];
 
 #ifdef INCLUDED
 static int outputsamples;
@@ -199,7 +199,7 @@ static socklen_t length=sizeof(addr);
 
 // Network buffers
 #define NET_BUFFER_SIZE 2048
-static unsigned char *iq_buffer[MAX_DDC];
+static unsigned char *iq_buffer[7/*MAX_DDC*/];
 static unsigned char *command_response_buffer;
 static unsigned char *high_priority_buffer;
 static unsigned char *mic_line_buffer;
diff --git a/portaudio.c b/portaudio.c
index 2ed083c..ac33586 100644
--- a/portaudio.c
+++ b/portaudio.c
@@ -454,8 +454,7 @@ int audio_write (RECEIVER *rx, float left, float right)
   return 0;
 }
 
-int cw_audio_write(float sample) {
-  RECEIVER *rx = active_receiver;
+int cw_audio_write(RECEIVER *rx,float sample) {
   float *buffer = rx->local_audio_buffer;
 
   if (rx->playback_handle != NULL && rx->local_audio_buffer != NULL) {
diff --git a/radio.c b/radio.c
index 5bbc536..05c1056 100644
--- a/radio.c
+++ b/radio.c
@@ -71,10 +71,12 @@
 #include "iambic.h"
 #endif
 #ifdef MIDI
+#include "alsa_midi.h"
+#include "midi_menu.h"
 // rather than including MIDI.h with all its internal stuff
 // (e.g. enum components) we just declare the single bit thereof
 // we need here to make a strict compiler happy.
-void MIDIstartup();
+//extern void MIDIstartup();
 #endif
 #ifdef CLIENT_SERVER
 #include "client_server.h"
@@ -95,6 +97,10 @@ void MIDIstartup();
 #define TOOLBAR_HEIGHT (30)
 #define WATERFALL_HEIGHT (105)
 
+#ifdef MIDI
+gboolean midi_enabled;
+#endif
+
 gint controller=NO_CONTROLLER;
 
 GtkWidget *fixed;
@@ -114,29 +120,39 @@ static GtkWidget *encoders;
 static cairo_surface_t *encoders_surface = NULL;
 #endif
 */
-	gint sat_mode;
+gint sat_mode;
 
-	int region=REGION_OTHER;
+int region=REGION_OTHER;
 
-	int echo=0;
+int echo=0;
 
-	int radio_sample_rate;
-	gboolean iqswap;
+int radio_sample_rate;
+gboolean iqswap;
 
-	static gint save_timer_id;
+static gint save_timer_id;
 
-	DISCOVERED *radio=NULL;
+DISCOVERED *radio=NULL;
 #ifdef CLIENT_SERVER
-	gboolean radio_is_remote=FALSE;
+gboolean radio_is_remote=FALSE;
 #endif
 
-	char property_path[128];
-        GMutex property_mutex;
+char property_path[128];
+GMutex property_mutex;
 
-RECEIVER *receiver[MAX_RECEIVERS];
+RECEIVER *receiver[7];
 RECEIVER *active_receiver;
 TRANSMITTER *transmitter;
 
+int RECEIVERS;
+int MAX_RECEIVERS;
+int MAX_DDC;
+#ifdef PURESIGNAL
+int PS_TX_FEEDBACK;
+int PS_RX_FEEDBACK;
+#endif
+
+
+
 int buffer_size=1024; // 64, 128, 256, 512, 1024, 2048
 int fft_size=2048; // 1024, 2048, 4096, 8192, 16384
 
@@ -198,7 +214,7 @@ int mic_ptt_tip_bias_ring=0;
 //int drive_level=0;
 //int tune_drive_level=0;
 
-int receivers=RECEIVERS;
+int receivers;
 
 ADC adc[2];
 DAC dac[2];
@@ -620,7 +636,7 @@ if(!radio_is_remote) {
       break;
 #ifdef SOAPYSDR
     case SOAPYSDR_PROTOCOL:
-      soapy_protocol_init(0,false);
+      soapy_protocol_init(FALSE);
       break;
 #endif
   }
@@ -648,12 +664,6 @@ if(!radio_is_remote) {
     y+=TOOLBAR_HEIGHT;
   }
 
-//
-// Now, if there should only one receiver be displayed
-// at startup, do the change. We must momentarily fake
-// the number of receivers otherwise radio_change_receivers
-// will do nothing.
-//
 g_print("create_visual: receivers=%d RECEIVERS=%d\n",receivers,RECEIVERS);
   if (receivers != RECEIVERS) {
     int r=receivers;
@@ -1100,16 +1110,10 @@ void start_radio() {
 #ifdef SOAPYSDR
   adc[0].antenna=0;
   if(device==SOAPYSDR_USB_DEVICE) {
-    adc[0].rx_gain=malloc(radio->info.soapy.rx_gains*sizeof(gint));
-    for (size_t i = 0; i < radio->info.soapy.rx_gains; i++) {
-      adc[0].rx_gain[i]=0;
-    }
+    adc[0].gain=0;
     adc[0].agc=FALSE;
     dac[0].antenna=1;
-    dac[0].tx_gain=malloc(radio->info.soapy.tx_gains*sizeof(gint));
-    for (size_t i = 0; i < radio->info.soapy.tx_gains; i++) {
-      dac[0].tx_gain[i]=0;
-    }
+    dac[0].gain=0;
   }
 #endif
 
@@ -1124,16 +1128,10 @@ void start_radio() {
 #ifdef SOAPYSDR
   adc[1].antenna=0;
   if(device==SOAPYSDR_USB_DEVICE) {
-    adc[1].rx_gain=malloc(radio->info.soapy.rx_gains*sizeof(gint));
-    for (size_t i = 0; i < radio->info.soapy.rx_gains; i++) {
-      adc[1].rx_gain[i]=0;
-    }
+    adc[1].gain=0;
     adc[1].agc=FALSE;
-
-    dac[1].tx_gain=malloc(radio->info.soapy.tx_gains*sizeof(gint));
-    for (size_t i = 0; i < radio->info.soapy.tx_gains; i++) {
-      dac[1].tx_gain[i]=0;
-    }
+    dac[1].antenna=1;
+    dac[1].gain=0;
   }
 
   radio_sample_rate=radio->info.soapy.sample_rate;
@@ -1154,11 +1152,6 @@ void start_radio() {
       display_sliders=0;
       display_toolbar=0;
       break;
-    case CONTROLLER_I2C:
-      display_zoompan=0;
-      display_sliders=0;
-      display_toolbar=0;
-      break;
   }
 #else
   display_zoompan=1;
@@ -1175,6 +1168,36 @@ void start_radio() {
 
   display_sequence_errors=TRUE;
 
+  g_print("%s: setup RECEIVERS protocol=%d\n",__FUNCTION__,protocol);
+  switch(protocol) {
+#ifdef SOAPYSDR
+    case SOAPYSDR_PROTOCOL:
+  g_print("%s: setup RECEIVERS SOAPYSDR\n",__FUNCTION__);
+      RECEIVERS=1;
+      MAX_RECEIVERS=RECEIVERS;
+#ifdef PURESIGNAL
+      PS_TX_FEEDBACK=0;
+      PS_RX_FEEDBACK=0;
+#endif
+      MAX_DDC=1;
+      break;
+#endif
+    default:
+  g_print("%s: setup RECEIVERS default\n",__FUNCTION__);
+      RECEIVERS=2;
+#ifdef PURESIGNAL
+      MAX_RECEIVERS=(RECEIVERS+2);
+      PS_TX_FEEDBACK=(RECEIVERS);
+      PS_RX_FEEDBACK=(RECEIVERS+1);
+#else
+      MAX_RECEIVERS=RECEIVERS;
+#endif
+      MAX_DDC=(RECEIVERS+2);
+      break;
+  }
+
+  receivers=RECEIVERS;
+
   radioRestoreState();
 
 //
@@ -1234,28 +1257,15 @@ void start_radio() {
     if(can_transmit) {
       soapy_protocol_create_transmitter(transmitter);
       soapy_protocol_set_tx_antenna(transmitter,dac[0].antenna);
-/*
-      for(int i=0;i<radio->info.soapy.tx_gains;i++) {
-        soapy_protocol_set_tx_gain_element(transmitter,radio->info.soapy.tx_gain[i],dac[0].tx_gain[i]);
-      }
-*/
       soapy_protocol_set_tx_gain(transmitter,transmitter->drive);
       soapy_protocol_set_tx_frequency(transmitter);
       soapy_protocol_start_transmitter(transmitter);
     }
 
     soapy_protocol_set_rx_antenna(rx,adc[0].antenna);
-/*
-    for(int i=0;i<radio->info.soapy.rx_gains;i++) {
-      soapy_protocol_set_gain_element(rx,radio->info.soapy.rx_gain[i],adc[0].rx_gain[i]);
-    }
-*/
-
     soapy_protocol_set_rx_frequency(rx,VFO_A);
     soapy_protocol_set_automatic_gain(rx,adc[0].agc);
-    for(int i=0;i<radio->info.soapy.rx_gains;i++) {
-      soapy_protocol_set_gain_element(rx,radio->info.soapy.rx_gain[i],adc[0].rx_gain[i]);
-    }
+    soapy_protocol_set_gain(rx);
 
     if(vfo[0].ctun) {
       setFrequency(vfo[0].ctun_frequency);
@@ -1263,7 +1273,7 @@ void start_radio() {
     soapy_protocol_start_receiver(rx);
 
 //g_print("radio: set rf_gain=%f\n",rx->rf_gain);
-    soapy_protocol_set_gain(rx,rx->rf_gain);
+    soapy_protocol_set_gain(rx);
 
   }
 #endif
@@ -1278,7 +1288,14 @@ void start_radio() {
   // running. So this is the last thing we do when starting the radio.
   //
 #ifdef MIDI
-  MIDIstartup();
+  g_print("%s: midi_enabled=%d midi_device_name=%s\n",__FUNCTION__,midi_enabled,midi_device_name);
+  if(midi_enabled && (midi_device_name!=NULL)) {
+    if(register_midi_device(midi_device_name)<0) {
+      midi_enabled=FALSE;
+    }
+  } else {
+    midi_enabled=FALSE;
+  }
 #endif
 
 #ifdef CLIENT_SERVER
@@ -2066,12 +2083,8 @@ g_print("radioRestoreState: %s\n",property_path);
 
 #ifdef SOAPYSDR
     if(device==SOAPYSDR_USB_DEVICE) {
-      char name[128];
-      for(int i=0;i<radio->info.soapy.rx_gains;i++) {
-        sprintf(name,"radio.adc[0].rx_gain.%s",radio->info.soapy.rx_gain[i]) ;
-        value=getProperty(name);
-        if(value!=NULL) adc[0].rx_gain[i]=atoi(value);
-      }
+      value=getProperty("radio.adc[0].gain");
+      if(value!=NULL) adc[0].gain=atoi(value);
       value=getProperty("radio.adc[0].agc");
       if(value!=NULL) adc[0].agc=atoi(value);
       value=getProperty("radio.adc[0].antenna");
@@ -2079,14 +2092,17 @@ g_print("radioRestoreState: %s\n",property_path);
   
       value=getProperty("radio.dac[0].antenna");
       if(value!=NULL) dac[0].antenna=atoi(value);
-      for(int i=0;i<radio->info.soapy.tx_gains;i++) {
-        sprintf(name,"radio.dac[0].tx_gain.%s",radio->info.soapy.tx_gain[i]);
-        value=getProperty(name);
-        if(value!=NULL) dac[0].tx_gain[i]=atoi(value);
-      }
+      value=getProperty("radio.dac[0].gain");
+      if(value!=NULL) dac[0].gain=atoi(value);
     }
 #endif
 
+#ifdef MIDI
+    midi_restore_state();
+    value=getProperty("radio.midi_enabled");
+    if(value) midi_enabled=atoi(value);
+#endif
+
     value=getProperty("radio.display_sequence_errors");
     if(value!=NULL) display_sequence_errors=atoi(value);
 
@@ -2322,49 +2338,32 @@ g_print("radioSaveState: %s\n",property_path);
 
 #ifdef SOAPYSDR
     if(device==SOAPYSDR_USB_DEVICE) {
-      char name[128];
-      for(int i=0;i<radio->info.soapy.rx_gains;i++) {
-        sprintf(name,"radio.adc[0].rx_gain.%s",radio->info.soapy.rx_gain[i]);
-        sprintf(value,"%d", adc[0].rx_gain[i]);
-        setProperty(name,value);
-      }
-      sprintf(name,"radio.adc[0].agc");
+      sprintf(value,"%f", adc[0].gain);
+      setProperty("radio.adc[0].gain",value);
       sprintf(value,"%d", soapy_protocol_get_automatic_gain(receiver[0]));
-      setProperty(name,value);
-      sprintf(name,"radio.adc[0].antenna");
+      setProperty("radio.adc[0].agc",value);
       sprintf(value,"%d", adc[0].antenna);
-      setProperty(name,value);
+      setProperty("radio.adc[0].antenna",value);
 
-      sprintf(name,"radio.dac[0].antenna");
       sprintf(value,"%d", dac[0].antenna);
-      setProperty(name,value);
-
-      for(int i=0;i<radio->info.soapy.tx_gains;i++) {
-        sprintf(name,"radio.dac[0].tx_gain.%s",radio->info.soapy.tx_gain[i]);
-        sprintf(value,"%d", dac[0].tx_gain[i]);
-        setProperty(name,value);
-      }
-
-      for(int i=0;i<radio->info.soapy.rx_gains;i++) {
-        sprintf(name,"radio.adc[1].rx_gain.%s",radio->info.soapy.rx_gain[i]);
-        sprintf(value,"%d", adc[1].rx_gain[i]);
-        setProperty(name,value);
-      }
-      sprintf(name,"radio.adc[1].agc");
-      sprintf(value,"%d", soapy_protocol_get_automatic_gain(receiver[1]));
-      setProperty(name,value);
-      sprintf(name,"radio.adc[1].antenna");
-      sprintf(value,"%d", adc[1].antenna);
-      setProperty(name,value);
-
-      sprintf(name,"radio.dac[1].antenna");
-      sprintf(value,"%d", dac[1].antenna);
-      setProperty(name,value);
-
-      for(int i=0;i<radio->info.soapy.tx_gains;i++) {
-        sprintf(name,"radio.dac[1].tx_gain.%s",radio->info.soapy.tx_gain[i]);
-        sprintf(value,"%d", dac[1].tx_gain[i]);
-        setProperty(name,value);
+      setProperty("radio.dac[0].antenna",value);
+
+      sprintf(value,"%f", dac[0].gain);
+      setProperty("radio.dac[0].gain",value);
+
+      if(receivers>1) {
+        sprintf(value,"%f", adc[1].gain);
+        setProperty("radio.adc[1].gain",value);
+        sprintf(value,"%d", soapy_protocol_get_automatic_gain(receiver[1]));
+        setProperty("radio.adc[1].agc",value);
+        sprintf(value,"%d", adc[1].antenna);
+        setProperty("radio.adc[1].antenna",value);
+  
+        sprintf(value,"%d", dac[1].antenna);
+        setProperty("radio.dac[1].antenna",value);
+  
+        sprintf(value,"%f", dac[1].gain);
+        setProperty("radio.dac[1].gain",value);
       }
     }
 #endif
@@ -2410,6 +2409,12 @@ g_print("radioSaveState: %s\n",property_path);
   }
 #endif
 
+#ifdef MIDI
+  sprintf(value,"%d",midi_enabled);
+  setProperty("radio.midi_enabled",value);
+  midi_save_state();
+#endif
+
   saveProperties(property_path);
   g_mutex_unlock(&property_mutex);
 }
@@ -2479,7 +2484,7 @@ void radio_change_region(int r) {
 #ifdef CLIENT_SERVER
 int remote_start(void *data) {
   char *server=(char *)data;
-  sprintf(property_path,"%s@%s.props",radio->name,server);
+  sprintf(property_path,"%s@%s.props",name,server);
   radio_is_remote=TRUE;
 #ifdef GPIO
   switch(controller) {
diff --git a/radio.h b/radio.h
index 7e3d4c0..4fbd580 100644
--- a/radio.h
+++ b/radio.h
@@ -78,22 +78,33 @@ extern char property_path[];
 
 extern int region;
 
-// specify how many receivers: for PURESIGNAL need two extra
-#define RECEIVERS 2
+extern int RECEIVERS;
+extern int MAX_RECEIVERS;
+extern int MAX_DDC;
 #ifdef PURESIGNAL
-#define MAX_RECEIVERS (RECEIVERS+2)
-#define PS_TX_FEEDBACK (RECEIVERS)
-#define PS_RX_FEEDBACK (RECEIVERS+1)
-#else
-#define MAX_RECEIVERS RECEIVERS
+extern int PS_TX_FEEDBACK;
+extern int PS_RX_FEEDBACK;
 #endif
-#define MAX_DDC (RECEIVERS+2)
+
+// specify how many receivers: for PURESIGNAL need two extra
+//#define RECEIVERS 2
+//#ifdef PURESIGNAL
+//#define MAX_RECEIVERS (RECEIVERS+2)
+//#define PS_TX_FEEDBACK (RECEIVERS)
+//#define PS_RX_FEEDBACK (RECEIVERS+1)
+//#else
+//#define MAX_RECEIVERS RECEIVERS
+//#endif
+//#define MAX_DDC (RECEIVERS+2)
 
 extern RECEIVER *receiver[];
 extern RECEIVER *active_receiver;
 
 extern TRANSMITTER *transmitter;
 
+#ifdef MIDI
+extern gboolean midi_enabled;
+#endif
 
 #define PA_DISABLED 0
 #define PA_ENABLED 1
diff --git a/radio_menu.c b/radio_menu.c
index 2602bcb..f77a6cc 100644
--- a/radio_menu.c
+++ b/radio_menu.c
@@ -84,19 +84,11 @@ static gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer user_d
 #ifdef SOAPYSDR
 static void rf_gain_value_changed_cb(GtkWidget *widget, gpointer data) {
   ADC *adc=(ADC *)data;
-  active_receiver->rf_gain=gtk_spin_button_get_value(GTK_SPIN_BUTTON(widget));
+  adc->gain=gtk_spin_button_get_value(GTK_SPIN_BUTTON(widget));
   
   if(radio->device==SOAPYSDR_USB_DEVICE) {
-    soapy_protocol_set_gain(receiver[0],active_receiver->rf_gain);
+    soapy_protocol_set_gain(receiver[0]);
   }
-
-/*
-  for(int i=0;i<radio->info.soapy.rx_gains;i++) {
-    int value=soapy_protocol_get_gain_element(active_receiver,radio->info.soapy.rx_gain[i]);
-    gtk_spin_button_set_value(GTK_SPIN_BUTTON(rx_gains[i]),(double)value);
-  }
-*/
-
 }
 
 static void rx_gain_value_changed_cb(GtkWidget *widget, gpointer data) {
@@ -548,21 +540,44 @@ void radio_menu(GtkWidget *parent) {
   
 #ifdef SOAPYSDR
     case SOAPYSDR_PROTOCOL:
-      {
-      GtkWidget *sample_rate_label=gtk_label_new(NULL);
-      gtk_label_set_markup(GTK_LABEL(sample_rate_label), "<b>Sample Rate:</b>");
-      gtk_grid_attach(GTK_GRID(grid),sample_rate_label,col,row,1,1);
-      row++;
+      if(strcmp(radio->name,"sdrplay")==0) {
+        GtkWidget *sample_rate_combo_box=gtk_combo_box_text_new();
+//        gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(sample_rate_combo_box),NULL,"96000");
+//        gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(sample_rate_combo_box),NULL,"192000");
+//        gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(sample_rate_combo_box),NULL,"384000");
+        gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(sample_rate_combo_box),NULL,"768000");
+        switch(radio_sample_rate) {
+          case 96000:
+            gtk_combo_box_set_active(GTK_COMBO_BOX(sample_rate_combo_box),0);
+            break;
+          case 192000:
+            gtk_combo_box_set_active(GTK_COMBO_BOX(sample_rate_combo_box),1);
+            break;
+          case 384000:
+            gtk_combo_box_set_active(GTK_COMBO_BOX(sample_rate_combo_box),2);
+            break;
+          case 768000:
+            gtk_combo_box_set_active(GTK_COMBO_BOX(sample_rate_combo_box),3);
+            break;
+        }
+        g_signal_connect(sample_rate_combo_box,"changed",G_CALLBACK(sample_rate_cb),radio);
+        gtk_grid_attach(GTK_GRID(grid),sample_rate_combo_box,col,row,1,1);
+	row++;
+      } else {
+        GtkWidget *sample_rate_label=gtk_label_new(NULL);
+        gtk_label_set_markup(GTK_LABEL(sample_rate_label), "<b>Sample Rate:</b>");
+        gtk_grid_attach(GTK_GRID(grid),sample_rate_label,col,row,1,1);
+        row++;
 
-      char rate[16];
-      sprintf(rate,"%d",radio->info.soapy.sample_rate);
+        char rate[16];
+        sprintf(rate,"%d",radio->info.soapy.sample_rate);
 
-      GtkWidget *sample_rate=gtk_radio_button_new_with_label(NULL,rate);
-      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (sample_rate), radio->info.soapy.sample_rate);
-      gtk_grid_attach(GTK_GRID(grid),sample_rate,col,row,1,1);
-      g_signal_connect(sample_rate,"toggled",G_CALLBACK(sample_rate_cb),GINT_TO_POINTER(radio->info.soapy.sample_rate));
+        GtkWidget *sample_rate=gtk_radio_button_new_with_label(NULL,rate);
+        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (sample_rate), radio->info.soapy.sample_rate);
+        gtk_grid_attach(GTK_GRID(grid),sample_rate,col,row,1,1);
+        g_signal_connect(sample_rate,"toggled",G_CALLBACK(sample_rate_cb),GINT_TO_POINTER(radio->info.soapy.sample_rate));
 
-      col++;
+        col++;
       }
       break;
 #endif
@@ -816,7 +831,6 @@ void radio_menu(GtkWidget *parent) {
   col=0;
   if(radio->device==SOAPYSDR_USB_DEVICE) {
     int i;
-/*
     if(radio->info.soapy.rx_gains>0) {
       GtkWidget *rx_gain=gtk_label_new(NULL);
       gtk_label_set_markup(GTK_LABEL(rx_gain), "<b>RX Gains:</b>");
@@ -835,50 +849,46 @@ void radio_menu(GtkWidget *parent) {
 
     row++;
     temp_row=row;
-*/
     col=0;
-/*
-    //rx_gains=g_new(GtkWidget*,radio->info.soapy.rx_gains);
-    for(i=0;i<radio->info.soapy.rx_gains;i++) {
+    if(strcmp(radio->name,"sdrplay")==0) {
+      for(i=0;i<radio->info.soapy.rx_gains;i++) {
+        col=0;
+        GtkWidget *rx_gain_label=gtk_label_new(radio->info.soapy.rx_gain[i]);
+        gtk_grid_attach(GTK_GRID(grid),rx_gain_label,col,row,1,1);
+        col++;
+        SoapySDRRange range=radio->info.soapy.rx_range[i];
+        if(range.step==0.0) {
+          range.step=1.0;
+        }
+        rx_gains[i]=gtk_spin_button_new_with_range(range.minimum,range.maximum,range.step);
+        gtk_widget_set_name (rx_gains[i], radio->info.soapy.rx_gain[i]);
+        int value=soapy_protocol_get_gain_element(active_receiver,radio->info.soapy.rx_gain[i]);
+        gtk_spin_button_set_value(GTK_SPIN_BUTTON(rx_gains[i]),(double)value);
+        gtk_grid_attach(GTK_GRID(grid),rx_gains[i],col,row,1,1);
+        g_signal_connect(rx_gains[i],"value_changed",G_CALLBACK(rx_gain_value_changed_cb),&adc[0]);
+  
+        row++;
+      }
+    } else {
+      // used single gain control - LimeSDR works out best setting for the 3 rx gains
       col=0;
-      GtkWidget *rx_gain_label=gtk_label_new(radio->info.soapy.rx_gain[i]);
-      gtk_grid_attach(GTK_GRID(grid),rx_gain_label,col,row,1,1);
+      GtkWidget *rf_gain_label=gtk_label_new(NULL);
+      gtk_label_set_markup(GTK_LABEL(rf_gain_label), "<b>RF Gain</b>");
+      gtk_grid_attach(GTK_GRID(grid),rf_gain_label,col,row,1,1);
       col++;
-      SoapySDRRange range=radio->info.soapy.rx_range[i];
-      if(range.step==0.0) {
-        range.step=1.0;
+      double max=100;
+      if(strcmp(radio->name,"lime")==0) {
+        max=60.0;
+      } else if(strcmp(radio->name,"plutosdr")==0) {
+        max=73.0;
       }
-      rx_gains[i]=gtk_spin_button_new_with_range(range.minimum,range.maximum,range.step);
-      gtk_widget_set_name (rx_gains[i], radio->info.soapy.rx_gain[i]);
-      int value=soapy_protocol_get_gain_element(active_receiver,radio->info.soapy.rx_gain[i]);
-      gtk_spin_button_set_value(GTK_SPIN_BUTTON(rx_gains[i]),(double)value);
-      //gtk_spin_button_set_value(GTK_SPIN_BUTTON(rx_gains[i]),(double)adc[0].rx_gain[i]);
-      gtk_grid_attach(GTK_GRID(grid),rx_gains[i],col,row,1,1);
-      g_signal_connect(rx_gains[i],"value_changed",G_CALLBACK(rx_gain_value_changed_cb),&adc[0]);
-
-      gtk_widget_set_sensitive(rx_gains[i], FALSE);
+      GtkWidget *rf_gain_b=gtk_spin_button_new_with_range(0.0,max,1.0);
+      gtk_spin_button_set_value(GTK_SPIN_BUTTON(rf_gain_b),active_receiver->rf_gain);
+      gtk_grid_attach(GTK_GRID(grid),rf_gain_b,col,row,1,1);
+      g_signal_connect(rf_gain_b,"value_changed",G_CALLBACK(rf_gain_value_changed_cb),&adc[0]);
 
       row++;
     }
-*/
-    // used single gain control - LimeSDR works out best setting for the 3 rx gains
-    col=0;
-    GtkWidget *rf_gain_label=gtk_label_new(NULL);
-    gtk_label_set_markup(GTK_LABEL(rf_gain_label), "<b>RF Gain</b>");
-    gtk_grid_attach(GTK_GRID(grid),rf_gain_label,col,row,1,1);
-    col++;
-    double max=100;
-    if(strcmp(radio->name,"lime")==0) {
-      max=60.0;
-    } else if(strcmp(radio->name,"plutosdr")==0) {
-      max=73.0;
-    }
-    GtkWidget *rf_gain_b=gtk_spin_button_new_with_range(0.0,max,1.0);
-    gtk_spin_button_set_value(GTK_SPIN_BUTTON(rf_gain_b),active_receiver->rf_gain);
-    gtk_grid_attach(GTK_GRID(grid),rf_gain_b,col,row,1,1);
-    g_signal_connect(rf_gain_b,"value_changed",G_CALLBACK(rf_gain_value_changed_cb),&adc[0]);
-
-    row++;
 
     if(radio->info.soapy.rx_has_automatic_gain) {
       GtkWidget *agc=gtk_check_button_new_with_label("Hardware AGC: ");
diff --git a/receiver.c b/receiver.c
index 7d93db8..eb9ba05 100644
--- a/receiver.c
+++ b/receiver.c
@@ -1183,6 +1183,7 @@ void receiver_change_sample_rate(RECEIVER *rx,int sample_rate) {
 
 g_print("receiver_change_sample_rate: id=%d rate=%d scale=%d buffer_size=%d output_samples=%d\n",rx->id,sample_rate,scale,rx->buffer_size,rx->output_samples);
 #ifdef PURESIGNAL
+  if(can_transmit && transmitter->puresignal) {
   if (rx->id == PS_RX_FEEDBACK) {
     if (protocol == ORIGINAL_PROTOCOL) {
       rx->pixels = 2* scale * rx->width;
@@ -1199,6 +1200,7 @@ g_print("receiver_change_sample_rate: id=%d rate=%d scale=%d buffer_size=%d outp
     g_mutex_unlock(&rx->mutex);
     return;
   }
+  }
 #endif
   if (rx->audio_output_buffer != NULL) {
     g_free(rx->audio_output_buffer);
diff --git a/receiver.h b/receiver.h
index 1132c0f..236d31d 100644
--- a/receiver.h
+++ b/receiver.h
@@ -25,7 +25,6 @@
 #else
 #include <pulse/pulseaudio.h>
 #include <pulse/simple.h>
-//#include <alsa/asoundlib.h>
 #endif
 
 enum _audio_t {
diff --git a/sliders.c b/sliders.c
index a13eb3b..d4f2461 100644
--- a/sliders.c
+++ b/sliders.c
@@ -391,7 +391,7 @@ static void rf_gain_value_changed_cb(GtkWidget *widget, gpointer data) {
     active_receiver->rf_gain=gtk_range_get_value(GTK_RANGE(af_gain_scale));
 #ifdef SOAPYSDR
     if(protocol==SOAPYSDR_PROTOCOL) {
-      soapy_protocol_set_gain(active_receiver,active_receiver->rf_gain);
+      soapy_protocol_set_gain(active_receiver);
     }
 #endif
 }
@@ -404,7 +404,7 @@ void set_rf_gain(int rx,double value) {
   receiver[rx]->rf_gain=value;
 #ifdef SOAPYSDR
   if(protocol==SOAPYSDR_PROTOCOL) {
-    soapy_protocol_set_gain(active_receiver,active_receiver->rf_gain);
+    soapy_protocol_set_gain(active_receiver);
   }
 #endif
   if(display_sliders) {
diff --git a/soapy_discovery.c b/soapy_discovery.c
index e1eaa4f..a8a026e 100644
--- a/soapy_discovery.c
+++ b/soapy_discovery.c
@@ -27,17 +27,23 @@
 #include "soapy_discovery.h"
 
 static int rtlsdr_count=0;
+static int sdrplay_count=0;
 
 static void get_info(char *driver) {
   size_t rx_rates_length, tx_rates_length, rx_gains_length, tx_gains_length, ranges_length, rx_antennas_length, tx_antennas_length, rx_bandwidth_length, tx_bandwidth_length;
   int i;
   SoapySDRKwargs args={};
   int software_version=0;
+  char *version;
+  char *address=NULL;
   int rtlsdr_val=0;
+  int sdrplay_val=0;
   char fw_version[16];
   char gw_version[16];
   char hw_version[16];
   char p_version[16];
+  char** tx_antennas;
+  char** tx_gains;
 
   fprintf(stderr,"soapy_discovery: get_info: %s\n", driver);
 
@@ -53,6 +59,12 @@ static void get_info(char *driver) {
     SoapySDRKwargs_set(&args, "rtl", count);
     rtlsdr_val=rtlsdr_count;
     rtlsdr_count++;
+  } else if(strcmp(driver,"sdrplay")==0) {
+    char label[16];
+    sprintf(label,"SDRplay Dev%d",sdrplay_count);
+    SoapySDRKwargs_set(&args, "label", label);
+    sdrplay_val=sdrplay_count;
+    sdrplay_count++;
   }
   SoapySDRDevice *sdr = SoapySDRDevice_make(&args);
   SoapySDRKwargs_clear(&args);
@@ -63,6 +75,9 @@ static void get_info(char *driver) {
 
   char *hardwarekey=SoapySDRDevice_getHardwareKey(sdr);
   fprintf(stderr,"HardwareKey=%s\n",hardwarekey);
+  if(strcmp(driver,"sdrplay")==0) {
+    address=hardwarekey;
+  }
 
   SoapySDRKwargs info=SoapySDRDevice_getHardwareInfo(sdr);
   for(i=0;i<info.size;i++) {
@@ -70,10 +85,18 @@ static void get_info(char *driver) {
     if(strcmp(info.keys[i],"firmwareVersion")==0) {
       strcpy(fw_version,info.vals[i]);
     }
+    if(strcmp(info.keys[i],"fw_version")==0) {
+      version=info.vals[i];
+    }
     if(strcmp(info.keys[i],"gatewareVersion")==0) {
       strcpy(gw_version,info.vals[i]);
       software_version=(int)(atof(info.vals[i])*100.0);
     }
+    if(strcmp(info.keys[i],"sdrplay_api_api_version")==0) {
+      /* take just the first 4 characters here */
+      info.vals[i][4]='\0';
+      version=info.vals[i];
+    }
     if(strcmp(info.keys[i],"hardwareVersion")==0) {
       strcpy(hw_version,info.vals[i]);
     }
@@ -123,20 +146,25 @@ static void get_info(char *driver) {
   } else if(strcmp(driver,"plutosdr")==0) {
     sample_rate=768000;
   } else if(strcmp(driver,"rtlsdr")==0) {
-    sample_rate=1048576;
+    sample_rate=1536000;
+  } else if(strcmp(driver,"sdrplay")==0) {
+    //sample_rate=96000;
+    sample_rate=768000;
   } else {
     sample_rate=1048576;
   }
 
   fprintf(stderr,"sample_rate selected %d\n",sample_rate);
 
-  SoapySDRRange *tx_rates=SoapySDRDevice_getSampleRateRange(sdr, SOAPY_SDR_TX, 1, &tx_rates_length);
-  fprintf(stderr,"Tx sample rates: ");
-  for (size_t i = 0; i < tx_rates_length; i++) {
-    fprintf(stderr,"%f -> %f (%f),", tx_rates[i].minimum, tx_rates[i].maximum, tx_rates[i].minimum/48000.0);
+  if(tx_channels>0) {
+    SoapySDRRange *tx_rates=SoapySDRDevice_getSampleRateRange(sdr, SOAPY_SDR_TX, 1, &tx_rates_length);
+    fprintf(stderr,"Tx sample rates: ");
+    for (size_t i = 0; i < tx_rates_length; i++) {
+      fprintf(stderr,"%f -> %f (%f),", tx_rates[i].minimum, tx_rates[i].maximum, tx_rates[i].minimum/48000.0);
+    }
+    fprintf(stderr,"\n");
+    free(tx_rates);
   }
-  fprintf(stderr,"\n");
-  free(tx_rates);
 
   double *bandwidths=SoapySDRDevice_listBandwidths(sdr, SOAPY_SDR_RX, 0, &rx_bandwidth_length);
   fprintf(stderr,"Rx bandwidths: ");
@@ -146,19 +174,23 @@ static void get_info(char *driver) {
   fprintf(stderr,"\n");
   free(bandwidths);
 
-  bandwidths=SoapySDRDevice_listBandwidths(sdr, SOAPY_SDR_TX, 0, &tx_bandwidth_length);
-  fprintf(stderr,"Tx bandwidths: ");
-  for (size_t i = 0; i < tx_bandwidth_length; i++) {
-    fprintf(stderr,"%f, ", bandwidths[i]);
+  if(tx_channels>0) {
+    bandwidths=SoapySDRDevice_listBandwidths(sdr, SOAPY_SDR_TX, 0, &tx_bandwidth_length);
+    fprintf(stderr,"Tx bandwidths: ");
+    for (size_t i = 0; i < tx_bandwidth_length; i++) {
+      fprintf(stderr,"%f, ", bandwidths[i]);
+    }
+    fprintf(stderr,"\n");
+    free(bandwidths);
   }
-  fprintf(stderr,"\n");
-  free(bandwidths);
 
   double bandwidth=SoapySDRDevice_getBandwidth(sdr, SOAPY_SDR_RX, 0);
   fprintf(stderr,"RX0: bandwidth=%f\n",bandwidth);
 
-  bandwidth=SoapySDRDevice_getBandwidth(sdr, SOAPY_SDR_TX, 0);
-  fprintf(stderr,"TX0: bandwidth=%f\n",bandwidth);
+  if(tx_channels>0) {
+    bandwidth=SoapySDRDevice_getBandwidth(sdr, SOAPY_SDR_TX, 0);
+    fprintf(stderr,"TX0: bandwidth=%f\n",bandwidth);
+  }
 
   SoapySDRRange *ranges = SoapySDRDevice_getFrequencyRange(sdr, SOAPY_SDR_RX, 0, &ranges_length);
   fprintf(stderr,"Rx freq ranges: ");
@@ -170,10 +202,13 @@ static void get_info(char *driver) {
   for (size_t i = 0; i < rx_antennas_length; i++) fprintf(stderr, "%s, ", rx_antennas[i]);
   fprintf(stderr,"\n");
 
-  char** tx_antennas = SoapySDRDevice_listAntennas(sdr, SOAPY_SDR_TX, 0, &tx_antennas_length);
-  fprintf(stderr, "Tx antennas: ");
-  for (size_t i = 0; i < tx_antennas_length; i++) fprintf(stderr, "%s, ", tx_antennas[i]);
-  fprintf(stderr,"\n");
+
+  if(tx_channels>0) {
+    tx_antennas = SoapySDRDevice_listAntennas(sdr, SOAPY_SDR_TX, 0, &tx_antennas_length);
+    fprintf(stderr, "Tx antennas: ");
+    for (size_t i = 0; i < tx_antennas_length; i++) fprintf(stderr, "%s, ", tx_antennas[i]);
+    fprintf(stderr,"\n");
+  }
 
   char **rx_gains = SoapySDRDevice_listGains(sdr, SOAPY_SDR_RX, 0, &rx_gains_length);
 
@@ -183,7 +218,9 @@ static void get_info(char *driver) {
   gboolean has_automatic_dc_offset_correction=SoapySDRDevice_hasDCOffsetMode(sdr, SOAPY_SDR_RX, 0);
   fprintf(stderr,"has_automaic_dc_offset_correction=%d\n",has_automatic_dc_offset_correction);
 
-  char **tx_gains = SoapySDRDevice_listGains(sdr, SOAPY_SDR_TX, 1, &tx_gains_length);
+  if(tx_channels>0) {
+    tx_gains = SoapySDRDevice_listGains(sdr, SOAPY_SDR_TX, 1, &tx_gains_length);
+  }
 
   size_t formats_length;
   char **formats = SoapySDRDevice_getStreamFormats(sdr,SOAPY_SDR_RX,0,&formats_length);
@@ -191,7 +228,18 @@ static void get_info(char *driver) {
   for (size_t i = 0; i < formats_length; i++) fprintf(stderr, "%s, ", formats[i]);
   fprintf(stderr,"\n");
 
-  fprintf(stderr,"float=%lu double=%lu\n",sizeof(float),sizeof(double));
+  size_t sensors;
+  char **sensor = SoapySDRDevice_listSensors(sdr, &sensors);
+  gboolean has_temp=FALSE;
+  char *ptr;
+  fprintf(stderr, "Sensors:\n");
+  for (size_t i = 0; i < sensors; i++) {
+    char *value=SoapySDRDevice_readSensor(sdr, sensor[i]);
+    fprintf(stderr, "    %s=%s\n", sensor[i],value);
+    if((ptr=strstr(sensor[i],"temp"))!=NULL) {
+      has_temp=TRUE;
+    }
+  }
 
   if(devices<MAX_DEVICES) {
     discovered[devices].device=SOAPYSDR_USB_DEVICE;
@@ -210,8 +258,13 @@ static void get_info(char *driver) {
     discovered[devices].info.soapy.sample_rate=sample_rate;
     if(strcmp(driver,"rtlsdr")==0) {
       discovered[devices].info.soapy.rtlsdr_count=rtlsdr_val;
+      discovered[devices].info.soapy.sdrplay_count=0;
+    } else if(strcmp(driver,"sdrplay")==0) {
+      discovered[devices].info.soapy.rtlsdr_count=0;
+      discovered[devices].info.soapy.sdrplay_count=sdrplay_val;
     } else {
       discovered[devices].info.soapy.rtlsdr_count=0;
+      discovered[devices].info.soapy.sdrplay_count=0;
     }
     if(strcmp(driver,"lime")==0) {
       sprintf(discovered[devices].info.soapy.version,"fw=%s gw=%s hw=%s p=%s", fw_version, gw_version, hw_version, p_version);
@@ -235,24 +288,37 @@ fprintf(stderr,"Rx gains: \n");
     discovered[devices].info.soapy.rx_antenna=rx_antennas;
 
     discovered[devices].info.soapy.tx_channels=tx_channels;
-    discovered[devices].info.soapy.tx_gains=tx_gains_length;
-    discovered[devices].info.soapy.tx_gain=tx_gains;
-    discovered[devices].info.soapy.tx_range=malloc(tx_gains_length*sizeof(SoapySDRRange));
-fprintf(stderr,"Tx gains: \n");
-    for (size_t i = 0; i < tx_gains_length; i++) {
-      fprintf(stderr,"%s ", tx_gains[i]);
-      SoapySDRRange tx_range=SoapySDRDevice_getGainElementRange(sdr, SOAPY_SDR_TX, 1, tx_gains[i]);
-      fprintf(stderr,"%f -> %f step=%f\n",tx_range.minimum,tx_range.maximum,tx_range.step);
-      discovered[devices].info.soapy.tx_range[i]=tx_range;
+    if(tx_channels>0) {
+      discovered[devices].info.soapy.tx_gains=tx_gains_length;
+      discovered[devices].info.soapy.tx_gain=tx_gains;
+      discovered[devices].info.soapy.tx_range=malloc(tx_gains_length*sizeof(SoapySDRRange));
+      fprintf(stderr,"Tx gains: \n");
+      for (size_t i = 0; i < tx_gains_length; i++) {
+        fprintf(stderr,"%s ", tx_gains[i]);
+        SoapySDRRange tx_range=SoapySDRDevice_getGainElementRange(sdr, SOAPY_SDR_TX, 1, tx_gains[i]);
+        fprintf(stderr,"%f -> %f step=%f\n",tx_range.minimum,tx_range.maximum,tx_range.step);
+        discovered[devices].info.soapy.tx_range[i]=tx_range;
+      }
+      discovered[devices].info.soapy.tx_antennas=tx_antennas_length;
+      discovered[devices].info.soapy.tx_antenna=tx_antennas;
     }
-    discovered[devices].info.soapy.tx_antennas=tx_antennas_length;
-    discovered[devices].info.soapy.tx_antenna=tx_antennas;
+    discovered[devices].info.soapy.sensors=sensors;
+    discovered[devices].info.soapy.sensor=sensor;
+    discovered[devices].info.soapy.has_temp=has_temp;
+
+
+    if(address!=NULL) {
+      strcpy(discovered[devices].info.soapy.address,address);
+    } else {
+      strcpy(discovered[devices].info.soapy.address,"USB");
+    }
+
     devices++;
   }
 
+  SoapySDRDevice_unmake(sdr);
 
   free(ranges);
-
 }
 
 void soapy_discovery() {
diff --git a/soapy_protocol.c b/soapy_protocol.c
index c10550a..ba308dc 100644
--- a/soapy_protocol.c
+++ b/soapy_protocol.c
@@ -26,6 +26,7 @@
 #include "SoapySDR/Device.h"
 #include "SoapySDR/Formats.h"
 #include "SoapySDR/Version.h"
+#include "SoapySDR/Logger.h"
 
 //#define TIMING
 #ifdef TIMING
@@ -39,12 +40,8 @@
 #include "filter.h"
 #include "receiver.h"
 #include "transmitter.h"
-//#include "wideband.h"
-//#include "adc.h"
-//#include "dac.h"
 #include "radio.h"
 #include "main.h"
-//#include "protocol1.h"
 #include "soapy_protocol.h"
 #include "audio.h"
 #include "signal.h"
@@ -52,15 +49,17 @@
 #include "ext.h"
 #include "error_handler.h"
 
-static double bandwidth=2000000.0;
 
-static SoapySDRDevice *soapy_device;
-static SoapySDRStream *rx_stream;
+#define MAX_CHANNELS 2
+static SoapySDRStream *rx_stream[MAX_CHANNELS];
 static SoapySDRStream *tx_stream;
+static SoapySDRDevice *soapy_device;
 static int max_samples;
 
 static int samples=0;
 
+static double bandwidth=2000000.0;
+
 static GThread *receive_thread_id;
 static gpointer receive_thread(gpointer data);
 
@@ -88,8 +87,16 @@ void soapy_protocol_set_mic_sample_rate(int rate) {
 }
 
 void soapy_protocol_change_sample_rate(RECEIVER *rx) {
+  int rc;
+
 // rx->mutex already locked
-  if(rx->sample_rate==radio_sample_rate) {
+  if(strcmp(radio->name,"sdrplay")==0) {
+    g_print("%s: setting samplerate=%f\n",__FUNCTION__,(double)rx->sample_rate);
+    rc=SoapySDRDevice_setSampleRate(soapy_device,SOAPY_SDR_RX,rx->adc,(double)rx->sample_rate);
+    if(rc!=0) {
+      g_print("%s: SoapySDRDevice_setSampleRate(%f) failed: %s\n",__FUNCTION__,(double)rx->sample_rate,SoapySDR_errToStr(rc));
+    }
+  } else if(rx->sample_rate==radio_sample_rate) {
     if(rx->resample_buffer!=NULL) {
       g_free(rx->resample_buffer);
       rx->resample_buffer=NULL;
@@ -121,34 +128,43 @@ void soapy_protocol_create_receiver(RECEIVER *rx) {
 
   mic_sample_divisor=rx->sample_rate/48000;
 
-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);
+  g_print("%s: device=%p adc=%d setting bandwidth=%f\n",__FUNCTION__,soapy_device,rx->adc,bandwidth);
+  rc=SoapySDRDevice_setBandwidth(soapy_device,SOAPY_SDR_RX,rx->adc,bandwidth);
+  if(rc!=0) {
+    g_print("%s: SoapySDRDevice_setBandwidth(%f) failed: %s\n",__FUNCTION__,(double)bandwidth,SoapySDR_errToStr(rc));
+  }
+
+  g_print("%s: setting samplerate=%f device=%p adc=%d mic_sample_divisor=%d\n",__FUNCTION__,(double)radio_sample_rate,soapy_device,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)radio_sample_rate,SoapySDR_errToStr(rc));
+    g_print("%s: SoapySDRDevice_setSampleRate(%f) failed: %s\n",__FUNCTION__,(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);
-  rc=SoapySDRDevice_setupStream(soapy_device,&rx_stream,SOAPY_SDR_RX,SOAPY_SDR_CF32,&channel,1,NULL);
+  g_print("%s: SoapySDRDevice_setupStream(version<0x00080000): channel=%ld\n",__FUNCTION__,channel);
+  rc=SoapySDRDevice_setupStream(soapy_device,&rx_stream[channel],SOAPY_SDR_RX,SOAPY_SDR_CF32,&channel,1,NULL);
   if(rc!=0) {
-    fprintf(stderr,"soapy_protocol_create_receiver: SoapySDRDevice_setupStream (RX) failed: %s\n",SoapySDR_errToStr(rc));
+    g_print("%s: SoapySDRDevice_setupStream (RX) failed: %s\n",__FUNCTION__,SoapySDR_errToStr(rc));
     _exit(-1);
   }
 #else
-fprintf(stderr,"soapy_protocol_create_receiver: SoapySDRDevice_setupStream(version>=0x00080000): channel=%ld\n",channel);
-  rx_stream=SoapySDRDevice_setupStream(soapy_device,SOAPY_SDR_RX,SOAPY_SDR_CF32,&channel,1,NULL);
-  if(rx_stream==NULL) {
-    fprintf(stderr,"soapy_protocol_create_receiver: SoapySDRDevice_setupStream (RX) failed (rx_stream is NULL)\n");
+  g_print("%s: SoapySDRDevice_setupStream(version>=0x00080000): channel=%ld\n",__FUNCTION__,channel);
+  rx_stream[channel]=SoapySDRDevice_setupStream(soapy_device,SOAPY_SDR_RX,SOAPY_SDR_CF32,&channel,1,NULL);
+  if(rx_stream[channel]==NULL) {
+    g_print("%s: SoapySDRDevice_setupStream (RX) failed (rx_stream is NULL)\n",__FUNCTION__);
     _exit(-1);
   }
 #endif
 
+  g_print("%s: id=%d soapy_device=%p rx_stream=%p\n",__FUNCTION__,rx->id,soapy_device,rx_stream);
 
-  max_samples=SoapySDRDevice_getStreamMTU(soapy_device,rx_stream);
+  max_samples=SoapySDRDevice_getStreamMTU(soapy_device,rx_stream[channel]);
+  g_print("%s: max_samples=%d\n",__FUNCTION__,max_samples);
   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) {
@@ -156,6 +172,7 @@ fprintf(stderr,"soapy_protocol_create_receiver: SoapySDRDevice_setupStream(versi
   } else {
     max_samples=1024;
   }
+  */
   rx->buffer=g_new(double,max_samples*2);
 
   if(rx->sample_rate==radio_sample_rate) {
@@ -169,54 +186,58 @@ fprintf(stderr,"soapy_protocol_create_receiver: SoapySDRDevice_setupStream(versi
   }
 
 
-fprintf(stderr,"soapy_protocol_create_receiver: max_samples=%d buffer=%p\n",max_samples,rx->buffer);
+g_print("%s: max_samples=%d buffer=%p\n",__FUNCTION__,max_samples,rx->buffer);
 
 }
 
 void soapy_protocol_start_receiver(RECEIVER *rx) {
   int rc;
 
-double rate=SoapySDRDevice_getSampleRate(soapy_device,SOAPY_SDR_RX,rx->adc);
-fprintf(stderr,"soapy_protocol_start_receiver: activate_stream rate=%f\n",rate);
-  rc=SoapySDRDevice_activateStream(soapy_device, rx_stream, 0, 0LL, 0);
+  g_print("%s: id=%d soapy_device=%p rx_stream=%p\n",__FUNCTION__,rx->id,soapy_device,rx_stream);
+
+  size_t channel=rx->adc;
+  double rate=SoapySDRDevice_getSampleRate(soapy_device,SOAPY_SDR_RX,rx->adc);
+  g_print("%s: rate=%f\n",__FUNCTION__,rate);
+
+  g_print("%s: activate Stream\n",__FUNCTION__);
+  rc=SoapySDRDevice_activateStream(soapy_device, rx_stream[channel], 0, 0LL, 0);
   if(rc!=0) {
-    fprintf(stderr,"soapy_protocol_start_receiver: SoapySDRDevice_activateStream failed: %s\n",SoapySDR_errToStr(rc));
+    g_print("%s: SoapySDRDevice_activateStream failed: %s\n",__FUNCTION__,SoapySDR_errToStr(rc));
     _exit(-1);
   }
 
-fprintf(stderr,"soapy_protocol_start_receiver: create receive_thread\n");
+  g_print("%s: create receiver_thread\n",__FUNCTION__);
   receive_thread_id = g_thread_new( "soapy_rx", receive_thread, rx);
   if( ! receive_thread_id )
   {
-    fprintf(stderr,"g_thread_new failed for receive_thread\n");
+    g_print("%s: g_thread_new failed for receive_thread\n",__FUNCTION__);
     exit( -1 );
   }
-  fprintf(stderr, "receive_thread: id=%p\n",receive_thread_id);
+  g_print("%s: receiver_thread_id=%p\n",__FUNCTION__,receive_thread_id);
 }
 
 void soapy_protocol_create_transmitter(TRANSMITTER *tx) {
   int rc;
 
-
-fprintf(stderr,"soapy_protocol_create_transmitter: setting samplerate=%f\n",(double)tx->iq_output_rate);
+  g_print("%s: setting samplerate=%f\n",__FUNCTION__,(double)tx->iq_output_rate);
   rc=SoapySDRDevice_setSampleRate(soapy_device,SOAPY_SDR_TX,tx->dac,(double)tx->iq_output_rate);
   if(rc!=0) {
-    fprintf(stderr,"soapy_protocol_configure_transmitter: SoapySDRDevice_setSampleRate(%f) failed: %s\n",(double)tx->iq_output_rate,SoapySDR_errToStr(rc));
+    g_print("%s: SoapySDRDevice_setSampleRate(%f) failed: %s\n",__FUNCTION__,(double)tx->iq_output_rate,SoapySDR_errToStr(rc));
   }
 
 
   size_t channel=tx->dac;
-fprintf(stderr,"soapy_protocol_create_transmitter: SoapySDRDevice_setupStream: channel=%ld\n",channel);
+  g_print("%s: SoapySDRDevice_setupStream: channel=%ld\n",__FUNCTION__,channel);
 #if defined(SOAPY_SDR_API_VERSION) && (SOAPY_SDR_API_VERSION < 0x00080000)
   rc=SoapySDRDevice_setupStream(soapy_device,&tx_stream,SOAPY_SDR_TX,SOAPY_SDR_CF32,&channel,1,NULL);
   if(rc!=0) {
-    fprintf(stderr,"soapy_protocol_create_transmitter: SoapySDRDevice_setupStream (RX) failed: %s\n",SoapySDR_errToStr(rc));
+    g_print("%s: SoapySDRDevice_setupStream (RX) failed: %s\n",__FUNCTION__,SoapySDR_errToStr(rc));
     _exit(-1);
   }
 #else
   tx_stream=SoapySDRDevice_setupStream(soapy_device,SOAPY_SDR_TX,SOAPY_SDR_CF32,&channel,1,NULL);
   if(tx_stream==NULL) {
-    fprintf(stderr,"soapy_protocol_create_transmitter: SoapySDRDevice_setupStream (TX) failed: %s\n",SoapySDR_errToStr(rc));
+    g_print("%s: SoapySDRDevice_setupStream (TX) failed: %s\n",__FUNCTION__,SoapySDR_errToStr(rc));
     _exit(-1);
   }
 #endif
@@ -225,7 +246,7 @@ fprintf(stderr,"soapy_protocol_create_transmitter: SoapySDRDevice_setupStream: c
   if(max_tx_samples>(2*tx->fft_size)) {
     max_tx_samples=2*tx->fft_size;
   }
-fprintf(stderr,"soapy_protocol_create_transmitter: max_tx_samples=%d\n",max_tx_samples);
+  g_print("%s: max_tx_samples=%d\n",__FUNCTION__,max_tx_samples);
   output_buffer=(float *)malloc(max_tx_samples*sizeof(float)*2);
 
 }
@@ -253,38 +274,46 @@ fprintf(stderr,"soapy_protocol_stop_transmitter: deactivateStream\n");
   }
 }
 
-void soapy_protocol_init(int rx,gboolean hf) {
+void soapy_protocol_init(gboolean hf) {
   SoapySDRKwargs args={};
+  char temp[32];
   int rc;
   int i;
 
-fprintf(stderr,"soapy_protocol_init: rx=%d hf=%d\n",rx,hf);
+
+  SoapySDR_setLogLevel(SOAPY_SDR_TRACE);
+
+g_print("%s: hf=%d driver=%s\n",__FUNCTION__,hf,radio->name);
 
   // initialize the radio
-fprintf(stderr,"soapy_protocol_init: SoapySDRDevice_make\n");
   SoapySDRKwargs_set(&args, "driver", radio->name);
   if(strcmp(radio->name,"rtlsdr")==0) {
-    char id[16];
-    sprintf(id,"%d",radio->info.soapy.rtlsdr_count);
-    SoapySDRKwargs_set(&args, "rtl", id);
+    sprintf(temp,"%d",radio->info.soapy.rtlsdr_count);
+    SoapySDRKwargs_set(&args, "rtl", temp);
 
     if(hf) {
       SoapySDRKwargs_set(&args, "direct_samp", "2");
     } else {
       SoapySDRKwargs_set(&args, "direct_samp", "0");
     }
+  } else if(strcmp(radio->name,"sdrplay")==0) {
+    sprintf(temp,"SDRplay Dev%d",radio->info.soapy.sdrplay_count);
+    g_print("%s: label=%s\n",__FUNCTION__,temp);
+    SoapySDRKwargs_set(&args, "label", temp);
   }
   soapy_device=SoapySDRDevice_make(&args);
   if(soapy_device==NULL) {
-    fprintf(stderr,"soapy_protocol: SoapySDRDevice_make failed: %s\n",SoapySDRDevice_lastError());
+    g_print("%s: SoapySDRDevice_make failed: %s\n",__FUNCTION__,SoapySDRDevice_lastError());
     _exit(-1);
   }
   SoapySDRKwargs_clear(&args);
 
+  g_print("%s: soapy_device=%p\n",__FUNCTION__,soapy_device);
+
   if(can_transmit) {
     if(transmitter->local_microphone) {
       if(audio_open_input()!=0) {
-        fprintf(stderr,"audio_open_input failed\n");
+        g_print("%s: audio_open_input failed\n",__FUNCTION__);
         transmitter->local_microphone=0;
       }
     }
@@ -306,8 +335,9 @@ static void *receive_thread(void *arg) {
   float fsample;
   running=TRUE;
 fprintf(stderr,"soapy_protocol: receive_thread\n");
+  size_t channel=rx->adc;
   while(running) {
-    elements=SoapySDRDevice_readStream(soapy_device,rx_stream,buffs,max_samples,&flags,&timeNs,timeoutUs);
+    elements=SoapySDRDevice_readStream(soapy_device,rx_stream[channel],buffs,max_samples,&flags,&timeNs,timeoutUs);
     //fprintf(stderr,"soapy_protocol_receive_thread: SoapySDRDevice_readStream failed: max_samples=%d read=%d\n",max_samples,elements);
     if(elements<0) {
       continue;
@@ -367,9 +397,9 @@ fprintf(stderr,"soapy_protocol: receive_thread\n");
   }
 
 fprintf(stderr,"soapy_protocol: receive_thread: SoapySDRDevice_deactivateStream\n");
-  SoapySDRDevice_deactivateStream(soapy_device,rx_stream,0,0LL);
+  SoapySDRDevice_deactivateStream(soapy_device,rx_stream[channel],0,0LL);
 fprintf(stderr,"soapy_protocol: receive_thread: SoapySDRDevice_closeStream\n");
-  SoapySDRDevice_closeStream(soapy_device,rx_stream);
+  SoapySDRDevice_closeStream(soapy_device,rx_stream[channel]);
 fprintf(stderr,"soapy_protocol: receive_thread: SoapySDRDevice_unmake\n");
   SoapySDRDevice_unmake(soapy_device);
   return NULL;
@@ -466,10 +496,10 @@ void soapy_protocol_set_tx_antenna(TRANSMITTER *tx,int ant) {
   }
 }
 
-void soapy_protocol_set_gain(RECEIVER *rx,double gain) {
+void soapy_protocol_set_gain(RECEIVER *rx) {
   int rc;
 //fprintf(stderr,"soapy_protocol_set_gain: adc=%d gain=%f\n",gain);
-  rc=SoapySDRDevice_setGain(soapy_device,SOAPY_SDR_RX,rx->adc,gain);
+  rc=SoapySDRDevice_setGain(soapy_device,SOAPY_SDR_RX,rx->adc,adc[rx->adc].gain);
   if(rc!=0) {
     fprintf(stderr,"soapy_protocol: SoapySDRDevice_setGain failed: %s\n",SoapySDR_errToStr(rc));
   }
diff --git a/soapy_protocol.h b/soapy_protocol.h
index 541dbf9..c5bb4d6 100644
--- a/soapy_protocol.h
+++ b/soapy_protocol.h
@@ -27,12 +27,12 @@ SoapySDRDevice *get_soapy_device();
 void soapy_protocol_create_receiver(RECEIVER *rx);
 void soapy_protocol_start_receiver(RECEIVER *rx);
 
-void soapy_protocol_init(int rx,gboolean hf);
+void soapy_protocol_init(gboolean hf);
 void soapy_protocol_stop();
 void soapy_protocol_set_rx_frequency(RECEIVER *rx,int v);
 void soapy_protocol_set_rx_antenna(RECEIVER *rx,int ant);
 void soapy_protocol_set_lna_gain(RECEIVER *rx,int gain);
-void soapy_protocol_set_gain(RECEIVER *rx,double gain);
+void soapy_protocol_set_gain(RECEIVER *rx);
 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);
diff --git a/switch_menu.c b/switch_menu.c
index 4148779..c72100b 100644
--- a/switch_menu.c
+++ b/switch_menu.c
@@ -191,7 +191,7 @@ next_function_set:
     gtk_grid_attach(GTK_GRID(grid),widget,col,row,1,1);
     col++;
 
-    if((controller==NO_CONTROLLER || controller==CONTROLLER1) && (i==0 || i==(max_switches-1))) {
+    if((controller==NO_CONTROLLER || controller==CONTROLLER1) && (temp_switches[i].switch_function==FUNCTION)) {
       widget=gtk_label_new(NULL);
       g_sprintf(label,"<b>%s</b>",sw_string[temp_switches[i].switch_function]);
       gtk_label_set_markup (GTK_LABEL(widget), label);
diff --git a/tx_menu.c b/tx_menu.c
index a6edc0a..570e417 100644
--- a/tx_menu.c
+++ b/tx_menu.c
@@ -233,9 +233,11 @@ static gboolean emp_cb (GtkWidget *widget, gpointer data) {
   return FALSE;
 }
 
+/*
 static void tune_value_changed_cb(GtkWidget *widget, gpointer data) {
   setTuneDrive(gtk_range_get_value(GTK_RANGE(tune_scale)));
 }
+*/
 
 void tx_menu(GtkWidget *parent) {
   int i;
diff --git a/vfo.c b/vfo.c
index b056147..6da5c02 100644
--- a/vfo.c
+++ b/vfo.c
@@ -953,14 +953,23 @@ void vfo_update() {
         long long af = vfo[0].ctun ? vfo[0].ctun_frequency : vfo[0].frequency;
         long long bf = vfo[1].ctun ? vfo[1].ctun_frequency : vfo[1].frequency;
 
+	if(vfo[0].entering_frequency) {
+	    af=vfo[0].entered_frequency;
+	}
+	if(vfo[1].entering_frequency) {
+	    bf=vfo[1].entered_frequency;
+	}
         int oob=0;
         if (can_transmit) oob=transmitter->out_of_band;
+
         sprintf(temp_text,"VFO A: %0lld.%06lld",af/(long long)1000000,af%(long long)1000000);
         if(txvfo == 0 && (isTransmitting() || oob)) {
             if (oob) sprintf(temp_text,"VFO A: Out of band");
             cairo_set_source_rgb(cr, 1.0, 0.0, 0.0);
         } else {
-            if(id==0) {
+            if(vfo[0].entering_frequency) {
+              cairo_set_source_rgb(cr, 1.0, 1.0, 0.0);
+            } else if(id==0) {
               cairo_set_source_rgb(cr, 0.0, 1.0, 0.0);
             } else {
               cairo_set_source_rgb(cr, 0.0, 0.65, 0.0);
@@ -975,7 +984,9 @@ void vfo_update() {
             if (oob) sprintf(temp_text,"VFO B: Out of band");
             cairo_set_source_rgb(cr, 1.0, 0.0, 0.0);
         } else {
-            if(id==1) {
+            if(vfo[1].entering_frequency) {
+              cairo_set_source_rgb(cr, 1.0, 1.0, 0.0);
+	    } else if(id==1) {
               cairo_set_source_rgb(cr, 0.0, 1.0, 0.0);
             } else {
               cairo_set_source_rgb(cr, 0.0, 0.65, 0.0);
diff --git a/vfo.h b/vfo.h
index 55e0562..f46d32f 100644
--- a/vfo.h
+++ b/vfo.h
@@ -44,6 +44,9 @@ struct _vfo {
   long long lo;
   long long offset;
 
+  gboolean entering_frequency;
+  gint64 entered_frequency;
+
 };
 
 extern struct _vfo vfo[MAX_VFOS];
-- 
2.45.2