// -*- C++ -*- generated by wxGlade 0.3.1 on Wed Sep 29 16:09:00 2004
/*
 * Copyright (C) 2008 Vaclav Peroutka <vaclavpe@seznam.cz>
 *
 * Licensed under the GNU General Public License Version 2
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include "AudMeS.h"

#include <math.h>

#ifdef __WXMSW__
#include <windows.h>
#endif
#include <libfccp/csv.h>

#include <atomic>
#include <fstream>
#include <iostream>

#include "dlg_audiointerface.h"
#include "event_ids.h"
#include "fourier.h"

wxIMPLEMENT_CLASS(MainFrame, wxFrame);

wxBEGIN_EVENT_TABLE(MainFrame, wxFrame)
  EVT_TOGGLEBUTTON(ID_SPANSTART, MainFrame::OnSpanStart)
  EVT_TOGGLEBUTTON(ID_GENSTART, MainFrame::OnGenStart)
  EVT_TOGGLEBUTTON(ID_OSCSTART, MainFrame::OnOscStart)
  EVT_TOGGLEBUTTON(ID_FRMSTART, MainFrame::OnFrmStart)
  EVT_MENU(wxID_ABOUT, MainFrame::OnAboutClick)
  EVT_MENU(wxID_EXIT, MainFrame::OnExitClick)
  EVT_CLOSE(MainFrame::OnClose)
  EVT_MENU(ID_SNDCARD, MainFrame::OnSelectSndCard)
  EVT_TIMER(ID_TIMERID, MainFrame::OnTimer)
  EVT_CHECKBOX(ID_GENLENB, MainFrame::OnGeneratorChanged)
  EVT_CHECKBOX(ID_GENRENB, MainFrame::OnGeneratorChanged)
  EVT_CHECKBOX(ID_GENSYNC, MainFrame::OnGeneratorChanged)
  EVT_TEXT_ENTER(ID_GENPHASE, MainFrame::OnGeneratorChanged)
  EVT_COMMAND_SCROLL(ID_GENLFREQ, MainFrame::OnGenScrollLChanged)
  EVT_COMMAND_SCROLL(ID_GENRFREQ, MainFrame::OnGenScrollRChanged)
  EVT_COMMAND_SCROLL(ID_GENLAMP, MainFrame::OnGenScrollChanged)
  EVT_COMMAND_SCROLL(ID_GENRAMP, MainFrame::OnGenScrollChanged)
  EVT_CHOICE(ID_OSCLTRIG, MainFrame::OnOscChoiceChanged)
  EVT_CHOICE(ID_OSCRTRIG, MainFrame::OnOscChoiceChanged)
  EVT_TEXT_ENTER(ID_TXT_FREQ_L, MainFrame::OnTxtFreqLChanged)
  EVT_TEXT_ENTER(ID_TXT_FREQ_R, MainFrame::OnTxtFreqRChanged)
  EVT_CHOICE(ID_GENSHP_L, MainFrame::OnGeneratorChanged)
  EVT_CHOICE(ID_GENSHP_R, MainFrame::OnGeneratorChanged)
  EVT_MENU(wxID_OPEN, MainFrame::OnOpenClick)
  EVT_MENU(wxID_SAVE, MainFrame::OnSaveClick)
  EVT_MENU(wxID_SAVEAS, MainFrame::OnSaveAsClick)
  EVT_MENU(ID_LOAD_FRM, MainFrame::OnLoadFRM)
  EVT_MENU(ID_SAVE_FRM, MainFrame::OnSaveFRM)
  EVT_MENU(ID_SAVE_SPE, MainFrame::OnSaveSPE)
  EVT_MENU(ID_SAVE_OSC, MainFrame::OnSaveOSC)
  EVT_BUTTON(ID_AUTOCAL, MainFrame::OnAutoCalClick)
  EVT_TOGGLEBUTTON(ID_SINC, MainFrame::OnAutoSincClick)
  EVT_CHOICE(ID_OSCXSCALE, MainFrame::OnOscXScaleChanged)
  EVT_CHOICE(ID_FFTLENGTH, MainFrame::OnOscXScaleChanged)
  EVT_CHOICE(ID_FFTAVG, MainFrame::OnFFTAvgChanged)
  EVT_CHOICE(ID_FFTREF, MainFrame::OnFFTScaleChanged)
  EVT_CHOICE(ID_FFTDBDIV, MainFrame::OnFFTScaleChanged)
wxEND_EVENT_TABLE()

float* g_OscBuffer_Left;
float* g_OscBuffer_Right;
long int g_OscBufferPosition;

float* g_SpeBuffer_Left;
float* g_SpeBuffer_Right;
long int g_SpeBufferPosition;

std::atomic<bool> g_OscBufferChanged{false};
std::atomic<bool> g_SpeBufferChanged{false};

///////////////////////////////////////////////////////////////////////
MainFrame::MainFrame(wxWindow* parent, int id, const wxString& title, const wxPoint& pos,
                     const wxSize& size, long WXUNUSED(style))
    : wxFrame(parent, id, title, pos, size, wxDEFAULT_FRAME_STYLE | wxFULL_REPAINT_ON_RESIZE) {
  // begin wxGlade: MainFrame::MainFrame
  notebook_1 = new wxNotebook(this, -1, wxDefaultPosition, wxDefaultSize, wxNB_BOTTOM);
  notebook_1_spe = new wxPanel(notebook_1, -1);
  notebook_1_osc = new wxPanel(notebook_1, -1);
  notebook_1_gen = new wxPanel(notebook_1, -1);
  notebook_1_frm = new wxPanel(notebook_1, -1);
  sizer_gen_l_staticbox = new wxStaticBox(notebook_1_gen, -1, wxT("Left channel"));
  sizer_gen_r_staticbox = new wxStaticBox(notebook_1_gen, -1, wxT("Right channel"));
  sizer_osc_l_staticbox = new wxStaticBox(notebook_1_osc, -1, wxT("Vertical Left"));
  sizer_osc_r_staticbox = new wxStaticBox(notebook_1_osc, -1, wxT("Vertical Right"));
  sizer_osc_h_staticbox = new wxStaticBox(notebook_1_osc, -1, wxT("Horizontal"));
  sizer_spe_fft_staticbox = new wxStaticBox(notebook_1_spe, -1, wxT("FFT"));
  sizer_spe_disp_staticbox = new wxStaticBox(notebook_1_spe, -1, wxT("Display"));
  sizer_spe_scale_staticbox = new wxStaticBox(notebook_1_spe, -1, wxT("Scale"));
  frame_1_menubar = new wxMenuBar();
  wxMenu* wxglade_tmp_menu_1 = new wxMenu();
  wxglade_tmp_menu_1->Append(wxID_OPEN, wxT("&Open config...\tAlt+O"), wxT(""), wxITEM_NORMAL);
  wxglade_tmp_menu_1->Append(wxID_SAVE, wxT("&Save config...\tAlt+S"), wxT(""), wxITEM_NORMAL);
  wxglade_tmp_menu_1->Append(wxID_SAVEAS, wxT("Save &As"), wxT(""), wxITEM_NORMAL);
  wxglade_tmp_menu_1->Append(ID_LOAD_FRM, wxT("Load freq.resp."), wxT(""), wxITEM_NORMAL);
  wxglade_tmp_menu_1->Append(ID_SAVE_FRM, wxT("Save freq.resp."), wxT(""), wxITEM_NORMAL);
  wxglade_tmp_menu_1->Append(ID_SAVE_SPE, wxT("Save spectrum"), wxT(""), wxITEM_NORMAL);
  wxglade_tmp_menu_1->Append(ID_SAVE_OSC, wxT("Save oscillogram"), wxT(""), wxITEM_NORMAL);
  wxglade_tmp_menu_1->AppendSeparator();
  wxglade_tmp_menu_1->Append(wxID_EXIT, wxT("&Close\tAlt+F4"), wxT(""), wxITEM_NORMAL);
  frame_1_menubar->Append(wxglade_tmp_menu_1, wxT("&File"));
  wxMenu* wxglade_tmp_menu_2 = new wxMenu();
  wxglade_tmp_menu_2->Append(ID_SNDCARD, wxT("Audio &Interface Configuration..."), wxT(""),
                             wxITEM_NORMAL);
  frame_1_menubar->Append(wxglade_tmp_menu_2, wxT("&Tools"));
  wxMenu* wxglade_tmp_menu_3 = new wxMenu();
  wxglade_tmp_menu_3->Append(wxID_ABOUT, wxT("&About..."), wxT(""), wxITEM_NORMAL);
  frame_1_menubar->Append(wxglade_tmp_menu_3, wxT("&Help"));
  SetMenuBar(frame_1_menubar);

  frame_1_statusbar = CreateStatusBar(1, 0);

  /* generator panel */
  checkbox_l_en = new wxCheckBox(notebook_1_gen, ID_GENLENB, wxT("Enable Output"));
  label_gen_wave_l = new wxStaticText(notebook_1_gen, -1, wxT("Waveform: "));
  const wxString choice_l_wav_choices[] = {wxT("Sine"),     wxT("Rectangular"), wxT("Saw"),
                                           wxT("Triangle"), wxT("Wh-Noise"),    wxT("Wobble")};
  choice_l_wav = new wxChoice(notebook_1_gen, ID_GENSHP_L, wxDefaultPosition, wxDefaultSize, 6,
                              choice_l_wav_choices, 0);
  label__gen_freq_l = new wxStaticText(notebook_1_gen, -1, wxT("Frequency [20..20000Hz]: "));
  slide_l_fr = new wxSlider(notebook_1_gen, ID_GENLFREQ, 80, 0, 200);
  label_gen_ampl_l = new wxStaticText(notebook_1_gen, -1, wxT("Amplitude [0..-60dB]: "));
  slide_l_am = new wxSlider(notebook_1_gen, ID_GENLAMP, 0, -60, 0);

  checkbox_r_en = new wxCheckBox(notebook_1_gen, ID_GENRENB, wxT("Enable Output"));
  label_gen_wave_r = new wxStaticText(notebook_1_gen, -1, wxT("Waveform: "));
  const wxString choice_r_wav_choices[] = {wxT("Sine"),     wxT("Rectangular"), wxT("Saw"),
                                           wxT("Triangle"), wxT("Wh-Noise"),    wxT("Wobble")};
  choice_r_wav = new wxChoice(notebook_1_gen, ID_GENSHP_R, wxDefaultPosition, wxDefaultSize, 6,
                              choice_r_wav_choices, 0);
  label_gen_freq_r = new wxStaticText(notebook_1_gen, -1, wxT("Frequency [20..20000Hz]: "));
  slide_r_fr = new wxSlider(notebook_1_gen, ID_GENRFREQ, 80, 0, 200);
  label_gen_ampl_r = new wxStaticText(notebook_1_gen, -1, wxT("Amplitude [0..-60dB]: "));
  slide_r_am = new wxSlider(notebook_1_gen, ID_GENRAMP, 0, -60, 0);
  button_gen_start = new wxToggleButton(notebook_1_gen, ID_GENSTART, wxT("Start"));

  checkbox_gen_sync = new wxCheckBox(notebook_1_gen, ID_GENSYNC, wxT("L and R are synchronized"));
  label_gen_sync =
      new wxStaticText(notebook_1_gen, -1, wxT("Phase between L and R [0..360 degrees]: "));
  text_gen_sync = new wxTextCtrl(notebook_1_gen, ID_GENPHASE, wxT("0"), wxDefaultPosition,
                                 wxDefaultSize, wxTE_PROCESS_ENTER);

  txt_freq_l = new wxTextCtrl(notebook_1_gen, ID_TXT_FREQ_L, wxT("315.0"), wxDefaultPosition,
                              wxDefaultSize, wxTE_PROCESS_ENTER);
  txt_freq_r = new wxTextCtrl(notebook_1_gen, ID_TXT_FREQ_R, wxT("315.0"), wxDefaultPosition,
                              wxDefaultSize, wxTE_PROCESS_ENTER);

  /* oscilloscope panel */
  window_osc = new CtrlOScope(notebook_1_osc, _T(""), _T(""));
  label_osc_div_l = new wxStaticText(notebook_1_osc, -1, wxT("[V/div]: "));
  const wxString choice_osc_l_res_choices[] = {
      wxT("0.2"), wxT("0.1"),  wxT("50m"),  wxT("20m"),  wxT("10m"), wxT("5m"),  wxT("2m"),
      wxT("1m"),  wxT("500u"), wxT("200u"), wxT("100u"), wxT("50u"), wxT("20u"), wxT("10u"),
  };
  choice_osc_l_res = new wxChoice(notebook_1_osc, -1, wxDefaultPosition, wxDefaultSize, 14,
                                  choice_osc_l_res_choices, 0);
  label_osc_off_l = new wxStaticText(notebook_1_osc, -1, wxT("Offset: "));
  const wxString choice_osc_l_off_choices[] = {wxT("1"),    wxT("0.8"),  wxT("0.6"),  wxT("0.4"),
                                               wxT("0.2"),  wxT("0"),    wxT("-0.2"), wxT("-0.4"),
                                               wxT("-0.6"), wxT("-0.8"), wxT("-1")};
  choice_osc_l_off = new wxChoice(notebook_1_osc, -1, wxDefaultPosition, wxDefaultSize, 11,
                                  choice_osc_l_off_choices, 0);

  label_osc_div_r = new wxStaticText(notebook_1_osc, -1, wxT("[V/div]: "));
  const wxString choice_osc_r_res_choices[] = {
      wxT("0.2"), wxT("0.1"),  wxT("50m"),  wxT("20m"),  wxT("10m"), wxT("5m"),  wxT("2m"),
      wxT("1m"),  wxT("500u"), wxT("200u"), wxT("100u"), wxT("50u"), wxT("20u"), wxT("10u"),
  };
  choice_osc_r_res = new wxChoice(notebook_1_osc, -1, wxDefaultPosition, wxDefaultSize, 14,
                                  choice_osc_r_res_choices, 0);
  label_osc_off_r = new wxStaticText(notebook_1_osc, -1, wxT("Offset: "));
  const wxString choice_osc_r_off_choices[] = {wxT("1"),    wxT("0.8"),  wxT("0.6"),  wxT("0.4"),
                                               wxT("0.2"),  wxT("0"),    wxT("-0.2"), wxT("-0.4"),
                                               wxT("-0.6"), wxT("-0.8"), wxT("-1")};
  choice_osc_r_off = new wxChoice(notebook_1_osc, -1, wxDefaultPosition, wxDefaultSize, 11,
                                  choice_osc_r_off_choices, 0);

  button_autocalibrate = new wxButton(notebook_1_osc, ID_AUTOCAL, wxT("V Autorange"));
  button_sinc = new wxToggleButton(notebook_1_osc, ID_SINC, wxT("sin(x)/x"));

  const wxString choice_osc_swp_choices[] = {
      wxT("10"),   wxT("20"),   wxT("50"),   wxT("100"),   wxT("200"),   wxT("500"),
      wxT("1000"), wxT("2000"), wxT("5000"), wxT("10000"), wxT("20000"), wxT("50000"),
  };
  choice_osc_swp = new wxChoice(notebook_1_osc, ID_OSCXSCALE, wxDefaultPosition, wxDefaultSize, 12,
                                choice_osc_swp_choices, 0);
  label_osc_time = new wxStaticText(notebook_1_osc, -1, wxT("Time [us/div]: "));

  // triggering control
  label_8 = new wxStaticText(notebook_1_osc, -1, wxT("Trigger: "));
  const wxString choice_osc_trig_source_choices[] = {wxT("Off"), wxT("Left channel"),
                                                     wxT("Right channel")};
  choice_osc_trig_source = new wxChoice(notebook_1_osc, ID_OSCLTRIG, wxDefaultPosition,
                                        wxDefaultSize, 3, choice_osc_trig_source_choices, 0);
  label_osc_trig = new wxStaticText(notebook_1_osc, -1, wxT("Trigger edge: "));
  const wxString choice_osc_trig_edge_choices[] = {wxT("Rising edge"), wxT("Falling edge")};
  choice_osc_trig_edge = new wxChoice(notebook_1_osc, ID_OSCRTRIG, wxDefaultPosition, wxDefaultSize,
                                      2, choice_osc_trig_edge_choices, 0);

  button_osc_start = new wxToggleButton(notebook_1_osc, ID_OSCSTART, wxT("Start"));

  /* Spectrum analyzer */
  label_5 = new wxStaticText(notebook_1_spe, -1, wxT("FFT Window Type:"));
  const wxString choice_fft_choices[] = {wxT("Rect"), wxT("Hanning"), wxT("Blackman"),
                                         wxT("BlackHarr")};
  choice_fft = new wxChoice(notebook_1_spe, ID_FFTWINDOW, wxDefaultPosition, wxDefaultSize, 4,
                            choice_fft_choices, 0);

  label_9 = new wxStaticText(notebook_1_spe, -1, wxT("Number of samples:"));
  const wxString choice_fftlength_choices[] = {wxT("128"),   wxT("256"),  wxT("512"),  wxT("1024"),
                                               wxT("2048"),  wxT("4096"), wxT("8192"), wxT("16384"),
                                               wxT("32768"), wxT("65536")};
  choice_fftlength = new wxChoice(notebook_1_spe, ID_FFTLENGTH, wxDefaultPosition, wxDefaultSize,
                                  10, choice_fftlength_choices, 0);

  label_rx = new wxStaticText(notebook_1_spe, -1, wxT("Freq:"));
  const wxString choice_fftry_choices[] = {wxT("2-2000"), wxT("20-20k"), wxT("10-100k")};
  choice_fftrx = new wxChoice(notebook_1_spe, ID_FFTWINDOW, wxDefaultPosition, wxDefaultSize, 3,
                              choice_fftry_choices, 0);

  label_avg = new wxStaticText(notebook_1_spe, -1, wxT("Averaging (N):"));
  const wxString choice_fftavg_choices[] = {wxT("1"), wxT("2"), wxT("5"), wxT("10"), wxT("20")};
  choice_fftavg = new wxChoice(notebook_1_spe, ID_FFTAVG, wxDefaultPosition, wxDefaultSize, 5,
                               choice_fftavg_choices, 0);

  label_spe_ref = new wxStaticText(notebook_1_spe, -1, wxT("Ref Level [dB]"));
  const wxString choice_spe_ref_choices[] = {wxT("0"),   wxT("-10"), wxT("-20"),
                                             wxT("-30"), wxT("-40"), wxT("-50")};
  choice_spe_ref = new wxChoice(notebook_1_spe, ID_FFTREF, wxDefaultPosition, wxDefaultSize, 6,
                                choice_spe_ref_choices, 0);

  label_spe_dbdiv = new wxStaticText(notebook_1_spe, -1, wxT("Amplitude [dB/div]"));
  const wxString choice_spe_dbdiv_choices[] = {wxT("3"), wxT("5"), wxT("10")};
  choice_spe_dbdiv = new wxChoice(notebook_1_spe, ID_FFTDBDIV, wxDefaultPosition, wxDefaultSize, 3,
                                  choice_spe_dbdiv_choices, 0);

  window_1_spe = new CtrlOScope(notebook_1_spe, _T("Hz"), _T("dB"));
  button_spe_start = new wxToggleButton(notebook_1_spe, ID_SPANSTART, wxT("Start"));

  /* Frequency response */
  label_1_frm = new wxStaticText(notebook_1_frm, -1, wxT("Number of points (max 120):"));
  text_ctrl1_frm = new wxTextCtrl(notebook_1_frm, -1, wxT("24"));
  label_2_frm = new wxStaticText(notebook_1_frm, -1, wxT("-"));
  text_ctrl2_frm = new wxTextCtrl(notebook_1_frm, -1, wxT("-"));
  button_frm_start = new wxToggleButton(notebook_1_frm, ID_FRMSTART, wxT("Start"));
  window_1_frm = new CtrlOScope(notebook_1_frm, _T("Hz"), _T("dB"));

  set_properties();
  do_layout();
  // end wxGlade
  set_custom_props();
}

void MainFrame::set_properties() {
  // begin wxGlade: MainFrame::set_properties
  SetTitle(wxT("AUDio MEasurement System"));
  int frame_1_statusbar_widths[] = {-1};
  frame_1_statusbar->SetStatusWidths(1, frame_1_statusbar_widths);
  frame_1_statusbar->SetStatusText("AUDio MEasurement System - version " AUDMES_VERSION_STRING);
  choice_l_wav->SetSelection(0);
  choice_r_wav->SetSelection(0);
  choice_osc_swp->SetSelection(6);
  choice_osc_l_res->SetSelection(0);
  choice_osc_l_off->SetSelection(0);
  choice_osc_trig_source->SetSelection(0);
  choice_osc_r_res->SetSelection(0);
  choice_osc_r_off->SetSelection(0);
  choice_osc_trig_edge->SetSelection(0);
  choice_fft->SetSelection(1);
  choice_fftlength->SetSelection(4);
  choice_fftrx->SetSelection(2);
  choice_fftavg->SetSelection(0);
  choice_spe_ref->SetSelection(0);
  choice_spe_dbdiv->SetSelection(2);
  // end wxGlade
}

void MainFrame::do_layout() {
  // begin wxGlade: MainFrame::do_layout
  wxBoxSizer* sizer_notebook = new wxBoxSizer(wxVERTICAL);
  wxBoxSizer* sizer_2 = new wxBoxSizer(wxVERTICAL);
  wxBoxSizer* sizer_3 = new wxBoxSizer(wxHORIZONTAL);
  wxBoxSizer* sizer_spe_9 = new wxBoxSizer(wxVERTICAL);
  wxBoxSizer* sizer_spe_10 = new wxBoxSizer(wxHORIZONTAL);
  wxBoxSizer* sizer_9 = new wxBoxSizer(wxVERTICAL);
  wxBoxSizer* sizer_10 = new wxBoxSizer(wxHORIZONTAL);
  wxBoxSizer* sizer_11 = new wxBoxSizer(wxVERTICAL);
  wxBoxSizer* sizer_11B = new wxBoxSizer(wxHORIZONTAL);
  wxStaticBoxSizer* sizer_12L = new wxStaticBoxSizer(sizer_osc_l_staticbox, wxVERTICAL);
  wxBoxSizer* sizer_16L = new wxBoxSizer(wxHORIZONTAL);
  wxBoxSizer* sizer_15L = new wxBoxSizer(wxHORIZONTAL);
  wxBoxSizer* sizer_14L = new wxBoxSizer(wxHORIZONTAL);
  wxStaticBoxSizer* sizer_12R = new wxStaticBoxSizer(sizer_osc_r_staticbox, wxVERTICAL);
  wxStaticBoxSizer* sizer_12H = new wxStaticBoxSizer(sizer_osc_h_staticbox, wxVERTICAL);
  wxBoxSizer* sizer_16R = new wxBoxSizer(wxHORIZONTAL);
  wxBoxSizer* sizer_15R = new wxBoxSizer(wxHORIZONTAL);
  wxBoxSizer* sizer_14R = new wxBoxSizer(wxHORIZONTAL);
  wxBoxSizer* sizer_13 = new wxBoxSizer(wxHORIZONTAL);
  wxStaticBoxSizer* sizer_GenEnL = new wxStaticBoxSizer(sizer_gen_l_staticbox, wxVERTICAL);
  wxStaticBoxSizer* sizer_GenEnR = new wxStaticBoxSizer(sizer_gen_r_staticbox, wxVERTICAL);
  wxBoxSizer* sizer_gen_sync = new wxBoxSizer(wxHORIZONTAL);
  wxBoxSizer* sizer_gen_sync2 = new wxBoxSizer(wxHORIZONTAL);

  wxBoxSizer* sizer_9_frm = new wxBoxSizer(wxVERTICAL);
  wxBoxSizer* sizer_10_frm = new wxBoxSizer(wxVERTICAL);
  wxBoxSizer* sizer_17_frm = new wxBoxSizer(wxHORIZONTAL);

  wxBoxSizer* sizer_txtfreql = new wxBoxSizer(wxVERTICAL);
  wxBoxSizer* sizer_txtfreqr = new wxBoxSizer(wxVERTICAL);

  wxFlexGridSizer* sizer_GenL = new wxFlexGridSizer(3, 2, 5, 5);
  wxFlexGridSizer* sizer_GenR = new wxFlexGridSizer(3, 2, 5, 5);

  wxBoxSizer* sizer_spe_ctrl = new wxBoxSizer(wxVERTICAL);
  wxBoxSizer* sizer_spe_window = new wxBoxSizer(wxHORIZONTAL);
  wxBoxSizer* sizer_spe_samples = new wxBoxSizer(wxHORIZONTAL);
  wxBoxSizer* sizer_spe_span = new wxBoxSizer(wxHORIZONTAL);
  wxBoxSizer* sizer_spe_avg = new wxBoxSizer(wxHORIZONTAL);
  wxBoxSizer* sizer_spe_ref = new wxBoxSizer(wxHORIZONTAL);
  wxBoxSizer* sizer_spe_dbdiv = new wxBoxSizer(wxHORIZONTAL);
  wxStaticBoxSizer* sizer_spe_fft = new wxStaticBoxSizer(sizer_spe_fft_staticbox, wxVERTICAL);
  wxStaticBoxSizer* sizer_spe_disp = new wxStaticBoxSizer(sizer_spe_disp_staticbox, wxVERTICAL);
  wxStaticBoxSizer* sizer_spe_scale = new wxStaticBoxSizer(sizer_spe_scale_staticbox, wxVERTICAL);

  // generator
  sizer_GenEnL->Add(checkbox_l_en, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL,
                    5);
  sizer_GenL->Add(label_gen_wave_l, 0, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 5);
  sizer_GenL->Add(choice_l_wav, 1, wxALL | wxEXPAND, 5);
  sizer_GenL->Add(label__gen_freq_l, 0, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 5);
  sizer_txtfreql->Add(slide_l_fr, 0, wxEXPAND, 5);
  sizer_txtfreql->Add(txt_freq_l, wxALL | wxEXPAND, 5);
  sizer_GenL->Add(sizer_txtfreql, 1, wxALL | wxEXPAND, 5);
  sizer_GenL->Add(label_gen_ampl_l, 0, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 5);
  sizer_GenL->Add(slide_l_am, 1, wxEXPAND, 5);
  sizer_GenEnL->Add(sizer_GenL, 0, wxALL | wxEXPAND, 5);
  sizer_3->Add(sizer_GenEnL, 0, wxALL | wxEXPAND, 5);

  sizer_GenEnR->Add(checkbox_r_en, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL,
                    5);
  sizer_GenR->Add(label_gen_wave_r, 0, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 5);
  sizer_GenR->Add(choice_r_wav, 1, wxALL | wxEXPAND, 5);
  sizer_GenR->Add(label_gen_freq_r, 0, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 5);
  sizer_txtfreqr->Add(slide_r_fr, 0, wxEXPAND, 5);
  sizer_txtfreqr->Add(txt_freq_r, wxALL | wxEXPAND, 5);
  sizer_GenR->Add(sizer_txtfreqr, 1, wxALL | wxEXPAND, 5);
  sizer_GenR->Add(label_gen_ampl_r, 0, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 5);
  sizer_GenR->Add(slide_r_am, 1, wxEXPAND, 5);
  sizer_GenEnR->Add(sizer_GenR, 0, wxALL | wxEXPAND, 5);
  sizer_3->Add(sizer_GenEnR, 0, wxALL | wxEXPAND, 5);

  sizer_2->Add(sizer_3, 0, wxALIGN_CENTER_HORIZONTAL, 0);
  sizer_gen_sync->Add(checkbox_gen_sync, 0,
                      wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5);
  sizer_gen_sync2->Add(label_gen_sync, 0,
                       wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5);
  sizer_gen_sync2->Add(text_gen_sync, 0,
                       wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5);
  sizer_2->Add(sizer_gen_sync, 0, wxALIGN_CENTER_HORIZONTAL, 0);
  sizer_2->Add(sizer_gen_sync2, 0, wxALIGN_CENTER_HORIZONTAL, 0);
  sizer_2->Add(button_gen_start, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5);
  notebook_1_gen->SetAutoLayout(true);
  notebook_1_gen->SetSizer(sizer_2);
  sizer_2->Fit(notebook_1_gen);
  sizer_2->SetSizeHints(notebook_1_gen);

  // oscilloscope
  sizer_10->Add(window_osc, 1, wxEXPAND, 0);  // CtrlOScope

  // sizer_14: wxHorizontal
  sizer_14L->Add(label_osc_div_l, 0, wxLEFT | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL,
                 5);                              // Res[V/Div]
  sizer_14L->Add(5, 5, 1, 0, 0);                  // spacer
  sizer_14L->Add(choice_osc_l_res, 0, wxALL, 5);  // 2, 4, 8......32768
  // sizer_12: StaticBox Left Channel (Red)
  sizer_12L->Add(sizer_14L, 1, wxEXPAND, 0);
  sizer_15L->Add(label_osc_off_l, 0,
                 wxLEFT | wxRIGHT | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL,
                 5);              // Offset [V/div]
  sizer_15L->Add(5, 5, 1, 0, 0);  // spacer
  sizer_15L->Add(choice_osc_l_off, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL,
                 5);  // 100, 80, ... -100
  sizer_12L->Add(sizer_15L, 1, wxEXPAND, 0);

  sizer_11B->Add(button_autocalibrate, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 5);
  sizer_11B->Add(5, 5, 1, 0, 0);                  // spacer
  sizer_11B->Add(button_sinc, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
  sizer_11->Add(sizer_11B, 0, wxALL | wxEXPAND, 5);

  sizer_11->Add(sizer_12L, 0, wxALL | wxEXPAND, 5);

  sizer_14R->Add(label_osc_div_r, 0, wxLEFT | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL,
                 5);                              // Res[V/div]
  sizer_14R->Add(5, 5, 1, 0, 0);                  // spacer
  sizer_14R->Add(choice_osc_r_res, 0, wxALL, 5);  // 2, 4, ... 32768
  // sizer_12_copy: StaticBox Right Channel (Green)
  sizer_12R->Add(sizer_14R, 1, wxEXPAND, 0);
  sizer_15R->Add(label_osc_off_r, 0,
                 wxLEFT | wxRIGHT | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL,
                 5);              // Offset [V/div]
  sizer_15R->Add(5, 5, 1, 0, 0);  // spacer
  sizer_15R->Add(choice_osc_r_off, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL,
                 5);  // 100, 80, ... -100
  sizer_12R->Add(sizer_15R, 1, wxEXPAND, 0);
  sizer_11->Add(sizer_12R, 0, wxALL | wxEXPAND, 5);

  // sizer_13: wxHORIZONTAL
  sizer_13->Add(label_osc_time, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);  // X Scale time
  sizer_13->Add(5, 5, 1, 0, 0);                                          // spacer
  sizer_13->Add(choice_osc_swp, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);  // 20, 50....50000
  sizer_12H->Add(sizer_13, 1, wxEXPAND, 0);

  sizer_16L->Add(label_8, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);  // Trigger
  sizer_16L->Add(5, 5, 1, 0, 0);                                   // spacer
  sizer_16L->Add(choice_osc_trig_source, 0, wxALL | wxALIGN_CENTER_VERTICAL,
                 5);  // Off, Left, ... Channel
  sizer_12H->Add(sizer_16L, 1, wxEXPAND, 0);

  sizer_16R->Add(label_osc_trig, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);  // Trigger Edge
  sizer_16R->Add(5, 5, 1, 0, 0);                                          // spacer
  sizer_16R->Add(choice_osc_trig_edge, 0, wxALL | wxALIGN_CENTER_VERTICAL,
                 5);  // Rising, ... edge
  sizer_12H->Add(sizer_16R, 1, wxEXPAND, 0);

  sizer_11->Add(sizer_12H, 1, wxEXPAND, 0);
  sizer_10->Add(sizer_11, 0, 0, 0);
  sizer_9->Add(sizer_10, 1, wxEXPAND, 0);
  sizer_9->Add(button_osc_start, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5);
  notebook_1_osc->SetAutoLayout(true);
  notebook_1_osc->SetSizer(sizer_9);
  sizer_9->Fit(notebook_1_osc);
  sizer_9->SetSizeHints(notebook_1_osc);

  // analyzer
  sizer_spe_10->Add(window_1_spe, 1, wxEXPAND, 0);
  wxSize window_1_spe_size = window_1_spe->GetSize();
  sizer_spe_10->SetMinSize(4 * window_1_spe_size.GetHeight(), window_1_spe_size.GetHeight());
  sizer_spe_window->Add(label_5, 0, wxLEFT | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL,
                        5);
  sizer_spe_window->Add(5, 5, 1, 0, 0);
  sizer_spe_window->Add(choice_fft, 0, wxALL, 5);
  sizer_spe_fft->Add(sizer_spe_window, 1, wxEXPAND, 0);

  sizer_spe_samples->Add(label_9, 0, wxLEFT | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL,
                         5);
  sizer_spe_samples->Add(5, 5, 1, 0, 0);
  sizer_spe_samples->Add(choice_fftlength, 0, wxALL, 5);
  sizer_spe_fft->Add(sizer_spe_samples, 1, wxEXPAND, 0);
  sizer_spe_ctrl->Add(sizer_spe_fft, 0, wxALL | wxEXPAND, 5);

  sizer_spe_span->Add(label_rx, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5);
  sizer_spe_span->Add(5, 5, 1, 0, 0);
  sizer_spe_span->Add(choice_fftrx, 0, wxALL, 5);
  sizer_spe_disp->Add(sizer_spe_span, 1, wxEXPAND, 0);

  sizer_spe_avg->Add(label_avg, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5);
  sizer_spe_avg->Add(5, 5, 1, 0, 0);
  sizer_spe_avg->Add(choice_fftavg, 0, wxALL, 5);
  sizer_spe_disp->Add(sizer_spe_avg, 1, wxEXPAND, 0);
  sizer_spe_ctrl->Add(sizer_spe_disp, 0, wxALL | wxEXPAND, 5);

  sizer_spe_ref->Add(label_spe_ref, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL,
                     5);
  sizer_spe_ref->Add(5, 5, 1, 0, 0);
  sizer_spe_ref->Add(choice_spe_ref, 0, wxALL, 5);
  sizer_spe_scale->Add(sizer_spe_ref, 1, wxEXPAND, 0);

  sizer_spe_dbdiv->Add(label_spe_dbdiv, 0,
                       wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5);
  sizer_spe_dbdiv->Add(5, 5, 1, 0, 0);
  sizer_spe_dbdiv->Add(choice_spe_dbdiv, 0, wxALL, 5);
  sizer_spe_scale->Add(sizer_spe_dbdiv, 1, wxEXPAND, 0);
  sizer_spe_ctrl->Add(sizer_spe_scale, 0, wxALL | wxEXPAND, 5);

  sizer_spe_10->Add(sizer_spe_ctrl, 0, wxEXPAND, 0);
  sizer_spe_9->Add(sizer_spe_10, 1, wxEXPAND, 0);
  sizer_spe_9->Add(button_spe_start, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL,
                   5);
  notebook_1_spe->SetAutoLayout(true);
  notebook_1_spe->SetSizer(sizer_spe_9);
  sizer_spe_9->Fit(notebook_1_spe);
  sizer_spe_9->SetSizeHints(notebook_1_spe);

  // frequency response
  sizer_17_frm->Add(label_1_frm, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5);
  sizer_17_frm->Add(text_ctrl1_frm, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL,
                    5);
  sizer_17_frm->Add(20, 20, 0, 0, 0);
  sizer_17_frm->Add(label_2_frm, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5);
  sizer_17_frm->Add(text_ctrl2_frm, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL,
                    5);
  sizer_17_frm->Add(20, 20, 0, 0, 0);
  sizer_10_frm->Add(sizer_17_frm, 0, wxEXPAND, 0);
  sizer_10_frm->Add(window_1_frm, 1, wxEXPAND, 0);
  sizer_9_frm->Add(sizer_10_frm, 1, wxEXPAND, 0);
  sizer_9_frm->Add(button_frm_start, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL,
                   5);
  notebook_1_frm->SetAutoLayout(true);
  notebook_1_frm->SetSizer(sizer_9_frm);
  sizer_9_frm->Fit(notebook_1_frm);
  sizer_9_frm->SetSizeHints(notebook_1_frm);

  // main notebook
  notebook_1->AddPage(notebook_1_gen, wxT("Generator"));
  notebook_1->AddPage(notebook_1_osc, wxT("Oscilloscope"));
  notebook_1->AddPage(notebook_1_spe, wxT("Spectrum Analyzer"));
  notebook_1->AddPage(notebook_1_frm, wxT("Frequency Response"));
  sizer_notebook->Add(notebook_1, 1, wxEXPAND, 0);
  SetAutoLayout(true);
  SetSizer(sizer_notebook);
  sizer_notebook->Fit(this);
  sizer_notebook->SetSizeHints(this);
  Layout();
  // end wxGlade
}

void MainFrame::setoscbuf() {
  m_OscBufferLength = (unsigned long)(2 + sweep_div * 10E-6 * m_SamplingFreq);
}

void MainFrame::set_custom_props() {
#ifdef __WXMSW__
  SetIcon(wxICON(AudMeSIcon));
#endif

#ifdef __LINUX__
#include "audmes.xpm"
  SetIcon(wxICON(audmes));
#endif

  m_PlayDev = 0;
  m_RecordDev = 0;
  m_SamplingFreq = 44100;

  choice_osc_l_res->SetSelection(0);
  choice_osc_r_res->SetSelection(0);
  choice_osc_l_off->SetSelection(5);
  choice_osc_r_off->SetSelection(5);

  sweep_div = wxAtoi(choice_osc_swp->GetString(choice_osc_swp->GetSelection()));
  setoscbuf();

  /* oscilloscope */
  window_osc->SetXRange(0, sweep_div * 10E-6, 0);
  window_osc->SetYRange(-1, 1, 0);
  window_osc->SetNumOfVerticals(10);

  /* analyzer */
  window_1_spe->SetXRange(10, 100000, 1);
  window_1_spe->SetYRange(-100, 0, 0);

  /* freq response */
  window_1_frm->SetXRange(20, 20000, 1);
  window_1_frm->SetYRange(-80, 0, 0);

  frm_running = 0;
  frm_measure = 0;
  frm_istep = 0;

  m_configfilename = wxT("");

  m_timer = new wxTimer(this, ID_TIMERID);
  m_timer->Start(100);

  m_SpeBufferLength = wxAtoi(choice_fftlength->GetString(choice_fftlength->GetSelection()));

  m_RWAudio = new RWAudio();
  m_SMASpeLeft = new SMA_2D(m_SpeBufferLength >> 1, 1);
  m_SMASpeRight = new SMA_2D(m_SpeBufferLength >> 1, 1);

  int ret = 0;

  ret = m_RWAudio->InitSnd((long int)(2.0 * m_OscBufferLength), m_SpeBufferLength, m_rtinfo,
                           m_SamplingFreq);

  if (ret)
    wxMessageBox(_T("Sound card issue:\n\nPlease check\nTools -> Audio interface Configuration\n"),
                 _T("Alert"), wxICON_INFORMATION | wxOK);
}

void MainFrame::OnAboutClick(wxCommandEvent& WXUNUSED(event)) {
  wxString s;
  s << wxT("AUDio MEasurement System - version ") << AUDMES_VERSION_STRING
    << wxT("\nVaclav Peroutka - vaclavpe@seznam.cz\n\n")
    << wxT("Project page: https://sourceforge.net/projects/audmes/\n\n") << m_rtinfo;

  wxMessageBox(s, _T("About application"), wxICON_INFORMATION | wxOK);
}

void MainFrame::OnExitClick(wxCommandEvent& WXUNUSED(event)) { Close(); }

void MainFrame::OnClose(wxCloseEvent& WXUNUSED(event)) {
  m_timer->Stop();
  Destroy();
}

void MainFrame::OnOpenClick(wxCommandEvent& WXUNUSED(event)) {
  wxMessageBox(wxT("Not yet implemented"), _T("About application"), wxICON_INFORMATION | wxOK);
}

void MainFrame::OnSaveClick(wxCommandEvent& WXUNUSED(event)) {
  wxMessageBox(wxT("Not yet implemented"), _T("About application"), wxICON_INFORMATION | wxOK);
}

void MainFrame::OnSaveAsClick(wxCommandEvent& WXUNUSED(event)) {
  wxMessageBox(wxT("Not yet implemented"), _T("About application"), wxICON_INFORMATION | wxOK);
}

void MainFrame::OnSaveFRM(wxCommandEvent& WXUNUSED(event)) {
  wxFileDialog saveFileDialog(this, _("Save frequency response file"), "", "",
                              "CSV files (*.csv)|*.csv", wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
  if (saveFileDialog.ShowModal() == wxID_CANCEL) return;

  std::ofstream frm;
  frm.open(saveFileDialog.GetPath().mb_str(), std::ios::trunc);
  if (!frm.is_open()) {
    wxLogError("Cannot save current contents in file '%s'.", saveFileDialog.GetPath());
    return;
  }
  frm << "Hz"
      << ","
      << "GainL"
      << ","
      << "GainR" << std::endl;
  for (unsigned int i = 0; i < frm_freqs.GetCount(); i++) {
    frm << frm_freqs[i] << "," << frm_lgains[i] << "," << frm_rgains[i] << std::endl;
  }
  frm.close();
}

void MainFrame::OnSaveSPE(wxCommandEvent& WXUNUSED(event)) {
  wxFileDialog saveFileDialog(this, _("Save frequency spectrum file"), "", "",
                              "CSV files (*.csv)|*.csv", wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
  if (saveFileDialog.ShowModal() == wxID_CANCEL) return;

  std::ofstream spe;
  spe.open(saveFileDialog.GetPath().mb_str(), std::ios::trunc);
  if (!spe.is_open()) {
    wxLogError("Cannot save current contents in file '%s'.", saveFileDialog.GetPath());
    return;
  }
  spe << "Hz"
      << ","
      << "MagL"
      << ","
      << "MagR" << std::endl;
  for (unsigned int i = 0; i < spe_freqs.GetCount(); i++) {
    spe << spe_freqs[i] << "," << spe_lmagns[i] << "," << spe_rmagns[i] << std::endl;
  }
  spe.close();
}

void MainFrame::OnSaveOSC(wxCommandEvent& WXUNUSED(event)) {
  wxFileDialog saveFileDialog(this, _("Save oscillogram file"), "", "", "CSV files (*.csv)|*.csv",
                              wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
  if (saveFileDialog.ShowModal() == wxID_CANCEL) return;

  std::ofstream osc;
  osc.open(saveFileDialog.GetPath().mb_str(), std::ios::trunc);
  if (!osc.is_open()) {
    wxLogError("Cannot save current contents in file '%s'.", saveFileDialog.GetPath());
    return;
  }
  osc << "time"
      << ","
      << "MagL"
      << ","
      << "MagR" << std::endl;
  for (unsigned int i = 0; i < osc_times.GetCount(); i++) {
    osc << osc_times[i] << "," << osc_lmagns[i] << "," << osc_rmagns[i] << std::endl;
  }
  osc.close();
}

void MainFrame::OnLoadFRM(wxCommandEvent& WXUNUSED(event)) {
  if (0 /* ...current content has not been saved... */) {
    if (wxMessageBox(_("Current content has not been saved! Proceed?"), _("Please confirm"),
                     wxICON_QUESTION | wxYES_NO, this) == wxNO)
      return;
  }

  wxFileDialog openFileDialog(this, _("Open frequency response file"), "", "",
                              "CSV files (*.csv)|*.csv", wxFD_OPEN | wxFD_FILE_MUST_EXIST);
  if (openFileDialog.ShowModal() == wxID_CANCEL) return;

  io::CSVReader<3> in(openFileDialog.GetPath());
  in.read_header(io::ignore_extra_column, "Hz", "GainL", "GainR");

  frm_freqs.Clear();
  frm_lgains.Clear();
  frm_rgains.Clear();
  double hz;
  double gainl, gainr;
  while (in.read_row(hz, gainl, gainr)) {
    frm_freqs.Add(hz);
    frm_lgains.Add(gainl);
    frm_rgains.Add(gainr);
  }
  DrawFreqResponse();
  Refresh();
}

void MainFrame::OnAutoCalClick(wxCommandEvent& WXUNUSED(event)) {
  if (button_osc_start->GetValue()) {
    float minValueL = 1;
    float maxValueL = -1;
    float minValueR = 1;
    float maxValueR = -1;

    for (unsigned long int i = 0; i < m_OscBufferLength; i++) {
      if (minValueL > g_OscBuffer_Left[i]) minValueL = g_OscBuffer_Left[i];
      if (maxValueL < g_OscBuffer_Left[i]) maxValueL = g_OscBuffer_Left[i];
      if (minValueR > g_OscBuffer_Right[i]) minValueR = g_OscBuffer_Right[i];
      if (maxValueR < g_OscBuffer_Right[i]) maxValueR = g_OscBuffer_Right[i];
    }

    auto diff = 1 / (maxValueL - minValueL);
    auto lgdiff = log(diff) / log(2);
    if (lgdiff > 13) lgdiff = 13;
    choice_osc_l_res->SetSelection((int)lgdiff);

    diff = 1 / (maxValueR - minValueR);
    lgdiff = log(diff) / log(2);
    if (lgdiff > 13) lgdiff = 13;
    choice_osc_r_res->SetSelection((int)lgdiff);
  } else {
    wxMessageBox(wxT("Please start recording"), _T("Could not auto calibrate"),
                 wxICON_INFORMATION | wxOK);
  }
}

void MainFrame::OnAutoSincClick(wxCommandEvent& WXUNUSED(event)) {
  if (button_sinc->GetValue()) {
    window_osc->SetInterp(CtrlOScope::Interpolation::SINC);
  } else {
    window_osc->SetInterp(CtrlOScope::Interpolation::LINE);
  }
}

static const int frm_low = 20;

void MainFrame::CalcFreqResponse() {
  /* periodically called by OnTimer
   * delays for measuring work by waiting for the next call
   */
  if (frm_istep <= (int)frm_ipoints) {
    float freq = frm_low * pow(10.0, 3.0 * frm_istep / frm_ipoints);

    if (0 == frm_measure) {
      // play new frequency e.g. from 20Hz to 20kHz
      m_RWAudio->PlaySetGenerator(freq, freq, RWAudio::SINE, RWAudio::SINE,
                                  pow(10, slide_l_am->GetValue() / 20.0),
                                  pow(10, slide_r_am->GetValue() / 20.0));
      wxString bla;
      bla.Printf(wxT("Frequency : %.1f "), freq);
      window_1_frm->ShowUserText(bla, 100, 20);
    }
    if (1 == frm_measure) g_SpeBufferChanged = false;  // now get audio data

    if (g_SpeBufferChanged.load() && frm_measure > 1) {
      // new audio data has arrived
      double l_rms = 0;
      double r_rms = 0;
      // compute RMS value in the grabbed wave and store it as a result
      for (unsigned long int ii = 0; ii < m_SpeBufferLength; ii++) {
        l_rms += g_SpeBuffer_Left[ii] * g_SpeBuffer_Left[ii];
        r_rms += g_SpeBuffer_Right[ii] * g_SpeBuffer_Right[ii];
      }
      frm_freqs.Add(freq);
      frm_lgains.Add(sqrt(l_rms / m_SpeBufferLength));
      frm_rgains.Add(sqrt(r_rms / m_SpeBufferLength));
      frm_measure = -1;  // zero after increment
      frm_istep++;
    }
    frm_measure++;
  } else {
    frm_running = false;
    window_1_frm->ShowUserText(wxString(""), 0, 0);
    button_frm_start->SetValue(false);
    button_frm_start->SetLabel(_T("Start"));
    SendGenSettings();  // stop generator
  }
}

void MainFrame::DrawFreqResponse(void) {
  wxArrayDouble left, right;
  double tmpval;
  left.Clear();
  right.Clear();
  for (unsigned int i = 0; i < frm_freqs.size(); i++) {
    tmpval = frm_lgains[i];
    left.Add(20.0 * log10(tmpval));
    tmpval = frm_rgains[i];
    right.Add(20.0 * log10(tmpval));
  }
  window_1_frm->SetTrack1(left);
  window_1_frm->SetTrack2(right);
  window_1_frm->SetTrackX(frm_freqs);
}

void MainFrame::DrawOscilloscope(void) {
  osc_lmagns.Clear();
  osc_rmagns.Clear();
  osc_times.Clear();
  double trigger_edge;
  double trigger_level = 0.0;
  unsigned long int xtrig = 0;  // point where the trigger occures

  const double range[] = {1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000};
  double range_div = 1 / range[choice_osc_l_res->GetSelection()];
  double shft_val = 20.0 * (choice_osc_l_off->GetSelection() - 5) / 100.0;
  double range_div2 = 1 / range[choice_osc_r_res->GetSelection()];
  double shft_val2 = 20.0 * (choice_osc_r_off->GetSelection() - 5) / 100.0;
  double hysteresis_level = range_div / 20.0;

  // triggering - re-done a little bit, more or less ...
  trigger_edge = (0 == choice_osc_trig_edge->GetSelection()) ? 1.0 : -1.0;
  switch (choice_osc_trig_source->GetSelection()) {
    case 1:
      // left channel - look for the value under hysteresis point and then over 0
      while (xtrig < m_OscBufferLength) {
        if ((trigger_level - hysteresis_level) > (trigger_edge * g_OscBuffer_Left[xtrig])) {
          break;
        }
        xtrig++;
      }
      while (xtrig < m_OscBufferLength) {
        if (trigger_level < (trigger_edge * g_OscBuffer_Left[xtrig])) {
          break;
        }
        xtrig++;
      }
      break;
    case 2:
      // right channel
      while (xtrig < m_OscBufferLength) {
        if ((trigger_level - hysteresis_level) > (trigger_edge * g_OscBuffer_Right[xtrig])) {
          break;
        }
        xtrig++;
      }
      while (xtrig < m_OscBufferLength) {
        if (trigger_level < (trigger_edge * g_OscBuffer_Right[xtrig])) {
          break;
        }
        xtrig++;
      }
      break;
    default:
      // no trigger
      break;
  }

  if (xtrig < m_OscBufferLength) {
    unsigned long int finalBufferPoint =
        xtrig + m_OscBufferLength;  // wrapped exactly for the OScopeCtrl X range
    if (finalBufferPoint > 2.0 * m_OscBufferLength) {
      finalBufferPoint = (unsigned long)(2.0 * m_OscBufferLength);
    }

    for (int i = 0; i + xtrig < finalBufferPoint; i++) {
      osc_lmagns.Add(g_OscBuffer_Left[i + xtrig] / range_div - shft_val);
      osc_rmagns.Add(g_OscBuffer_Right[i + xtrig] / range_div2 - shft_val2);
      osc_times.Add((double)i / m_SamplingFreq);
    }
  }

  window_osc->SetTrack1(osc_lmagns);
  window_osc->SetTrack2(osc_rmagns);
  window_osc->SetTrackX(osc_times);
}

double MainFrame::calc_dc(const float* data, int size) {
  double dc = 0.0;
  for (int i = 0; i < size; i++) {
    dc += data[i];
  }
  return dc / size;
}

void MainFrame::DrawSpectrum(void) {
  double *realin, *realout, *imagout, *windowf;
  int nsampl = m_SpeBufferLength;
  realin = (double*)malloc(nsampl * sizeof(double));
  realout = (double*)malloc(nsampl * sizeof(double));
  imagout = (double*)malloc(nsampl * sizeof(double));
  windowf = (double*)malloc(nsampl * sizeof(double));
  spe_freqs.Clear();
  spe_lmagns.Clear();
  spe_rmagns.Clear();

  // calculate window
  const double multiplier = 2 * M_PI / nsampl;
  switch (choice_fft->GetSelection()) {
    case 1:  // Hanning
      for (int i = 0; i < nsampl; i++) {
        windowf[i] = 2 * (0.5 + -0.5 * cos(i * multiplier)) / (double)nsampl;
      }
      break;
    case 2:  // Blackman
      for (int i = 0; i < nsampl; i++) {
        windowf[i] = 2.4 * (0.42 - 0.5 * cos(multiplier * i) + 0.08 * cos(2 * multiplier * i)) /
                     (double)nsampl;
      }
      break;
    case 3:  // Blackman Harris minimum 4 term
      for (int i = 0; i < nsampl; i++) {
        windowf[i] = 2.63 *
                     (0.35875 + -0.48829 * cos(i * multiplier) + 0.14128 * cos(i * multiplier * 2) +
                      -0.01168 * cos(i * multiplier * 3)) /
                     (double)nsampl;
      }
      break;
    default:  // Rectangle
      for (int i = 0; i < nsampl; i++) {
        windowf[i] = 1.0 / (double)nsampl;
      }
      break;
  }

  double dval = 0.0;
  double dmax = 0.0;
  double dval_db = 0.0;

  // left channel
  double offsetL = calc_dc(g_SpeBuffer_Left, nsampl);
  for (int i = 0; i < nsampl; i++) {
    // copy and apply window
    realin[i] = (g_SpeBuffer_Left[i] - offsetL) * windowf[i];
  }

  if (fft_double(nsampl, 0, realin, NULL, realout, imagout)) {
    // use only up to nsampl/2 and skip DC
    for (int i = 1; i < nsampl / 2; i++) {
      // multiply amplitude by 2 to compensate
      dval = 2 * sqrt(realout[i] * realout[i] + imagout[i] * imagout[i]);
      m_SMASpeLeft->AddVal(i, dval);
      dval_db = 20.0 * log10(m_SMASpeLeft->GetSMA(i));
      spe_lmagns.Add(dval_db);
    }
  } else {
    /* wrong computation */
    for (int i = 0; i < nsampl / 2; i++) {
      spe_lmagns.Add(-150);
    }
  }

  /* find frequency index with highest amplitude */
  int fmax = 0;
  for (int i = 0; i < nsampl / 2; i++) {
    if (m_SMASpeLeft->GetSMA(i) > dmax) {
      dmax = m_SMASpeLeft->GetSMA(i);
      fmax = i;
    }
  }

  /* use that frequency as base and calculate distortion
   * but only if the magnitude is above -90 db
   */
  double freq = 0.0;
  double thd = 0.0;
  double thdval[10] = {1.0E-6};
  if (fmax > 0 && dmax > 0.00003) {
    freq = (double)fmax * m_SamplingFreq / nsampl;
    for (int i = 0; i < 10; i++) {
      int j = fmax * (i + 1);
      if (j < nsampl / 2)
        thdval[i] = m_SMASpeLeft->GetSMA(j);
      else
        thdval[i] = 0.0;
    }
    thd = 100 *
          (thdval[1] + thdval[2] + thdval[3] + thdval[4] + thdval[5] + thdval[6] + thdval[7] +
           thdval[8] + thdval[9]) /
          thdval[0];
  }

  /* display base frequency, magnitude and distortion */
  wxString freqency;
  freqency.Printf(wxT("Frequency : %.1lf Hz, Magnitude: %.1lf dB, THD : %lf%%, Avg: %d/%d"), freq,
                  20.0 * log10(thdval[0]), thd, m_SMASpeLeft->GetNumSummed(1),
                  m_SMASpeLeft->GetNumAverage());
  frame_1_statusbar->SetStatusText(freqency);

  // right channel
  double offsetR = calc_dc(g_SpeBuffer_Right, nsampl);
  for (int i = 0; i < nsampl; i++) {
    // copy and apply window
    realin[i] = (g_SpeBuffer_Right[i] - offsetR) * windowf[i];
  }

  if (fft_double(nsampl, 0, realin, NULL, realout, imagout)) {
    // use only up to nsampl/2 and skip DC
    for (int i = 1; i < nsampl / 2; i++) {
      dval = 2 * sqrt(realout[i] * realout[i] + imagout[i] * imagout[i]);
      m_SMASpeRight->AddVal(i, dval);
      dval_db = (20.0 * log10(m_SMASpeRight->GetSMA(i)));
      spe_rmagns.Add(dval_db);
    }
  } else {
    /* wrong computation */
    for (int i = 0; i < nsampl / 2; i++) {
      spe_rmagns.Add(-150);
    }
  }

  double fbase = (double)m_SamplingFreq / nsampl;
  // frequencies without DC
  for (int i = 1; i < nsampl / 2; i++) {
    spe_freqs.Add(fbase * i);
  }
  window_1_spe->SetTrack1(spe_lmagns);
  window_1_spe->SetTrack2(spe_rmagns);
  window_1_spe->SetTrackX(spe_freqs);
  switch (choice_fftrx->GetSelection()) {
    case 1:
      window_1_spe->SetXRange(20, 20000, 1);
      break;
    case 2:
      window_1_spe->SetXRange(10, 100000, 1);
      break;
    default:
      window_1_spe->SetXRange(2, 2000, 1);
      break;
  }
  free(realin);
  free(realout);
  free(imagout);
  free(windowf);
}

void MainFrame::OnTimer(wxTimerEvent& WXUNUSED(event)) {
  bool refresh = false;
  if (g_OscBufferChanged.load() && button_osc_start->GetValue()) {
    DrawOscilloscope();
    g_OscBufferChanged = false;
    refresh = true;
  }
  if (frm_running) {
    CalcFreqResponse();
    DrawFreqResponse();
    refresh = true;
  }
  if (g_SpeBufferChanged.load() && button_spe_start->GetValue()) {
    DrawSpectrum();
    refresh = true;
    g_SpeBufferChanged = false;
  }
  if (refresh) {
    Refresh();
    Update();
  }
}

void MainFrame::OnOscXScaleChanged(wxCommandEvent& WXUNUSED(event)) {
  sweep_div = wxAtoi(choice_osc_swp->GetString(choice_osc_swp->GetSelection()));
  setoscbuf();
  window_osc->SetXRange(0, sweep_div * 10E-6, 0);

  m_SpeBufferLength = wxAtoi(choice_fftlength->GetString(choice_fftlength->GetSelection()));

  m_RWAudio->ChangeBufLen((unsigned long)(2.0 * m_OscBufferLength),
                          m_SpeBufferLength);  // we need bigger buffer because of synchronization
  m_SMASpeLeft->SetNumRecords(m_SpeBufferLength >> 1);
  m_SMASpeRight->SetNumRecords(m_SpeBufferLength >> 1);

  g_SpeBufferChanged = false;
  g_OscBufferChanged = false;
}

void MainFrame::OnFFTAvgChanged(wxCommandEvent& WXUNUSED(event)) {
  int numAverage = wxAtoi(choice_fftavg->GetString(choice_fftavg->GetSelection()));
  m_SMASpeLeft->SetNumAverage(numAverage);
  m_SMASpeRight->SetNumAverage(numAverage);
}

void MainFrame::OnFFTScaleChanged(wxCommandEvent& WXUNUSED(event)) {
  double ref;
  double dbDiv;
  choice_spe_ref->GetString(choice_spe_ref->GetSelection()).ToDouble(&ref);
  choice_spe_dbdiv->GetString(choice_spe_dbdiv->GetSelection()).ToDouble(&dbDiv);
  double lo = ref - 10 * dbDiv;
  window_1_spe->SetYRange(lo, ref, 0);
}

void MainFrame::OnSpanStart(wxCommandEvent& WXUNUSED(event)) {
  if (button_spe_start->GetValue()) {
    m_SMASpeLeft->SetNumRecords(m_SpeBufferLength >> 1);
    m_SMASpeRight->SetNumRecords(m_SpeBufferLength >> 1);
    button_spe_start->SetLabel(_T("Stop"));
  } else {
    button_spe_start->SetLabel(_T("Start"));
  }
}

void MainFrame::OnGenStart(wxCommandEvent& WXUNUSED(event)) {
  if (button_gen_start->GetValue()) {
    button_gen_start->SetLabel(_T("Stop"));
  } else {
    button_gen_start->SetLabel(_T("Start"));
  }
  SendGenSettings();
}

void MainFrame::OnOscStart(wxCommandEvent& WXUNUSED(event)) {
  if (button_osc_start->GetValue()) {
    button_osc_start->SetLabel(_T("Stop"));
  } else {
    button_osc_start->SetLabel(_T("Start"));
  }
}

void MainFrame::OnFrmStart(wxCommandEvent& WXUNUSED(event)) {
  if (button_frm_start->GetValue()) {
    long ip;
    button_frm_start->SetLabel(_T("Stop"));
    wxString tpoints = text_ctrl1_frm->GetValue();
    tpoints.ToLong(&ip, 10);
    if (ip > 120) ip = 120;
    if (ip < 1) ip = 1;
    frm_ipoints = (int)ip;
    frm_istep = 0;

    frm_freqs.Clear();
    frm_lgains.Clear();
    frm_rgains.Clear();

    frm_measure = 0;
    frm_running = true;
  } else {
    frm_running = false;
    button_frm_start->SetValue(false);
    button_frm_start->SetLabel(_T("Start"));
    SendGenSettings();  // stop generator
  }
}

void MainFrame::OnGeneratorChanged(wxCommandEvent& WXUNUSED(event)) {
  if (checkbox_gen_sync->IsChecked()) {
    slide_r_fr->Enable(false);
    label_gen_wave_r->Enable(false);
    txt_freq_r->Enable(false);
    choice_r_wav->Enable(false);
    label_gen_freq_r->Enable(false);
  } else {
    slide_r_fr->Enable(true);
    choice_r_wav->Enable(true);
    txt_freq_r->Enable(true);
    label_gen_wave_r->Enable(true);
    label_gen_freq_r->Enable(true);
  }

  if (button_gen_start->GetValue()) {
    SendGenSettings();
  }
}

void MainFrame::OnOscChoiceChanged(wxCommandEvent& WXUNUSED(event)) {}

void MainFrame::OnGenScrollLChanged(wxScrollEvent& WXUNUSED(event)) {
  wxString bla;

  bla.Printf(wxT("%.1f"), floor(20.0 * pow(10.0, 3.0 * slide_l_fr->GetValue() / 200.0)));
  txt_freq_l->SetValue(bla);
  if (button_gen_start->GetValue()) {
    SendGenSettings();
  }
}

void MainFrame::OnGenScrollRChanged(wxScrollEvent& WXUNUSED(event)) {
  wxString bla;

  bla.Printf(wxT("%.1f"), floor(20.0 * pow(10.0, 3.0 * slide_r_fr->GetValue() / 200.0)));
  txt_freq_r->SetValue(bla);
  if (button_gen_start->GetValue()) {
    SendGenSettings();
  }
}

void MainFrame::OnGenScrollChanged(wxScrollEvent& WXUNUSED(event)) {
  wxString bla;

  bla.Printf(wxT("Amplitude: %d dB"), slide_l_am->GetValue());
  label_gen_ampl_l->SetLabel(bla);

  bla.Printf(wxT("Amplitude: %d dB"), slide_r_am->GetValue());
  label_gen_ampl_r->SetLabel(bla);

  if (button_gen_start->GetValue()) {
    SendGenSettings();
  }
}

void MainFrame::SendGenSettings() {
  float freq_l, freq_r;
  double phas2;
  double doubleToFreq;
  float gain_l, gain_r;

  if ((checkbox_l_en->IsChecked()) && (button_gen_start->GetValue())) {
    gain_l = 1.0 * pow(10, slide_l_am->GetValue() / 20.0);
  } else {
    gain_l = 0.0;
  }
  if ((checkbox_r_en->IsChecked()) && (button_gen_start->GetValue())) {
    gain_r = 1.0 * pow(10, slide_r_am->GetValue() / 20.0);
  } else {
    gain_r = 0.0;
  }

  text_gen_sync->GetValue().ToDouble(&phas2);

  txt_freq_l->GetValue().ToDouble(&doubleToFreq);
  freq_l = (float)doubleToFreq;

  txt_freq_r->GetValue().ToDouble(&doubleToFreq);
  freq_r = (float)doubleToFreq;

  RWAudio::Waveform shapeleft = static_cast<RWAudio::Waveform>(choice_l_wav->GetSelection());
  RWAudio::Waveform shaperight = static_cast<RWAudio::Waveform>(choice_r_wav->GetSelection());

  if (checkbox_gen_sync->IsChecked()) {
    freq_r = freq_l;
    shaperight = shapeleft;
  } else {
    phas2 = 0.0;
  }

  m_RWAudio->PlaySetGenerator(freq_l, freq_r, shapeleft, shaperight, gain_l, gain_r);
  m_RWAudio->PlaySetPhaseDiff(phas2 * 3.14159 / 180.0);  // should be in degrees now
}

void MainFrame::OnSelectSndCard(wxCommandEvent& WXUNUSED(event)) {
  unsigned int recdev, pldev;
  RWAudioDevList playDevList;
  RWAudioDevList recordDevList;
  unsigned int newFrequency = m_SamplingFreq;
  AIStreamSettings m_StreamSettings;

  m_RWAudio->GetRWAudioDevices(&playDevList, &recordDevList);

  AudioInterfaceDialog dlg(this);

  m_StreamSettings.playDev = m_PlayDev;
  m_StreamSettings.recordDev = m_RecordDev;
  m_StreamSettings.freq = m_SamplingFreq;
  dlg.SetDevices(recordDevList, playDevList, m_StreamSettings);
  if (wxID_OK == dlg.ShowModal()) {
    // send settings to RWAudio
    dlg.GetSelectedDevs(&recdev, &pldev, &newFrequency);
    m_RWAudio->SetSndDevices(recdev, pldev, newFrequency);
    m_PlayDev = pldev;
    m_RecordDev = recdev;
    m_SamplingFreq = newFrequency;
    m_SMASpeLeft->SetNumRecords(m_SpeBufferLength >> 1);
    m_SMASpeRight->SetNumRecords(m_SpeBufferLength >> 1);
    setoscbuf();
    m_RWAudio->ChangeBufLen((unsigned long)(2.0 * m_OscBufferLength),
                            m_SpeBufferLength);  // we need bigger buffer because of synchronization
    g_OscBufferChanged = false;
  }
}

void MainFrame::OnTxtFreqLChanged(wxCommandEvent& WXUNUSED(event)) {
  if (button_gen_start->GetValue()) {
    SendGenSettings();
  }
}

void MainFrame::OnTxtFreqRChanged(wxCommandEvent& WXUNUSED(event)) {
  if (button_gen_start->GetValue()) {
    SendGenSettings();
  }
}

class AudMeSApp : public wxApp {
 public:
  virtual bool OnInit();
};

wxIMPLEMENT_APP(AudMeSApp);

bool AudMeSApp::OnInit() {
  wxInitAllImageHandlers();
  MainFrame* frame_1 = new MainFrame(NULL, -1, wxT(""));
  SetTopWindow(frame_1);
  frame_1->Show();
  return true;
}
