Blob Blame History Raw
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b73f92c..e3bca15 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -44,8 +44,8 @@ message(STATUS "Compilation date = XX${DATE_RESULT}XX")
 #
 set(FREEDV_VERSION_MAJOR 1)
 set(FREEDV_VERSION_MINOR 4)
-set(FREEDV_VERSION_PATCH FALSE)
-set(FREEDV_VERSION_SUFFIX "")
+set(FREEDV_VERSION_PATCH 1)
+set(FREEDV_VERSION_SUFFIX "devel")
 
 set(FREEDV_VERSION ${FREEDV_VERSION_MAJOR}.${FREEDV_VERSION_MINOR})
 if(FREEDV_VERSION_PATCH)
diff --git a/README.md b/README.md
index 82b83b4..bb4dfb2 100644
--- a/README.md
+++ b/README.md
@@ -2,13 +2,13 @@
 
 This document describes how to build the FreeDV GUI program for various operating systems.  FreeDV GUI is developed on Ubuntu Linux, and then cross compiled for Windows using Fedora Linux (Fedora has great cross compiling support) and Docker.
 
-# Further Reading
+## Further Reading
 
   * http://freedv.org - introduction, documentation, downloads
   * [FreeDV GUI User Manual](USER_MANUAL.md)
   * [Building for Windows using Docker](docker/README_docker.md)
   
-# Building and installing on Ubuntu Linux (16-18)
+## Building on Ubuntu Linux (16-19)
   ```
   $ sudo apt install libc6-i386 libspeexdsp-dev libsamplerate0-dev sox git \
   libwxgtk3.0-dev portaudio19-dev libhamlib-dev libasound2-dev libao-dev \
@@ -17,8 +17,14 @@ This document describes how to build the FreeDV GUI program for various operatin
   $ cd freedv-gui
   $ ./build_linux.sh
   ```
+  Then run with:
+  ```
+  $ ./build_linux/src/freedv
+  ```
+  
+  Note this build all libraries locally, nothing is installed on your machine.  ```make install``` is not required.
 
-# Building and installing on Fedora Linux
+## Building on Fedora Linux
   ```
   $ sudo dnf groupinstall "Development Tools"
   $ sudo dnf install cmake wxGTK3-devel portaudio-devel libsamplerate-devel \
@@ -28,13 +34,18 @@ This document describes how to build the FreeDV GUI program for various operatin
   $ cd freedv-gui
   $ ./build_linux.sh
   ```
-  
-# Run FreeDV:
-   ```
-   $ ./build_linux/src/freedv
-   ```
+  Then run with:
+  ```
+  $ ./build_linux/src/freedv
+  ```
+
+## Testing
 
-The ```wav``` directory contains test files of modulated audio that you can use to test FreeDV (see USER_MANUAL.txt)
+The ```wav``` directory contains test files of modulated audio that you can use to test FreeDV (see the [USER_MANUAL](USER_MANUAL.md))
+
+## Building for Windows using Docker
+
+The Windows build process above has been automated using a Docker container, see the freedv-gui Docker [README](docker/README_docker.md)
 
 ## Building for Windows on Fedora (Cross compiling)
 
@@ -100,10 +111,6 @@ Testing FreeDV API:
   $ play -t .s16 -r 16000 -b 16 out.raw
 ```
 
-## Building for Windows using Docker
-
-The Windows build process above has been automated using a Docker container, see docker/README.md
-
 ## Building and installing on OSX
 
 Please see README.osx
diff --git a/USER_MANUAL.md b/USER_MANUAL.md
index 87aa0d1..d99fb5d 100644
--- a/USER_MANUAL.md
+++ b/USER_MANUAL.md
@@ -88,6 +88,16 @@ Receive Tab | From Computer To Speaker/Headphone | The decoded audio from your c
 Transmit Tab | From Microphone to Computer | Your voice from the microphone to your computer
 Transmit Tab | From Computer To Radio | The FreeDV signal from your computer sent **to** your radio rig interface for transmission
 
+### Changing Audio Devices
+
+If you change audio devices (e.g. add or remove sound cards, USB hardware), it's a good idea to check the Tools/Audio Config dialog before pressing **Start**, to verify the audio devices are as expected. This is particularly important if any audio devices e.g. Headsets, USB Sound Cards, or Virtual Cables have been disconnected since the last time FreeDV was used.
+
+Hitting **Refresh** in the lower left hand corner of the Tools/Audio Config will normally update the audio devices list. Keeping a screen shot of a known working configuration will be useful for new users. Unexpected audio configuration changes may also occur following a Windows updates.
+
+Another solution is to re-start FreeDV and check Tools/Audio Config again after changing any audio hardware.
+
+If you change/remove USB audio devices without refreshing Tools/Audio COnfig, FreeDV may crash.
+
 ## Sound Card Levels
 
 Sound card levels are generally adjusted in the computer's Control
@@ -166,6 +176,12 @@ please unplug and plug back in the USB device.  Windows/FreeDV won't
 recognise the device on the new COM Port until it has been
 unplugged/plugged.
 
+### USB or LSB?
+
+On bands beneath 10 MHz, LSB is used for FreeDV.  On 10MHz and above, USB is used. After much debate, the FreeDV community has adopted the same conventions as SSB, based on the reasoning that FreeDV is a voice mode. 
+
+As an aid to the above, FreeDV will show the current mode on the bottom of the window upon pressing the Start button if Hamlib is enabled and your radio supports retrieving frequency and mode information over CAT. If your radio is using an unexpected mode (e.g. LSB on 20 meters), it will display that mode on the bottom of the window next to the Clear button in red letters. When a session is not active, Hamlib isn't enabled or if your radio doesn't support retrieving frequency and mode over CAT, it will remain grayed out with "unk" displaying instead of the mode (for "unknown").
+
 ## Common Problems
 
 ### Overdriving Transmit Level
@@ -247,11 +263,42 @@ AVX             *       Supports AVX intruction extensions
 FMA             -       Supports FMA extensions using YMM state``
 ```
 
+On Linux, you can check for `avx` in the **flags** section of `/proc/cpuinfo`
+or the output of the `lscpu` command:
+```
+lscpu | grep -o "avx[^ ]*"
+```
+will display `avx` (or `avx2`) if your CPU supports the instructions.
+
 ### I installed a new version and FreeDV stopped working
 
 You may need to clean out the previous configuration.  Try
 Tools-Restore Defaults.
 
+### FreeDV crashes when I press Start
+
+Have you removed/changed USB audio devices? If you remove/change USB audio devices without pressing Tools/Audio Config, FreeDV may crash.  See Changing Audio Devices above.
+
+### FreeDV can't be opened on OSX because the developer cannot be verified
+
+From January 2020 Apple is enforcing notarization for all OSX applications.  The FreeDV developers do not wish to operate within the Apple ecosystem due to the cost/intrusiveness of this requirement.
+
+![notarization](contrib/osx_notarization1.png)
+
+Security & Privacy shows the Open Anyway option for FreeDV:
+
+![notarization](contrib/osx_notarization2.png)
+![notarization](contrib/osx_notarization3.png)
+
+Or you can use command line options:
+
+```
+xattr -d com.apple.quarantine FreeDV.app
+```
+or
+```
+xattr -d -r com.apple.quarantine FreeDV.app
+```
 
 ## Voice Keyer 
 
@@ -381,25 +428,13 @@ FreeDV 2020 Tips:
 
 ### Horus Binary Mode
 
-Horus Binary mode (HorusB) High Altitude Balloon (HAB) telemetry using
-the same FSK modem as 2400A/B and 800XA.
+The FreeDV GUI also supports the Horus Binary (HorusB) modulation, which is used 
+for telemetry on high-altitude balloon flights. This uses the same FSK modem as
+2400A/B and 800XA. 
 
-Connect your UHF SSB radio to FreeDV, and it will output telemetry
-messages to the UDP port specified on Tools-Options "UDP Messages".
-For Project Horus work, the port 55690 is used. Check the "Enable UDP
-messages" checkbox.
-
-You can test Horus telemetry decodes by "Playing" [this](http://rowetel.com/downloads/horus/4fsk_binary_100Rb_8khzfs.wav) test file using Tools - Start/Stop Play File - from Radio.  On Linux, you can
-monitoring the messages using netcat:
-```
-  $ nc -ul 55690 
-```
-At the bottom of Tools-Options, the "APiVerbose" check box enables
-printing of verbose API debug messages to the console, which will also
-work in Windows if Tools-Options "Windows Debug Console" is checked.
+Refer to the HorusBinary github page for information on how to decode and upload 
+HAB telemetry using this option: https://github.com/projecthorus/horusbinary/wiki
 
-A Python script is required to upload the telemetry messages to the HabHub
-server, please see https://github.com/projecthorus/horusbinary#usage---via-freedv
 
 ## Tools - Filter
 
diff --git a/cmake/BuildCodec2.cmake b/cmake/BuildCodec2.cmake
index dc1f3cd..bdbcbfd 100644
--- a/cmake/BuildCodec2.cmake
+++ b/cmake/BuildCodec2.cmake
@@ -19,7 +19,7 @@ ExternalProject_Add(build_codec2_lpcnet
    SOURCE_DIR codec2_src
    BINARY_DIR codec2_build
    GIT_REPOSITORY https://github.com/drowe67/codec2.git
-   GIT_TAG v0.9.2 
+   GIT_TAG origin/brad-2020
    CMAKE_ARGS ${CODEC2_CMAKE_ARGS} ${SPEEXDSP_CMAKE_ARGS} 
    CMAKE_CACHE_ARGS -DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=${CMAKE_OSX_DEPLOYMENT_TARGET}
    INSTALL_COMMAND ""
diff --git a/cmake/BuildLPCNet.cmake b/cmake/BuildLPCNet.cmake
index 5c2cae1..715efb0 100644
--- a/cmake/BuildLPCNet.cmake
+++ b/cmake/BuildLPCNet.cmake
@@ -9,7 +9,7 @@ ExternalProject_Add(build_lpcnetfreedv
    SOURCE_DIR LPCNet_src
    BINARY_DIR LPCNet_build
    GIT_REPOSITORY https://github.com/drowe67/LPCNet.git
-   GIT_TAG v0.1
+   GIT_TAG origin/master
    CMAKE_ARGS ${LPCNET_CMAKE_ARGS}
    CMAKE_CACHE_ARGS -DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=${CMAKE_OSX_DEPLOYMENT_TARGET}
    INSTALL_COMMAND ""
diff --git a/cmake/BuildWxWidgets.cmake b/cmake/BuildWxWidgets.cmake
index 0ffd3f8..9e1d34b 100644
--- a/cmake/BuildWxWidgets.cmake
+++ b/cmake/BuildWxWidgets.cmake
@@ -10,7 +10,7 @@ endif()
 if(MINGW AND CMAKE_CROSSCOMPILING)
     set(CONFIGURE_COMMAND ./configure --build=${HOST} --host=${HOST} --target=${HOST} --disable-shared --prefix=${CMAKE_BINARY_DIR}/external/dist)
 elseif(APPLE)
-    set(CONFIGURE_COMMAND ./configure --disable-shared --with-macosx-version-min=10.9 --prefix=${CMAKE_BINARY_DIR}/external/dist CXXFLAGS=-stdlib=libc++\ -std=c++11\ -DWX_PRECOMP\ -O2\ -fno-strict-aliasing\ -fno-common)
+    set(CONFIGURE_COMMAND ./configure --disable-shared --with-macosx-version-min=10.9 --prefix=${CMAKE_BINARY_DIR}/external/dist --with-libjpeg=builtin --with-libpng=builtin --with-regex=builtin --with-libtiff=builtin CXXFLAGS=-stdlib=libc++\ -std=c++11\ -DWX_PRECOMP\ -O2\ -fno-strict-aliasing\ -fno-common)
 else()
 #    set(CONFIGURE_COMMAND "true")
 #    set(MAKE_COMMAND $(MAKE) -C build/msw -f makefile.gcc SHARED=0 UNICODE=1 BUILD=release PREFIX=${CMAKE_BINARY_DIR}/external/dist)
diff --git a/contrib/osx_notarization1.png b/contrib/osx_notarization1.png
new file mode 100644
index 0000000..7111891
Binary files /dev/null and b/contrib/osx_notarization1.png differ
diff --git a/contrib/osx_notarization2.png b/contrib/osx_notarization2.png
new file mode 100644
index 0000000..a1a2d3f
Binary files /dev/null and b/contrib/osx_notarization2.png differ
diff --git a/contrib/osx_notarization3.png b/contrib/osx_notarization3.png
new file mode 100644
index 0000000..151af6e
Binary files /dev/null and b/contrib/osx_notarization3.png differ
diff --git a/docker/README_docker.md b/docker/README_docker.md
index e9adec4..ea3ba55 100644
--- a/docker/README_docker.md
+++ b/docker/README_docker.md
@@ -19,17 +19,23 @@ Building is only required once
 ```
 docker-compose -f docker-compose-win.yml build
 ```
+
 ## Running the image(s)
 ```
 docker-compose -f docker-compose-win.yml up 
 ```
 
-You can optionally control via the environment variables GIT_REPO and GIT_REF which branch/commit from which repo is being built. The if these are not defined default is the `master` branch  of (https://github.com/drowe67/freedv-gui.git) and does not have to be specified explicitly.
+You can optionally control via the environment variables FDV_GIT_REPO, FDV_GIT_BRANCH, FDV_CMAKE, FDV_CLEAN, for example:
 
+Build from freedv-gui branch =my-super-branch:
 ```bash
-export GIT_REF=my-super-branch
-export GIT_REPO=http://github.com/dummy/freedv-gui.git
-docker-compose -f docker-compose-win.yml up 
+FDV_GIT_BRANCH=my-super-branch docker-compose -f docker-compose-win.yml up 
+
+```
+
+Build a 32 bit image (default is 64 bit):
+```bash
+FDV_CMAKE=mingw32-cmake docker-compose -f docker-compose-win.yml up 
 
 ```
 ## Accessing created output
@@ -38,3 +44,11 @@ If you started the docker services using `docker up`, you can easily access the
 ```
 docker cp fdv_win_fed30_c:/home/build/freedv-gui/build_win64/FreeDV-1.4.0-devel-20190725-7cd03db-win64.exe .
 ```
+
+## Automation
+
+Script to automate the steps above:
+```
+  ./freedv_build_windows.sh 64
+  ./freedv_build_windows.sh 32
+```
diff --git a/docker/fdv_win_fedora/entrypoint.sh b/docker/fdv_win_fedora/entrypoint.sh
index 0dfd8dc..31c14a5 100755
--- a/docker/fdv_win_fedora/entrypoint.sh
+++ b/docker/fdv_win_fedora/entrypoint.sh
@@ -7,7 +7,7 @@ cd $BUILDROOT
 # variables that can be passed in to Docker process ------------------
 
 # note this is just the freedv-gui repo, codec2 and LPCNet repos are hard coded in build_windows.sh
-GIT_REPO=${FDV_GIT_REPO:-http://github.com/drowe67/freedv-gui.git}
+GIT_REPO=${FDV_GIT_REPO:-https://github.com/drowe67/freedv-gui.git}
 GIT_BRANCH=${FDV_GIT_BRANCH:-master}
 
 # override with "mingw32-cmake" for a 32 bit build
@@ -20,8 +20,11 @@ echo "FDV_CMAKE=$CMAKE"
 
 # OK lets get started -----------------------------------------------
 
-if [ ! -d freedv-gui ] ; then git clone --depth=1 $GIT_REPO ; fi
-cd freedv-gui && git pull && git checkout $GIT_BRANCH
+if [ ! -d freedv-gui ] ; then git clone $GIT_REPO ; fi
+cd freedv-gui
+git pull -v
+git branch
+git checkout $GIT_BRANCH
 echo "--------------------- starting build_windows.sh ---------------------"
 GIT_REPO=$GIT_REPO GIT_REF=$GIT_REF CMAKE=$CMAKE ./build_windows.sh
 if [ $CMAKE = "mingw64-cmake" ]; then
diff --git a/docker/freedv_build_upload_windows.sh b/docker/freedv_build_upload_windows.sh
deleted file mode 100755
index ba5d247..0000000
--- a/docker/freedv_build_upload_windows.sh
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/bin/bash -x
-
-# Automation to cross compile freedv-gui for Windows using Docker on a
-# remote machine, then extract it from Docker and upload to a web
-# server
-#
-# usage:
-# $ BUILD_MACHINE=machine_name UPLOAD_PORT=1234 UPLOAD_USER_PATH=user@mywebserver:downloads \
-#   freedv_build_upload_windows.sh 32|64
-
-[ -z $BUILD_MACHINE ] && { echo "set BUILD_MACHINE"; exit 1; }
-[ -z $UPLOAD_PORT ] && { echo "set UPLOAD_PORT"; exit 1; }
-[ -z $UPLOAD_USER_PATH ] && { echo "set UPLOAD_USER_PATH"; exit 1; }
-[ -z $UPLOAD_USER_PATH ] && { echo "set UPLOAD_USER_PATH"; exit 1; }
-[ -z $FDV_GIT_BRANCH ] && FDV_GIT_BRANCH=master
-
-FDV_CMAKE=mingw64-cmake
-if [ $# -eq 1 ]; then
-    if [ $1 -eq 32 ]; then
-        FDV_CMAKE=mingw32-cmake
-    fi
-fi
-
-log=build_log.txt
-ssh $BUILD_MACHINE "cd freedv-gui/docker; FDV_CMAKE=$FDV_CMAKE FDV_GIT_BRANCH=$FDV_GIT_BRANCH docker-compose -f docker-compose-win.yml up" > $log
-package_docker_path=$(cat $log | sed  -n "s/.*package: \(.*exe\) .*/\1/p")
-echo $package_docker_path
-ssh $BUILD_MACHINE "docker cp fdv_win_fed28_c:$package_docker_path freedv-gui/docker"
-package_file=$(basename $package_docker_path)
-scp $BUILD_MACHINE:freedv-gui/docker/$package_file .
-scp -P $UPLOAD_PORT $package_file $UPLOAD_USER_PATH
diff --git a/docker/freedv_build_windows.sh b/docker/freedv_build_windows.sh
new file mode 100755
index 0000000..59e09b1
--- /dev/null
+++ b/docker/freedv_build_windows.sh
@@ -0,0 +1,21 @@
+#!/bin/bash -x
+
+# Automation to cross compile freedv-gui for Windows using Docker
+#
+# usage:
+#   $ [FDV_GIT_BRANCH=your_branch] ./freedv_build_windows.sh 64|32
+
+[ -z $FDV_GIT_BRANCH ] && FDV_GIT_BRANCH=master
+
+FDV_CMAKE=mingw64-cmake
+if [ $# -eq 1 ]; then
+    if [ $1 -eq 32 ]; then
+        FDV_CMAKE=mingw32-cmake
+    fi
+fi
+
+log=build_log.txt
+FDV_CMAKE=$FDV_CMAKE FDV_GIT_BRANCH=$FDV_GIT_BRANCH docker-compose -f docker-compose-win.yml up > $log
+package_docker_path=$(cat $log | sed  -n "s/.*package: \(.*exe\) .*/\1/p")
+echo $package_docker_path
+docker cp fdv_win_fed30_c:$package_docker_path .
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index a652907..7cb3b94 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -47,10 +47,24 @@ set(FREEDV_SOURCES
     sox/xmalloc.c
     topFrame.h
     version.h
+    osx_interface.h
+)
+
+set(FREEDV_SOURCES_OSX
+    osx_interface.mm
+)
+
+set(FREEDV_LINK_LIBS_OSX
+    "-framework AVFoundation"
 )
 
 # WIN32 is needed for Windows GUI apps and is ignored for UNIX like systems.
-add_executable(freedv WIN32 ${FREEDV_SOURCES} ${RES_FILES})
+# In addition, there are some required OSX-specific code files for platform specific handling.
+if(APPLE)
+    add_executable(freedv WIN32 ${FREEDV_SOURCES} ${RES_FILES} ${FREEDV_SOURCES_OSX})
+else()
+    add_executable(freedv WIN32 ${FREEDV_SOURCES} ${RES_FILES})
+endif(APPLE)
 
 # Link imported or build tree targets.
 target_link_libraries(freedv codec2 lpcnetfreedv)
@@ -66,7 +80,18 @@ if(FREEDV_STATIC_DEPS)
 endif(FREEDV_STATIC_DEPS)
 
 # Link other dependencies
+if(APPLE)
+target_link_libraries(freedv ${FREEDV_LINK_LIBS} ${FREEDV_LINK_LIBS_OSX})
+else()
 target_link_libraries(freedv ${FREEDV_LINK_LIBS})
+endif(APPLE)
+
+# For older Xcode (< 9.0), bypass usage of @available.
+if(APPLE)
+if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0.0.900037)
+add_definitions(-DAPPLE_OLD_XCODE)
+endif(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0.0.900037)
+endif(APPLE)
 
 # Insert source and generated header directories before other search directories.
 include_directories(BEFORE ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
@@ -80,6 +105,7 @@ if(APPLE)
     add_custom_command(
         TARGET freedv
         POST_BUILD
+        COMMAND rm ARGS -rf FreeDV.* dist_tmp
         COMMAND mkdir ARGS -p FreeDV.app/Contents/MacOS
         COMMAND mkdir ARGS -p FreeDV.app/Contents/Resources/English.lproj
         COMMAND cp ARGS ${CMAKE_CURRENT_SOURCE_DIR}/info.plist FreeDV.app/Contents
diff --git a/src/fdmdv2_main.cpp b/src/fdmdv2_main.cpp
index f6e0360..d28c53c 100644
--- a/src/fdmdv2_main.cpp
+++ b/src/fdmdv2_main.cpp
@@ -21,6 +21,7 @@
 //==========================================================================
 
 #include "fdmdv2_main.h"
+#include "osx_interface.h"
 
 #define wxUSE_FILEDLG   1
 #define wxUSE_LIBPNG    1
@@ -577,8 +578,10 @@ MainFrame::MainFrame(wxString plugInName, wxWindow *parent) : TopFrame(plugInNam
         m_rb800xa->SetValue(1);
     if (mode == 6)
         m_rb2400b->SetValue(1);
+#ifdef __HORUS__
     if (mode == 7)
         m_rbHorusBinary->SetValue(1);
+#endif
     if (mode == 8 && isAvxPresent)
         m_rb2020->SetValue(1);
     pConfig->SetPath(wxT("/"));
@@ -731,12 +734,6 @@ MainFrame::~MainFrame()
     stopUDPThread();
 #endif
 
-    if (wxGetApp().m_hamlib)
-    {
-        wxGetApp().m_hamlib->close();
-        delete wxGetApp().m_hamlib;
-    }
-
     if (wxGetApp().m_serialport)
     {
         delete wxGetApp().m_serialport;
@@ -835,8 +832,10 @@ MainFrame::~MainFrame()
             mode = 5;
         if (m_rb2400b->GetValue())
             mode = 6;
+#ifdef __HORUS__
         if (m_rbHorusBinary->GetValue())
             mode = 7;
+#endif
         if (m_rb2020->GetValue())
             mode = 8;
        pConfig->Write(wxT("/Audio/mode"), mode);
@@ -2430,6 +2429,13 @@ void MainFrame::OnRecFileFromModulator(wxCommandEvent& event)
 //-------------------------------------------------------------------------
 void MainFrame::OnExit(wxCommandEvent& event)
 {
+    // Note: sideband detection needs to be disabled here instead
+    // of in the destructor due to its need to touch the UI.
+    if (wxGetApp().m_hamlib)
+    {
+        wxGetApp().m_hamlib->disable_sideband_detection();
+    }
+
     //fprintf(stderr, "MainFrame::OnExit\n");
     wxUnusedVar(event);
 #ifdef _USE_TIMER
@@ -2455,6 +2461,7 @@ void MainFrame::OnExit(wxCommandEvent& event)
     m_togBtnAnalog->Disable();
     //m_togBtnALC->Disable();
     //m_btnTogPTT->Disable();
+
     Pa_Terminate();
     Destroy();
 }
@@ -2611,7 +2618,11 @@ bool MainFrame::OpenHamlibRig() {
     bool status = wxGetApp().m_hamlib->connect(rig, port.mb_str(wxConvUTF8), serial_rate);
     if (status == false)
         wxMessageBox("Couldn't connect to Radio with hamlib", wxT("Error"), wxOK | wxICON_ERROR, this);
- 
+    else
+    {
+        wxGetApp().m_hamlib->enable_sideband_detection(m_txtSSBStatus);
+    }
+
     return status;
 } 
 
@@ -2642,7 +2653,9 @@ void MainFrame::OnTogBtnOnOff(wxCommandEvent& event)
         m_rb700d->Disable();
         m_rb800xa->Disable();
         m_rb2400b->Disable();
+#ifdef __HORUS__
         m_rbHorusBinary->Disable();
+#endif
         m_rb2020->Disable();
         if (m_rbPlugIn != NULL)
             m_rbPlugIn->Disable();
@@ -2678,6 +2691,7 @@ void MainFrame::OnTogBtnOnOff(wxCommandEvent& event)
         if (m_rb2400b->GetValue()) {
             g_mode = FREEDV_MODE_2400B;
         }
+#ifdef __HORUS__
         if (m_rbHorusBinary->GetValue()) {
             g_mode = -1;  /* TODO; a better way of handling (enumerating?) non-freedv modes */
 
@@ -2694,6 +2708,7 @@ void MainFrame::OnTogBtnOnOff(wxCommandEvent& event)
             m_textInterleaverSync->SetLabel("");
             g_modemInbufferSize = (int)(FRAME_DURATION * horus_get_Fs(g_horus));
         }
+#endif
         if (m_rb2020->GetValue() && isAvxPresent) {
             g_mode = FREEDV_MODE_2020;
             g_Nc = 31;                         /* TODO: be nice if we didn't have to hard code this, maybe API call? */
@@ -2819,6 +2834,16 @@ void MainFrame::OnTogBtnOnOff(wxCommandEvent& event)
         //printf("m_textEncoding = %d\n", wxGetApp().m_textEncoding);
         //printf("g_stats.snr: %f\n", g_stats.snr_est);
 
+        // attempt to start sound cards and tx/rx processing
+        if (VerifyMicrophonePermissions())
+        {
+            startRxStream();
+        }
+        else
+        {
+            wxMessageBox(wxString("Microphone permissions must be granted to FreeDV for it to function properly."), wxT("Error"), wxOK | wxICON_ERROR, this);
+        }
+
         // attempt to start PTT ......
         
         if (wxGetApp().m_boolHamlibUseForPTT)
@@ -2827,10 +2852,6 @@ void MainFrame::OnTogBtnOnOff(wxCommandEvent& event)
             OpenSerialPort();
         }
 
-        // attempt to start sound cards and tx/rx processing
-
-        startRxStream();
-
         if (m_RxRunning)
         {
 #ifdef _USE_TIMER
@@ -2867,6 +2888,7 @@ void MainFrame::OnTogBtnOnOff(wxCommandEvent& event)
                 if (hamlib->ptt(false, hamlibError) == false) {
                     wxMessageBox(wxString("Hamlib PTT Error: ") + hamlibError, wxT("Error"), wxOK | wxICON_ERROR, this);
                 }
+                hamlib->disable_sideband_detection();
                 hamlib->close();
             }
         }
@@ -2914,7 +2936,9 @@ void MainFrame::OnTogBtnOnOff(wxCommandEvent& event)
         m_rb700d->Enable();
         m_rb800xa->Enable();
         m_rb2400b->Enable();
+#ifdef __HORUS__
         m_rbHorusBinary->Enable();
+#endif
         if(isAvxPresent)
             m_rb2020->Enable();
         if (m_rbPlugIn != NULL)
@@ -4094,8 +4118,6 @@ void txRxProcessing()
                 fprintf(stderr, "  nout: %d\n", nout);
             }
             ret = codec2_fifo_write(cbData->outfifo1, outsound_card, nout);
-            // should never fire as we check there is enough room before entering while loop
-            assert(ret != -1);
         }
     }
    
@@ -4479,48 +4501,29 @@ void MainFrame::CloseSerialPort(void)
 // Tests the underlying platform for AVX support.  2020 needs AVX support to run
 // in real-time, and old processors do not offer AVX support
 //
-void __cpuid(int* cpuinfo, int info)
-{
-    __asm__ __volatile__(
-        "xchg %%ebx, %%edi;"
-        "cpuid;"
-        "xchg %%ebx, %%edi;"
-        :"=a" (cpuinfo[0]), "=D" (cpuinfo[1]), "=c" (cpuinfo[2]), "=d" (cpuinfo[3])
-        :"0" (info)
-    );
-}
-
-// These methods are defined for Windows but must be created otherwise
-unsigned long long __xgetbv(unsigned int index)
-{
-    unsigned int eax, edx;
-    __asm__ __volatile__(
-        "xgetbv;"
-        : "=a" (eax), "=d"(edx)
-        : "c" (index)
-    );
-    return ((unsigned long long)edx << 32) | eax;
-}
 
+#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || defined(_M_IX86)
 void MainFrame::checkAvxSupport(void)
 {
 
-    int cpuinfo[4];
-    __cpuid(cpuinfo, 1);
-
-    bool avxSupported = false;
+    isAvxPresent = false;
+    uint32_t eax, ebx, ecx, edx;
+    eax = ebx = ecx = edx = 0;
+    __cpuid(1, eax, ebx, ecx, edx);
 
-    avxSupported = cpuinfo[2] & (1 << 28) || false;
-    bool osxsaveSupported = cpuinfo[2] & (1 << 27) || false;
-    if (osxsaveSupported && avxSupported)
-    {
-        // _XCR_XFEATURE_ENABLED_MASK = 0
-        unsigned long long xcrFeatureMask = __xgetbv(0);
-        avxSupported = (xcrFeatureMask & 0x6) == 0x6;
+    if (ecx & (1<<27) && ecx & (1<<28)) {
+        // CPU supports XSAVE and AVX
+        uint32_t xcr0, xcr0_high;
+        asm("xgetbv" : "=a" (xcr0), "=d" (xcr0_high) : "c" (0));
+        isAvxPresent = (xcr0 & 6) == 6;    // AVX state saving enabled?
     }
-
-    isAvxPresent = avxSupported;
 }
+#else
+void MainFrame::checkAvxSupport(void)
+{
+    isAvxPresent = false;
+}
+#endif
 
 #ifdef __UDP_SUPPORT__
 
diff --git a/src/fdmdv2_main.h b/src/fdmdv2_main.h
index 59e99ad..180555f 100644
--- a/src/fdmdv2_main.h
+++ b/src/fdmdv2_main.h
@@ -52,7 +52,9 @@
 
 #include <stdint.h>
 #include <speex/speex_preprocess.h>
-
+#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || defined(_M_IX86)
+#include <cpuid.h>
+#endif
 #ifdef _WIN32
 #include <windows.h>
 #else
diff --git a/src/hamlib.cpp b/src/hamlib.cpp
index e383c8f..e87a307 100644
--- a/src/hamlib.cpp
+++ b/src/hamlib.cpp
@@ -31,7 +31,11 @@ typedef std::vector<const struct rig_caps *> riglist_t;
 static bool rig_cmp(const struct rig_caps *rig1, const struct rig_caps *rig2);
 static int build_list(const struct rig_caps *rig, rig_ptr_t);
 
-Hamlib::Hamlib() : m_rig(NULL) {
+Hamlib::Hamlib() : 
+    m_rig(NULL),
+    m_sidebandBox(NULL),
+    m_currFreq(0),
+    m_currMode(RIG_MODE_USB)  {
     /* Stop hamlib from spewing info to stderr. */
     rig_set_debug(RIG_DEBUG_NONE);
 
@@ -163,6 +167,121 @@ bool Hamlib::ptt(bool press, wxString &hamlibError) {
     return retcode == RIG_OK;
 }
 
+int Hamlib::hamlib_freq_cb(RIG* rig, vfo_t currVFO, freq_t currFreq, void* ptr)
+{
+    Hamlib* thisPtr = (Hamlib*)ptr;
+    thisPtr->m_currFreq = currFreq;
+    thisPtr->update_sideband_status();
+    return RIG_OK;
+}
+
+int Hamlib::hamlib_mode_cb(RIG* rig, vfo_t currVFO, rmode_t currMode, pbwidth_t passband, void* ptr)
+{
+    Hamlib* thisPtr = (Hamlib*)ptr;
+    thisPtr->m_currMode = currMode;
+    thisPtr->update_sideband_status();
+    return RIG_OK;
+}
+
+void Hamlib::enable_sideband_detection(wxStaticText* statusBox)
+{
+    // Enable control.
+    m_sidebandBox = statusBox;
+    m_sidebandBox->Enable(true);
+
+    // Populate initial state.
+    rmode_t mode = RIG_MODE_NONE;
+    pbwidth_t passband = 0;
+    int result = rig_get_mode(m_rig, RIG_VFO_CURR, &mode, &passband);
+    if (result != RIG_OK)
+    {
+        fprintf(stderr, "rig_get_mode: error = %s \n", rigerror(result));
+    }
+    else
+    {
+        freq_t freq = 0;
+        result = rig_get_freq(m_rig, RIG_VFO_CURR, &freq);
+        if (result != RIG_OK)
+        {
+            fprintf(stderr, "rig_get_freq: error = %s \n", rigerror(result));
+        }
+        else
+        {
+            m_currMode = mode;
+            m_currFreq = freq;
+            update_sideband_status();
+        }
+    }
+
+    // If we couldn't get current mode/frequency for any reason, disable the UI for it.
+    if (result != RIG_OK)
+    {
+        m_sidebandBox->SetLabel(wxT("unk"));
+        m_sidebandBox->Enable(false);
+        m_sidebandBox = NULL;
+    }
+    else
+    {
+        // TBD: Due to hamlib not supporting polling on Windows, the bottom is temporarily
+        // disabled. When/if that changes, re-enabling is a simple matter of removing
+        // the #if/#endif below.
+#if 0
+        // Enable rig callbacks.
+        rig_set_freq_callback(m_rig, &hamlib_freq_cb, this);
+        rig_set_mode_callback(m_rig, &hamlib_mode_cb, this);
+        rig_set_trn(m_rig, RIG_TRN_POLL);
+#endif
+    }
+}
+
+void Hamlib::disable_sideband_detection()
+{
+    if (m_sidebandBox != NULL) 
+    {
+        // TBD: Due to hamlib not supporting polling on Windows, the bottom is temporarily
+        // disabled. When/if that changes, re-enabling is a simple matter of removing
+        // the #if/#endif below.
+#if 0
+        // Disable callbacks.
+        rig_set_trn(m_rig, RIG_TRN_OFF);
+        rig_set_freq_callback(m_rig, NULL, NULL);
+        rig_set_mode_callback(m_rig, NULL, NULL);
+#endif
+    
+        // Disable control.
+        m_sidebandBox->SetLabel(wxT("unk"));
+        m_sidebandBox->Enable(false);
+        m_sidebandBox = NULL;
+    }
+}
+
+void Hamlib::update_sideband_status()
+{
+    // Update string value.
+    if (m_currMode == RIG_MODE_USB || m_currMode == RIG_MODE_PKTUSB)
+        m_sidebandBox->SetLabel(wxT("USB"));
+    else if (m_currMode == RIG_MODE_LSB || m_currMode == RIG_MODE_PKTLSB)
+        m_sidebandBox->SetLabel(wxT("LSB"));
+    else
+        m_sidebandBox->SetLabel(rig_strrmode(m_currMode));
+
+    // Update color
+    bool isMatchingSideband = 
+        (m_currFreq >= 10000000 && (m_currMode == RIG_MODE_USB || m_currMode == RIG_MODE_PKTUSB)) ||
+        (m_currFreq < 10000000 && (m_currMode == RIG_MODE_LSB || m_currMode == RIG_MODE_PKTLSB));
+    if (isMatchingSideband)
+    {
+        m_sidebandBox->SetForegroundColour(wxColor(*wxBLACK));
+    }
+    else
+    {
+        m_sidebandBox->SetForegroundColour(wxColor(*wxRED));
+    }
+
+    // Refresh
+    m_sidebandBox->Refresh();
+}
+
 void Hamlib::close(void) {
     if(m_rig) {
         rig_close(m_rig);
diff --git a/src/hamlib.h b/src/hamlib.h
index 65af2d4..3f7d7c2 100644
--- a/src/hamlib.h
+++ b/src/hamlib.h
@@ -1,11 +1,12 @@
 #ifndef HAMLIB_H
 #define HAMLIB_H
 
+#include <wx/stattext.h>
+#include <wx/combobox.h>
+#include <vector>
 extern "C" {
 #include <hamlib/rig.h>
 }
-#include <wx/combobox.h>
-#include <vector>
 
 class Hamlib {
 
@@ -15,6 +16,8 @@ class Hamlib {
         void populateComboBox(wxComboBox *cb);
         bool connect(unsigned int rig_index, const char *serial_port, const int serial_rate);
         bool ptt(bool press, wxString &hamlibError);
+        void enable_sideband_detection(wxStaticText* statusBox);
+        void disable_sideband_detection();
         void close(void);
         int get_serial_rate(void);
         int get_data_bits(void);
@@ -23,9 +26,19 @@ class Hamlib {
         typedef std::vector<const struct rig_caps *> riglist_t;
 
     private:
+        static int hamlib_freq_cb(RIG* rig, vfo_t currVFO, freq_t currFreq, void* ptr);
+        static int hamlib_mode_cb(RIG* rig, vfo_t currVFO, rmode_t currMode, pbwidth_t passband, void* ptr);
+
+        void update_sideband_status();
+
         RIG *m_rig;
         /* Sorted list of rigs. */
         riglist_t m_rigList;
+
+        /* Sideband detection status box and state. */
+        wxStaticText* m_sidebandBox;
+        freq_t m_currFreq;
+        rmode_t m_currMode;
 };
 
 #endif /*HAMLIB_H*/
diff --git a/src/info.plist b/src/info.plist
index 8f0d4c3..444ebf2 100644
--- a/src/info.plist
+++ b/src/info.plist
@@ -4,6 +4,8 @@
 <dict>
 	<key>CFBundleDevelopmentRegion</key>
 	<string>en</string>
+	<key>NSMicrophoneUsageDescription</key>
+	<string>Required to transmit your voice to the other party.</string>
 	<key>CFBundleExecutable</key>
 	<string>freedv</string>
 	<key>CFBundleIconFile</key>
@@ -40,6 +42,8 @@
 <dict>
 	<key>CFBundleDevelopmentRegion</key>
 	<string>en</string>
+	<key>NSMicrophoneUsageDescription</key>
+	<string>Required to transmit your voice to the other party.</string>
 	<key>CFBundleExecutable</key>
 	<string>freedv</string>
 	<key>CFBundleIconFile</key>
@@ -74,6 +78,8 @@
 <dict>
 	<key>CFBundleDevelopmentRegion</key>
 	<string>en</string>
+	<key>NSMicrophoneUsageDescription</key>
+	<string>Required to transmit your voice to the other party.</string>
 	<key>CFBundleExecutable</key>
 	<string>freedv</string>
 	<key>CFBundleIconFile</key>
@@ -101,4 +107,4 @@
 	<key>NSPrincipalClass</key>
 	<string>NSApplication</string>
 </dict>
-</plist>
\ No newline at end of file
+</plist>
diff --git a/src/osx_interface.h b/src/osx_interface.h
new file mode 100644
index 0000000..d598676
--- /dev/null
+++ b/src/osx_interface.h
@@ -0,0 +1,35 @@
+//==========================================================================
+// Name:            osx_interface.h
+//
+// Purpose:         Provides C wrapper to needed Objective-C calls.
+// Created:         Nov. 23, 2019
+// Authors:         Mooneer Salem
+// 
+// License:
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the GNU General License version 2.1,
+//  as published by the Free Software Foundation.  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 License
+//  along with this program; if not, see <http://www.gnu.org/licenses/>.
+//
+//==========================================================================
+#ifndef __OSX_INTERFACE__
+#define __OSX_INTERFACE__
+
+// Checks whether FreeDV has permissions to access the microphone on OSX Catalina
+// and above. If the user doesn't grant permissions (returns FALSE), the GUI 
+// should be able to properly handle the situation.
+#ifdef __APPLE__
+extern "C" bool VerifyMicrophonePermissions();
+#else
+// Stub for non-Apple platforms
+extern "C" bool VerifyMicrophonePermissions() { return true; }
+#endif // __APPLE__
+
+#endif // __OSX_INTERFACE__
diff --git a/src/osx_interface.mm b/src/osx_interface.mm
new file mode 100644
index 0000000..123fa5e
--- /dev/null
+++ b/src/osx_interface.mm
@@ -0,0 +1,81 @@
+//==========================================================================
+// Name:            osx_interface.mm
+//
+// Purpose:         Provides C wrapper to needed Objective-C calls.
+// Created:         Nov. 23, 2019
+// Authors:         Mooneer Salem
+// 
+// License:
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the GNU General License version 2.1,
+//  as published by the Free Software Foundation.  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 License
+//  along with this program; if not, see <http://www.gnu.org/licenses/>.
+//
+//==========================================================================
+
+#import <AVFoundation/AVFoundation.h>
+#include <mutex>
+#include <condition_variable>
+#include "osx_interface.h"
+
+static std::mutex osx_permissions_mutex;
+static std::condition_variable osx_permissions_condvar;
+static bool globalHasAccess = false;
+
+bool VerifyMicrophonePermissions()
+{
+    bool hasAccess = true;
+#ifndef APPLE_OLD_XCODE
+    if (@available(macOS 10.14, *)) {
+        // OSX >= 10.14: Request permission to access the camera and microphone.
+        switch ([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio])
+        {
+            case AVAuthorizationStatusAuthorized:
+            {
+                // The user has previously granted access to the camera.
+                break;
+            }
+            case AVAuthorizationStatusNotDetermined:
+            {
+                // The app hasn't yet asked the user for camera access.
+                // Note that this call is asynchronous but we need to wait for a response before
+                // proceeding. TBD for improvement.
+                [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) {
+                    std::unique_lock<std::mutex> lk(osx_permissions_mutex);
+                    globalHasAccess = granted;
+                    osx_permissions_condvar.notify_one();
+                }];
+
+                {
+                    std::unique_lock<std::mutex> lk(osx_permissions_mutex);
+                    osx_permissions_condvar.wait(lk);
+                    hasAccess = globalHasAccess;
+                }
+                break;
+            }
+            case AVAuthorizationStatusDenied:
+            case AVAuthorizationStatusRestricted:
+            {
+                // The user has previously denied access or otherwise can't grant permissions.
+                hasAccess = false;
+                break;
+            }
+            default:
+            {
+                // Other result not previously handled.
+                assert(0);
+            }
+        }
+    }
+#endif // !APPLE_OLD_XCODE
+
+    return hasAccess;
+}
+
diff --git a/src/topFrame.cpp b/src/topFrame.cpp
index bf5cf9a..ac89557 100644
--- a/src/topFrame.cpp
+++ b/src/topFrame.cpp
@@ -133,6 +133,7 @@ TopFrame::TopFrame(wxString plugInName, wxWindow* parent, wxWindowID id, const w
     // Box for S/N ratio (Numeric)
     //------------------------------
     m_textSNR = new wxStaticText(this, wxID_ANY, wxT(" 0.0"), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE);
+    m_textSNR->SetMinSize(wxSize(40,-1));
     snrSizer->Add(m_textSNR, 0, wxALIGN_CENTER_HORIZONTAL, 1);
 
     //------------------------------
@@ -236,6 +237,14 @@ TopFrame::TopFrame(wxString plugInName, wxWindow* parent, wxWindowID id, const w
     wxBoxSizer* lowerSizer;
     lowerSizer = new wxBoxSizer(wxHORIZONTAL);
 
+    wxBoxSizer* ssbStatusSizer;
+    ssbStatusSizer = new wxBoxSizer(wxVERTICAL);
+    m_txtSSBStatus = new wxStaticText(this, wxID_ANY, wxT("unk"), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
+    m_txtSSBStatus->Enable(false); // enabled only if Hamlib is turned on
+    m_txtSSBStatus->SetMinSize(wxSize(40,-1));
+    ssbStatusSizer->Add(m_txtSSBStatus, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL|wxEXPAND, 1);
+    lowerSizer->Add(ssbStatusSizer, 0, wxALIGN_CENTER_VERTICAL|wxALL, 1);
+
     m_BtnCallSignReset = new wxButton(this, wxID_ANY, _("Clear"), wxDefaultPosition, wxDefaultSize, 0);
     lowerSizer->Add(m_BtnCallSignReset, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 1);
 
@@ -352,8 +361,10 @@ TopFrame::TopFrame(wxString plugInName, wxWindow* parent, wxWindowID id, const w
     sbSizer_mode->Add(m_rb1600, 0, wxALIGN_LEFT|wxALL, 1);
     m_rb2400b = new wxRadioButton( this, wxID_ANY, wxT("2400B"), wxDefaultPosition, wxDefaultSize, 0);
     sbSizer_mode->Add(m_rb2400b, 0, wxALIGN_LEFT|wxALL, 1);
+#ifdef __HORUS__
     m_rbHorusBinary = new wxRadioButton( this, wxID_ANY, wxT("HorusB"), wxDefaultPosition, wxDefaultSize, 0);
     sbSizer_mode->Add(m_rbHorusBinary, 0, wxALIGN_LEFT|wxALL, 1);
+#endif
     m_rb2020 = new wxRadioButton( this, wxID_ANY, wxT("2020"), wxDefaultPosition, wxDefaultSize,  0);
     sbSizer_mode->Add(m_rb2020, 0, wxALIGN_LEFT|wxALL, 1);
 
diff --git a/src/topFrame.h b/src/topFrame.h
index 97690b3..4f7b70f 100644
--- a/src/topFrame.h
+++ b/src/topFrame.h
@@ -90,6 +90,7 @@ class TopFrame : public wxFrame
 
         wxButton*     m_BtnCallSignReset;
         wxTextCtrl*   m_txtCtrlCallSign;
+        wxStaticText* m_txtSSBStatus;
         wxStaticText* m_txtChecksumGood;
         wxStaticText* m_txtChecksumBad;
 
@@ -118,7 +119,9 @@ class TopFrame : public wxFrame
         wxRadioButton *m_rb1600;
         wxRadioButton *m_rb2400a;
         wxRadioButton *m_rb2400b;
+#ifdef __HORUS__
         wxRadioButton *m_rbHorusBinary;
+#endif
         wxRadioButton *m_rb2020;
         wxRadioButton *m_rbPlugIn;