//==========================================================================
// Name:            fdmdv2_main.cpp
//
// Purpose:         FreeDV main()
// Created:         Apr. 9, 2012
// Authors:         David Rowe, David Witten
//
// License:
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public 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 Public License
//  along with this program; if not, see <http://www.gnu.org/licenses/>.
//
//==========================================================================

#include "fdmdv2_main.h"
#include "osx_interface.h"

#define wxUSE_FILEDLG   1
#define wxUSE_LIBPNG    1
#define wxUSE_LIBJPEG   1
#define wxUSE_GIF       1
#define wxUSE_PCX       1
#define wxUSE_LIBTIFF   1

//-------------------------------------------------------------------
// Bunch of globals used for communication with sound card call
// back functions
// ------------------------------------------------------------------

int g_in, g_out;

// freedv states
int                 g_Nc;
int                 g_mode;
struct freedv      *g_pfreedv;
struct MODEM_STATS  g_stats;
float               g_pwr_scale;
int                 g_clip;
int                 g_freedv_verbose;

// test Frames
int                 g_testFrames;
int                 g_test_frame_sync_state;
int                 g_test_frame_count;
int                 g_channel_noise;
int                 g_resyncs;
float               g_sig_pwr_av = 0.0;
struct FIFO        *g_error_pattern_fifo;
short              *g_error_hist, *g_error_histn;
float               g_tone_phase;

// time averaged magnitude spectrum used for waterfall and spectrum display
float               g_avmag[MODEM_STATS_NSPEC];

// GUI controls that affect rx and tx processes
int   g_SquelchActive;
float g_SquelchLevel;
int   g_analog;
int   g_split;
int   g_tx;
float g_snr;
bool  g_half_duplex;
bool  g_modal;
SRC_STATE  *g_spec_src;  // sample rate converter for spectrum

// sending and receiving Call Sign data
struct FIFO         *g_txDataInFifo;
struct FIFO         *g_rxDataOutFifo;

// tx/rx processing states
int                 g_State, g_prev_State, g_interleaverSyncState;
paCallBackData     *g_rxUserdata;
int                 g_dump_timing;
int                 g_dump_fifo_state;

// FIFOs used for plotting waveforms
struct FIFO        *g_plotDemodInFifo;
struct FIFO        *g_plotSpeechOutFifo;
struct FIFO        *g_plotSpeechInFifo;

// Soundcard config
int                 g_nSoundCards;
int                 g_soundCard1InDeviceNum;
int                 g_soundCard1OutDeviceNum;
int                 g_soundCard1SampleRate;
int                 g_soundCard2InDeviceNum;
int                 g_soundCard2OutDeviceNum;
int                 g_soundCard2SampleRate;

// PortAudio over/underflow counters

int                 g_infifo1_full;
int                 g_outfifo1_empty;
int                 g_infifo2_full;
int                 g_outfifo2_empty;
int                 g_PAstatus1[4];
int                 g_PAstatus2[4];
int                 g_PAframesPerBuffer1;
int                 g_PAframesPerBuffer2;

// playing and recording from sound files

SNDFILE            *g_sfPlayFile;
bool                g_playFileToMicIn;
bool                g_loopPlayFileToMicIn;
int                 g_playFileToMicInEventId;

SNDFILE            *g_sfRecFile;
bool                g_recFileFromRadio;
unsigned int        g_recFromRadioSamples;
int                 g_recFileFromRadioEventId;

SNDFILE            *g_sfPlayFileFromRadio;
bool                g_playFileFromRadio;
int                 g_sfFs;
bool                g_loopPlayFileFromRadio;
int                 g_playFileFromRadioEventId;
float               g_blink;

SNDFILE            *g_sfRecFileFromModulator;
bool                g_recFileFromModulator = false;
int                 g_recFromModulatorSamples;
int                 g_recFileFromModulatorEventId;


wxWindow           *g_parent;

// Click to tune rx and tx frequency offset states
float               g_RxFreqOffsetHz;
COMP                g_RxFreqOffsetPhaseRect;
float               g_TxFreqOffsetHz;
COMP                g_TxFreqOffsetPhaseRect;

// buffer sizes dependent upon sample rate
int                 g_modemInbufferSize;
int                 g_speechOutbufferSize;

// experimental mutex to make sound card callbacks mutually exclusive
// TODO: review code and see if we need this any more, as fifos should
// now be thread safe

wxMutex g_mutexProtectingCallbackData;

// Speex pre-processor states

SpeexPreprocessState *g_speex_st;

// Option test file to log samples

FILE *ftest;
FILE *g_logfile;

// UDP socket available to send messages

wxDatagramSocket *g_sock;

#ifdef __HORUS__
// Horus Balloon telemetry support

struct horus *g_horus;
SRC_STATE    *g_horus_src;
#endif

// WxWidgets - initialize the application

IMPLEMENT_APP(MainApp);

//-------------------------------------------------------------------------
// OnInit()
//-------------------------------------------------------------------------
bool MainApp::OnInit()
{
    if(!wxApp::OnInit())
    {
        return false;
    }
    SetVendorName(wxT("CODEC2-Project"));
    SetAppName(wxT("FreeDV"));      // not needed, it's the default value

#ifdef FILE_RATHER_THAN_REGISTRY
    // Force use of file-based configuration persistance on Windows platforma
    wxConfig *pConfig = new wxConfig();
    wxFileConfig *pFConfig = new wxFileConfig(wxT("FreeDV"), wxT("CODEC2-Project"), wxT("FreeDV.conf"), wxT("FreeDV.conf"),  wxCONFIG_USE_LOCAL_FILE | wxCONFIG_USE_RELATIVE_PATH);
    pConfig->Set(pFConfig);
    pConfig->SetRecordDefaults();
#else
    wxConfigBase *pConfig = wxConfigBase::Get();
    pConfig->SetRecordDefaults();
#endif

    m_rTopWindow = wxRect(0, 0, 0, 0);
    m_strRxInAudio.Empty();
    m_strRxOutAudio.Empty();
    m_textVoiceInput.Empty();
    m_textVoiceOutput.Empty();
    m_strSampleRate.Empty();
    m_strBitrate.Empty();

    // Look for Plug In

    m_plugIn = false;
    #ifdef __WXMSW__
    wchar_t dll_path[] = L"afreedvplugin.dll";
    m_plugInHandle = LoadLibrary(dll_path);
    #else
    m_plugInHandle = dlopen("afreedvplugin.so", RTLD_LAZY);
    #endif
    
    if (m_plugInHandle) {
        printf("plugin: .so found\n");
        
        // lets get some information abt the plugIn

        void (*plugin_namefp)(char s[]);
        void *(*plugin_openfp)(char *param_names[], int *nparams, int (*aplugin_get_persistant)(char *, char *));

        #ifdef __WXMSW__
        plugin_namefp = (void (*)(char*))GetProcAddress((HMODULE)m_plugInHandle, "plugin_name");
        plugin_openfp = (void* (*)(char**,int *, int (*)(char *, char *)))GetProcAddress((HMODULE)m_plugInHandle, "plugin_open");
        m_plugin_startfp = (void (*)(void *))GetProcAddress((HMODULE)m_plugInHandle, "plugin_start");
        m_plugin_stopfp = (void (*)(void *))GetProcAddress((HMODULE)m_plugInHandle, "plugin_stop");
        m_plugin_rx_samplesfp = (void (*)(void *, short *, int))GetProcAddress((HMODULE)m_plugInHandle, "plugin_rx_samples");
        #else
        plugin_namefp = (void (*)(char*))dlsym(m_plugInHandle, "plugin_name");
        plugin_openfp = (void* (*)(char**,int *, int (*)(char *, char *)))dlsym(m_plugInHandle, "plugin_open");
        m_plugin_startfp = (void (*)(void *))dlsym(m_plugInHandle, "plugin_start");
        m_plugin_stopfp = (void (*)(void *))dlsym(m_plugInHandle, "plugin_stop");
        m_plugin_rx_samplesfp = (void (*)(void *, short *, int))dlsym(m_plugInHandle, "plugin_rx_samples");
        #endif
        
        if ((plugin_namefp != NULL) && (plugin_openfp != NULL)) {

            char s[256];
            m_plugIn = true;
            (plugin_namefp)(s);
            fprintf(stderr, "plugin name: %s\n", s);
            m_plugInName = s;

            char param_name1[80], param_name2[80];
            char *param_names[2] = {param_name1, param_name2};
            int  nparams, i;
            m_plugInStates = (plugin_openfp)(param_names, &nparams, plugin_get_persistant);
            m_numPlugInParam = nparams;
            for(i=0; i<nparams; i++) {
                m_plugInParamName[i] = param_names[i];
                wxString configStr = "/" + m_plugInName + "/" + m_plugInParamName[i];
                m_txtPlugInParam[i] = pConfig->Read(configStr, wxT(""));
                //fprintf(stderr, "  plugin param name[%d]: %s\n", i, param_names[i]);
                fprintf(stderr, "  plugin param name[%d]: %s values: %s\n", i, m_plugInParamName[i].mb_str().data(), m_txtPlugInParam[i].mb_str().data());
            }
        }
        
        else {
            fprintf(stderr, "plugin: fps not found...\n");           
        }
    }

    // Create the main application window

    frame = new MainFrame(m_plugInName, NULL);
    SetTopWindow(frame);

    // Should guarantee that the first plot tab defined is the one
    // displayed. But it doesn't when built from command line.  Why?

    frame->m_auiNbookCtrl->ChangeSelection(0);
    frame->Layout();
    frame->Show();
    g_parent =frame;

    return true;
}

//-------------------------------------------------------------------------
// OnExit()
//-------------------------------------------------------------------------
int MainApp::OnExit()
{
    //fprintf(stderr, "MainApp::OnExit\n");
    if (m_plugIn) {
        #ifdef __WXMSW__
        FreeLibrary((HMODULE)m_plugInHandle);
        #else
        dlclose(m_plugInHandle);
        #endif
    }
    return 0;
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-=
// Class MainFrame(wxFrame* pa->ent) : TopFrame(parent)
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-=
MainFrame::MainFrame(wxString plugInName, wxWindow *parent) : TopFrame(plugInName, parent)
{
    m_zoom              = 1.;

    #ifdef __WXMSW__
    g_logfile = stderr;
    #else
    g_logfile = stderr;
    #endif


    SetMinSize(wxSize(400,400));

    // Init Hamlib library, but we dont start talking to any rigs yet

    wxGetApp().m_hamlib = new Hamlib();

    // Init Serialport library, but as for Hamlib we dont start talking to any rigs yet

    wxGetApp().m_serialport = new Serialport();

    // Check for AVX support in the processor.  If it's not present, 2020 won't be processed
    // fast enough
    checkAvxSupport();
    if(!isAvxPresent)
        m_rb2020->Disable();

    tools->AppendSeparator();
    wxMenuItem* m_menuItemToolsConfigDelete;
    m_menuItemToolsConfigDelete = new wxMenuItem(tools, wxID_ANY, wxString(_("&Restore defaults")) , wxT("Delete config file/keys and restore defaults"), wxITEM_NORMAL);
    this->Connect(m_menuItemToolsConfigDelete->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainFrame::OnDeleteConfig));

    tools->Append(m_menuItemToolsConfigDelete);

    wxConfigBase *pConfig = wxConfigBase::Get();

    // restore frame position and size
    int x = pConfig->Read(wxT("/MainFrame/left"),       20);
    int y = pConfig->Read(wxT("/MainFrame/top"),        20);
    int w = pConfig->Read(wxT("/MainFrame/width"),     800);
    int h = pConfig->Read(wxT("/MainFrame/height"),    550);

    // sanitise frame position as a first pass at Win32 registry bug

    //fprintf(g_logfile, "x = %d y = %d w = %d h = %d\n", x,y,w,h);
    if (x < 0 || x > 2048) x = 20;
    if (y < 0 || y > 2048) y = 20;
    if (w < 0 || w > 2048) w = 800;
    if (h < 0 || h > 2048) h = 550;

    wxGetApp().m_show_wf            = pConfig->Read(wxT("/MainFrame/show_wf"),           1);
    wxGetApp().m_show_spect         = pConfig->Read(wxT("/MainFrame/show_spect"),        1);
    wxGetApp().m_show_scatter       = pConfig->Read(wxT("/MainFrame/show_scatter"),      1);
    wxGetApp().m_show_timing        = pConfig->Read(wxT("/MainFrame/show_timing"),       1);
    wxGetApp().m_show_freq          = pConfig->Read(wxT("/MainFrame/show_freq"),         1);
    wxGetApp().m_show_speech_in     = pConfig->Read(wxT("/MainFrame/show_speech_in"),    1);
    wxGetApp().m_show_speech_out    = pConfig->Read(wxT("/MainFrame/show_speech_out"),   1);
    wxGetApp().m_show_demod_in      = pConfig->Read(wxT("/MainFrame/show_demod_in"),     1);
    wxGetApp().m_show_test_frame_errors = pConfig->Read(wxT("/MainFrame/show_test_frame_errors"),     1);
    wxGetApp().m_show_test_frame_errors_hist = pConfig->Read(wxT("/MainFrame/show_test_frame_errors_hist"),     1);

    wxGetApp().m_rxNbookCtrl        = pConfig->Read(wxT("/MainFrame/rxNbookCtrl"),    (long)0);

    g_SquelchActive = pConfig->Read(wxT("/Audio/SquelchActive"), (long)0);
    g_SquelchLevel = pConfig->Read(wxT("/Audio/SquelchLevel"), (int)(SQ_DEFAULT_SNR*2));
    g_SquelchLevel /= 2.0;

    Move(x, y);
    SetClientSize(w, h);
    
    if(wxGetApp().m_show_wf)
    {
        // Add Waterfall Plot window
        m_panelWaterfall = new PlotWaterfall((wxFrame*) m_auiNbookCtrl, false, 0);
        m_panelWaterfall->SetToolTip(_("Left click to tune, Right click to toggle mono/colour"));
        m_auiNbookCtrl->AddPage(m_panelWaterfall, _("Waterfall"), true, wxNullBitmap);
    }
    if(wxGetApp().m_show_spect)
    {
        // Add Spectrum Plot window
        m_panelSpectrum = new PlotSpectrum((wxFrame*) m_auiNbookCtrl, g_avmag,
                                           MODEM_STATS_NSPEC*((float)MAX_F_HZ/MODEM_STATS_MAX_F_HZ));
        m_panelSpectrum->SetToolTip(_("Left click to tune"));
        m_auiNbookCtrl->AddPage(m_panelSpectrum, _("Spectrum"), true, wxNullBitmap);
    }
    if(wxGetApp().m_show_scatter)
    {
        // Add Scatter Plot window
        m_panelScatter = new PlotScatter((wxFrame*) m_auiNbookCtrl);
        m_auiNbookCtrl->AddPage(m_panelScatter, _("Scatter"), true, wxNullBitmap);
    }
    if(wxGetApp().m_show_demod_in)
    {
        // Add Demod Input window
        m_panelDemodIn = new PlotScalar((wxFrame*) m_auiNbookCtrl, 1, WAVEFORM_PLOT_TIME, 1.0/WAVEFORM_PLOT_FS, -1, 1, 1, 0.2, "%2.1f", 0);
        m_auiNbookCtrl->AddPage(m_panelDemodIn, _("Frm Radio"), true, wxNullBitmap);
        g_plotDemodInFifo = codec2_fifo_create(2*WAVEFORM_PLOT_BUF);
    }

    if(wxGetApp().m_show_speech_in)
    {
        // Add Speech Input window
        m_panelSpeechIn = new PlotScalar((wxFrame*) m_auiNbookCtrl, 1, WAVEFORM_PLOT_TIME, 1.0/WAVEFORM_PLOT_FS, -1, 1, 1, 0.2, "%2.1f", 0);
        m_auiNbookCtrl->AddPage(m_panelSpeechIn, _("Frm Mic"), true, wxNullBitmap);
        g_plotSpeechInFifo = codec2_fifo_create(4*WAVEFORM_PLOT_BUF);
    }

    if(wxGetApp().m_show_speech_out)
    {
        // Add Speech Output window
        m_panelSpeechOut = new PlotScalar((wxFrame*) m_auiNbookCtrl, 1, WAVEFORM_PLOT_TIME, 1.0/WAVEFORM_PLOT_FS, -1, 1, 1, 0.2, "%2.1f", 0);
        m_auiNbookCtrl->AddPage(m_panelSpeechOut, _("To Spkr/Hdphns"), true, wxNullBitmap);
        g_plotSpeechOutFifo = codec2_fifo_create(2*WAVEFORM_PLOT_BUF);
    }

    if(wxGetApp().m_show_timing)
    {
        // Add Timing Offset window
        m_panelTimeOffset = new PlotScalar((wxFrame*) m_auiNbookCtrl, 1, 5.0, DT, -0.5, 0.5, 1, 0.1, "%2.1f", 0);
        m_auiNbookCtrl->AddPage(m_panelTimeOffset, L"Timing \u0394", true, wxNullBitmap);
    }
    if(wxGetApp().m_show_freq)
    {
        // Add Frequency Offset window
        m_panelFreqOffset = new PlotScalar((wxFrame*) m_auiNbookCtrl, 1, 5.0, DT, -200, 200, 1, 50, "%3.0fHz", 0);
        m_auiNbookCtrl->AddPage(m_panelFreqOffset, L"Frequency \u0394", true, wxNullBitmap);
    }

    if(wxGetApp().m_show_test_frame_errors)
    {
        // Add Test Frame Errors window
        m_panelTestFrameErrors = new PlotScalar((wxFrame*) m_auiNbookCtrl, 2*MODEM_STATS_NC_MAX, 30.0, DT, 0, 2*MODEM_STATS_NC_MAX+2, 1, 1, "", 1);
        m_auiNbookCtrl->AddPage(m_panelTestFrameErrors, L"Test Frame Errors", true, wxNullBitmap);
    }

    if(wxGetApp().m_show_test_frame_errors_hist)
    {
        // Add Test Frame Historgram window.  1 column for every bit, 2 bits per carrier
        m_panelTestFrameErrorsHist = new PlotScalar((wxFrame*) m_auiNbookCtrl, 1, 1.0, 1.0/(2*MODEM_STATS_NC_MAX), 0.001, 0.1, 1.0/MODEM_STATS_NC_MAX, 0.1, "%0.0E", 0);
        m_auiNbookCtrl->AddPage(m_panelTestFrameErrorsHist, L"Test Frame Histogram", true, wxNullBitmap);
        m_panelTestFrameErrorsHist->setBarGraph(1);
        m_panelTestFrameErrorsHist->setLogY(1);
    }

    wxGetApp().m_framesPerBuffer = pConfig->Read(wxT("/Audio/framesPerBuffer"), (int)PA_FPB);
    wxGetApp().m_fifoSize_ms = pConfig->Read(wxT("/Audio/fifoSize_ms"), (int)FIFO_SIZE);

    g_soundCard1InDeviceNum  = pConfig->Read(wxT("/Audio/soundCard1InDeviceNum"),         -1);
    g_soundCard1OutDeviceNum = pConfig->Read(wxT("/Audio/soundCard1OutDeviceNum"),        -1);
    g_soundCard1SampleRate   = pConfig->Read(wxT("/Audio/soundCard1SampleRate"),          -1);

    g_soundCard2InDeviceNum  = pConfig->Read(wxT("/Audio/soundCard2InDeviceNum"),         -1);
    g_soundCard2OutDeviceNum = pConfig->Read(wxT("/Audio/soundCard2OutDeviceNum"),        -1);
    g_soundCard2SampleRate   = pConfig->Read(wxT("/Audio/soundCard2SampleRate"),          -1);

    g_nSoundCards = 0;
    if ((g_soundCard1InDeviceNum > -1) && (g_soundCard1OutDeviceNum > -1)) {
        g_nSoundCards = 1;
        if ((g_soundCard2InDeviceNum > -1) && (g_soundCard2OutDeviceNum > -1))
            g_nSoundCards = 2;
    }

    wxGetApp().m_playFileToMicInPath = pConfig->Read("/File/playFileToMicInPath",   wxT(""));
    wxGetApp().m_recFileFromRadioPath = pConfig->Read("/File/recFileFromRadioPath", wxT(""));
    wxGetApp().m_recFileFromRadioSecs = pConfig->Read("/File/recFileFromRadioSecs", 60);
    wxGetApp().m_recFileFromModulatorPath = pConfig->Read("/File/recFileFromModulatorPath", wxT(""));
    wxGetApp().m_recFileFromModulatorSecs = pConfig->Read("/File/recFileFromModulatorSecs", 10);
    wxGetApp().m_playFileFromRadioPath = pConfig->Read("/File/playFileFromRadioPath", wxT(""));

    // PTT -------------------------------------------------------------------

    wxGetApp().m_boolHalfDuplex     = pConfig->ReadBool(wxT("/Rig/HalfDuplex"),     true);
    wxGetApp().m_leftChannelVoxTone = pConfig->ReadBool("/Rig/leftChannelVoxTone",  false);
 
    wxGetApp().m_txtVoiceKeyerWaveFilePath = pConfig->Read(wxT("/VoiceKeyer/WaveFilePath"), wxT(""));
    wxGetApp().m_txtVoiceKeyerWaveFile = pConfig->Read(wxT("/VoiceKeyer/WaveFile"), wxT("voicekeyer.wav"));
    wxGetApp().m_intVoiceKeyerRxPause = pConfig->Read(wxT("/VoiceKeyer/RxPause"), 10);
    wxGetApp().m_intVoiceKeyerRepeats = pConfig->Read(wxT("/VoiceKeyer/Repeats"), 5);
 
    wxGetApp().m_boolHamlibUseForPTT = pConfig->ReadBool("/Hamlib/UseForPTT", false);
    wxGetApp().m_intHamlibIcomCIVHex = pConfig->ReadLong("/Hamlib/IcomCIVHex", 0);
    wxGetApp().m_intHamlibRig = pConfig->ReadLong("/Hamlib/RigName", 0);
    wxGetApp().m_strHamlibSerialPort = pConfig->Read("/Hamlib/SerialPort", "");
    wxGetApp().m_intHamlibSerialRate = pConfig->ReadLong("/Hamlib/SerialRate", 0);
    
    wxGetApp().m_boolUseSerialPTT   = pConfig->ReadBool(wxT("/Rig/UseSerialPTT"),   false);
    wxGetApp().m_strRigCtrlPort     = pConfig->Read(wxT("/Rig/Port"),               wxT(""));
    wxGetApp().m_boolUseRTS         = pConfig->ReadBool(wxT("/Rig/UseRTS"),         true);
    wxGetApp().m_boolRTSPos         = pConfig->ReadBool(wxT("/Rig/RTSPolarity"),    true);
    wxGetApp().m_boolUseDTR         = pConfig->ReadBool(wxT("/Rig/UseDTR"),         false);
    wxGetApp().m_boolDTRPos         = pConfig->ReadBool(wxT("/Rig/DTRPolarity"),    false);

    assert(wxGetApp().m_serialport != NULL);

    // -----------------------------------------------------------------------

    bool slow = false; // prevents compile error when using default bool
    wxGetApp().m_snrSlow = pConfig->Read("/Audio/snrSlow", slow);

    bool t = true;     // prevents compile error when using default bool
    wxGetApp().m_codec2LPCPostFilterEnable     = pConfig->Read(wxT("/Filter/codec2LPCPostFilterEnable"),    t);
    wxGetApp().m_codec2LPCPostFilterBassBoost  = pConfig->Read(wxT("/Filter/codec2LPCPostFilterBassBoost"), t);
    wxGetApp().m_codec2LPCPostFilterGamma      = (float)pConfig->Read(wxT("/Filter/codec2LPCPostFilterGamma"),     CODEC2_LPC_PF_GAMMA*100)/100.0;
    wxGetApp().m_codec2LPCPostFilterBeta       = (float)pConfig->Read(wxT("/Filter/codec2LPCPostFilterBeta"),      CODEC2_LPC_PF_BETA*100)/100.0;
    //printf("main(): m_codec2LPCPostFilterBeta: %f\n", wxGetApp().m_codec2LPCPostFilterBeta);

    wxGetApp().m_speexpp_enable     = pConfig->Read(wxT("/Filter/speexpp_enable"),    t);
    wxGetApp().m_700C_EQ     = pConfig->Read(wxT("/Filter/700C_EQ"),    t);

    wxGetApp().m_MicInBassFreqHz = (float)pConfig->Read(wxT("/Filter/MicInBassFreqHz"),    1);
    wxGetApp().m_MicInBassGaindB = (float)pConfig->Read(wxT("/Filter/MicInBassGaindB"),    (long)0)/10.0;
    wxGetApp().m_MicInTrebleFreqHz = (float)pConfig->Read(wxT("/Filter/MicInTrebleFreqHz"),    1);
    wxGetApp().m_MicInTrebleGaindB = (float)pConfig->Read(wxT("/Filter/MicInTrebleGaindB"),    (long)0)/10.0;
    wxGetApp().m_MicInMidFreqHz = (float)pConfig->Read(wxT("/Filter/MicInMidFreqHz"),    1);
    wxGetApp().m_MicInMidGaindB = (float)pConfig->Read(wxT("/Filter/MicInMidGaindB"),    (long)0)/10.0;
    wxGetApp().m_MicInMidQ = (float)pConfig->Read(wxT("/Filter/MicInMidQ"),              (long)100)/100.0;

    bool f = false;
    wxGetApp().m_MicInEQEnable = (float)pConfig->Read(wxT("/Filter/MicInEQEnable"), f);

    wxGetApp().m_SpkOutBassFreqHz = (float)pConfig->Read(wxT("/Filter/SpkOutBassFreqHz"),    1);
    wxGetApp().m_SpkOutBassGaindB = (float)pConfig->Read(wxT("/Filter/SpkOutBassGaindB"),    (long)0)/10.0;
    wxGetApp().m_SpkOutTrebleFreqHz = (float)pConfig->Read(wxT("/Filter/SpkOutTrebleFreqHz"),    1);
    wxGetApp().m_SpkOutTrebleGaindB = (float)pConfig->Read(wxT("/Filter/SpkOutTrebleGaindB"),    (long)0)/10.0;
    wxGetApp().m_SpkOutMidFreqHz = (float)pConfig->Read(wxT("/Filter/SpkOutMidFreqHz"),    1);
    wxGetApp().m_SpkOutMidGaindB = (float)pConfig->Read(wxT("/Filter/SpkOutMidGaindB"),    (long)0)/10.0;
    wxGetApp().m_SpkOutMidQ = (float)pConfig->Read(wxT("/Filter/SpkOutMidQ"),                (long)100)/100.0;

    wxGetApp().m_SpkOutEQEnable = (float)pConfig->Read(wxT("/Filter/SpkOutEQEnable"), f);

    wxGetApp().m_callSign = pConfig->Read("/Data/CallSign", wxT(""));
    wxGetApp().m_textEncoding = pConfig->Read("/Data/TextEncoding", 1);
    wxGetApp().m_enable_checksum = pConfig->Read("/Data/EnableChecksumOnMsgRx", f);

    wxGetApp().m_events = pConfig->Read("/Events/enable", f);
    wxGetApp().m_events_spam_timer = (int)pConfig->Read(wxT("/Events/spam_timer"), 10);
    wxGetApp().m_events_regexp_match = pConfig->Read("/Events/regexp_match", wxT("s=(.*)"));
    wxGetApp().m_events_regexp_replace = pConfig->Read("/Events/regexp_replace", 
                                                       wxT("curl http://qso.freedv.org/cgi-bin/onspot.cgi?s=\\1"));
    // make sure regexp lists are terminated by a \n

    if (wxGetApp().m_events_regexp_match.Last() != '\n') {
        wxGetApp().m_events_regexp_match = wxGetApp().m_events_regexp_match+'\n';
    }
    if (wxGetApp().m_events_regexp_replace.Last() != '\n') {
        wxGetApp().m_events_regexp_replace = wxGetApp().m_events_regexp_replace+'\n';
    }

    wxGetApp().m_udp_enable = (float)pConfig->Read(wxT("/UDP/enable"), f);
    wxGetApp().m_udp_port = (int)pConfig->Read(wxT("/UDP/port"), 3000);

    wxGetApp().m_FreeDV700txClip = (float)pConfig->Read(wxT("/FreeDV700/txClip"), t);
    wxGetApp().m_FreeDV700txBPF = (float)pConfig->Read(wxT("/FreeDV700/txBPF"), t);
    wxGetApp().m_FreeDV700Combine = 1;
    wxGetApp().m_FreeDV700Interleave = (int)pConfig->Read(wxT("/FreeDV700/interleave"), 1);
    wxGetApp().m_FreeDV700ManualUnSync = (float)pConfig->Read(wxT("/FreeDV700/manualUnSync"), f);

    wxGetApp().m_PhaseEstBW = (float)pConfig->Read(wxT("/OFDM/PhaseEstBW"), f);
    wxGetApp().m_PhaseEstDPSK = (float)pConfig->Read(wxT("/OFDM/PhaseEstDPSK"), f);

    wxGetApp().m_noise_snr = (float)pConfig->Read(wxT("/Noise/noise_snr"), 2);
    
    wxGetApp().m_debug_console = (float)pConfig->Read(wxT("/Debug/console"), f);
    g_freedv_verbose = pConfig->Read(wxT("/Debug/APIverbose"), (long)0);

    wxGetApp().m_attn_carrier_en = 0;
    wxGetApp().m_attn_carrier    = 0;

    wxGetApp().m_tone = 0;
    wxGetApp().m_tone_freq_hz = 1000;
    wxGetApp().m_tone_amplitude = 500;

    int mode  = pConfig->Read(wxT("/Audio/mode"), (long)0);
    if (mode == 0)
        m_rb1600->SetValue(1);
    if (mode == 3)
        m_rb700c->SetValue(1);
    if (mode == 4)
        m_rb700d->SetValue(1);
    if (mode == 5)
        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("/"));

//    this->Connect(m_menuItemHelpUpdates->GetId(), wxEVT_UPDATE_UI, wxUpdateUIEventHandler(TopFrame::OnHelpCheckUpdatesUI));
    //m_togRxID->Connect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnRxIDUI), NULL, this);
    //m_togTxID->Connect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnTxIDUI), NULL, this);
    m_togBtnOnOff->Connect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnOnOffUI), NULL, this);
    m_togBtnSplit->Connect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnSplitClickUI), NULL, this);
    m_togBtnAnalog->Connect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnAnalogClickUI), NULL, this);
    //m_togBtnALC->Connect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnALCClickUI), NULL, this);
   // m_btnTogPTT->Connect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnPTT_UI), NULL, this);

    m_togBtnSplit->Disable();
    //m_togRxID->Disable();
    //m_togTxID->Disable();
    m_togBtnAnalog->Disable();
    m_btnTogPTT->Disable();
    m_togBtnVoiceKeyer->Disable();
    //m_togBtnALC->Disable();

    // squelch settings
    char sqsnr[15];
    m_sliderSQ->SetValue((int)((g_SquelchLevel+5.0)*2.0));
    sprintf(sqsnr, "%4.1f", g_SquelchLevel);
    wxString sqsnr_string(sqsnr);
    m_textSQ->SetLabel(sqsnr_string);
    m_ckboxSQ->SetValue(g_SquelchActive);

    // SNR settings

    m_ckboxSNR->SetValue(wxGetApp().m_snrSlow);
    setsnrBeta(wxGetApp().m_snrSlow);

#ifdef _USE_TIMER
    Bind(wxEVT_TIMER, &MainFrame::OnTimer, this);       // ID_MY_WINDOW);
    m_plotTimer.SetOwner(this, ID_TIMER_WATERFALL);
    //m_panelWaterfall->Refresh();
#endif

    m_RxRunning = false;

#ifdef _USE_ONIDLE
    Connect(wxEVT_IDLE, wxIdleEventHandler(MainFrame::OnIdle), NULL, this);
#endif //_USE_ONIDLE

    g_sfPlayFile = NULL;
    g_playFileToMicIn = false;
    g_loopPlayFileToMicIn = false;

    g_sfRecFile = NULL;
    g_recFileFromRadio = false;

    g_sfPlayFileFromRadio = NULL;
    g_playFileFromRadio = false;
    g_loopPlayFileFromRadio = false;

    g_sfRecFileFromModulator = NULL;
    g_recFileFromModulator = false;

    // init click-tune states

    g_RxFreqOffsetHz = 0.0;
    g_RxFreqOffsetPhaseRect.real = cos(0.0);
    g_RxFreqOffsetPhaseRect.imag = sin(0.0);
    m_panelWaterfall->setRxFreq(FDMDV_FCENTRE - g_RxFreqOffsetHz);
    m_panelSpectrum->setRxFreq(FDMDV_FCENTRE - g_RxFreqOffsetHz);

    g_TxFreqOffsetHz = 0.0;
    g_TxFreqOffsetPhaseRect.real = cos(0.0);
    g_TxFreqOffsetPhaseRect.imag = sin(0.0);

    g_tx = 0;
    g_split = 0;

    // data states
    g_txDataInFifo = codec2_fifo_create(MAX_CALLSIGN*VARICODE_MAX_BITS);
    g_rxDataOutFifo = codec2_fifo_create(MAX_CALLSIGN*VARICODE_MAX_BITS);

    sox_biquad_start();

    g_testFrames = 0;
    g_test_frame_sync_state = 0;
    g_resyncs = 0;
    wxGetApp().m_testFrames = false;
    wxGetApp().m_channel_noise = false;
    g_tone_phase = 0.0;

    g_modal = false;

#ifdef __EXPERIMENTAL_UDP__
    // Start UDP listener thread

    m_UDPThread = NULL;
    startUDPThread();
#endif

    optionsDlg = new OptionsDlg(NULL);
    m_schedule_restore = false;

    vk_state = VK_IDLE;

    // Init optional Windows debug console so we can see all those printfs

#ifdef __WXMSW__
    if (wxGetApp().m_debug_console) {
        // somewhere to send printfs while developing
        int ret = AllocConsole();
        freopen("CONOUT$", "w", stdout); 
        freopen("CONOUT$", "w", stderr); 
        fprintf(stderr, "AllocConsole: %d m_debug_console: %d\n", ret, wxGetApp().m_debug_console);
    }
#endif

    //#define FTEST
    #ifdef FTEST
    ftest = fopen("ftest.raw", "wb");
    assert(ftest != NULL);
    #endif

    /* experimental checkbox control of thread priority, used
       to helpo debug 700D windows sound break up */
    
    wxGetApp().m_txRxThreadHighPriority = true;
    g_dump_timing = g_dump_fifo_state = 0;
    
    UDPInit();
}

//-------------------------------------------------------------------------
// ~MainFrame()
//-------------------------------------------------------------------------
MainFrame::~MainFrame()
{
    int x;
    int y;
    int w;
    int h;

    //fprintf(stderr, "MainFrame::~MainFrame()\n");
    #ifdef FTEST
    fclose(ftest);
    #endif

    #ifdef __WXMSW__
    fclose(g_logfile);
    #endif

#ifdef __EXPERIMENTAL_UDP__
    stopUDPThread();
#endif

    if (wxGetApp().m_serialport)
    {
        delete wxGetApp().m_serialport;
    }

    wxConfigBase *pConfig = wxConfigBase::Get();
    if(pConfig)
    {
        if (!IsIconized()) {
            GetClientSize(&w, &h);
            GetPosition(&x, &y);
            //fprintf(stderr, "x = %d y = %d w = %d h = %d\n", x,y,w,h);
            pConfig->Write(wxT("/MainFrame/left"),               (long) x);
            pConfig->Write(wxT("/MainFrame/top"),                (long) y);
            pConfig->Write(wxT("/MainFrame/width"),              (long) w);
            pConfig->Write(wxT("/MainFrame/height"),             (long) h);
        }
        pConfig->Write(wxT("/MainFrame/show_wf"),           wxGetApp().m_show_wf);
        pConfig->Write(wxT("/MainFrame/show_spect"),        wxGetApp().m_show_spect);
        pConfig->Write(wxT("/MainFrame/show_scatter"),      wxGetApp().m_show_scatter);
        pConfig->Write(wxT("/MainFrame/show_timing"),       wxGetApp().m_show_timing);
        pConfig->Write(wxT("/MainFrame/show_freq"),         wxGetApp().m_show_freq);
        pConfig->Write(wxT("/MainFrame/show_speech_in"),    wxGetApp().m_show_speech_in);
        pConfig->Write(wxT("/MainFrame/show_speech_out"),   wxGetApp().m_show_speech_out);
        pConfig->Write(wxT("/MainFrame/show_demod_in"),     wxGetApp().m_show_demod_in);
        pConfig->Write(wxT("/MainFrame/show_test_frame_errors"), wxGetApp().m_show_test_frame_errors);
        pConfig->Write(wxT("/MainFrame/show_test_frame_errors_hist"), wxGetApp().m_show_test_frame_errors_hist);

        pConfig->Write(wxT("/MainFrame/rxNbookCtrl"), wxGetApp().m_rxNbookCtrl);

        pConfig->Write(wxT("/Audio/SquelchActive"),         g_SquelchActive);
        pConfig->Write(wxT("/Audio/SquelchLevel"),          (int)(g_SquelchLevel*2.0));

        pConfig->Write(wxT("/Audio/framesPerBuffer"),       wxGetApp().m_framesPerBuffer);
        pConfig->Write(wxT("/Audio/fifoSize_ms"),              wxGetApp().m_fifoSize_ms);

        pConfig->Write(wxT("/Audio/soundCard1InDeviceNum"),   g_soundCard1InDeviceNum);
        pConfig->Write(wxT("/Audio/soundCard1OutDeviceNum"),  g_soundCard1OutDeviceNum);
        pConfig->Write(wxT("/Audio/soundCard1SampleRate"),    g_soundCard1SampleRate );

        pConfig->Write(wxT("/Audio/soundCard2InDeviceNum"),   g_soundCard2InDeviceNum);
        pConfig->Write(wxT("/Audio/soundCard2OutDeviceNum"),  g_soundCard2OutDeviceNum);
        pConfig->Write(wxT("/Audio/soundCard2SampleRate"),    g_soundCard2SampleRate );

        pConfig->Write(wxT("/VoiceKeyer/WaveFilePath"), wxGetApp().m_txtVoiceKeyerWaveFilePath);
        pConfig->Write(wxT("/VoiceKeyer/WaveFile"), wxGetApp().m_txtVoiceKeyerWaveFile);
        pConfig->Write(wxT("/VoiceKeyer/RxPause"), wxGetApp().m_intVoiceKeyerRxPause);
        pConfig->Write(wxT("/VoiceKeyer/Repeats"), wxGetApp().m_intVoiceKeyerRepeats);

        pConfig->Write(wxT("/Rig/HalfDuplex"),              wxGetApp().m_boolHalfDuplex);
        pConfig->Write(wxT("/Rig/leftChannelVoxTone"),      wxGetApp().m_leftChannelVoxTone);
        pConfig->Write("/Hamlib/UseForPTT", wxGetApp().m_boolHamlibUseForPTT);
        pConfig->Write("/Hamlib/RigName", wxGetApp().m_intHamlibRig);
        pConfig->Write("/Hamlib/SerialPort", wxGetApp().m_strHamlibSerialPort);
        pConfig->Write("/Hamlib/SerialRate", wxGetApp().m_intHamlibSerialRate);
        pConfig->Write("/Hamlib/IcomCIVHex", wxGetApp().m_intHamlibIcomCIVHex);


        pConfig->Write(wxT("/File/playFileToMicInPath"),    wxGetApp().m_playFileToMicInPath);
        pConfig->Write(wxT("/File/recFileFromRadioPath"),   wxGetApp().m_recFileFromRadioPath);
        pConfig->Write(wxT("/File/recFileFromRadioSecs"),   wxGetApp().m_recFileFromRadioSecs);
        pConfig->Write(wxT("/File/recFileFromModulatorPath"),   wxGetApp().m_recFileFromModulatorPath);
        pConfig->Write(wxT("/File/recFileFromModulatorSecs"),   wxGetApp().m_recFileFromModulatorSecs);
        pConfig->Write(wxT("/File/playFileFromRadioPath"),  wxGetApp().m_playFileFromRadioPath);

        pConfig->Write(wxT("/Audio/snrSlow"), wxGetApp().m_snrSlow);

        pConfig->Write(wxT("/Data/CallSign"), wxGetApp().m_callSign);
        pConfig->Write(wxT("/Data/TextEncoding"), wxGetApp().m_textEncoding);
        pConfig->Write(wxT("/Data/EnableChecksumOnMsgRx"), wxGetApp().m_enable_checksum);
        pConfig->Write(wxT("/Events/enable"), wxGetApp().m_events);
        pConfig->Write(wxT("/Events/spam_timer"), wxGetApp().m_events_spam_timer);
        pConfig->Write(wxT("/Events/regexp_match"), wxGetApp().m_events_regexp_match);
        pConfig->Write(wxT("/Events/regexp_replace"), wxGetApp().m_events_regexp_replace);
 
        pConfig->Write(wxT("/UDP/enable"), wxGetApp().m_udp_enable);
        pConfig->Write(wxT("/UDP/port"),  wxGetApp().m_udp_port);

        pConfig->Write(wxT("/Filter/MicInEQEnable"), wxGetApp().m_MicInEQEnable);
        pConfig->Write(wxT("/Filter/SpkOutEQEnable"), wxGetApp().m_SpkOutEQEnable);

        pConfig->Write(wxT("/FreeDV700/txClip"), wxGetApp().m_FreeDV700txClip);
        pConfig->Write(wxT("/OFDM/PhaseEstBW"), wxGetApp().m_PhaseEstBW);
        pConfig->Write(wxT("/OFDM/PhaseEstDPSK"), wxGetApp().m_PhaseEstDPSK);
        pConfig->Write(wxT("/Noise/noise_snr"), wxGetApp().m_noise_snr);

        pConfig->Write(wxT("/Debug/console"), wxGetApp().m_debug_console);

        int mode;
        if (m_rb1600->GetValue())
            mode = 0;
        if (m_rb700c->GetValue())
            mode = 3;
        if (m_rb700d->GetValue())
            mode = 4;
        if (m_rb800xa->GetValue())
            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);
    }

    //m_togRxID->Disconnect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnRxIDUI), NULL, this);
    //m_togTxID->Disconnect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnTxIDUI), NULL, this);
    m_togBtnOnOff->Disconnect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnOnOffUI), NULL, this);
    m_togBtnSplit->Disconnect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnSplitClickUI), NULL, this);
    m_togBtnAnalog->Disconnect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnAnalogClickUI), NULL, this);
    //m_togBtnALC->Disconnect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnALCClickUI), NULL, this);
    //m_btnTogPTT->Disconnect(wxEVT_UPDATE_UI, wxUpdateUIEventHandler(MainFrame::OnTogBtnPTT_UI), NULL, this);

    sox_biquad_finish();

    if (m_RxRunning)
    {
        stopRxStream();
    }
    if (g_sfPlayFile != NULL)
    {
        sf_close(g_sfPlayFile);
        g_sfPlayFile = NULL;
    }
    if (g_sfRecFile != NULL)
    {
        sf_close(g_sfRecFile);
        g_sfRecFile = NULL;
    }
    if (g_sfRecFileFromModulator != NULL)
    {
        sf_close(g_sfRecFileFromModulator);
        g_sfRecFileFromModulator = NULL;
    }
#ifdef _USE_TIMER
    if(m_plotTimer.IsRunning())
    {
        m_plotTimer.Stop();
        Unbind(wxEVT_TIMER, &MainFrame::OnTimer, this);
    }
#endif //_USE_TIMER

#ifdef _USE_ONIDLE
    Disconnect(wxEVT_IDLE, wxIdleEventHandler(MainFrame::OnIdle), NULL, this);
#endif // _USE_ONIDLE

    delete wxConfigBase::Set((wxConfigBase *) NULL);

    if (optionsDlg != NULL) {
        delete optionsDlg;
        optionsDlg = NULL;
    }
}


#ifdef _USE_ONIDLE
void MainFrame::OnIdle(wxIdleEvent &evt) {
}
#endif


#ifdef _USE_TIMER
//----------------------------------------------------------------
// OnTimer()
//
// when the timer fires every DT seconds we update the GUI displays.
// the tabs only the plot that is visible actually gets updated, this
// keeps CPU load reasonable
//----------------------------------------------------------------
void MainFrame::OnTimer(wxTimerEvent &evt)
{

    int r,c;

    if (m_panelWaterfall->checkDT()) {
        m_panelWaterfall->setRxFreq(FDMDV_FCENTRE - g_RxFreqOffsetHz);
        m_panelWaterfall->m_newdata = true;
        m_panelWaterfall->Refresh();
    }

    m_panelSpectrum->setRxFreq(FDMDV_FCENTRE - g_RxFreqOffsetHz);
    m_panelSpectrum->m_newdata = true;
    m_panelSpectrum->Refresh();

    /* update scatter/eye plot ------------------------------------------------------------*/

    if (g_mode != -1 ) {
        if ((g_mode == FREEDV_MODE_800XA) || (g_mode == FREEDV_MODE_2400B) ) {
            
            /* FSK Mode - eye diagram ---------------------------------------------------------*/
        
            /* add samples row by row */

            int i;
            for (i=0; i<g_stats.neyetr; i++) {
                m_panelScatter->add_new_samples_eye(&g_stats.rx_eye[i][0], g_stats.neyesamp);
            }
        }
        else {
            /* PSK Modes - scatter plot -------------------------------------------------------*/
            for (r=0; r<g_stats.nr; r++) {
        
                if ((freedv_get_mode(g_pfreedv) == FREEDV_MODE_1600)
                        || (freedv_get_mode(g_pfreedv) == FREEDV_MODE_700D)
                        || (freedv_get_mode(g_pfreedv) == FREEDV_MODE_2020)) {
                    m_panelScatter->add_new_samples_scatter(&g_stats.rx_symbols[r][0]);
                }
        
                if (/*(freedv_get_mode(g_pfreedv) == FREEDV_MODE_700B) ||*/(freedv_get_mode(g_pfreedv) == FREEDV_MODE_700C)) {
            
                    if (wxGetApp().m_FreeDV700Combine) {
                        m_panelScatter->setNc(g_Nc/2); /* m_FreeDV700Combine may have changed at run time */

                        /* 
                           FreeDV 700C uses diversity, so optionaly combine
                           symbols for scatter plot, as combined symbols are
                           used for demodulation.  Note we need to use a copy
                           of the symbols, as we are not sure when the stats
                           will be updated.
                        */

                        COMP rx_symbols_copy[g_Nc/2];

                        for(c=0; c<g_Nc/2; c++)
                            rx_symbols_copy[c] = fcmult(0.5, cadd(g_stats.rx_symbols[r][c], g_stats.rx_symbols[r][c+g_Nc/2]));
                        m_panelScatter->add_new_samples_scatter(rx_symbols_copy);
                    }
                    else {
                        m_panelScatter->setNc(g_Nc); /* m_FreeDV700Combine may have changed at run time */
                        /*
                          Sometimes useful to plot carriers separately, e.g. to determine if tx carrier power is constant
                          across carriers.
                        */
                        m_panelScatter->add_new_samples_scatter(&g_stats.rx_symbols[r][0]);
                    }
                }
       
            }
        }
    }
    
    m_panelScatter->Refresh();

    // Oscilliscope type speech plots -------------------------------------------------------

    short speechInPlotSamples[WAVEFORM_PLOT_BUF];
    if (codec2_fifo_read(g_plotSpeechInFifo, speechInPlotSamples, WAVEFORM_PLOT_BUF)) {
        memset(speechInPlotSamples, 0, WAVEFORM_PLOT_BUF*sizeof(short));
        //fprintf(stderr, "empty!\n");
    }
    m_panelSpeechIn->add_new_short_samples(0, speechInPlotSamples, WAVEFORM_PLOT_BUF, 32767);
    m_panelSpeechIn->Refresh();

    short speechOutPlotSamples[WAVEFORM_PLOT_BUF];
    if (codec2_fifo_read(g_plotSpeechOutFifo, speechOutPlotSamples, WAVEFORM_PLOT_BUF))
        memset(speechOutPlotSamples, 0, WAVEFORM_PLOT_BUF*sizeof(short));
    m_panelSpeechOut->add_new_short_samples(0, speechOutPlotSamples, WAVEFORM_PLOT_BUF, 32767);
    m_panelSpeechOut->Refresh();

    short demodInPlotSamples[WAVEFORM_PLOT_BUF];
    if (codec2_fifo_read(g_plotDemodInFifo, demodInPlotSamples, WAVEFORM_PLOT_BUF)) {
        memset(demodInPlotSamples, 0, WAVEFORM_PLOT_BUF*sizeof(short));
    }
    m_panelDemodIn->add_new_short_samples(0,demodInPlotSamples, WAVEFORM_PLOT_BUF, 32767);
    m_panelDemodIn->Refresh();

    // Demod states -----------------------------------------------------------------------

    m_panelTimeOffset->add_new_sample(0, (float)g_stats.rx_timing/FDMDV_NOM_SAMPLES_PER_FRAME);
    m_panelTimeOffset->Refresh();

    m_panelFreqOffset->add_new_sample(0, g_stats.foff);
    m_panelFreqOffset->Refresh();

    // SNR text box and gauge ------------------------------------------------------------

    // LP filter g_stats.snr_est some more to stabilise the
    // display. g_stats.snr_est already has some low pass filtering
    // but we need it fairly fast to activate squelch.  So we
    // optionally perform some further filtering for the display
    // version of SNR.  The "Slow" checkbox controls the amount of
    // filtering.  The filtered snr also controls the squelch

    float snr_limited;
    // some APIs pass us invalid values, so lets trap it rather than bombing
    if (!(isnan(g_stats.snr_est) || isinf(g_stats.snr_est))) {
        if (g_mode == -1) {
            // no averaging of SNR for horus telemetry, just report latest and greatest 
            g_snr = g_stats.snr_est;
        }
        else {
            g_snr = m_snrBeta*g_snr + (1.0 - m_snrBeta)*g_stats.snr_est;
        }
    }
    snr_limited = g_snr;
    if (snr_limited < -5.0) snr_limited = -5.0;
    if (snr_limited > 20.0) snr_limited = 20.0;
    char snr[15];
    sprintf(snr, "%d", (int)(g_snr+0.5)); // round to nearest dB

    //fprintf(stderr, "g_mode: %d snr_est: %f m_snrBeta: %f g_snr: %f snr_limited: %f\n", g_mode, g_stats.snr_est,  m_snrBeta, g_snr, snr_limited);

    wxString snr_string(snr);
    m_textSNR->SetLabel(snr_string);
    m_gaugeSNR->SetValue((int)(snr_limited+5));

 
    // Level Gauge -----------------------------------------------------------------------

    float tooHighThresh;
    if (!g_tx && m_RxRunning)
    {
        // receive mode - display From Radio peaks
        // peak from this DT sampling period
        int maxDemodIn = 0;
        for(int i=0; i<WAVEFORM_PLOT_BUF; i++)
            if (maxDemodIn < abs(demodInPlotSamples[i]))
                maxDemodIn = abs(demodInPlotSamples[i]);

        // peak from last second
        if (maxDemodIn > m_maxLevel)
            m_maxLevel = maxDemodIn;

        tooHighThresh = FROM_RADIO_MAX;
    }
    else
    {
        // transmit mode - display From Mic peaks

        // peak from this DT sampling period
        int maxSpeechIn = 0;
        for(int i=0; i<WAVEFORM_PLOT_BUF; i++)
            if (maxSpeechIn < abs(speechInPlotSamples[i]))
                maxSpeechIn = abs(speechInPlotSamples[i]);

        // peak from last second
        if (maxSpeechIn > m_maxLevel)
            m_maxLevel = maxSpeechIn;

       tooHighThresh = FROM_MIC_MAX;
    }

    // Peak Reading meter: updates peaks immediately, then slowly decays
    int maxScaled = (int)(100.0 * ((float)m_maxLevel/32767.0));
    m_gaugeLevel->SetValue(maxScaled);
    //printf("maxScaled: %d\n", maxScaled);
    if (((float)maxScaled/100) > tooHighThresh)
        m_textLevel->SetLabel("Too High");
    else
        m_textLevel->SetLabel("");

    m_maxLevel *= LEVEL_BETA;

    // sync LED (Colours don't work on Windows) ------------------------

    //fprintf(stderr, "g_State: %d  m_rbSync->GetValue(): %d\n", g_State, m_rbSync->GetValue());
    if (g_State) {
        if (g_prev_State == 0) {
            g_resyncs++;
        }
        m_textSync->SetForegroundColour( wxColour( 0, 255, 0 ) ); // green
	m_textSync->SetLabel("Modem");
     }
    else {
        m_textSync->SetForegroundColour( wxColour( 255, 0, 0 ) ); // red
	m_textSync->SetLabel("Modem");
     }
    g_prev_State = g_State;
    if ((g_mode == FREEDV_MODE_700D) || (g_mode == FREEDV_MODE_2020)){
        if (g_interleaverSyncState) {
            m_textInterleaverSync->SetForegroundColour( wxColour( 0, 255, 0 ) ); // green
            m_textInterleaverSync->SetLabel("Intrlvr ("+wxString::Format(wxT("%i"),wxGetApp().m_FreeDV700Interleave)+")");
        } else {
            m_textInterleaverSync->SetForegroundColour( wxColour( 255, 0, 0 ) ); // red
            m_textInterleaverSync->SetLabel("Intrlvr ("+wxString::Format(wxT("%i"),wxGetApp().m_FreeDV700Interleave)+")");
        }
    }
    
    // send Callsign ----------------------------------------------------

    char callsign[MAX_CALLSIGN];
    strncpy(callsign, (const char*) wxGetApp().m_callSign.mb_str(wxConvUTF8), MAX_CALLSIGN-1);

    // buffer 1 txt message to ensure tx data fifo doesn't "run dry"

    if ((unsigned)codec2_fifo_used(g_txDataInFifo) < strlen(callsign)) {
        unsigned int  i;

        //fprintf(g_logfile, "tx callsign: %s.\n", callsign);

        /* optionally append checksum */

        if (wxGetApp().m_enable_checksum) {

            unsigned char checksum = 0;
            char callsign_checksum_cr[MAX_CALLSIGN+1];

            for(i=0; i<strlen(callsign); i++)
                checksum += callsign[i];
            sprintf(callsign_checksum_cr, "%s%2x", callsign, checksum);
            callsign_checksum_cr[strlen(callsign)+2] = 13;
            callsign_checksum_cr[strlen(callsign)+3] = 0;
            strcpy(callsign, callsign_checksum_cr);
        }
        else {
            callsign[strlen(callsign)] = 13;
            callsign[strlen(callsign)+1] = 0;
        }

        //fprintf(g_logfile, "tx callsign: %s.\n", callsign);

        // write chars to tx data fifo

        for(i=0; i<strlen(callsign); i++) {
            short ashort = (short)callsign[i];
            codec2_fifo_write(g_txDataInFifo, &ashort, 1);
        }
    }

    // See if any Callsign info received --------------------------------

    short ashort;
    while (codec2_fifo_read(g_rxDataOutFifo, &ashort, 1) == 0) {

        if ((ashort == 13) || ((m_pcallsign - m_callsign) > MAX_CALLSIGN-1)) {
            // CR completes line
            *m_pcallsign = 0;
            
            /* Checksums can be disabled, e.g. for compatability with
               older vesions.  In that case we print msg but don't do
               any event processing.  If checksums enabled, only print
               out when checksum is good. */

            if (wxGetApp().m_enable_checksum) {
                // lets see if checksum is OK
            
                unsigned char checksum_rx = 0;
                if (strlen(m_callsign) > 2) {
                    for(unsigned int i=0; i<strlen(m_callsign)-2; i++)
                        checksum_rx += m_callsign[i];
                }
                unsigned int checksum_tx;
                int ret = sscanf(&m_callsign[strlen(m_callsign)-2], "%2x", &checksum_tx);
                //fprintf(g_logfile, "rx callsign: %s.\n  checksum tx: %02x checksum rx: %02x\n", m_callsign, checksum_tx, checksum_rx);

                wxString s;
                if (ret && (checksum_tx == checksum_rx)) {
                    m_callsign[strlen(m_callsign)-2] = 0;
                    s.Printf("%s", m_callsign);
                    m_txtCtrlCallSign->SetValue(s);

#ifdef __UDP_EXPERIMENTAL__
                    char s1[MAX_CALLSIGN];
                    sprintf(s1,"rx_txtmsg %s", m_callsign);
                    processTxtEvent(s1);

                    m_checksumGood++;
                    s.Printf("%d", m_checksumGood);
                    m_txtChecksumGood->SetLabel(s);              
#endif
                }
                else {
#ifdef __UDP_EXPERIMENTAL__
                    m_checksumBad++;
                    s.Printf("%d", m_checksumBad);
                    m_txtChecksumBad->SetLabel(s);        
#endif
                }
            }

            //fprintf(g_logfile,"resetting callsign %s %ld\n", m_callsign, m_pcallsign-m_callsign);
            // reset ptr to start of string
            m_pcallsign = m_callsign;
        }
        else {
            //fprintf(g_logfile, "new char %d %c\n", ashort, (char)ashort);
            *m_pcallsign++ = (char)ashort;
        }

        /* If checksums disabled, display txt chars as they arrive */

        if (!wxGetApp().m_enable_checksum) {
            m_txtCtrlCallSign->SetValue(m_callsign);
        }
    }


    // Run time update of EQ filters -----------------------------------

    if (m_newMicInFilter || m_newSpkOutFilter) {
        g_mutexProtectingCallbackData.Lock();
        deleteEQFilters(g_rxUserdata);
        designEQFilters(g_rxUserdata);
        g_mutexProtectingCallbackData.Unlock();
        m_newMicInFilter = m_newSpkOutFilter = false;
    }
    g_rxUserdata->micInEQEnable = wxGetApp().m_MicInEQEnable;
    g_rxUserdata->spkOutEQEnable = wxGetApp().m_SpkOutEQEnable;

    if (g_mode == -1)  {
#ifdef __HORUS__
        // Horus telemetry
        char bits[80], freqoffset[80];
        sprintf(bits, "Bits: %d", horus_get_total_payload_bits(g_horus)); wxString bits_string(bits); m_textBits->SetLabel(bits_string);
        sprintf(freqoffset, "FrqOff: %4.0f", g_stats.foff);
        wxString freqoffset_string(freqoffset); m_textFreqOffset->SetLabel(freqoffset_string);
        /* can't get sensible number for this, perhaps as it's a burst modem */
        //sprintf(clockoffset, "ClkOff: %5d", (int)round(g_stats.clock_offset*1E6));
        //wxString clockoffset_string(clockoffset); m_textClockOffset->SetLabel(clockoffset_string);
#endif
    }
    else {
        // set some run time options (if applicable to this mode)

        freedv_set_clip(g_pfreedv, (int)wxGetApp().m_FreeDV700txClip);   // 700C/700D/2020
        freedv_set_tx_bpf(g_pfreedv, (int)wxGetApp().m_FreeDV700txBPF);  // 700D
        freedv_set_phase_est_bandwidth_mode(g_pfreedv, (int)wxGetApp().m_PhaseEstBW); // 700D/2020
        freedv_set_dpsk(g_pfreedv, (int)wxGetApp().m_PhaseEstDPSK);  // 700D/2020
         
        // Test Frame Bit Error Updates ------------------------------------

        // Toggle test frame mode at run time

        if (!freedv_get_test_frames(g_pfreedv) && wxGetApp().m_testFrames) {

            // reset stats on check box off to on transition

            freedv_set_test_frames(g_pfreedv, 1);
            freedv_set_total_bits(g_pfreedv, 0);
            freedv_set_total_bit_errors(g_pfreedv, 0);
        }
        freedv_set_test_frames(g_pfreedv, wxGetApp().m_testFrames);
        freedv_set_test_frames_diversity(g_pfreedv, wxGetApp().m_FreeDV700Combine);
        g_channel_noise = wxGetApp().m_channel_noise;

        // update stats on main page

        char bits[80], errors[80], ber[80], resyncs[80], clockoffset[80], freqoffset[80], syncmetric[80];
        sprintf(bits, "Bits: %d", freedv_get_total_bits(g_pfreedv)); wxString bits_string(bits); m_textBits->SetLabel(bits_string);
        sprintf(errors, "Errs: %d", freedv_get_total_bit_errors(g_pfreedv)); wxString errors_string(errors); m_textErrors->SetLabel(errors_string);
        float b = (float)freedv_get_total_bit_errors(g_pfreedv)/(1E-6+freedv_get_total_bits(g_pfreedv));
        sprintf(ber, "BER: %4.3f", b); wxString ber_string(ber); m_textBER->SetLabel(ber_string);
        sprintf(resyncs, "Resyncs: %d", g_resyncs); wxString resyncs_string(resyncs); m_textResyncs->SetLabel(resyncs_string);

        sprintf(freqoffset, "FrqOff: %3.1f", g_stats.foff);
        wxString freqoffset_string(freqoffset); m_textFreqOffset->SetLabel(freqoffset_string);
        sprintf(syncmetric, "Sync: %3.2f", g_stats.sync_metric);
        wxString syncmetric_string(syncmetric); m_textSyncMetric->SetLabel(syncmetric_string);

        // Codec 2 700C VQ "auto EQ" equaliser variance
        if ((g_mode == FREEDV_MODE_700C) || (g_mode == FREEDV_MODE_700D)) {
            struct CODEC2 *c2 = freedv_get_codec2(g_pfreedv);
            assert(c2 != NULL);
            float var = codec2_get_var(c2);
            char var_str[80]; sprintf(var_str, "Var: %4.1f", var);
            wxString var_string(var_str); m_textCodec2Var->SetLabel(var_string);
        }
        
        if (g_State) {

            sprintf(clockoffset, "ClkOff: %5d", (int)round(g_stats.clock_offset*1E6));
            wxString clockoffset_string(clockoffset); m_textClockOffset->SetLabel(clockoffset_string);

            // update error pattern plots if supported

            int sz_error_pattern = freedv_get_sz_error_pattern(g_pfreedv);
            //fprintf(stderr, "sz_error_pattern: %d\n", sz_error_pattern);
            if (sz_error_pattern) {
                short error_pattern[sz_error_pattern];

                if (codec2_fifo_read(g_error_pattern_fifo, error_pattern, sz_error_pattern) == 0) {
                    int i,b;

                    /* both modes map IQ to alternate bits, but on same carrier */

                    if (freedv_get_mode(g_pfreedv) == FREEDV_MODE_1600) {
                        /* FreeDV 1600 mapping from error pattern to two bits on each carrier */

                        for(b=0; b<g_Nc*2; b++) {
                            for(i=b; i<sz_error_pattern; i+= 2*g_Nc) {
                                m_panelTestFrameErrors->add_new_sample(b, b + 0.8*error_pattern[i]);
                                g_error_hist[b] += error_pattern[i];
                                g_error_histn[b]++;
                            }
                            //if (b%2)
                            //    printf("g_error_hist[%d]: %d\n", b/2, g_error_hist[b/2]);
                        }

                         /* calculate BERs and send to plot */

                        float ber[2*MODEM_STATS_NC_MAX];
                        for(b=0; b<2*MODEM_STATS_NC_MAX; b++) {
                            ber[b] = 0.0;
                        }
                        for(b=0; b<g_Nc*2; b++) {
                            ber[b+1] = (float)g_error_hist[b]/g_error_histn[b];
                        }
                        assert(g_Nc*2 <= 2*MODEM_STATS_NC_MAX);
                        m_panelTestFrameErrorsHist->add_new_samples(0, ber, 2*MODEM_STATS_NC_MAX);
                    }
       
                    if (/*(freedv_get_mode(g_pfreedv) == FREEDV_MODE_700B) || */(freedv_get_mode(g_pfreedv) == FREEDV_MODE_700C)) {
                        int c;
                        //fprintf(stderr, "after g_error_pattern_fifo read 2\n");
                        
                        /* 
                           FreeDV 700 mapping from error pattern to bit on each carrier, see 
                           data bit to carrier mapping in:

                              codec2-dev/octave/cohpsk_frame_design.ods
 
                           We can plot a histogram of the errors/carrier before or after diversity
                           recombination.  Actually one bar for each IQ bit in carrier order.
                        */

                        int hist_Nc = sz_error_pattern/4;
                        //fprintf(stderr, "hist_Nc: %d\n", hist_Nc);

                        for(i=0; i<sz_error_pattern; i++) {
                            /* maps to IQ bits from each symbol to a "carrier" (actually one line for each IQ bit in carrier order) */
                            c = floor(i/4);
                            /* this will clock in 4 bits/carrier to plot */
                            m_panelTestFrameErrors->add_new_sample(c, c + 0.8*error_pattern[i]);
                            g_error_hist[c] += error_pattern[i];
                            g_error_histn[c]++;
                            //printf("i: %d c: %d\n", i, c);
                        }
                        for(; i<2*MODEM_STATS_NC_MAX*4; i++) {
                            c = floor(i/4);
                            m_panelTestFrameErrors->add_new_sample(c, c);
                            //printf("i: %d c: %d\n", i, c);
                        }

                        /* calculate BERs and send to plot */

                        float ber[2*MODEM_STATS_NC_MAX];
                        for(b=0; b<2*MODEM_STATS_NC_MAX; b++) {
                            ber[b] = 0.0;
                        }
                        for(b=0; b<hist_Nc; b++) {
                            ber[b+1] = (float)g_error_hist[b]/g_error_histn[b];
                        }
                        assert(hist_Nc <= 2*MODEM_STATS_NC_MAX);
                        m_panelTestFrameErrorsHist->add_new_samples(0, ber, 2*MODEM_STATS_NC_MAX);
                    }
 
                    m_panelTestFrameErrors->Refresh();       
                    m_panelTestFrameErrorsHist->Refresh();
                }
            }
        }

        /* FIFO and PortAudio under/overflow debug counters */

        optionsDlg->DisplayFifoPACounters();
    }

    // command from UDP thread that is best processed in main thread to avoid seg faults

    if (m_schedule_restore) {
        if (IsIconized())
            Restore();
        m_schedule_restore = false;
    }

#ifdef __UDP_EXPERIMENTAL__
    // Light Spam Timer LED if at least one timer is running

    int i;
    optionsDlg->SetSpamTimerLight(false);
    for(i=0; i<MAX_EVENT_RULES; i++)
        if (spamTimer[i].IsRunning())
            optionsDlg->SetSpamTimerLight(true);        
#endif

    // Blink file playback status line

    if (g_playFileFromRadio) {
        g_blink += DT;
        //fprintf(g_logfile, "g_blink: %f\n", g_blink);
        if ((g_blink >= 1.0) && (g_blink < 2.0))
            SetStatusText(wxT("Playing into from radio"), 0);
        if (g_blink >= 2.0) {
            SetStatusText(wxT(""), 0);
            g_blink = 0.0;
        }
    }

    if (g_mode != -1) {
        // Voice Keyer state machine

        VoiceKeyerProcessEvent(VK_DT);

        // Detect Sync state machine

        DetectSyncProcessEvent();
    }
}
#endif


//-------------------------------------------------------------------------
// OnCloseFrame()
//-------------------------------------------------------------------------
void MainFrame::OnCloseFrame(wxCloseEvent& event)
{
    //fprintf(stderr, "MainFrame::OnCloseFrame()\n");
    Pa_Terminate();
    Destroy();
}

//-------------------------------------------------------------------------
// OnTop()
//-------------------------------------------------------------------------
void MainFrame::OnTop(wxCommandEvent& event)
{
    int style = GetWindowStyle();

    if (style & wxSTAY_ON_TOP)
    {
        style &= ~wxSTAY_ON_TOP;
    }
    else
    {
        style |= wxSTAY_ON_TOP;
    }
    SetWindowStyle(style);
}

//-------------------------------------------------------------------------
// OnDeleteConfig()
//-------------------------------------------------------------------------
void MainFrame::OnDeleteConfig(wxCommandEvent&)
{
    wxConfigBase *pConfig = wxConfigBase::Get();
    if(pConfig->DeleteAll())
    {
        wxLogMessage(wxT("Config file/registry key successfully deleted.  Please restart FreeDV."));

        delete wxConfigBase::Set(NULL);
        wxConfigBase::DontCreateOnDemand();
    }
    else
    {
        wxLogError(wxT("Deleting config file/registry key failed."));
    }
}

//-------------------------------------------------------------------------
// Paint()
//-------------------------------------------------------------------------
void MainFrame::OnPaint(wxPaintEvent& WXUNUSED(event))
{
    wxPaintDC dc(this);

    if(GetMenuBar()->IsChecked(ID_PAINT_BG))
    {
        dc.Clear();
    }
    dc.SetUserScale(m_zoom, m_zoom);
}

//-------------------------------------------------------------------------
// OnCmdSliderScroll()
//-------------------------------------------------------------------------
void MainFrame::OnCmdSliderScroll(wxScrollEvent& event)
{
    char sqsnr[15];
    g_SquelchLevel = (float)m_sliderSQ->GetValue()/2.0 - 5.0;   
    sprintf(sqsnr, "%4.1f", g_SquelchLevel); // 0.5 dB steps
    wxString sqsnr_string(sqsnr);
    m_textSQ->SetLabel(sqsnr_string);

    event.Skip();
}

//-------------------------------------------------------------------------
// OnCheckSQClick()
//-------------------------------------------------------------------------
void MainFrame::OnCheckSQClick(wxCommandEvent& event)
{
    if(!g_SquelchActive)
    {
        g_SquelchActive = true;
    }
    else
    {
        g_SquelchActive = false;
    }
}

void MainFrame::setsnrBeta(bool snrSlow)
{
    if(snrSlow)
    {
        m_snrBeta = 0.95; // make this closer to 1.0 to smooth SNR est further
    }
    else
    {
        m_snrBeta = 0.0; // no smoothing of SNR estimate from demodulator
    }
}

//-------------------------------------------------------------------------
// OnCheckSQClick()
//-------------------------------------------------------------------------
void MainFrame::OnCheckSNRClick(wxCommandEvent& event)
{
    wxGetApp().m_snrSlow = m_ckboxSNR->GetValue();
    setsnrBeta(wxGetApp().m_snrSlow);
    //printf("m_snrSlow: %d\n", (int)wxGetApp().m_snrSlow);
}

// check for space bar press (only when running)

int MainApp::FilterEvent(wxEvent& event)
{
    if ((event.GetEventType() == wxEVT_KEY_DOWN) && 
        (((wxKeyEvent&)event).GetKeyCode() == WXK_SPACE))
        {
            // only use space to toggle PTT if we are running and no modal dialogs (like options) up
            //fprintf(stderr,"frame->m_RxRunning: %d g_modal: %d\n",
            //        frame->m_RxRunning, g_modal);
            if (frame->m_RxRunning && !g_modal) {

                // space bar controls rx/rx if keyer not running
                if (frame->vk_state == VK_IDLE) {
                    if (frame->m_btnTogPTT->GetValue())
                        frame->m_btnTogPTT->SetValue(false);
                    else
                        frame->m_btnTogPTT->SetValue(true);

                    frame->togglePTT();
                }
                else // spavce bar stops keyer
                    frame->VoiceKeyerProcessEvent(VK_SPACE_BAR);
                    
                return true; // absorb space so we don't toggle control with focus (e.g. Start)

            }
        }

    return -1;
}

//-------------------------------------------------------------------------
// OnTogBtnPTT ()
//-------------------------------------------------------------------------
void MainFrame::OnTogBtnPTT (wxCommandEvent& event)
{
    togglePTT();
    event.Skip();
}

void MainFrame::togglePTT(void) {

    // Change tabbed page in centre panel depending on PTT state

    if (g_tx)
    {
        // tx-> rx transition, swap to the page we were on for last rx
        m_auiNbookCtrl->ChangeSelection(wxGetApp().m_rxNbookCtrl);

        // enable sync text

        m_textSync->Enable();
        m_textInterleaverSync->Enable();
    }
    else
    {
        // rx-> tx transition, swap to Mic In page to monitor speech
        wxGetApp().m_rxNbookCtrl = m_auiNbookCtrl->GetSelection();
        m_auiNbookCtrl->ChangeSelection(m_auiNbookCtrl->GetPageIndex((wxWindow *)m_panelSpeechIn));

        // disable sync text

        m_textSync->Disable();
        m_textInterleaverSync->Disable();
        
#ifdef __UDP_EXPERIMENTAL__
        char e[80]; sprintf(e,"ptt"); processTxtEvent(e);
#endif
    }

    g_tx = m_btnTogPTT->GetValue();

    // Hamlib PTT

    if (wxGetApp().m_boolHamlibUseForPTT) {        
        Hamlib *hamlib = wxGetApp().m_hamlib; 
        wxString hamlibError;
        if (wxGetApp().m_boolHamlibUseForPTT && hamlib != NULL) {
            if (hamlib->ptt(g_tx, hamlibError) == false) {
                wxMessageBox(wxString("Hamlib PTT Error: ") + hamlibError, wxT("Error"), wxOK | wxICON_ERROR, this);
            }
        }
    }

    // Serial PTT

    if (wxGetApp().m_boolUseSerialPTT && (wxGetApp().m_serialport->isopen())) {
        wxGetApp().m_serialport->ptt(g_tx);
    }

    // reset level gauge

    m_maxLevel = 0;
    m_textLevel->SetLabel(wxT(""));
    m_gaugeLevel->SetValue(0);
           
}

/*
   Voice Keyer:

   + space bar turns keyer off
   + 5 secs of valid sync turns it off

   [X] complete state machine and builds OK
   [ ] file select dialog
   [ ] test all states
   [ ] restore size
*/

void MainFrame::OnTogBtnVoiceKeyerClick (wxCommandEvent& event)
{
    if (vk_state == VK_IDLE)
        VoiceKeyerProcessEvent(VK_START);
    else
        VoiceKeyerProcessEvent(VK_SPACE_BAR);
        
    event.Skip();
}


int MainFrame::VoiceKeyerStartTx(void)
{
    int next_state;

    // start playing wave file or die trying

    SF_INFO sfInfo;
    sfInfo.format = 0;

    g_sfPlayFile = sf_open(wxGetApp().m_txtVoiceKeyerWaveFile.mb_str(), SFM_READ, &sfInfo);
    if(g_sfPlayFile == NULL) {
        wxString strErr = sf_strerror(NULL);
        wxMessageBox(strErr, wxT("Couldn't open:") + wxGetApp().m_txtVoiceKeyerWaveFile, wxOK);
        m_togBtnVoiceKeyer->SetValue(false);
        next_state = VK_IDLE;
    }
    else {
        SetStatusText(wxT("Voice Keyer: Playing File") + wxGetApp().m_txtVoiceKeyerWaveFile + wxT(" to Mic Input") , 0);
        g_loopPlayFileToMicIn = false;
        g_playFileToMicIn = true;

        m_btnTogPTT->SetValue(true); togglePTT();
        next_state = VK_TX;
    }

    return next_state;
}


void MainFrame::VoiceKeyerProcessEvent(int vk_event) {
    int next_state = vk_state;

    switch(vk_state) {

    case VK_IDLE:
        if (vk_event == VK_START) {
            // sample these puppies at start just in case they are changed while VK running
            vk_rx_pause = wxGetApp().m_intVoiceKeyerRxPause;
            vk_repeats = wxGetApp().m_intVoiceKeyerRepeats;
            fprintf(stderr, "vk_rx_pause: %d vk_repeats: %d\n", vk_rx_pause, vk_repeats);

            vk_repeat_counter = 0;
            next_state = VoiceKeyerStartTx();
        }
        break;
        
     case VK_TX:

        // In this state we are transmitting and playing a wave file
        // to Mic In

        if (vk_event == VK_SPACE_BAR) {
            m_btnTogPTT->SetValue(false); togglePTT();
            StopPlayFileToMicIn();
            m_togBtnVoiceKeyer->SetValue(false);
            next_state = VK_IDLE;
        }

        if (vk_event == VK_PLAY_FINISHED) {
            m_btnTogPTT->SetValue(false); togglePTT();
            vk_repeat_counter++;
            if (vk_repeat_counter > vk_repeats) {
                m_togBtnVoiceKeyer->SetValue(false);
                next_state = VK_IDLE;
            }
            else {
                vk_rx_time = 0.0;
                next_state = VK_RX;
            }
        }

        break;

     case VK_RX:

        // in this state we are receiving and waiting for
        // delay timer or valid sync

        if (vk_event == VK_DT) {
            if (freedv_get_sync(g_pfreedv) == 1) {
                // if we detect sync simulate a smooth transition to SYNC_WAIT State - TODO: review
                if (vk_rx_time >= DT) {
                    vk_rx_time -= DT;
                } else {
                    next_state = VK_SYNC_WAIT;
                }
            } else {
                vk_rx_time += DT;
                if (vk_rx_time >= vk_rx_pause) {
                    next_state = VoiceKeyerStartTx();
                }
            }
        }

        if (vk_event == VK_SPACE_BAR) {
            m_togBtnVoiceKeyer->SetValue(false);
            next_state = VK_IDLE;
        }

        break;

     case VK_SYNC_WAIT:

        // In this state we wait for valid sync to last
        // VK_SYNC_WAIT_TIME seconds

        if (vk_event == VK_SPACE_BAR) {
            m_togBtnVoiceKeyer->SetValue(false);
            next_state = VK_IDLE;
        }

        if (vk_event == VK_DT) {
            if (freedv_get_sync(g_pfreedv) == 0) {
                // if we lose sync simulate a smooth transition to return in RX State - TODO: review
                if (vk_rx_time >= DT) {
                    vk_rx_time -= DT;
                } else {
                    next_state = VK_RX;
                }
            } else {
                vk_rx_time += DT;
            }

            // drop out of voice keyer if we get a few seconds of valid sync

            if (vk_rx_time >= VK_SYNC_WAIT_TIME) {
                m_togBtnVoiceKeyer->SetValue(false);
                next_state = VK_IDLE;
            }
        }
        break;

    default:
        // catch anything we missed

        m_btnTogPTT->SetValue(false); togglePTT();
        m_togBtnVoiceKeyer->SetValue(false);
        next_state = VK_IDLE;
    }

    //if ((vk_event != VK_DT) || (vk_state != next_state))
    //    fprintf(stderr, "VoiceKeyerProcessEvent: vk_state: %d vk_event: %d next_state: %d  vk_repeat_counter: %d\n", vk_state, vk_event, next_state, vk_repeat_counter);
    vk_state = next_state;
}


// State machine to detect sync and send a UDP message

void MainFrame::DetectSyncProcessEvent(void) {
    int next_state = ds_state;

    switch(ds_state) {

    case DS_IDLE:
        if (freedv_get_sync(g_pfreedv) == 1) {
            next_state = DS_SYNC_WAIT;
            ds_rx_time = 0;
        }
        break;

    case DS_SYNC_WAIT:

        // In this state we wait fo a few seconds of valid sync, then
        // send UDP message

        if (freedv_get_sync(g_pfreedv) == 0) {
            next_state = DS_IDLE;
        } else {
            ds_rx_time += DT;
        }

        if (ds_rx_time >= DS_SYNC_WAIT_TIME) {
            char s[100]; sprintf(s, "rx sync");
            if (wxGetApp().m_udp_enable) {
                UDPSend(wxGetApp().m_udp_port, s, strlen(s)+1);
            }
            ds_rx_time = 0;
            next_state = DS_UNSYNC_WAIT;
        }
        break;

    case DS_UNSYNC_WAIT:

        // In this state we wait for sync to end

        if (freedv_get_sync(g_pfreedv) == 0) {
            ds_rx_time += DT;
            if (ds_rx_time >= DS_SYNC_WAIT_TIME) {
                next_state = DS_IDLE;
            }
        } else {
            ds_rx_time = 0;
        }
        break;
       
    default:
        // catch anything we missed

        next_state = DS_IDLE;
    }

    ds_state = next_state;
}


//-------------------------------------------------------------------------
// OnTogBtnRxID()
//-------------------------------------------------------------------------
void MainFrame::OnTogBtnRxID(wxCommandEvent& event)
{
    // empty any junk in rx data FIFO
    short junk;
    while(codec2_fifo_read(g_rxDataOutFifo,&junk,1) == 0);
    event.Skip();
}

//-------------------------------------------------------------------------
// OnTogBtnTxID()
//-------------------------------------------------------------------------
void MainFrame::OnTogBtnTxID(wxCommandEvent& event)
{
    event.Skip();
}

void MainFrame::OnTogBtnSplitClick(wxCommandEvent& event) {
    if (g_split)
        g_split = 0;
    else
        g_split = 1;
    event.Skip();
}

//-------------------------------------------------------------------------
// OnTogBtnAnalogClick()
//-------------------------------------------------------------------------
void MainFrame::OnTogBtnAnalogClick (wxCommandEvent& event)
{
    if (g_analog == 0) {
        g_analog = 1;
        m_panelSpectrum->setFreqScale(MODEM_STATS_NSPEC*((float)MAX_F_HZ/(FS/2)));
        m_panelWaterfall->setFs(FS);
    }
    else {
        g_analog = 0;
        m_panelSpectrum->setFreqScale(MODEM_STATS_NSPEC*((float)MAX_F_HZ/(freedv_get_modem_sample_rate(g_pfreedv)/2)));
        m_panelWaterfall->setFs(freedv_get_modem_sample_rate(g_pfreedv));
    }

    g_State = g_prev_State = g_interleaverSyncState = 0;
    g_stats.snr_est = 0;

    event.Skip();
}

void MainFrame::OnCallSignReset(wxCommandEvent& event)
{
    m_pcallsign = m_callsign;
    memset(m_callsign, 0, MAX_CALLSIGN);
    wxString s;
    s.Printf("%s", m_callsign);
    m_txtCtrlCallSign->SetValue(s);
#ifdef __UDP__EXPERIMENTAL__
    m_checksumGood = m_checksumBad = 0;
    m_txtChecksumGood->SetLabel(_("0"));
    m_txtChecksumBad->SetLabel(_("0"));
#endif
}


// Force manual resync, just in case demod gets stuck on false sync

void MainFrame::OnReSync(wxCommandEvent& event)
{
    if (m_RxRunning)  {
        fprintf(stderr,"OnReSync\n");
        if (g_mode != -1) {
            freedv_set_sync(g_pfreedv, FREEDV_SYNC_UNSYNC);
        }
    }  
}


void MainFrame::OnBerReset(wxCommandEvent& event)
{
    if (m_RxRunning)  {
        if (g_mode == -1) {
#ifdef __HORUS__
            horus_set_total_payload_bits(g_horus, 0);
#endif
        } else {
            freedv_set_total_bits(g_pfreedv, 0);
            freedv_set_total_bit_errors(g_pfreedv, 0);
            g_resyncs = 0;
            int i;
            for(i=0; i<2*g_Nc; i++) {
                g_error_hist[i] = 0;
                g_error_histn[i] = 0;
            }
            // resets variance stats every time it is called
            freedv_set_eq(g_pfreedv, wxGetApp().m_700C_EQ);
        }
    }
}

#ifdef ALC
//-------------------------------------------------------------------------
// OnTogBtnALCClick()
//-------------------------------------------------------------------------
void MainFrame::OnTogBtnALCClick(wxCommandEvent& event)
{
    wxMessageBox(wxT("Got Click!"), wxT("OnTogBtnALCClick"), wxOK);

    event.Skip();
}
#endif

// extra panel added to file open dialog to add loop checkbox
MyExtraPlayFilePanel::MyExtraPlayFilePanel(wxWindow *parent): wxPanel(parent)
{
    m_cb = new wxCheckBox(this, -1, wxT("Loop"));
    m_cb->SetToolTip(_("When checked file will repeat forever"));
    m_cb->SetValue(g_loopPlayFileToMicIn);

    // bug: I can't this to align right.....
    wxBoxSizer *sizerTop = new wxBoxSizer(wxHORIZONTAL);
    sizerTop->Add(m_cb, 0, 0, 0);
    SetSizerAndFit(sizerTop);
}

static wxWindow* createMyExtraPlayFilePanel(wxWindow *parent)
{
    return new MyExtraPlayFilePanel(parent);
}

void MainFrame::StopPlayFileToMicIn(void)
{
    g_mutexProtectingCallbackData.Lock();
    g_playFileToMicIn = false;
    sf_close(g_sfPlayFile);
    SetStatusText(wxT(""));
    g_mutexProtectingCallbackData.Unlock();
}

//-------------------------------------------------------------------------
// OnPlayFileToMicIn()
//-------------------------------------------------------------------------
void MainFrame::OnPlayFileToMicIn(wxCommandEvent& event)
{
    wxUnusedVar(event);

    if(g_playFileToMicIn) {
        StopPlayFileToMicIn();
        VoiceKeyerProcessEvent(VK_PLAY_FINISHED);
    }
    else
    {
        wxString    soundFile;
        SF_INFO     sfInfo;

        wxFileDialog openFileDialog(
                                    this,
                                    wxT("Play File to Mic In"),
                                    wxGetApp().m_playFileToMicInPath,
                                    wxEmptyString,
                                    wxT("WAV and RAW files (*.wav;*.raw)|*.wav;*.raw|")
                                    wxT("All files (*.*)|*.*"),
                                    wxFD_OPEN | wxFD_FILE_MUST_EXIST
                                    );

        // add the loop check box
        openFileDialog.SetExtraControlCreator(&createMyExtraPlayFilePanel);

        if(openFileDialog.ShowModal() == wxID_CANCEL)
        {
            return;     // the user changed their mind...
        }

        wxString fileName, extension;
        soundFile = openFileDialog.GetPath();
        wxFileName::SplitPath(soundFile, &wxGetApp().m_playFileToMicInPath, &fileName, &extension);
        //wxLogDebug("m_playFileToMicInPath: %s", wxGetApp().m_playFileToMicInPath);
        sfInfo.format = 0;

        if(!extension.IsEmpty())
        {
            extension.LowerCase();
            if(extension == wxT("raw"))
            {
                sfInfo.format     = SF_FORMAT_RAW | SF_FORMAT_PCM_16;
                sfInfo.channels   = 1;
                sfInfo.samplerate = freedv_get_speech_sample_rate(g_pfreedv);
            }
        }
        g_sfPlayFile = sf_open(soundFile.c_str(), SFM_READ, &sfInfo);
        if(g_sfPlayFile == NULL)
        {
            wxString strErr = sf_strerror(NULL);
            wxMessageBox(strErr, wxT("Couldn't open sound file"), wxOK);
            return;
        }

        wxWindow * const ctrl = openFileDialog.GetExtraControl();

        // Huh?! I just copied wxWidgets-2.9.4/samples/dialogs ....
        g_loopPlayFileToMicIn = static_cast<MyExtraPlayFilePanel*>(ctrl)->getLoopPlayFileToMicIn();

        SetStatusText(wxT("Playing File: ") + fileName + wxT(" to Mic Input") , 0);
        g_playFileToMicIn = true;
    }
}

//-------------------------------------------------------------------------
// OnPlayFileFromRadio()
// This puppy "plays" a recorded file into the demodulator input, allowing us
// to replay off air signals.
//-------------------------------------------------------------------------
void MainFrame::OnPlayFileFromRadio(wxCommandEvent& event)
{
    wxUnusedVar(event);

    printf("OnPlayFileFromRadio:: %d\n", (int)g_playFileFromRadio);
    if (g_playFileFromRadio)
    {
        fprintf(stderr, "OnPlayFileFromRadio:: Stop\n");
        g_mutexProtectingCallbackData.Lock();
        g_playFileFromRadio = false;
        sf_close(g_sfPlayFileFromRadio);
        SetStatusText(wxT(""),0);
        SetStatusText(wxT(""),1);
        g_mutexProtectingCallbackData.Unlock();
    }
    else
    {
        wxString    soundFile;
        SF_INFO     sfInfo;

        wxFileDialog openFileDialog(
                                    this,
                                    wxT("Play File - From Radio"),
                                    wxGetApp().m_playFileFromRadioPath,
                                    wxEmptyString,
                                    wxT("WAV and RAW files (*.wav;*.raw)|*.wav;*.raw|")
                                    wxT("All files (*.*)|*.*"),
                                    wxFD_OPEN | wxFD_FILE_MUST_EXIST
                                    );

        // add the loop check box
        openFileDialog.SetExtraControlCreator(&createMyExtraPlayFilePanel);

        if(openFileDialog.ShowModal() == wxID_CANCEL)
        {
            return;     // the user changed their mind...
        }

        wxString fileName, extension;
        soundFile = openFileDialog.GetPath();
        wxFileName::SplitPath(soundFile, &wxGetApp().m_playFileFromRadioPath, &fileName, &extension);
        //wxLogDebug("m_playFileToFromRadioPath: %s", wxGetApp().m_playFileFromRadioPath);
        sfInfo.format = 0;

        if(!extension.IsEmpty())
        {
            extension.LowerCase();
            if(extension == wxT("raw"))
            {
                sfInfo.format     = SF_FORMAT_RAW | SF_FORMAT_PCM_16;
                sfInfo.channels   = 1;
                if (g_mode == -1) {
#ifdef __HORUS__
                     sfInfo.samplerate = horus_get_Fs(g_horus);
#endif
                }
                else {
                    sfInfo.samplerate = freedv_get_modem_sample_rate(g_pfreedv);
                }
            }
        }
        g_sfPlayFileFromRadio = sf_open(soundFile.c_str(), SFM_READ, &sfInfo);
        g_sfFs = sfInfo.samplerate;
        if(g_sfPlayFileFromRadio == NULL)
        {
            wxString strErr = sf_strerror(NULL);
            wxMessageBox(strErr, wxT("Couldn't open sound file"), wxOK);
            return;
        }

        wxWindow * const ctrl = openFileDialog.GetExtraControl();

        // Huh?! I just copied wxWidgets-2.9.4/samples/dialogs ....
        g_loopPlayFileFromRadio = static_cast<MyExtraPlayFilePanel*>(ctrl)->getLoopPlayFileToMicIn();

        SetStatusText(wxT("Playing into from radio"), 0);
        if(extension == wxT("raw")) {
            wxString stringnumber = wxString::Format(wxT("%d"), (int)sfInfo.samplerate);
            SetStatusText(wxT("raw file assuming Fs=") + stringnumber, 1);
        }
        fprintf(stderr, "OnPlayFileFromRadio:: Playing File Fs = %d\n", (int)sfInfo.samplerate);
        g_playFileFromRadio = true;
        g_blink = 0.0;
    }
}

// extra panel added to file save dialog to set number of seconds to record for

MyExtraRecFilePanel::MyExtraRecFilePanel(wxWindow *parent): wxPanel(parent)
{
    wxBoxSizer *sizerTop = new wxBoxSizer(wxHORIZONTAL);

    wxStaticText* staticText = new wxStaticText(this, wxID_ANY, _("Seconds:"), wxDefaultPosition, wxDefaultSize, 0);
    sizerTop->Add(staticText, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);
    m_secondsToRecord = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0);
    m_secondsToRecord->SetToolTip(_("Number of seconds to record for"));
    m_secondsToRecord->SetValue(wxString::Format(wxT("%i"), wxGetApp().m_recFileFromRadioSecs));
    sizerTop->Add(m_secondsToRecord, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);
    SetSizerAndFit(sizerTop);
}

static wxWindow* createMyExtraRecFilePanel(wxWindow *parent)
{
    return new MyExtraRecFilePanel(parent);
}

//-------------------------------------------------------------------------
// OnRecFileFromRadio()
//-------------------------------------------------------------------------
void MainFrame::OnRecFileFromRadio(wxCommandEvent& event)
{
    wxUnusedVar(event);

    if (g_recFileFromRadio) {
        fprintf(stderr, "Stopping Record....\n");
        g_mutexProtectingCallbackData.Lock();
        g_recFileFromRadio = false;
        sf_close(g_sfRecFile);
        SetStatusText(wxT(""));
        g_mutexProtectingCallbackData.Unlock();
    }
    else {

        wxString    soundFile;
        SF_INFO     sfInfo;

        wxFileDialog openFileDialog(
                                    this,
                                    wxT("Record File From Radio"),
                                    wxGetApp().m_recFileFromRadioPath,
                                    wxEmptyString,
                                    wxT("WAV and RAW files (*.wav;*.raw)|*.wav;*.raw|")
                                    wxT("All files (*.*)|*.*"),
                                    wxFD_SAVE
                                    );

        // add the loop check box
        openFileDialog.SetExtraControlCreator(&createMyExtraRecFilePanel);

        if(openFileDialog.ShowModal() == wxID_CANCEL)
        {
            return;     // the user changed their mind...
        }

        wxString fileName, extension;
        soundFile = openFileDialog.GetPath();
        wxFileName::SplitPath(soundFile, &wxGetApp().m_recFileFromRadioPath, &fileName, &extension);
        wxLogDebug("m_recFileFromRadioPath: %s", wxGetApp().m_recFileFromRadioPath);
        wxLogDebug("soundFile: %s", soundFile);
        sfInfo.format = 0;

        int sample_rate;
        if (g_mode == -1) {
#ifdef __HORUS__
            sample_rate = horus_get_Fs(g_horus);
#endif
    }
        else {
            sample_rate = freedv_get_modem_sample_rate(g_pfreedv);
        }
        
        if(!extension.IsEmpty())
        {
            extension.LowerCase();
            if(extension == wxT("raw"))
            {
                sfInfo.format     = SF_FORMAT_RAW | SF_FORMAT_PCM_16;
                sfInfo.channels   = 1;
                sfInfo.samplerate = sample_rate;
            }
            else if(extension == wxT("wav"))
            {
                sfInfo.format     = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
                sfInfo.channels   = 1;
                sfInfo.samplerate = sample_rate;
            } else {
                wxMessageBox(wxT("Invalid file format"), wxT("Record File From Radio"), wxOK);
                return;
            }
        }
        else {
            wxMessageBox(wxT("Invalid file format"), wxT("Record File From Radio"), wxOK);
            return;
        }

        // Bug: on Win32 I cant read m_recFileFromRadioSecs, so have hard coded it
#ifdef __WIN32__
        long secs = wxGetApp().m_recFileFromRadioSecs;
        g_recFromRadioSamples = FS*(unsigned int)secs;
#else
        // work out number of samples to record

        wxWindow * const ctrl = openFileDialog.GetExtraControl();
        wxString secsString = static_cast<MyExtraRecFilePanel*>(ctrl)->getSecondsToRecord();
        wxLogDebug("test: %s secsString: %s\n", wxT("testing 123"), secsString);

        long secs;
        if (secsString.ToLong(&secs)) {
            wxGetApp().m_recFileFromRadioSecs = (unsigned int)secs;
            //printf(" secondsToRecord: %d\n",  (unsigned int)secs);
            g_recFromRadioSamples = FS*(unsigned int)secs;
            //printf("g_recFromRadioSamples: %d\n", g_recFromRadioSamples);
        }
        else {
            wxMessageBox(wxT("Invalid number of Seconds"), wxT("Record File From Radio"), wxOK);
            return;
        }
#endif

        g_sfRecFile = sf_open(soundFile.c_str(), SFM_WRITE, &sfInfo);
        if(g_sfRecFile == NULL)
        {
            wxString strErr = sf_strerror(NULL);
            wxMessageBox(strErr, wxT("Couldn't open sound file"), wxOK);
            return;
        }

        SetStatusText(wxT("Recording File: ") + fileName + wxT(" From Radio") , 0);
        g_recFileFromRadio = true;
    }

}

//-------------------------------------------------------------------------
// OnRecFileFromModulator()
//-------------------------------------------------------------------------
void MainFrame::OnRecFileFromModulator(wxCommandEvent& event)
{
    wxUnusedVar(event);

    if (g_recFileFromModulator) {
        g_mutexProtectingCallbackData.Lock();
        g_recFileFromModulator = false;
        sf_close(g_sfRecFileFromModulator);
        SetStatusText(wxT(""));
        g_mutexProtectingCallbackData.Unlock();
        wxMessageBox(wxT("Recording modulator output to file complete")
                     , wxT("Recording Modulation Output"), wxOK);
    }
    else {

        wxString    soundFile;
        SF_INFO     sfInfo;

        if (g_pfreedv == NULL) {
            wxMessageBox(wxT("You need to press the Control - Start button before you can configure recording")
                         , wxT("Recording Modulation Output"), wxOK);
            return;
        }

         wxFileDialog openFileDialog(
                                    this,
                                    wxT("Record File From Modulator"),
                                    wxGetApp().m_recFileFromModulatorPath,
                                    wxEmptyString,
                                    wxT("WAV and RAW files (*.wav;*.raw)|*.wav;*.raw|")
                                    wxT("All files (*.*)|*.*"),
                                    wxFD_SAVE
                                    );

        // add the loop check box
        openFileDialog.SetExtraControlCreator(&createMyExtraRecFilePanel);

        if(openFileDialog.ShowModal() == wxID_CANCEL)
        {
            return;     // the user changed their mind...
        }

        wxString fileName, extension;
        soundFile = openFileDialog.GetPath();
        wxFileName::SplitPath(soundFile, &wxGetApp().m_recFileFromModulatorPath, &fileName, &extension);
        wxLogDebug("m_recFileFromModulatorPath: %s", wxGetApp().m_recFileFromModulatorPath);
        wxLogDebug("soundFile: %s", soundFile);
        sfInfo.format = 0;

        int sample_rate;
        if (g_mode == -1) {
#ifdef __HORUS__
            sample_rate = horus_get_Fs(g_horus);
#endif
        }
        else {
            sample_rate = freedv_get_modem_sample_rate(g_pfreedv);
        }

        if(!extension.IsEmpty())
        {
            extension.LowerCase();
            if(extension == wxT("raw"))
            {
                sfInfo.format     = SF_FORMAT_RAW | SF_FORMAT_PCM_16;
                sfInfo.channels   = 1;
                sfInfo.samplerate = sample_rate;
            }
            else if(extension == wxT("wav"))
            {
                sfInfo.format     = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
                sfInfo.channels   = 1;
                sfInfo.samplerate = sample_rate;
            } else {
                wxMessageBox(wxT("Invalid file format"), wxT("Record File From Radio"), wxOK);
                return;
            }
        }
        else {
            wxMessageBox(wxT("Invalid file format"), wxT("Record File From Radio"), wxOK);
            return;
        }

        // Bug: on Win32 I cant read m_recFileFromModemSecs, so have hard coded it
#ifdef __WIN32__
        long secs = wxGetApp().m_recFileFromModulatorSecs;
        g_recFromModulatorSamples = sample_rate * (unsigned int)secs;
#else
        // work out number of samples to record

        wxWindow * const ctrl = openFileDialog.GetExtraControl();
        wxString secsString = static_cast<MyExtraRecFilePanel*>(ctrl)->getSecondsToRecord();
        wxLogDebug("test: %s secsString: %s\n", wxT("testing 123"), secsString);

        long secs;
        if (secsString.ToLong(&secs)) {
            wxGetApp().m_recFileFromModulatorSecs = (unsigned int)secs;
            //printf(" secondsToRecord: %d\n",  (unsigned int)secs);
            g_recFromModulatorSamples = sample_rate*(unsigned int)secs;
            //printf("g_recFromRadioSamples: %d\n", g_recFromRadioSamples);
        }
        else {
            wxMessageBox(wxT("Invalid number of Seconds"), wxT("Record File From Modulator"), wxOK);
            return;
        }
#endif

        g_sfRecFileFromModulator = sf_open(soundFile.c_str(), SFM_WRITE, &sfInfo);
        if(g_sfRecFileFromModulator == NULL)
        {
            wxString strErr = sf_strerror(NULL);
            wxMessageBox(strErr, wxT("Couldn't open sound file"), wxOK);
            return;
        }

        SetStatusText(wxT("Recording File: ") + fileName + wxT(" From Radio") , 0);
        g_recFileFromModulator = true;
    }

}

//-------------------------------------------------------------------------
// OnExit()
//-------------------------------------------------------------------------
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_mode_detection();
    }

    //fprintf(stderr, "MainFrame::OnExit\n");
    wxUnusedVar(event);
#ifdef _USE_TIMER
    m_plotTimer.Stop();
#endif // _USE_TIMER
    if(g_sfPlayFile != NULL)
    {
        sf_close(g_sfPlayFile);
        g_sfPlayFile = NULL;
    }
    if(g_sfRecFile != NULL)
    {
        sf_close(g_sfRecFile);
        g_sfRecFile = NULL;
    }
    if(m_RxRunning)
    {
        stopRxStream();
    }
    m_togBtnSplit->Disable();
    //m_togRxID->Disable();
    //m_togTxID->Disable();
    m_togBtnAnalog->Disable();
    //m_togBtnALC->Disable();
    //m_btnTogPTT->Disable();

    Pa_Terminate();
    Destroy();
}

//-------------------------------------------------------------------------
// OnExitClick()
//-------------------------------------------------------------------------
void MainFrame::OnExitClick(wxCommandEvent& event)
{
    OnExit(event);
}

//-------------------------------------------------------------------------
// OnToolsAudio()
//-------------------------------------------------------------------------
void MainFrame::OnToolsAudio(wxCommandEvent& event)
{
    wxUnusedVar(event);
    int rv = 0;
    AudioOptsDialog *dlg = new AudioOptsDialog(NULL);
    rv = dlg->ShowModal();
    if(rv == wxID_OK)
    {
        dlg->ExchangeData(EXCHANGE_DATA_OUT);
    }
    delete dlg;
}

//-------------------------------------------------------------------------
// OnToolsAudioUI()
//-------------------------------------------------------------------------
void MainFrame::OnToolsAudioUI(wxUpdateUIEvent& event)
{
    event.Enable(!m_RxRunning);
}

//-------------------------------------------------------------------------
// OnToolsFilter()
//-------------------------------------------------------------------------
void MainFrame::OnToolsFilter(wxCommandEvent& event)
{
    wxUnusedVar(event);
    FilterDlg *dlg = new FilterDlg(NULL, m_RxRunning, &m_newMicInFilter, &m_newSpkOutFilter);
    dlg->ShowModal();
    delete dlg;
}

//-------------------------------------------------------------------------
// OnToolsOptions()
//-------------------------------------------------------------------------
void MainFrame::OnToolsOptions(wxCommandEvent& event)
{
    wxUnusedVar(event);
    g_modal = true;
    //fprintf(stderr,"g_modal: %d\n", g_modal);
    optionsDlg->Show();
}

//-------------------------------------------------------------------------
// OnToolsOptionsUI()
//-------------------------------------------------------------------------
void MainFrame::OnToolsOptionsUI(wxUpdateUIEvent& event)
{
}

//-------------------------------------------------------------------------
// OnToolsComCfg()
//-------------------------------------------------------------------------
void MainFrame::OnToolsComCfg(wxCommandEvent& event)
{
    wxUnusedVar(event);

    ComPortsDlg *dlg = new ComPortsDlg(NULL);

    dlg->ShowModal();

    delete dlg;
}

//-------------------------------------------------------------------------
// OnToolsComCfgUI()
//-------------------------------------------------------------------------
void MainFrame::OnToolsComCfgUI(wxUpdateUIEvent& event)
{
    event.Enable(!m_RxRunning);
}

//-------------------------------------------------------------------------
// OnToolsPlugInCfg()
//-------------------------------------------------------------------------
void MainFrame::OnToolsPlugInCfg(wxCommandEvent& event)
{
    wxUnusedVar(event);
    PlugInDlg *dlg = new PlugInDlg(wxGetApp().m_plugInName, wxGetApp().m_numPlugInParam, wxGetApp().m_plugInParamName);
    dlg->ShowModal();
    delete dlg;
}
               
void MainFrame::OnToolsPlugInCfgUI(wxUpdateUIEvent& event)
{
    event.Enable(!m_RxRunning && wxGetApp().m_plugIn);
}


//-------------------------------------------------------------------------
// OnHelpCheckUpdates()
//-------------------------------------------------------------------------
void MainFrame::OnHelpCheckUpdates(wxCommandEvent& event)
{
    wxMessageBox("Got Click!", "OnHelpCheckUpdates", wxOK);
    event.Skip();
}

//-------------------------------------------------------------------------
// OnHelpCheckUpdatesUI()
//-------------------------------------------------------------------------
void MainFrame::OnHelpCheckUpdatesUI(wxUpdateUIEvent& event)
{
    event.Enable(false);
}

//-------------------------------------------------------------------------
//OnHelpAbout()
//-------------------------------------------------------------------------
void MainFrame::OnHelpAbout(wxCommandEvent& event)
{
    wxUnusedVar(event);
    wxString msg;
    msg.Printf( wxT("FreeDV GUI %s\n\n")
                wxT("For Help and Support visit: http://freedv.org\n\n")

                wxT("GNU Public License V2.1\n\n")
                wxT("Copyright (c) David Witten KD0EAG and David Rowe VK5DGR\n\n")
                wxT("freedv-gui version: %s\n")
                wxT("freedv-gui git hash: %s\n")
                wxT("codec2 git hash: %s\n")
                wxT("lpcnet git hash: %s\n"),
                FREEDV_VERSION, FREEDV_VERSION, GIT_HASH, freedv_get_hash(), lpcnet_get_hash());
                
    wxMessageBox(msg, wxT("About"), wxOK | wxICON_INFORMATION, this);
}


// Attempt to talk to rig using Hamlib

bool MainFrame::OpenHamlibRig() {
    if (wxGetApp().m_boolHamlibUseForPTT != true)
       return false;
    if (wxGetApp().m_intHamlibRig == 0)
        return false;
    if (wxGetApp().m_hamlib == NULL)
        return false;

    int rig = wxGetApp().m_intHamlibRig;
    wxString port = wxGetApp().m_strHamlibSerialPort;
    int serial_rate = wxGetApp().m_intHamlibSerialRate;
    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_mode_detection(m_txtModeStatus, g_mode == FREEDV_MODE_2400B);
    }

    return status;
} 

//-------------------------------------------------------------------------
// OnTogBtnOnOff()
//-------------------------------------------------------------------------
void MainFrame::OnTogBtnOnOff(wxCommandEvent& event)
{
    wxString startStop = m_togBtnOnOff->GetLabel();

    // we are attempting to start

    if (startStop.IsSameAs("Start"))
    {
        fprintf(stderr, "Start .....\n");
        //
        // Start Running -------------------------------------------------
        //

        // modify some button states when running

        m_togBtnOnOff->SetLabel(wxT("Stop"));

        vk_state = VK_IDLE;

        m_rb1600->Disable();
        m_rb700c->Disable();
        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();

        m_textSync->Enable();
        m_textInterleaverSync->Enable();

        // determine what mode we are using

        if (m_rb1600->GetValue()) {
            g_mode = FREEDV_MODE_1600;
            g_Nc = 16;
            m_panelScatter->setNc(g_Nc+1);  /* +1 for BPSK pilot */
        }
        if (m_rb700c->GetValue()) {
            g_mode = FREEDV_MODE_700C;
            g_Nc = 14;
            if (wxGetApp().m_FreeDV700Combine) {
                m_panelScatter->setNc(g_Nc/2);  /* diversity combnation */
            }
            else {
                m_panelScatter->setNc(g_Nc); 
            }
        }
        if (m_rb700d->GetValue()) {
            g_mode = FREEDV_MODE_700D;
            g_Nc = 17;                         /* TODO: be nice if we didn't have to hard code this, maybe API call? */
            m_panelScatter->setNc(g_Nc); 
        }
        if (m_rb800xa->GetValue()) {
            g_mode = FREEDV_MODE_800XA;
        }
        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 */

            g_horus = horus_open(HORUS_MODE_BINARY);
            horus_set_verbose(g_horus, g_freedv_verbose);
            
            /* init a sample rate converted for monitoring modem signal */

            int src_error;
            g_horus_src = src_new(SRC_SINC_FASTEST, 1, &src_error);
            assert(g_horus_src != NULL);

            m_textSync->Disable();
            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? */
            m_panelScatter->setNc(g_Nc);
        }

        if (g_mode != -1) { 
            // init freedv states

            m_togBtnSplit->Enable();
            m_togBtnAnalog->Enable();
            m_btnTogPTT->Enable();
            m_togBtnVoiceKeyer->Enable();

            if ((g_mode == FREEDV_MODE_700D) || (g_mode == FREEDV_MODE_2020)) {
                // 700D has some init time stuff so treat it special
                struct freedv_advanced adv;
                adv.interleave_frames = wxGetApp().m_FreeDV700Interleave;
                g_pfreedv = freedv_open_advanced(g_mode, &adv);
                m_textInterleaverSync->SetLabel("Intrlvr ("+wxString::Format(wxT("%i"),wxGetApp().m_FreeDV700Interleave)+")");
                if (wxGetApp().m_FreeDV700ManualUnSync) {
                    freedv_set_sync(g_pfreedv, FREEDV_SYNC_MANUAL);
                } else {
                    freedv_set_sync(g_pfreedv, FREEDV_SYNC_AUTO);
                }
            } else {
                g_pfreedv = freedv_open(g_mode);
                m_textInterleaverSync->SetLabel("");
            }

            // Codec 2 VQ Equaliser
            if ((g_mode == FREEDV_MODE_700C) || (g_mode == FREEDV_MODE_700D)) {
                freedv_set_eq(g_pfreedv, wxGetApp().m_700C_EQ);
            }
                    
            if (g_freedv_verbose)
                freedv_set_verbose(g_pfreedv, 2);
            else
                freedv_set_verbose(g_pfreedv, 0);
            
            freedv_set_callback_txt(g_pfreedv, &my_put_next_rx_char, &my_get_next_tx_char, NULL);

            freedv_set_callback_error_pattern(g_pfreedv, my_freedv_put_error_pattern, (void*)m_panelTestFrameErrors);
            g_error_pattern_fifo = codec2_fifo_create(2*freedv_get_sz_error_pattern(g_pfreedv)+1);
            g_error_hist = new short[MODEM_STATS_NC_MAX*2];
            g_error_histn = new short[MODEM_STATS_NC_MAX*2];
            int i;
            for(i=0; i<2*MODEM_STATS_NC_MAX; i++) {
                g_error_hist[i] = 0;
                g_error_histn[i] = 0;
            }

            assert(g_pfreedv != NULL);
        
            // Set processing buffer sizes, these are FRAME_DURATION (20ms) chunks of modem and speech that are a useful size for the
            // various operations we do before and after passing to the freedv_api layer.
            g_modemInbufferSize = (int)(FRAME_DURATION * freedv_get_modem_sample_rate(g_pfreedv));
            g_speechOutbufferSize = (int)(FRAME_DURATION * freedv_get_speech_sample_rate(g_pfreedv));

            // init Codec 2 LPC Post Filter (FreeDV 1600)

            struct CODEC2 *c2 = freedv_get_codec2(g_pfreedv);
            if (c2 != NULL) {
                codec2_set_lpc_post_filter(c2,
                                           wxGetApp().m_codec2LPCPostFilterEnable,
                                           wxGetApp().m_codec2LPCPostFilterBassBoost,
                                           wxGetApp().m_codec2LPCPostFilterBeta,
                                           wxGetApp().m_codec2LPCPostFilterGamma);
            }
            
            // Init Speex pre-processor states
            // by inspecting Speex source it seems that only denoiser is on by default

            fprintf(stderr, "freedv_get_n_speech_samples(g_pfreedv): %d\n", freedv_get_n_speech_samples(g_pfreedv));
            fprintf(stderr, "freedv_get_speech_sample_rate(g_pfreedv): %d\n", freedv_get_speech_sample_rate(g_pfreedv));
            
            if (wxGetApp().m_speexpp_enable)
                g_speex_st = speex_preprocess_state_init(freedv_get_n_speech_samples(g_pfreedv), freedv_get_speech_sample_rate(g_pfreedv));

            // adjust spectrum and waterfall freq scaling base on mode

            m_panelSpectrum->setFreqScale(MODEM_STATS_NSPEC*((float)MAX_F_HZ/(freedv_get_modem_sample_rate(g_pfreedv)/2)));
            m_panelWaterfall->setFs(freedv_get_modem_sample_rate(g_pfreedv));

            // Init text msg decoding

            freedv_set_varicode_code_num(g_pfreedv, wxGetApp().m_textEncoding);

            // scatter plot (PSK) or Eye (FSK) mode
            
            if ((g_mode == FREEDV_MODE_800XA) || (g_mode == FREEDV_MODE_2400A) || (g_mode == FREEDV_MODE_2400B)) {
                m_panelScatter->setEyeScatter(PLOT_SCATTER_MODE_EYE);
            }
            else {
                m_panelScatter->setEyeScatter(PLOT_SCATTER_MODE_SCATTER);
            }

        }

        modem_stats_open(&g_stats);
        int src_error;
        g_spec_src = src_new(SRC_SINC_FASTEST, 1, &src_error);
        assert(g_spec_src != NULL);
        g_State = g_prev_State = g_interleaverSyncState = 0;
        g_snr = 0.0;
        g_half_duplex = wxGetApp().m_boolHalfDuplex;

        m_pcallsign = m_callsign;
        memset(m_callsign, 0, sizeof(m_callsign));
#ifdef __UDP_EXPERIMENTAL__
        m_checksumGood = m_checksumBad = 0;
        wxString s;
        s.Printf("%d", m_checksumGood);
        m_txtChecksumGood->SetLabel(s);              
        s.Printf("%d", m_checksumBad);
        m_txtChecksumBad->SetLabel(s);        
#endif

        m_maxLevel = 0;
        m_textLevel->SetLabel(wxT(""));
        m_gaugeLevel->SetValue(0);

        //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)
            OpenHamlibRig();
        if (wxGetApp().m_boolUseSerialPTT) {
            OpenSerialPort();
        }

        if (m_RxRunning)
        {
#ifdef _USE_TIMER
            m_plotTimer.Start(_REFRESH_TIMER_PERIOD, wxTIMER_CONTINUOUS);
#endif // _USE_TIMER
        }
#ifdef __UDP_EXPERIMENTAL__
        char e[80]; sprintf(e,"start"); processTxtEvent(e);
#endif
    }

    // Stop was pressed or start up failed

    if (startStop.IsSameAs("Stop") || !m_RxRunning ) {
        fprintf(stderr, "Stop .....\n");
        //
        // Stop Running -------------------------------------------------
        //

#ifdef __UDP_EXPERIMENTAL__
        optionsDlg->SetSpamTimerLight(false);
#endif

#ifdef _USE_TIMER
        m_plotTimer.Stop();
#endif // _USE_TIMER

        // ensure we are not transmitting and shut down audio processing

        if (wxGetApp().m_boolHamlibUseForPTT) {
            Hamlib *hamlib = wxGetApp().m_hamlib; 
            wxString hamlibError;
            if (wxGetApp().m_boolHamlibUseForPTT && hamlib != NULL) {
                if (hamlib->ptt(false, hamlibError) == false) {
                    wxMessageBox(wxString("Hamlib PTT Error: ") + hamlibError, wxT("Error"), wxOK | wxICON_ERROR, this);
                }
                hamlib->disable_mode_detection();
                hamlib->close();
            }
        }

        if (wxGetApp().m_boolUseSerialPTT) {
            CloseSerialPort();
        }

        m_btnTogPTT->SetValue(false);
        VoiceKeyerProcessEvent(VK_SPACE_BAR);

        stopRxStream();
        src_delete(g_spec_src);       
        modem_stats_close(&g_stats);

        // free up states, clean up

        if (g_mode == -1) {
#ifdef __HORUS__
            // Horus clean up
            src_delete(g_horus_src);
            horus_close(g_horus);
#endif
        }
        else {
            // FreeDV clean up
            delete[] g_error_hist;
            delete[] g_error_histn;
            codec2_fifo_destroy(g_error_pattern_fifo);
            freedv_close(g_pfreedv);
            if (wxGetApp().m_speexpp_enable)
                speex_preprocess_state_destroy(g_speex_st);
        }

        m_newMicInFilter = m_newSpkOutFilter = true;

        m_textSync->Disable();
        m_textInterleaverSync->Disable();
        
        m_togBtnSplit->Disable();
        m_togBtnAnalog->Disable();
        m_btnTogPTT->Disable();
        m_togBtnVoiceKeyer->Disable();
        m_togBtnOnOff->SetLabel(wxT("Start"));
        m_rb1600->Enable();
        m_rb700c->Enable();
        m_rb700d->Enable();
        m_rb800xa->Enable();
        m_rb2400b->Enable();
#ifdef __HORUS__
        m_rbHorusBinary->Enable();
#endif
        if(isAvxPresent)
            m_rb2020->Enable();
        if (m_rbPlugIn != NULL)
            m_rbPlugIn->Enable();
           
#ifdef __UDP_EXPERIMENTAL__
        char e[80]; sprintf(e,"stop"); processTxtEvent(e);
#endif
    }
}

//-------------------------------------------------------------------------
// stopRxStream()
//-------------------------------------------------------------------------
void MainFrame::stopRxStream()
{
    if(m_RxRunning)
    {
        m_RxRunning = false;

        //fprintf(stderr, "waiting for thread to stop\n");
        m_txRxThread->m_run = 0;
        m_txRxThread->Wait();
        //fprintf(stderr, "thread stopped\n");

        m_rxInPa->stop();
        m_rxInPa->streamClose();
        delete m_rxInPa;
        if(m_rxOutPa != m_rxInPa) {
			m_rxOutPa->stop();
			m_rxOutPa->streamClose();
			delete m_rxOutPa;
		}

        if (g_nSoundCards == 2) {
            m_txInPa->stop();
            m_txInPa->streamClose();
            delete m_txInPa;
            if(m_txInPa != m_txOutPa) {
				m_txOutPa->stop();
				m_txOutPa->streamClose();
				delete m_txOutPa;
			}
        }

        destroy_fifos();
        destroy_src();
        deleteEQFilters(g_rxUserdata);
        delete g_rxUserdata;
    }
}

void MainFrame::destroy_fifos(void)
{
    codec2_fifo_destroy(g_rxUserdata->infifo1);
    codec2_fifo_destroy(g_rxUserdata->outfifo1);
    codec2_fifo_destroy(g_rxUserdata->infifo2);
    codec2_fifo_destroy(g_rxUserdata->outfifo2);
    codec2_fifo_destroy(g_rxUserdata->rxinfifo);
    codec2_fifo_destroy(g_rxUserdata->rxoutfifo);
}

void MainFrame::destroy_src(void)
{
    src_delete(g_rxUserdata->insrc1);
    src_delete(g_rxUserdata->outsrc1);
    src_delete(g_rxUserdata->insrc2);
    src_delete(g_rxUserdata->outsrc2);
    src_delete(g_rxUserdata->insrcsf);
}

void  MainFrame::initPortAudioDevice(PortAudioWrap *pa, int inDevice, int outDevice,
                                     int soundCard, int sampleRate, int inputChannels)
{
    // Note all of the wrapper functions below just set values in a
    // portaudio struct so can't return any errors. So no need to trap
    // any errors in this function.

    // init input params

    pa->setInputDevice(inDevice);
    if(inDevice != paNoDevice) {
        pa->setInputChannelCount(inputChannels);           // stereo input
        pa->setInputSampleFormat(PA_SAMPLE_TYPE);
        pa->setInputLatency(pa->getInputDefaultHighLatency());
        //fprintf(stderr,"PA in; low: %f high: %f\n", pa->getInputDefaultLowLatency(), pa->getInputDefaultHighLatency());
        pa->setInputHostApiStreamInfo(NULL);
    }

    pa->setOutputDevice(paNoDevice);
    
    // init output params
    
    pa->setOutputDevice(outDevice);
    if(outDevice != paNoDevice) {
        pa->setOutputChannelCount(2);                      // stereo output
        pa->setOutputSampleFormat(PA_SAMPLE_TYPE);
        pa->setOutputLatency(pa->getOutputDefaultHighLatency());
        //fprintf(stderr,"PA out; low: %f high: %f\n", pa->getOutputDefaultLowLatency(), pa->getOutputDefaultHighLatency());
        pa->setOutputHostApiStreamInfo(NULL);
    }

    // init params that affect input and output

    /*
      DR 2013:

      On Linux, setting this to wxGetApp().m_framesPerBuffer caused
      intermittant break up on the audio from my IC7200 on Ubuntu 14.
      After a day of bug hunting I found that 0, as recommended by the
      PortAudio documentation, fixed the problem.

      DR 2018:

      During 700D testing some break up in from radio audio, so made
      this adjustable again.
    */

    pa->setFramesPerBuffer(wxGetApp().m_framesPerBuffer);

    pa->setSampleRate(sampleRate);
    pa->setStreamFlags(paClipOff);
}

//-------------------------------------------------------------------------
// startRxStream()
//-------------------------------------------------------------------------
void MainFrame::startRxStream()
{
    int   src_error;
    const PaDeviceInfo *deviceInfo1 = NULL, *deviceInfo2 = NULL;
    int   inputChannels1, inputChannels2;
    bool  two_rx=false;
    bool  two_tx=false;

    fprintf(stderr, "startRxStream .....\n");
    if(!m_RxRunning) {
        m_RxRunning = true;
        if(Pa_Initialize())
        {
            wxMessageBox(wxT("Port Audio failed to initialize"), wxT("Pa_Initialize"), wxOK);
        }

        m_rxInPa = new PortAudioWrap();
        if(g_soundCard1InDeviceNum != g_soundCard1OutDeviceNum)
            two_rx=true;
        if(g_soundCard2InDeviceNum != g_soundCard2OutDeviceNum)
            two_tx=true;
        
        fprintf(stderr, "two_rx: %d two_tx: %d\n", two_rx, two_tx);
        if(two_rx)
            m_rxOutPa = new PortAudioWrap();
        else
            m_rxOutPa = m_rxInPa;

        if (g_nSoundCards == 0) {
            wxMessageBox(wxT("No Sound Cards configured, use Tools - Audio Config to configure"), wxT("Error"), wxOK);
            delete m_rxInPa;
            if(two_rx)
                delete m_rxOutPa;
            m_RxRunning = false;
            return;
        }

        // Init Sound card 1 ----------------------------------------------
        // sanity check on sound card device numbers

        if ((m_rxInPa->getDeviceCount() <= g_soundCard1InDeviceNum) ||
            (m_rxOutPa->getDeviceCount() <= g_soundCard1OutDeviceNum)) {
            wxMessageBox(wxT("Sound Card 1 not present"), wxT("Error"), wxOK);
            delete m_rxInPa;
            if(two_rx)
                delete m_rxOutPa;
            m_RxRunning = false;
            return;
        }

        // work out how many input channels this device supports.

        deviceInfo1 = Pa_GetDeviceInfo(g_soundCard1InDeviceNum);
        if (deviceInfo1 == NULL) {
            wxMessageBox(wxT("Couldn't get device info from Port Audio for Sound Card 1"), wxT("Error"), wxOK);
            delete m_rxInPa;
            if(two_rx)
				delete m_rxOutPa;
            m_RxRunning = false;
            return;
        }
        if (deviceInfo1->maxInputChannels == 1)
            inputChannels1 = 1;
        else
            inputChannels1 = 2;

        if(two_rx) {
            initPortAudioDevice(m_rxInPa, g_soundCard1InDeviceNum, paNoDevice, 1,
                            g_soundCard1SampleRate, inputChannels1);
            initPortAudioDevice(m_rxOutPa, paNoDevice, g_soundCard1OutDeviceNum, 1,
                            g_soundCard1SampleRate, inputChannels1);
		}
        else
            initPortAudioDevice(m_rxInPa, g_soundCard1InDeviceNum, g_soundCard1OutDeviceNum, 1,
                            g_soundCard1SampleRate, inputChannels1);

        // Init Sound Card 2 ------------------------------------------------

        if (g_nSoundCards == 2) {

            m_txInPa = new PortAudioWrap();
            if(two_tx)
                m_txOutPa = new PortAudioWrap();
            else
                m_txOutPa = m_txInPa;

            // sanity check on sound card device numbers

            //printf("m_txInPa->getDeviceCount(): %d\n", m_txInPa->getDeviceCount());
            //printf("g_soundCard2InDeviceNum: %d\n", g_soundCard2InDeviceNum);
            //printf("g_soundCard2OutDeviceNum: %d\n", g_soundCard2OutDeviceNum);

            if ((m_txInPa->getDeviceCount() <= g_soundCard2InDeviceNum) ||
                (m_txOutPa->getDeviceCount() <= g_soundCard2OutDeviceNum)) {
                wxMessageBox(wxT("Sound Card 2 not present"), wxT("Error"), wxOK);
                delete m_rxInPa;
                if(two_rx)
                    delete m_rxOutPa;
                delete m_txInPa;
                if(two_tx)
                    delete m_txOutPa;
                m_RxRunning = false;
                return;
            }

            deviceInfo2 = Pa_GetDeviceInfo(g_soundCard2InDeviceNum);
            if (deviceInfo2 == NULL) {
                wxMessageBox(wxT("Couldn't get device info from Port Audio for Sound Card 1"), wxT("Error"), wxOK);
                delete m_rxInPa;
                if(two_rx)
					delete m_rxOutPa;
                delete m_txInPa;
                if(two_tx)
					delete m_txOutPa;
                m_RxRunning = false;
                return;
            }
            if (deviceInfo2->maxInputChannels == 1)
                inputChannels2 = 1;
            else
                inputChannels2 = 2;

            if(two_tx) {
				initPortAudioDevice(m_txInPa, g_soundCard2InDeviceNum, paNoDevice, 2,
                                g_soundCard2SampleRate, inputChannels2);
				initPortAudioDevice(m_txOutPa, paNoDevice, g_soundCard2OutDeviceNum, 2,
                                g_soundCard2SampleRate, inputChannels2);
			}
			else
				initPortAudioDevice(m_txInPa, g_soundCard2InDeviceNum, g_soundCard2OutDeviceNum, 2,
                                g_soundCard2SampleRate, inputChannels2);
        }

        // Init call back data structure ----------------------------------------------

        g_rxUserdata = new paCallBackData;
        g_rxUserdata->inputChannels1 = inputChannels1;
        if (deviceInfo2 != NULL)
            g_rxUserdata->inputChannels2 = inputChannels2;

        // init sample rate conversion states

        g_rxUserdata->insrc1 = src_new(SRC_SINC_FASTEST, 1, &src_error);
        assert(g_rxUserdata->insrc1 != NULL);
        g_rxUserdata->outsrc1 = src_new(SRC_SINC_FASTEST, 1, &src_error);
        assert(g_rxUserdata->outsrc1 != NULL);
        g_rxUserdata->insrc2 = src_new(SRC_SINC_FASTEST, 1, &src_error);
        assert(g_rxUserdata->insrc2 != NULL);
        g_rxUserdata->outsrc2 = src_new(SRC_SINC_FASTEST, 1, &src_error);
        assert(g_rxUserdata->outsrc2 != NULL);

        g_rxUserdata->insrcsf = src_new(SRC_SINC_FASTEST, 1, &src_error);
        assert(g_rxUserdata->insrcsf != NULL);

        // create FIFOs used to interface between Port Audio and txRx
        // processing loop, which iterates about once every 20ms.
        // Sample rate conversion, stats for spectral plots, and
        // transmit processng are all performed in the txRxProcessing
        // loop.

        int m_fifoSize_ms = wxGetApp().m_fifoSize_ms;        
        int soundCard1FifoSizeSamples = m_fifoSize_ms*g_soundCard1SampleRate/1000;
        int soundCard2FifoSizeSamples = m_fifoSize_ms*g_soundCard2SampleRate/1000;

        g_rxUserdata->infifo1 = codec2_fifo_create(soundCard1FifoSizeSamples);
        g_rxUserdata->outfifo1 = codec2_fifo_create(soundCard1FifoSizeSamples);
        g_rxUserdata->outfifo2 = codec2_fifo_create(soundCard2FifoSizeSamples);
        g_rxUserdata->infifo2 = codec2_fifo_create(soundCard2FifoSizeSamples);

        fprintf(stderr, "fifoSize_ms: %d infifo1/outfilo1: %d infifo2/outfilo2: %d\n",
                wxGetApp().m_fifoSize_ms, soundCard1FifoSizeSamples, soundCard2FifoSizeSamples);

        // reset debug stats for FIFOs
        
        g_infifo1_full = g_outfifo1_empty = g_infifo2_full = g_outfifo2_empty = 0;
        g_infifo1_full = g_outfifo1_empty = g_infifo2_full = g_outfifo2_empty = 0;
        for (int i=0; i<4; i++) {
            g_PAstatus1[i] = g_PAstatus2[i] = 0;
        }        

        // These FIFOs interface between the 20ms txRxProcessing()
        // loop and the demodulator, which requires a variable number
        // of input samples to adjust for timing clock differences
        // between remote tx and rx.  These FIFOs also help with the
        // different processing block size of different FreeDV modes.

        // TODO: might be able to tune these on a per waveform basis, or refactor
        // to a neater design with less layers of FIFOs

        int modem_samplerate, rxInFifoSizeSamples, rxOutFifoSizeSamples;
        if (g_mode == -1) {
#ifdef __HORUS__
            modem_samplerate = horus_get_Fs(g_horus);
            rxInFifoSizeSamples = horus_get_max_demod_in(g_horus);
            rxOutFifoSizeSamples = rxInFifoSizeSamples;
#endif
        }
        else {
            modem_samplerate = freedv_get_modem_sample_rate(g_pfreedv);
            rxInFifoSizeSamples = freedv_get_n_max_modem_samples(g_pfreedv);
            rxOutFifoSizeSamples = freedv_get_n_speech_samples(g_pfreedv);
        }

        // add an extra 40ms to give a bit of headroom for processing loop adding samples
        // which operates on 20ms buffers

        rxInFifoSizeSamples += 0.04*modem_samplerate;
        rxOutFifoSizeSamples += 0.04*modem_samplerate;

        g_rxUserdata->rxinfifo = codec2_fifo_create(rxInFifoSizeSamples);
        g_rxUserdata->rxoutfifo = codec2_fifo_create(rxOutFifoSizeSamples);

        fprintf(stderr, "rxInFifoSizeSamples: %d rxOutFifoSizeSamples: %d\n", rxInFifoSizeSamples, rxOutFifoSizeSamples);
        
        // Init Equaliser Filters ------------------------------------------------------

        m_newMicInFilter = m_newSpkOutFilter = true;
        designEQFilters(g_rxUserdata);
        g_rxUserdata->micInEQEnable = wxGetApp().m_MicInEQEnable;
        g_rxUserdata->spkOutEQEnable = wxGetApp().m_SpkOutEQEnable;

        // optional tone in left channel to reliably trigger vox
        
        g_rxUserdata->leftChannelVoxTone = wxGetApp().m_leftChannelVoxTone;
        g_rxUserdata->voxTonePhase = 0;

        // Start sound card 1 ----------------------------------------------------------

        m_rxInPa->setUserData(g_rxUserdata);
        m_rxErr = m_rxInPa->setCallback(rxCallback);

        m_rxErr = m_rxInPa->streamOpen();

        if(m_rxErr != paNoError) {
            wxMessageBox(wxT("Sound Card 1 Open/Setup error."), wxT("Error"), wxOK);
			delete m_rxInPa;
			if(two_rx)
				delete m_rxOutPa;
			delete m_txInPa;
			if(two_tx)
				delete m_txOutPa;
            destroy_fifos();
            destroy_src();
            deleteEQFilters(g_rxUserdata);
            delete g_rxUserdata;
            m_RxRunning = false;
            return;
        }

        m_rxErr = m_rxInPa->streamStart();
        if(m_rxErr != paNoError) {
            wxMessageBox(wxT("Sound Card 1 Stream Start Error."), wxT("Error"), wxOK);
			delete m_rxInPa;
			if(two_rx)
				delete m_rxOutPa;
			delete m_txInPa;
			if(two_tx)
				delete m_txOutPa;
            destroy_fifos();
            destroy_src();
            deleteEQFilters(g_rxUserdata);
            delete g_rxUserdata;
            m_RxRunning = false;
            return;
        }

        // Start separate output stream if needed

        if(two_rx) {
            m_rxOutPa->setUserData(g_rxUserdata);
            m_rxErr = m_rxOutPa->setCallback(rxCallback);

            m_rxErr = m_rxOutPa->streamOpen();

            if(m_rxErr != paNoError) {
                wxMessageBox(wxT("Sound Card 1 Second Stream Open/Setup error."), wxT("Error"), wxOK);
                delete m_rxInPa;
                delete m_rxOutPa;
                delete m_txOutPa;
                if(two_tx)
                    delete m_txOutPa;
                destroy_fifos();
                destroy_src();
                deleteEQFilters(g_rxUserdata);
                delete g_rxUserdata;
                m_RxRunning = false;
                return;
            }

            m_rxErr = m_rxOutPa->streamStart();
            if(m_rxErr != paNoError) {
                wxMessageBox(wxT("Sound Card 1 Second Stream Start Error."), wxT("Error"), wxOK);
                m_rxInPa->stop();
                m_rxInPa->streamClose();
                delete m_rxInPa;
                delete m_rxOutPa;
                delete m_txOutPa;
                if(two_tx)
                    delete m_txOutPa;
                destroy_fifos();
                destroy_src();
                deleteEQFilters(g_rxUserdata);
                delete g_rxUserdata;
                m_RxRunning = false;
                return;
            }
        }

        fprintf(stderr, "started stream 1\n");
        
        // Start sound card 2 ----------------------------------------------------------

        if (g_nSoundCards == 2) {

            // question: can we use same callback data
            // (g_rxUserdata)or both sound card callbacks?  Is there a
            // chance of them both being called at the same time?  We
            // could need a mutex ...

            m_txInPa->setUserData(g_rxUserdata);
            m_txErr = m_txInPa->setCallback(txCallback);
            m_txErr = m_txInPa->streamOpen();

            if(m_txErr != paNoError) {
                fprintf(stderr, "Err: %d\n", m_txErr);
                wxMessageBox(wxT("Sound Card 2 Open/Setup error."), wxT("Error"), wxOK);
                m_rxInPa->stop();
                m_rxInPa->streamClose();
                delete m_rxInPa;
                if(two_rx) {
                    m_rxOutPa->stop();
                    m_rxOutPa->streamClose();
                    delete m_rxOutPa;
                }
                delete m_txInPa;
                if(two_tx)
                    delete m_txOutPa;
                destroy_fifos();
                destroy_src();
                deleteEQFilters(g_rxUserdata);
                delete g_rxUserdata;
                m_RxRunning = false;
                return;
            }
            m_txErr = m_txInPa->streamStart();
            if(m_txErr != paNoError) {
                wxMessageBox(wxT("Sound Card 2 Start Error."), wxT("Error"), wxOK);
                m_rxInPa->stop();
                m_rxInPa->streamClose();
                delete m_rxInPa;
                if(two_rx) {
                    m_rxOutPa->stop();
                    m_rxOutPa->streamClose();
                    delete m_rxOutPa;
                }
                delete m_txInPa;
                if(two_tx)
                    delete m_txOutPa;
                destroy_fifos();
                destroy_src();
                deleteEQFilters(g_rxUserdata);
                delete g_rxUserdata;
                m_RxRunning = false;
                return;
            }

            // Start separate output stream if needed

            if (two_tx) {

                // question: can we use same callback data
                // (g_rxUserdata)or both sound card callbacks?  Is there a
                // chance of them both being called at the same time?  We
                // could need a mutex ...

                m_txOutPa->setUserData(g_rxUserdata);
                m_txErr = m_txOutPa->setCallback(txCallback);
                m_txErr = m_txOutPa->streamOpen();

                if(m_txErr != paNoError) {
                    wxMessageBox(wxT("Sound Card 2 Second Stream Open/Setup error."), wxT("Error"), wxOK);
                    m_rxInPa->stop();
                    m_rxInPa->streamClose();
                    delete m_rxInPa;
                    if(two_rx) {
                        m_rxOutPa->stop();
                        m_rxOutPa->streamClose();
                        delete m_rxOutPa;
                    }
                    m_txInPa->stop();
                    m_txInPa->streamClose();
                    delete m_txInPa;
                    delete m_txOutPa;
                    destroy_fifos();
                    destroy_src();
                    deleteEQFilters(g_rxUserdata);
                    delete g_rxUserdata;
                    m_RxRunning = false;
                    return;
                }
                m_txErr = m_txOutPa->streamStart();
                if(m_txErr != paNoError) {
                    wxMessageBox(wxT("Sound Card 2 Second Stream Start Error."), wxT("Error"), wxOK);
                    m_rxInPa->stop();
                    m_rxInPa->streamClose();
                    m_txInPa->stop();
                    m_txInPa->streamClose();
                    delete m_txInPa;
                    if(two_rx) {
                        m_rxOutPa->stop();
                        m_rxOutPa->streamClose();
                        delete m_rxOutPa;
                    }
                    delete m_txInPa;
                    delete m_txOutPa;
                    destroy_fifos();
                    destroy_src();
                    deleteEQFilters(g_rxUserdata);
                    delete g_rxUserdata;
                    m_RxRunning = false;
                    return;
                }
            }
        }

        fprintf(stderr, "starting tx/rx processing thread\n");
        
        // start tx/rx processing thread

        m_txRxThread = new txRxThread;

        if ( m_txRxThread->Create() != wxTHREAD_NO_ERROR )
        {
            wxLogError(wxT("Can't create thread!"));
        }

        if (wxGetApp().m_txRxThreadHighPriority) {
            m_txRxThread->SetPriority(WXTHREAD_MAX_PRIORITY);
        }

        if ( m_txRxThread->Run() != wxTHREAD_NO_ERROR )
        {
            wxLogError(wxT("Can't start thread!"));
        }

    }
}

#ifdef __UDP_EPERIMENTAL__

void MainFrame::processTxtEvent(char event[]) {
    int rule = 0;

    //printf("processTxtEvent:\n");
    //printf("  event: %s\n", event);

    // process with regexp and issue system command

    // Each regexp in our list is separated by a newline.  We want to try all of them.

    wxString event_str(event);
    int match_end, replace_end;
    match_end = replace_end = 0;
    wxString regexp_match_list = wxGetApp().m_events_regexp_match;
    wxString regexp_replace_list = wxGetApp().m_events_regexp_replace;

    bool found_match = false;

    while (((match_end = regexp_match_list.Find('\n')) != wxNOT_FOUND) && (rule < MAX_EVENT_RULES)) {
        //printf("match_end: %d\n", match_end);
        if ((replace_end = regexp_replace_list.Find('\n')) != wxNOT_FOUND) {
            //printf("replace_end = %d\n", replace_end);

            // candidate match and replace regexps strings exist, so lets try them

            wxString regexp_match = regexp_match_list.SubString(0, match_end-1);
            wxString regexp_replace = regexp_replace_list.SubString(0, replace_end-1);
            //printf("match: %s replace: %s\n", (const char *)regexp_match.c_str(), (const char *)regexp_replace.c_str());
            wxRegEx re(regexp_match);
            //printf("  checking for match against: %s\n", (const char *)regexp_match.c_str());

            // if we found a match, lets run the replace regexp and issue the system command

            wxString event_str_rep = event_str;
           
            if (re.Replace(&event_str_rep, regexp_replace) != 0) {
                fprintf(stderr, "  found match! event_str: %s\n", (const char *)event_str.c_str());
                found_match = true;

                bool enableSystem = false;
                if (wxGetApp().m_events)
                    enableSystem = true;

                // no syscall if spam timer still running

                if (spamTimer[rule].IsRunning()) {
                    enableSystem = false;
                    fprintf(stderr, "  spam timer running\n");
                }

                const char *event_out = event_str_rep.ToUTF8();
                wxString event_out_with_return_code;

                if (enableSystem) {
                    int ret = wxExecute(event_str_rep);
                    event_out_with_return_code.Printf(_T("%s -> returned %d"), event_out, ret);
                    spamTimer[rule].Start((wxGetApp().m_events_spam_timer)*1000, wxTIMER_ONE_SHOT);
                }
                else
                    event_out_with_return_code.Printf(_T("%s T: %d"), event_out, spamTimer[rule].IsRunning());

                // update event log GUI if currently displayed
                
                if (optionsDlg != NULL) {                  
                    optionsDlg->updateEventLog(wxString(event), event_out_with_return_code);                     
                }
            }
        }
        regexp_match_list = regexp_match_list.SubString(match_end+1, regexp_match_list.length());
        regexp_replace_list = regexp_replace_list.SubString(replace_end+1, regexp_replace_list.length());

        rule++;
    }
 
    if ((optionsDlg != NULL) && !found_match) {                  
        optionsDlg->updateEventLog(wxString(event), _("<no match>"));                     
    }
}
#endif


#define SBQ_MAX_ARGS 5

void *MainFrame::designAnEQFilter(const char filterType[], float freqHz, float gaindB, float Q)
{
    char  *arg[SBQ_MAX_ARGS];
    char   argstorage[SBQ_MAX_ARGS][80];
    void  *sbq;
    int    i, argc;

    assert((strcmp(filterType, "bass") == 0)   ||
           (strcmp(filterType, "treble") == 0) ||
           (strcmp(filterType, "equalizer") == 0));

    for(i=0; i<SBQ_MAX_ARGS; i++) {
        arg[i] = &argstorage[i][0];
    }

    argc = 0;

    if ((strcmp(filterType, "bass") == 0) || (strcmp(filterType, "treble") == 0)) {
        sprintf(arg[argc++], "%s", filterType);
        sprintf(arg[argc++], "%f", gaindB+1E-6);
        sprintf(arg[argc++], "%f", freqHz);
    }

    if (strcmp(filterType, "equalizer") == 0) {
        sprintf(arg[argc++], "%s", filterType);
        sprintf(arg[argc++], "%f", freqHz);
        sprintf(arg[argc++], "%f", Q);
        sprintf(arg[argc++], "%f", gaindB+1E-6);
    }

    assert(argc <= SBQ_MAX_ARGS);
    // Note - the argc count doesn't include the command!
    sbq = sox_biquad_create(argc-1, (const char **)arg);
    assert(sbq != NULL);

    return sbq;
}

void  MainFrame::designEQFilters(paCallBackData *cb)
{
    // init Mic In Equaliser Filters

    if (m_newMicInFilter) {
        //printf("designing new Min In filters\n");
        cb->sbqMicInBass   = designAnEQFilter("bass", wxGetApp().m_MicInBassFreqHz, wxGetApp().m_MicInBassGaindB);
        cb->sbqMicInTreble = designAnEQFilter("treble", wxGetApp().m_MicInTrebleFreqHz, wxGetApp().m_MicInTrebleGaindB);
        cb->sbqMicInMid    = designAnEQFilter("equalizer", wxGetApp().m_MicInMidFreqHz, wxGetApp().m_MicInMidGaindB, wxGetApp().m_MicInMidQ);
    }

    // init Spk Out Equaliser Filters

    if (m_newSpkOutFilter) {
        //printf("designing new Spk Out filters\n");
        //printf("designEQFilters: wxGetApp().m_SpkOutBassFreqHz: %f\n",wxGetApp().m_SpkOutBassFreqHz);
        cb->sbqSpkOutBass   = designAnEQFilter("bass", wxGetApp().m_SpkOutBassFreqHz, wxGetApp().m_SpkOutBassGaindB);
        cb->sbqSpkOutTreble = designAnEQFilter("treble", wxGetApp().m_SpkOutTrebleFreqHz, wxGetApp().m_SpkOutTrebleGaindB);
        cb->sbqSpkOutMid    = designAnEQFilter("equalizer", wxGetApp().m_SpkOutMidFreqHz, wxGetApp().m_SpkOutMidGaindB, wxGetApp().m_SpkOutMidQ);
    }
}

void  MainFrame::deleteEQFilters(paCallBackData *cb)
{
    if (m_newMicInFilter) {
        sox_biquad_destroy(cb->sbqMicInBass);
        sox_biquad_destroy(cb->sbqMicInTreble);
        sox_biquad_destroy(cb->sbqMicInMid);
    }
    if (m_newSpkOutFilter) {
        sox_biquad_destroy(cb->sbqSpkOutBass);
        sox_biquad_destroy(cb->sbqSpkOutTreble);
        sox_biquad_destroy(cb->sbqSpkOutMid);
    }
}

// returns number of output samples generated by resampling
int resample(SRC_STATE *src,
            short      output_short[],
            short      input_short[],
            int        output_sample_rate,
            int        input_sample_rate,
            int        length_output_short, // maximum output array length in samples
            int        length_input_short
            )
{
    SRC_DATA src_data;
    float    input[length_input_short];
    float    output[length_output_short];
    int      ret;

    assert(src != NULL);

    src_short_to_float_array(input_short, input, length_input_short);

    src_data.data_in = input;
    src_data.data_out = output;
    src_data.input_frames = length_input_short;
    src_data.output_frames = length_output_short;
    src_data.end_of_input = 0;
    src_data.src_ratio = (float)output_sample_rate/input_sample_rate;

    ret = src_process(src, &src_data);
    assert(ret == 0);

    assert(src_data.output_frames_gen <= length_output_short);
    src_float_to_short_array(output, output_short, src_data.output_frames_gen);

    return src_data.output_frames_gen;
}


// Decimates samples using an algorithm that produces nice plots of
// speech signals at a low sample rate.  We want a low sample rate so
// we don't hammer the graphics system too hard.  Saves decimated data
// to a fifo for plotting on screen.

void resample_for_plot(struct FIFO *plotFifo, short buf[], int length, int fs)
{
    int decimation = fs/WAVEFORM_PLOT_FS;
    int nSamples, sample;
    int i, st, en, max, min;
    short dec_samples[length];

    nSamples = length/decimation;

    for(sample = 0; sample < nSamples; sample += 2)
    {
        st = decimation*sample;
        en = decimation*(sample+2);
        max = min = 0;
        for(i=st; i<en; i++ )
        {
            if (max < buf[i]) max = buf[i];
            if (min > buf[i]) min = buf[i];
        }
        dec_samples[sample] = max;
        dec_samples[sample+1] = min;
    }
    codec2_fifo_write(plotFifo, dec_samples, nSamples);
}


//---------------------------------------------------------------------------------------------
// Main real time procesing for tx and rx of FreeDV signals, run in its own thread
//---------------------------------------------------------------------------------------------

void txRxProcessing()
{
    wxStopWatch sw;

    paCallBackData  *cbData = g_rxUserdata;

    // Buffers re-used by tx and rx processing.  We take samples from
    // the sound card, and resample them for the freedv modem input
    // sample rate.  Typically the sound card is running at 48 or 44.1
    // kHz, and the modem at 8kHz, however some modems such as FreeDV
    // 2400A/B and the horus baloon telemetry run at 48 kHz.

    // allocate enough room for 20ms processing buffers at maximum
    // sample rate of 48 kHz.  Note these buffer are used by rx and tx
    // side processing
    
    short           infreedv[10*N48];
    short           insound_card[10*N48];
    short           outfreedv[10*N48];
    short           outsound_card[10*N48];
    int             nout, freedv_samplerate;
    int             nfreedv;

    // analog mode runs at the standard FS = 8000 Hz

    if (g_mode == -1) {
#ifdef __HORUS__
        freedv_samplerate = horus_get_Fs(g_horus);
#endif
    }
    else {
        if (g_analog) {
            freedv_samplerate = FS;
        } 
        else {
            freedv_samplerate = freedv_get_modem_sample_rate(g_pfreedv);
        }
    }

    //
    //  RX side processing --------------------------------------------
    //

    // while we have enough input samples available ...

    // attempt to read one processing buffer of receive samples
    // (20ms), number of samples is scaled for the sound card sample
    // rate, so we get the right number of samples for the output
    // decoded audio

    int nsam = g_soundCard1SampleRate * (float)g_modemInbufferSize/freedv_samplerate;
    assert(nsam <= 10*N48);
    assert(nsam != 0);
    while ((codec2_fifo_read(cbData->infifo1, insound_card, nsam) == 0) && ((g_half_duplex && !g_tx) || !g_half_duplex)) {

        /* convert sound card sample rate FreeDV input sample rate */
        
        nfreedv = resample(cbData->insrc1, infreedv, insound_card, freedv_samplerate, g_soundCard1SampleRate, N48, nsam);
        assert(nfreedv <= N48);

        // optionally save "from radio" signal (write demod input to file) ----------------------------
        // Really useful for testing and development as it allows us
        // to repeat tests using off air signals

        if (g_recFileFromRadio && (g_sfRecFile != NULL)) {
            //printf("g_recFromRadioSamples: %d  n8k: %d \n", g_recFromRadioSamples);
            if (g_recFromRadioSamples < (unsigned)nfreedv) {
                sf_write_short(g_sfRecFile, infreedv, g_recFromRadioSamples);
                wxCommandEvent event( wxEVT_COMMAND_MENU_SELECTED, g_recFileFromRadioEventId );
                // call stop/start record menu item, should be thread safe
                g_parent->GetEventHandler()->AddPendingEvent( event );
                g_recFromRadioSamples = 0;
            }
            else {
                sf_write_short(g_sfRecFile, infreedv, nfreedv);
                g_recFromRadioSamples -= nfreedv;
            }
        }

        // optionally read "from radio" signal from file (read demod input from file) -----------------

        if (g_playFileFromRadio && (g_sfPlayFileFromRadio != NULL)) {
            unsigned int nsf = nfreedv*g_sfFs/freedv_samplerate;
            short        insf[nsf];
            unsigned int n = sf_read_short(g_sfPlayFileFromRadio, insf, nsf);
            //fprintf(stderr, "resample %d to %d\n", g_sfFs, freedv_samplerate);
            nfreedv = resample(cbData->insrcsf, infreedv, insf, freedv_samplerate, g_sfFs, N48, nsf);
            assert(nfreedv <= N48);

            if (n == 0) {
                if (g_loopPlayFileFromRadio)
                    sf_seek(g_sfPlayFileFromRadio, 0, SEEK_SET);
                else {
                    printf("playFileFromRadio finished, issuing event!\n");
                    wxCommandEvent event( wxEVT_COMMAND_MENU_SELECTED, g_playFileFromRadioEventId );
                    // call stop/start play menu item, should be thread safe
                    g_parent->GetEventHandler()->AddPendingEvent( event );
                }
            }
        }

        resample_for_plot(g_plotDemodInFifo, infreedv, nfreedv, freedv_samplerate);

        if (g_mode != -1) {
            // send latest squelch level to FreeDV API, as it handles squelch internally

            freedv_set_squelch_en(g_pfreedv, g_SquelchActive);
            freedv_set_snr_squelch_thresh(g_pfreedv, g_SquelchLevel);
        }

        // Optional tone interferer -----------------------------------------------------

        if (wxGetApp().m_tone) {
            float w = 2.0*M_PI*wxGetApp().m_tone_freq_hz/freedv_samplerate;
            float s;
            int i;
            for(i=0; i<nfreedv; i++) {
                s = (float)wxGetApp().m_tone_amplitude*cos(g_tone_phase);   
                infreedv[i] += (int)s;             
                g_tone_phase += w;
                //fprintf(stderr, "%f\n", s);
            }
            g_tone_phase -= 2.0*M_PI*floor(g_tone_phase/(2.0*M_PI));                                         
        }

        // compute rx spectrum - do here so update rate is constant across modes -------

        // if necc, resample to Fs = 8kHz for spectrum and waterfall
        // TODO: for some future modes (like 2400A), it might be
        // useful to have different Fs spectrum
        
        COMP  rx_fdm[nfreedv];
        float rx_spec[MODEM_STATS_NSPEC];
        int i, nspec;
        for(i=0; i<nfreedv; i++) {
            rx_fdm[i].real = infreedv[i];
        }            
        if (freedv_samplerate == FS) {
            for(i=0; i<nfreedv; i++) {
                rx_fdm[i].real = infreedv[i];
            }
            nspec = nfreedv;
        } else {
            int   nfreedv_8kHz = nfreedv*FS/freedv_samplerate;
            short infreedv_8kHz[nfreedv_8kHz];
            nout = resample(g_spec_src, infreedv_8kHz, infreedv, FS, freedv_samplerate, nfreedv_8kHz, nfreedv);
            //fprintf(stderr, "resampling, nfreedv: %d nout: %d nfreedv_8kHz: %d \n", nfreedv, nout, nfreedv_8kHz);
            assert(nout <= nfreedv_8kHz);
            for(i=0; i<nout; i++) {
                rx_fdm[i].real = infreedv_8kHz[i];
            }
            nspec = nout;
        }

        modem_stats_get_rx_spectrum(&g_stats, rx_spec, rx_fdm, nspec);

        // Average rx spectrum data using a simple IIR low pass filter

        for(i = 0; i<MODEM_STATS_NSPEC; i++) {
            g_avmag[i] = BETA * g_avmag[i] + (1.0 - BETA) * rx_spec[i];
        }

        // Get some audio to send to headphones/speaker.  If in analog
        // mode we pass thru the "from radio" audio to the
        // headphones/speaker.

        if (g_analog) {
            memcpy(outfreedv, infreedv, sizeof(short)*nfreedv);
        }
        else {
            // Write 20ms chunks of input samples for modem rx processing

            codec2_fifo_write(cbData->rxinfifo, infreedv, nfreedv);
            per_frame_rx_processing(cbData->rxoutfifo, cbData->rxinfifo);
            
            // Read 20ms chunk of samples from modem rx processing,
            // this will typically be decoded output speech, and is
            // (currently at least) fixed at a sample rate of 8 kHz
            
            memset(outfreedv, 0, sizeof(short)*g_speechOutbufferSize);
            //fprintf(stderr, "rxoutfifo free: %d used: %d\n", codec2_fifo_free(cbData->rxoutfifo), codec2_fifo_used(cbData->rxoutfifo));
            codec2_fifo_read(cbData->rxoutfifo, outfreedv, g_speechOutbufferSize);
        }

        // Optional Spk Out EQ Filtering, need mutex as filter can change at run time from another thread

        g_mutexProtectingCallbackData.Lock();
        if (cbData->spkOutEQEnable) {
            sox_biquad_filter(cbData->sbqSpkOutBass,   outfreedv, outfreedv, g_speechOutbufferSize);
            sox_biquad_filter(cbData->sbqSpkOutTreble, outfreedv, outfreedv, g_speechOutbufferSize);
            sox_biquad_filter(cbData->sbqSpkOutMid,    outfreedv, outfreedv, g_speechOutbufferSize);
        }
        g_mutexProtectingCallbackData.Unlock();

        if (g_mode == -1)
            resample_for_plot(g_plotSpeechOutFifo, outfreedv, g_speechOutbufferSize, freedv_samplerate);            
        else
            resample_for_plot(g_plotSpeechOutFifo, outfreedv, g_speechOutbufferSize, freedv_get_speech_sample_rate(g_pfreedv));

        // resample to output sound card rate
        
        if (g_nSoundCards == 1) {
            //fprintf(stderr, "nout %d, outfifo1 free: %d used: %d \n", nout, codec2_fifo_free(cbData->outfifo1), codec2_fifo_used(cbData->outfifo1));            
            if (g_mode == -1) {
                // Horus demod is special case, just echo input samples as it's nice to hear the
                // off-air modem signal
                nout = resample(cbData->outsrc2, outsound_card, infreedv, g_soundCard1SampleRate, freedv_samplerate, N48, nfreedv);
                codec2_fifo_write(cbData->outfifo1, outsound_card, nout);
            }
            else {
                if (g_analog) /* special case */
                    nout = resample(cbData->outsrc2, outsound_card, outfreedv, g_soundCard1SampleRate, freedv_get_modem_sample_rate(g_pfreedv), N48, nfreedv);                    
                else
                    nout = resample(cbData->outsrc2, outsound_card, outfreedv, g_soundCard1SampleRate, freedv_get_speech_sample_rate(g_pfreedv), N48, g_speechOutbufferSize);
                codec2_fifo_write(cbData->outfifo1, outsound_card, nout);
            }
        }
        else {
            if (g_mode == -1) {
                // Horus demod is special case, just echo input samples as it's nice to hear the
                // off-air modem signal
                nout = resample(cbData->outsrc2, outsound_card, infreedv, g_soundCard1SampleRate, freedv_samplerate, N48, nfreedv);
                codec2_fifo_write(cbData->outfifo2, outsound_card, nout);
            }
            else {
                if (g_analog) /* special case */
                    nout = resample(cbData->outsrc2, outsound_card, outfreedv, g_soundCard2SampleRate, freedv_get_modem_sample_rate(g_pfreedv), N48, nfreedv);                    
                else
                    nout = resample(cbData->outsrc2, outsound_card, outfreedv, g_soundCard2SampleRate, freedv_get_speech_sample_rate(g_pfreedv), N48, g_speechOutbufferSize);
                codec2_fifo_write(cbData->outfifo2, outsound_card, nout);
            }
        }
    }

    //
    //  TX side processing --------------------------------------------
    //

    if ((g_mode != -1) && ((g_nSoundCards == 2) && ((g_half_duplex && g_tx) || !g_half_duplex))) {

        // This while loop locks the modulator to the sample rate of
        // sound card 1.  We want to make sure that modulator samples
        // are uninterrupted by differences in sample rate between
        // this sound card and sound card 2.

        // Run code inside this while loop as soon as we have enough
        // room for one frame of modem samples.  Aim is to keep
        // outfifo1 nice and full so we don't have any gaps ix tx
        // signal.

        unsigned int nsam_one_modem_frame = g_soundCard2SampleRate * freedv_get_n_nom_modem_samples(g_pfreedv)/freedv_samplerate;
        
 	if (g_dump_fifo_state) {
	  // If this drops to zero we have a problem as we will run out of output samples
	  // to send to the sound driver via PortAudio
	  fprintf(stderr, "outfifo1 used: %6d free: %6d nsam_one_modem_frame: %d\n",
                  codec2_fifo_used(cbData->outfifo1), codec2_fifo_free(cbData->outfifo1), nsam_one_modem_frame);
	}

        int nsam_in_48 = g_soundCard2SampleRate * freedv_get_n_speech_samples(g_pfreedv)/freedv_get_speech_sample_rate(g_pfreedv);
        assert(nsam_in_48 < 10*N48);
        while((unsigned)codec2_fifo_free(cbData->outfifo1) >= nsam_one_modem_frame) {

            // OK to generate a frame of modem output samples we need
            // an input frame of speech samples from the microphone.

            // infifo2 is written to by another sound card so it may
            // over or underflow, but we don't really care.  It will
            // just result in a short interruption in audio being fed
            // to codec2_enc, possibly making a click every now and
            // again in the decoded audio at the other end.

            // zero speech input just in case infifo2 underflows
            memset(insound_card, 0, nsam_in_48*sizeof(short));
            codec2_fifo_read(cbData->infifo2, insound_card, nsam_in_48);

            nout = resample(cbData->insrc2, infreedv, insound_card, freedv_get_speech_sample_rate(g_pfreedv), g_soundCard2SampleRate, 10*N48, nsam_in_48);

            // optionally use file for mic input signal
            if (g_playFileToMicIn && (g_sfPlayFile != NULL)) {
                int n = sf_read_short(g_sfPlayFile, infreedv, nout);
                //fprintf(stderr, "n: %d nout: %d\n", n, nout);
                if (n != nout) {
                    if (g_loopPlayFileToMicIn)
                        sf_seek(g_sfPlayFile, 0, SEEK_SET);
                    else {
                        wxCommandEvent event( wxEVT_COMMAND_MENU_SELECTED, g_playFileToMicInEventId );
                        // call stop/start play menu item, should be thread safe
                        g_parent->GetEventHandler()->AddPendingEvent( event );
                    }
                }
            }

            // Optional Speex pre-processor for acoustic noise reduction

            if (wxGetApp().m_speexpp_enable) {
                speex_preprocess_run(g_speex_st, infreedv);
            }

            // Optional Mic In EQ Filtering, need mutex as filter can change at run time

            g_mutexProtectingCallbackData.Lock();
            if (cbData->micInEQEnable) {
                sox_biquad_filter(cbData->sbqMicInBass, infreedv, infreedv, nout);
                sox_biquad_filter(cbData->sbqMicInTreble, infreedv, infreedv, nout);
                sox_biquad_filter(cbData->sbqMicInMid, infreedv, infreedv, nout);
            }
            g_mutexProtectingCallbackData.Unlock();

            resample_for_plot(g_plotSpeechInFifo, infreedv, nout, freedv_get_speech_sample_rate(g_pfreedv));

            nfreedv = freedv_get_n_nom_modem_samples(g_pfreedv);

            if (g_analog) {
                nfreedv = freedv_get_n_speech_samples(g_pfreedv);

                // Boost the "from mic" -> "to radio" audio in analog
                // mode.  The need for the gain was found by
                // experiment - analog SSB sounded too quiet compared
                // to digital. With digital voice we generally drive
                // the "to radio" (SSB radio mic input) at about 25%
                // of the peak level for normal SSB voice. So we
                // introduce 6dB gain to make analog SSB sound the
                // same level as the digital.  Watch out for clipping.
                for(int i=0; i<nfreedv; i++) {
                    float out = (float)infreedv[i]*2.0;
                    if (out > 32767) out = 32767.0;
                    if (out < -32767) out = -32767.0;
                    outfreedv[i] = out;
                }
            }
            else {
                COMP tx_fdm[nfreedv];
                COMP tx_fdm_offset[nfreedv];
                int  i;

                if (g_mode == FREEDV_MODE_800XA || g_mode == FREEDV_MODE_2400B) {
                    /* 800XA doesn't support complex output just yet */
                    freedv_tx(g_pfreedv, outfreedv, infreedv);
                }
                else {
                    freedv_comptx(g_pfreedv, tx_fdm, infreedv);
  
                    freq_shift_coh(tx_fdm_offset, tx_fdm, g_TxFreqOffsetHz, freedv_get_modem_sample_rate(g_pfreedv), &g_TxFreqOffsetPhaseRect, nfreedv);
                    for(i=0; i<nfreedv; i++)
                        outfreedv[i] = tx_fdm_offset[i].real;
                }
            }

            // Save modulated output file if requested
            if (g_recFileFromModulator && (g_sfRecFileFromModulator != NULL)) {
                if (g_recFromModulatorSamples < nfreedv) {
                    sf_write_short(g_sfRecFileFromModulator, outfreedv, g_recFromModulatorSamples);  // try infreedv to bypass codec and modem, was outfreedv
                     wxCommandEvent event( wxEVT_COMMAND_MENU_SELECTED, g_recFileFromModulatorEventId );
                    // call stop/start record menu item, should be thread safe
                    g_parent->GetEventHandler()->AddPendingEvent( event );
                    g_recFromModulatorSamples = 0;
                    g_recFileFromModulator = false;
                    sf_close(g_sfRecFileFromModulator);
                    wxPrintf("write mod output to file complete\n", g_recFromModulatorSamples);  // consider a popup
                }
                else {
                    sf_write_short(g_sfRecFileFromModulator, outfreedv, nfreedv);
                    g_recFromModulatorSamples -= nfreedv;
                }
            }

            // output one frame of modem signal

            if (g_analog)
                nout = resample(cbData->outsrc1, outsound_card, outfreedv, g_soundCard1SampleRate, freedv_get_speech_sample_rate(g_pfreedv), 10*N48, nfreedv);
            else
                nout = resample(cbData->outsrc1, outsound_card, outfreedv, g_soundCard1SampleRate, freedv_samplerate, 10*N48, nfreedv);
            if (g_dump_fifo_state) {
                fprintf(stderr, "  nout: %d\n", nout);
            }
            codec2_fifo_write(cbData->outfifo1, outsound_card, nout);
        }
    }
   
    if (g_dump_timing) {
        fprintf(stderr, "%4ld", sw.Time());
    }
}

//----------------------------------------------------------------
// per_frame_rx_processing()
//----------------------------------------------------------------

// We need the extra layer of fifos to decouple 20ms processing loop
// used for common processing across modes from variable size demod
// processing.  For example demod nin is time varying to account for
// sample clock and timing offsets and the demod processing frame
// varies betwwen modes, e.g. 20ms (1600) or 160ms (700D)

void per_frame_rx_processing(
                             FIFO    *output_fifo,   // decoded speech samples
                             FIFO    *input_fifo
                             )
{
    int i;

    if (g_mode == -1) {
#ifdef __HORUS__
        // Horus processing ---------------------------------------------------

        int   max_nin = horus_get_max_demod_in(g_horus);
        int   max_ascii_out = horus_get_max_ascii_out_len(g_horus);
        //int   max_nout = max_nin*FS/horus_get_Fs(g_horus);
        short input_buf[max_nin];
        char  ascii_out[max_ascii_out];
        int   nin;

        nin = horus_nin(g_horus);
        while (codec2_fifo_read(input_fifo, input_buf, nin) == 0) {
            if (g_freedv_verbose) {
                fprintf(stderr, "per_frame: nin = %d input_fifo free: %d used: %d\n", nin, codec2_fifo_free(input_fifo), codec2_fifo_used(input_fifo));
            }
            if (horus_rx(g_horus, ascii_out, input_buf, 0)) {
                // unfort fifo deals with shorts
                short ch;
                for (i=0; i<(int)strlen(ascii_out); i++) {
                    ch = (short)ascii_out[i];
                    codec2_fifo_write(g_rxDataOutFifo, &ch, 1);
                }
                ch = 13; // CR to make it appear on txt line
                codec2_fifo_write(g_rxDataOutFifo, &ch, 1);

                UDPSend(wxGetApp().m_udp_port, ascii_out, strlen(ascii_out));
                horus_get_modem_extended_stats(g_horus, &g_stats);
                if (g_freedv_verbose) {
                    fprintf(stderr, "  fsk f_est: ");
                    for(i=0; i<horus_get_mFSK(g_horus); i++) {
                        fprintf(stderr, "%5.0f ", g_stats.f_est[i]);
                    }
                    fprintf(stderr, "\n");
                }
            }

            /* need to know how many samples demod wants next time */

            nin = horus_nin(g_horus);
            assert(nin <= max_nin);

            #ifdef TT
            // Just echo modem audio as it's useful to listen to the modem signal

            // resample to 8kHz.  This is a bit annoying as it will
            // then get resampled back to the sound card rate in
            // txRxProcessing().  TODO: we need to add support to the
            // freedv_api for sample rate at the speech side, not just
            // the modem side
            
            nout = resample(g_horus_src, output_buf, input_buf, FS, horus_get_Fs(g_horus), max_nout, nin);
            assert(nout <= max_nout);
            //fprintf(stderr, "nin: %d nout_max: %d nout: %d\n", nin, nout, nout_max);
            codec2_fifo_write(output_fifo, output_buf, nout);
            #endif
        }
#endif
    }
    else {
        // FreeDV processing ----------------------------------------------------

        short               input_buf[freedv_get_n_max_modem_samples(g_pfreedv)];
        short               output_buf[freedv_get_n_speech_samples(g_pfreedv)];
        COMP                rx_fdm[freedv_get_n_max_modem_samples(g_pfreedv)];
        COMP                rx_fdm_offset[freedv_get_n_max_modem_samples(g_pfreedv)];
        int                 nin, nout;

        nin = freedv_nin(g_pfreedv);
        //fprintf(stderr, "nin: %d max_modem_samples: %d\n", nin, freedv_get_n_max_modem_samples(g_pfreedv));
        while (codec2_fifo_read(input_fifo, input_buf, nin) == 0) {
            assert(nin <= freedv_get_n_max_modem_samples(g_pfreedv));

            #ifdef FTEST
            fwrite(input_buf, sizeof(short), nin, ftest);
            #endif
            
            // demod per frame processing

            for(i=0; i<nin; i++) {
                rx_fdm[i].real = (float)input_buf[i];
                rx_fdm[i].imag = 0.0;
            }
       
            // Optional channel noise

            if (g_channel_noise) {
                fdmdv_simulate_channel(&g_sig_pwr_av, rx_fdm, nin, wxGetApp().m_noise_snr);
            }

            // Optional frequency shifting
            
            freq_shift_coh(rx_fdm_offset, rx_fdm, g_RxFreqOffsetHz, freedv_get_modem_sample_rate(g_pfreedv), &g_RxFreqOffsetPhaseRect, nin);
            if (g_dump_timing) {
                fprintf(stderr,"  rx");
            }
            nout = freedv_comprx(g_pfreedv, output_buf, rx_fdm_offset);

            codec2_fifo_write(output_fifo, output_buf, nout);
            //fprintf(stderr, "ret = %d\n", ret);
            
            nin = freedv_nin(g_pfreedv);
            g_State = freedv_get_sync(g_pfreedv);
            g_interleaverSyncState = freedv_get_sync_interleaver(g_pfreedv);

            //fprintf(g_logfile, "g_State: %d g_stats.sync: %d snr: %f \n", g_State, g_stats.sync, f->snr);

            // grab extended stats so we can plot spectrum, scatter diagram etc

            freedv_get_modem_extended_stats(g_pfreedv, &g_stats);
        }
    }


}


//-------------------------------------------------------------------------
// rxCallback()
//
// Sound card 1 callback from PortAudio, that is used for processing rx
// side:
//
// + infifo1 is the "from radio" off air modem signal from the SSB rx that we send to the demod.
// + In single sound card mode outfifo1 is the "to speaker/headphones" decoded speech output.
// + In dual sound card mode outfifo1 is the "to radio" modulator signal to the SSB tx.
//
//-------------------------------------------------------------------------

int MainFrame::rxCallback(
                            const void      *inputBuffer,
                            void            *outputBuffer,
                            unsigned long   framesPerBuffer,
                            const PaStreamCallbackTimeInfo* timeInfo,
                            PaStreamCallbackFlags statusFlags,
                            void            *userData
                         )
{
    paCallBackData  *cbData = (paCallBackData*)userData;
    short           *rptr    = (short*)inputBuffer;
    short           *wptr    = (short*)outputBuffer;

    short           indata[MAX_FPB];
    short           outdata[MAX_FPB];

    unsigned int    i;

    (void) timeInfo;
    (void) statusFlags;

    if (statusFlags & 0x1) {  // input underflow
        g_PAstatus1[0]++;  
    }
    if (statusFlags & 0x2) {  // input overflow
        g_PAstatus1[1]++;
    }
    if (statusFlags & 0x4) {  // output underflow
        g_PAstatus1[2]++;
    }
    if (statusFlags & 0x8) {  // output overflow
        g_PAstatus1[3]++;
    }
   
    g_PAframesPerBuffer1 = framesPerBuffer;

    //
    //  RX side processing --------------------------------------------
    //

    // assemble a mono buffer and write to FIFO

    assert(framesPerBuffer < MAX_FPB);

    if (rptr) {
        for(i = 0; i < framesPerBuffer; i++, rptr += cbData->inputChannels1)
            indata[i] = rptr[0];                       
        if (codec2_fifo_write(cbData->infifo1, indata, framesPerBuffer)) {
            g_infifo1_full++;
        }
    }

    // OK now set up output samples for this callback

    if (wptr) {
         if (codec2_fifo_read(cbData->outfifo1, outdata, framesPerBuffer) == 0) {

            // write signal to both channels

            for(i = 0; i < framesPerBuffer; i++, wptr += 2) {
                if (cbData->leftChannelVoxTone) {
                    cbData->voxTonePhase += 2.0*M_PI*VOX_TONE_FREQ/g_soundCard1SampleRate;
                    cbData->voxTonePhase -= 2.0*M_PI*floor(cbData->voxTonePhase/(2.0*M_PI));
                    wptr[0] = VOX_TONE_AMP*cos(cbData->voxTonePhase);                              
                }
                else
                    wptr[0] = outdata[i];
                               
                wptr[1] = outdata[i];
            }
        }
        else {
            g_outfifo1_empty++;
            // zero output if no data available
            for(i = 0; i < framesPerBuffer; i++, wptr += 2) {
                wptr[0] = 0;
                wptr[1] = 0;
            }
        }
    }

    return paContinue;
}


//-------------------------------------------------------------------------
// txCallback()
//-------------------------------------------------------------------------
int MainFrame::txCallback(
                            const void *inputBuffer,
                            void *outputBuffer,
                            unsigned long framesPerBuffer,
                            const PaStreamCallbackTimeInfo *outTime,
                            PaStreamCallbackFlags statusFlags,
                            void *userData
                        )
{
    paCallBackData  *cbData = (paCallBackData*)userData;
    unsigned int    i;
    short           *rptr    = (short*)inputBuffer;
    short           *wptr    = (short*)outputBuffer;
    short           indata[MAX_FPB];
    short           outdata[MAX_FPB];

    if (statusFlags & 0x1) { // input underflow
        g_PAstatus2[0]++;
    }
    if (statusFlags & 0x2) { // input overflow
        g_PAstatus2[1]++;
    }
    if (statusFlags & 0x4) { // output underflow
        g_PAstatus2[2]++;
    }
    if (statusFlags & 0x8) { // output overflow
        g_PAstatus2[3]++;
    }
    
    g_PAframesPerBuffer2 = framesPerBuffer;

    // assemble a mono buffer and write to FIFO

    assert(framesPerBuffer < MAX_FPB);
    
    if (rptr) {
        for(i = 0; i < framesPerBuffer; i++, rptr += cbData->inputChannels2)
            indata[i] = rptr[0];                        
        if (codec2_fifo_write(cbData->infifo2, indata, framesPerBuffer)) {
            g_infifo2_full++;
        }
    }

    // OK now set up output samples for this callback

    if (wptr) {
        if (codec2_fifo_read(cbData->outfifo2, outdata, framesPerBuffer) == 0) {
		
            // write signal to both channels */
            for(i = 0; i < framesPerBuffer; i++, wptr += 2) {
                wptr[0] = outdata[i];
                wptr[1] = outdata[i];
            }
        }
        else {
            g_outfifo2_empty++;
            // zero output if no data available
            for(i = 0; i < framesPerBuffer; i++, wptr += 2) {
                wptr[0] = 0;
                wptr[1] = 0;
            }
        }
    }
    
    return paContinue;
}

// Callback from plot_spectrum & plot_waterfall.  would be nice to
// work out a way to do this without globals.

void fdmdv2_clickTune(float freq) {

    // The demod is hard-wired to expect a centre frequency of
    // FDMDV_FCENTRE.  So we want to take the signal centered on the
    // click tune freq and re-centre it on FDMDV_FCENTRE.  For example
    // if the click tune freq is 1500Hz, and FDMDV_CENTRE is 1200 Hz,
    // we need to shift the input signal centred on 1500Hz down to
    // 1200Hz, an offset of -300Hz.

    // Bit of an "indent" as we are often trying to get it back
    // exactly in the centre

    if (fabs(FDMDV_FCENTRE - freq) < 10.0) {
        freq = FDMDV_FCENTRE;
        fprintf(stderr, "indent!\n");
    }

    if (g_split) {
        g_RxFreqOffsetHz = FDMDV_FCENTRE - freq;
    }
    else {
        g_TxFreqOffsetHz = freq - FDMDV_FCENTRE;
        g_RxFreqOffsetHz = FDMDV_FCENTRE - freq;
    }
    fprintf(stderr, "g_TxFreqOffsetHz: %f g_RxFreqOffsetHz: %f\n", g_TxFreqOffsetHz, g_RxFreqOffsetHz);
}

//----------------------------------------------------------------
// OpenSerialPort()
//----------------------------------------------------------------

void MainFrame::OpenSerialPort(void)
{
    Serialport *serialport = wxGetApp().m_serialport;

    if(!wxGetApp().m_strRigCtrlPort.IsEmpty()) {
       serialport->openport(wxGetApp().m_strRigCtrlPort.c_str(), 
                            wxGetApp().m_boolUseRTS, 
                            wxGetApp().m_boolRTSPos, 
                            wxGetApp().m_boolUseDTR,
                            wxGetApp().m_boolDTRPos);
       if (serialport->isopen()) {
            // always start PTT in Rx state
           serialport->ptt(false);
       }
       else {
           wxMessageBox("Couldn't open Serial Port", wxT("About"), wxOK | wxICON_ERROR, this);
       }
    }
}


//----------------------------------------------------------------
// CloseSerialPort()
//----------------------------------------------------------------

void MainFrame::CloseSerialPort(void)
{
    Serialport *serialport = wxGetApp().m_serialport;
    if (serialport->isopen()) {
        // always end with PTT in rx state

        serialport->ptt(false);
        serialport->closeport();
    }
}

//
// checkAvxSupport
//
// Tests the underlying platform for AVX support.  2020 needs AVX support to run
// in real-time, and old processors do not offer AVX support
//

#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || defined(_M_IX86)
void MainFrame::checkAvxSupport(void)
{

    isAvxPresent = false;
    uint32_t eax, ebx, ecx, edx;
    eax = ebx = ecx = edx = 0;
    __cpuid(1, eax, ebx, ecx, edx);

    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?
    }
}
#else
void MainFrame::checkAvxSupport(void)
{
    isAvxPresent = false;
}
#endif

#ifdef __UDP_SUPPORT__

//----------------------------------------------------------------
// PollUDP() - see if any commands on UDP port
//----------------------------------------------------------------

// test this puppy with netcat: 
//   $ echo "hello" | nc -u -q1 localhost 3000

int MainFrame::PollUDP(void)
{
    // this will block until message received, so we put it in it's own thread

    char buf[1024];
    char reply[80];
    size_t n = m_udp_sock->RecvFrom(m_udp_addr, buf, sizeof(buf)).LastCount();

    if (n) {
        wxString bufstr = wxString::From8BitData(buf, n);
        bufstr.Trim();
        wxString ipaddr = m_udp_addr.IPAddress();
        printf("Received: \"%s\" from %s:%u\n",
               (const char *)bufstr.c_str(),
               (const char *)ipaddr.c_str(), m_udp_addr.Service());

        // for security only accept commands from local host

        sprintf(reply,"nope\n");
        if (ipaddr.Cmp(_("127.0.0.1")) == 0) {

            // process commands

            if (bufstr.Cmp(_("restore")) == 0) {
                m_schedule_restore = true;  // Make Restore happen in main thread to avoid crashing
                sprintf(reply,"ok\n");
            }
                
            wxString itemToSet, val;
            if (bufstr.StartsWith(_("set "), &itemToSet)) {
                if (itemToSet.StartsWith("txtmsg ", &val)) {
                    // note: if options dialog is open this will get overwritten
                    wxGetApp().m_callSign = val;
                }  
                sprintf(reply,"ok\n");
            }
            if (bufstr.StartsWith(_("ptton"), &itemToSet)) {
                // note: if options dialog is open this will get overwritten
                m_btnTogPTT->SetValue(true);
                togglePTT();
                sprintf(reply,"ok\n");
            }
            if (bufstr.StartsWith(_("pttoff"), &itemToSet)) {
                // note: if options dialog is open this will get overwritten
                m_btnTogPTT->SetValue(false);
                togglePTT();
                sprintf(reply,"ok\n");
            }
                
        }
        else {
            printf("We only accept messages from locahost!\n");
        }

       if ( m_udp_sock->SendTo(m_udp_addr, reply, strlen(reply)).LastCount() != strlen(reply)) {
           printf("ERROR: failed to send data\n");
        }
    }

    return n;
}

void MainFrame::startUDPThread(void) {
    fprintf(stderr, "starting UDP thread!\n");
    m_UDPThread = new UDPThread;
    m_UDPThread->mf = this;
    if (m_UDPThread->Create() != wxTHREAD_NO_ERROR ) {
        wxLogError(wxT("Can't create thread!"));
    }
    if (m_UDPThread->Run() != wxTHREAD_NO_ERROR ) {
        wxLogError(wxT("Can't start thread!"));
        delete m_UDPThread;
    }
}

void MainFrame::stopUDPThread(void) {
    printf("stopping UDP thread!\n");
    if ((m_UDPThread != NULL) && m_UDPThread->m_run) {
        m_UDPThread->m_run = 0;
        m_UDPThread->Wait();
        m_UDPThread = NULL;
    }
}

void *UDPThread::Entry() {
    //fprintf(stderr, "UDP thread started!\n");
    while (m_run) {
        if (wxGetApp().m_udp_enable) {
            printf("m_udp_enable\n");
            mf->m_udp_addr.Service(wxGetApp().m_udp_port);
            mf->m_udp_sock = new wxDatagramSocket(mf->m_udp_addr, wxSOCKET_NOWAIT);

            while (m_run && wxGetApp().m_udp_enable) {
                if (mf->PollUDP() == 0) {
                    wxThread::Sleep(20);
                }
            }

            delete mf->m_udp_sock;
        }
        wxThread::Sleep(20);
    }
    return NULL;
}

#endif

char my_get_next_tx_char(void *callback_state) {
    short ch = 0;
    
    codec2_fifo_read(g_txDataInFifo, &ch, 1);
    //fprintf(stderr, "get_next_tx_char: %c\n", (char)ch);
    return (char)ch;
}

void my_put_next_rx_char(void *callback_state, char c) {
    short ch = (short)c;
    //fprintf(stderr, "put_next_rx_char: %c\n", (char)c);
    codec2_fifo_write(g_rxDataOutFifo, &ch, 1);
}

// Callback from FreeDv API to update error plots

void my_freedv_put_error_pattern(void *state, short error_pattern[], int sz_error_pattern) {
    codec2_fifo_write(g_error_pattern_fifo, error_pattern, sz_error_pattern);
    //fprintf(stderr, "my_freedv_put_error_pattern: sz_error_pattern: %d ret: %d used: %d\n", 
    //        sz_error_pattern, ret, codec2_fifo_used(g_error_pattern_fifo) );
}

void freq_shift_coh(COMP rx_fdm_fcorr[], COMP rx_fdm[], float foff, float Fs, COMP *foff_phase_rect, int nin)
{
    COMP  foff_rect;
    float mag;
    int   i;

    foff_rect.real = cosf(2.0*M_PI*foff/Fs);
    foff_rect.imag = sinf(2.0*M_PI*foff/Fs);
    for(i=0; i<nin; i++) {
	*foff_phase_rect = cmult(*foff_phase_rect, foff_rect);
	rx_fdm_fcorr[i] = cmult(rx_fdm[i], *foff_phase_rect);
    }

    /* normalise digital oscilator as the magnitude can drift over time */

    mag = cabsolute(*foff_phase_rect);
    foff_phase_rect->real /= mag;	 
    foff_phase_rect->imag /= mag;	 
}

int plugin_get_persistant(char name[], char value[]) {
    wxString n,v;
    int i;

    for(i=0; i<wxGetApp().m_numPlugInParam; i++) {

        n = wxGetApp().m_plugInParamName[i];

        if (strcmp(n.mb_str().data(), name) == 0) {
            v = wxGetApp().m_txtPlugInParam[i];
            strcpy(value, v.mb_str().data());
            fprintf(stderr, "plugin_get_persistant called name: %s value: %s\n", name, v.mb_str().data());
        }
    }

    return 0;
}


/*
  Sending simple message via UDP

  http://cool-emerald.blogspot.com.au/2018/01/udptcp-socket-programming-with-wxwidgets.html#udp
*/


void UDPInit(void) {
    // Create the socket
    
    wxIPV4address addr_rx;
    addr_rx.AnyAddress();
    //addr_rx.Service(3000);
    g_sock = new wxDatagramSocket(addr_rx);

    // We use IsOk() here to see if the server is really listening

    if (!g_sock->IsOk()) {
        fprintf(stderr, "UDPInit: Could not listen at the specified port !\n");
        return;
    }
    
    wxIPV4address addrReal;
    if (!g_sock->GetLocal(addrReal)){
        fprintf(stderr, "UDPInit: Couldn't get the address we bound to\n");
    }
    else {
        fprintf(stderr, "Server listening at %s:%u \n", (const char*)addrReal.IPAddress().c_str(), addrReal.Service());
    }
}

void UDPSend(int port, char *buf, unsigned int n) {
    fprintf(stderr, "UDPSend buf: %s n: %d\n", buf, n);

    wxIPV4address addr_tx;
    addr_tx.Hostname("localhost");
    addr_tx.Service(port);
 
    if ( g_sock->SendTo(addr_tx, (const void*)buf, n).LastCount() != n ) {
        fprintf(stderr, "UDPSend: failed to send data");
        return;
    }
}
