]> code.bitgloo.com Git - clyne/stmdsp.git/commitdiff
improved siggen prompt; WIP split wxmain
authorClyne Sullivan <clyne@bitgloo.com>
Mon, 22 Mar 2021 00:09:53 +0000 (20:09 -0400)
committerClyne Sullivan <clyne@bitgloo.com>
Mon, 22 Mar 2021 00:09:53 +0000 (20:09 -0400)
gui/wxmain.cpp
gui/wxmain_devdata.h [new file with mode: 0644]
gui/wxmain_mfile.h [new file with mode: 0644]
gui/wxmain_mrun.h [new file with mode: 0644]
gui/wxsiggen.cpp [new file with mode: 0644]
gui/wxsiggen.hpp [new file with mode: 0644]

index 1a10b84d842343339c7edfa0a03f1b38d8255874..64f36e462963771c87320ea5e1707a5eda2911f6 100644 (file)
@@ -10,6 +10,7 @@
  */
 
 #include "wxmain.hpp"
+#include "wxsiggen.hpp"
 
 #include <wx/combobox.h>
 #include <wx/dcclient.h>
 #include <sys/mman.h>
 #include <vector>
 
-static const std::array<wxString, 6> srateValues {
-    "8 kS/s",
-    "16 kS/s",
-    "20 kS/s",
-    "32 kS/s",
-    "48 kS/s",
-    "96 kS/s"
-};
-static const std::array<unsigned int, 6> srateNums {
-    8000,
-    16000,
-    20000,
-    32000,
-    48000,
-    96000
-};
-
-static const char *makefile_text_h7 = R"make(
-all:
-       @arm-none-eabi-g++ -x c++ -Os -fno-exceptions -fno-rtti \
-                       -mcpu=cortex-m7 -mthumb -mfloat-abi=hard -mfpu=fpv5-d16 -mtune=cortex-m7 \
-                          -nostartfiles \
-                          -Wl,-Ttext-segment=0x00000000 -Wl,-zmax-page-size=512 -Wl,-eprocess_data_entry \
-                          $0 -o $0.o
-       @cp $0.o $0.orig.o
-       @arm-none-eabi-strip -s -S --strip-unneeded $0.o
-       @arm-none-eabi-objcopy --remove-section .ARM.attributes \
-                           --remove-section .comment \
-                           --remove-section .noinit \
-                           $0.o
-       arm-none-eabi-size $0.o
-)make";
-static const char *makefile_text_l4 = R"make(
-all:
-       @arm-none-eabi-g++ -x c++ -Os -fno-exceptions -fno-rtti \
-                          -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -mtune=cortex-m4 \
-                          -nostartfiles \
-                          -Wl,-Ttext-segment=0x10000000 -Wl,-zmax-page-size=512 -Wl,-eprocess_data_entry \
-                          $0 -o $0.o
-       @cp $0.o $0.orig.o
-       @arm-none-eabi-strip -s -S --strip-unneeded $0.o
-       @arm-none-eabi-objcopy --remove-section .ARM.attributes \
-                           --remove-section .comment \
-                           --remove-section .noinit \
-                           $0.o
-       arm-none-eabi-size $0.o
-)make";
-
-static const char *file_header_h7 = R"cpp(
-#include <cstdint>
-
-using adcsample_t = uint16_t;
-constexpr unsigned int SIZE = $0;
-adcsample_t *process_data(adcsample_t *samples, unsigned int size);
-extern "C" void process_data_entry()
-{
-    ((void (*)())process_data)();
-}
-
-constexpr double PI = 3.14159265358979323846L;
-__attribute__((naked))
-auto sin(double x) {
-asm("vmov.f64 r1, r2, d0;"
-    "eor r0, r0;"
-    "svc 1;"
-    "vmov.f64 d0, r1, r2;"
-    "bx lr");
-return 0;
-}
-__attribute__((naked))
-auto cos(double x) {
-asm("vmov.f64 r1, r2, d0;"
-       "mov r0, #1;"
-       "svc 1;"
-       "vmov.f64 d0, r1, r2;"
-       "bx lr");
-return 0;
-}
-__attribute__((naked))
-auto tan(double x) {
-asm("vmov.f64 r1, r2, d0;"
-       "mov r0, #2;"
-       "svc 1;"
-       "vmov.f64 d0, r1, r2;"
-       "bx lr");
-return 0;
-}
-__attribute__((naked))
-auto sqrt(double x) {
-asm("vsqrt.f64 d0, d0; bx lr");
-return 0;
-}
-
-auto readalt() {
-adcsample_t s;
-asm("svc 3; mov %0, r0" : "=&r"(s));
-return s;
-}
-
-// End stmdspgui header code
-
-)cpp";
-static const char *file_header_l4 = R"cpp(
-#include <cstdint>
-
-using adcsample_t = uint16_t;
-constexpr unsigned int SIZE = $0;
-adcsample_t *process_data(adcsample_t *samples, unsigned int size);
-extern "C" void process_data_entry()
-{
-    ((void (*)())process_data)();
-}
-
-constexpr float PI = 3.14159265358979L;
-__attribute__((naked))
-auto sin(float x) {
-asm("vmov.f32 r1, s0;"
-    "eor r0, r0;"
-    "svc 1;"
-    "vmov.f32 s0, r1;"
-    "bx lr");
-return 0;
-}
-__attribute__((naked))
-auto cos(float x) {
-asm("vmov.f32 r1, s0;"
-       "mov r0, #1;"
-       "svc 1;"
-       "vmov.f32 s0, r1;"
-       "bx lr");
-return 0;
-}
-__attribute__((naked))
-auto tan(float x) {
-asm("vmov.f32 r1, s0;"
-       "mov r0, #2;"
-       "svc 1;"
-       "vmov.f32 s0, r1;"
-       "bx lr");
-return 0;
-}
-__attribute__((naked))
-auto sqrt(float) {
-asm("vsqrt.f32 s0, s0; bx lr");
-return 0;
-}
-
-auto readalt() {
-adcsample_t s;
-asm("push {r4-r6}; svc 3; mov %0, r0; pop {r4-r6}" : "=&r"(s));
-return s;
-}
-
-// End stmdspgui header code
-
-)cpp";
-
-
-static const char *file_content = 
-R"cpp(adcsample_t *process_data(adcsample_t *samples, unsigned int size)
-{
-    return samples;
-}
-)cpp";
-
+#include "wxmain_devdata.h"
 
 enum Id {
     MeasureTimer = 1,
@@ -218,7 +55,11 @@ enum Id {
     MCodeDisassemble
 };
 
-MainFrame::MainFrame() : wxFrame(nullptr, wxID_ANY, "stmdspgui", wxPoint(50, 50), wxSize(640, 800))
+#include "wxmain_mfile.h"
+#include "wxmain_mrun.h"
+
+MainFrame::MainFrame() :
+    wxFrame(nullptr, wxID_ANY, "stmdspgui", wxDefaultPosition, wxSize(640, 800))
 {
     // Main frame structure:
     // Begin with a main splitter for the code and terminal panes
@@ -527,234 +368,6 @@ wxString MainFrame::compileEditorCode()
     }
 }
 
-void MainFrame::onFileNew(wxCommandEvent&)
-{
-    m_open_file_path = "";
-    m_text_editor->SetText(file_content);
-    m_text_editor->DiscardEdits();
-    m_status_bar->SetStatusText("Ready.");
-}
-
-void MainFrame::onFileOpen(wxCommandEvent&)
-{
-    wxFileDialog openDialog(this, "Open filter file", "", "",
-                            "C++ source file (*.cpp)|*.cpp",
-                            wxFD_OPEN | wxFD_FILE_MUST_EXIST);
-
-    if (openDialog.ShowModal() != wxID_CANCEL) {
-        if (wxFileInputStream file_stream (openDialog.GetPath()); file_stream.IsOk()) {
-            auto size = file_stream.GetSize();
-            auto buffer = new char[size + 1];
-            buffer[size] = '\0';
-            if (file_stream.ReadAll(buffer, size)) {
-                m_open_file_path = openDialog.GetPath();
-                m_text_editor->SetText(buffer);
-                m_text_editor->DiscardEdits();
-                m_compile_output->ChangeValue("");
-                m_status_bar->SetStatusText("Ready.");
-            } else {
-                m_status_bar->SetStatusText("Failed to read file contents.");
-            }
-            delete[] buffer;
-        } else {
-            m_status_bar->SetStatusText("Failed to open file.");
-        }
-    } else {
-        m_status_bar->SetStatusText("Ready.");
-    }
-}
-
-void MainFrame::onFileOpenTemplate(wxCommandEvent& event)
-{
-    auto file_path = wxGetCwd() + "/templates/" + m_menu_bar->GetLabel(event.GetId());
-
-    if (wxFileInputStream file_stream (file_path); file_stream.IsOk()) {
-        auto size = file_stream.GetSize();
-        auto buffer = new char[size + 1];
-        buffer[size] = '\0';
-        if (file_stream.ReadAll(buffer, size)) {
-            m_open_file_path = "";
-            m_text_editor->SetText(buffer);
-            //m_text_editor->DiscardEdits();
-            m_status_bar->SetStatusText("Ready.");
-        } else {
-            m_status_bar->SetStatusText("Failed to read file contents.");
-        }
-        delete[] buffer;
-    } else {
-        m_status_bar->SetStatusText("Ready.");
-    }
-}
-
-
-void MainFrame::onFileSave(wxCommandEvent& ce)
-{
-    if (m_text_editor->IsModified()) {
-        if (m_open_file_path.IsEmpty()) {
-            onFileSaveAs(ce);
-        } else {
-            if (wxFile file (m_open_file_path, wxFile::write); file.IsOpened()) {
-                file.Write(m_text_editor->GetText());
-                file.Close();
-                m_text_editor->DiscardEdits();
-                m_status_bar->SetStatusText("Saved.");
-            } else {
-                m_status_bar->SetStatusText("Save failed: couldn't open file.");
-            }
-        }
-    } else {
-        m_status_bar->SetStatusText("No modifications to save.");
-    }
-}
-
-void MainFrame::onFileSaveAs(wxCommandEvent&)
-{
-    if (m_text_editor->IsModified()) {
-        wxFileDialog saveDialog(this, "Save filter file", "", "",
-                                "C++ source file (*.cpp)|*.cpp",
-                                wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
-
-        if (saveDialog.ShowModal() != wxID_CANCEL) {
-            if (wxFile file (saveDialog.GetPath(), wxFile::write); file.IsOpened()) {
-                file.Write(m_text_editor->GetText());
-                file.Close();
-                m_text_editor->DiscardEdits();
-                m_open_file_path = saveDialog.GetPath();
-                m_status_bar->SetStatusText("Saved.");
-            } else {
-                m_status_bar->SetStatusText("Save failed: couldn't open file.");
-            }
-        }
-    } else {
-        m_status_bar->SetStatusText("No modifications to save.");
-    }
-}
-
-void MainFrame::onFileQuit(wxCommandEvent&)
-{
-    Close(true);
-}
-
-void MainFrame::onRunConnect(wxCommandEvent& ce)
-{
-    auto menuItem = dynamic_cast<wxMenuItem *>(ce.GetEventUserData());
-
-    if (!m_device) {
-        stmdsp::scanner scanner;
-        if (auto devices = scanner.scan(); devices.size() > 0) {
-            m_device = new stmdsp::device(devices.front());
-            if (m_device->connected()) {
-                auto rate = m_device->get_sample_rate();
-                m_rate_select->SetSelection(rate);
-
-                updateMenuOptions();
-                menuItem->SetItemLabel("&Disconnect");
-                m_status_bar->SetStatusText("Connected.");
-            } else {
-                delete m_device;
-                m_device = nullptr;
-
-                menuItem->SetItemLabel("&Connect");
-                m_status_bar->SetStatusText("Failed to connect.");
-            }
-        } else {
-            m_status_bar->SetStatusText("No devices found.");
-        }
-    } else {
-        delete m_device;
-        m_device = nullptr;
-        updateMenuOptions();
-        menuItem->SetItemLabel("&Connect");
-        m_status_bar->SetStatusText("Disconnected.");
-    }
-}
-
-void MainFrame::onRunStart(wxCommandEvent& ce)
-{
-    auto menuItem = dynamic_cast<wxMenuItem *>(ce.GetEventUserData());
-
-    if (!m_is_running) {
-        if (m_run_measure->IsChecked()) {
-            m_device->continuous_start_measure();
-            m_measure_timer->StartOnce(1000);
-        } else {
-            if (m_device->is_siggening() && m_wav_clip) {
-                m_measure_timer->Start(m_device->get_buffer_size() * 500 / 
-                                       srateNums[m_rate_select->GetSelection()]);
-            } else if (m_conv_result_log) {
-                m_measure_timer->Start(15);
-            } else if (m_run_draw_samples->IsChecked()) {
-                m_measure_timer->Start(300);
-            }
-
-            m_device->continuous_start();
-        }
-
-        m_rate_select->Enable(false);
-        menuItem->SetItemLabel("&Stop");
-        m_status_bar->SetStatusText("Running.");
-        m_is_running = true;
-    } else {
-        m_device->continuous_stop();
-        m_measure_timer->Stop();
-
-        m_rate_select->Enable(true);
-        menuItem->SetItemLabel("&Start");
-        m_status_bar->SetStatusText("Ready.");
-        m_is_running = false;
-
-        if (m_run_draw_samples->IsChecked())
-            m_compile_output->Refresh();
-    }
-}
-
-void MainFrame::onRunLogResults(wxCommandEvent& ce)
-{
-    auto menuItem = dynamic_cast<wxMenuItem *>(ce.GetEventUserData());
-    if (menuItem->IsChecked()) {
-        wxFileDialog dialog (this, "Choose log file", "", "", "*.csv",
-                            wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
-
-        if (dialog.ShowModal() != wxID_CANCEL) {
-            if (m_conv_result_log) {
-                m_conv_result_log->Close();
-                delete m_conv_result_log;
-                m_conv_result_log = nullptr;
-            }
-
-            m_conv_result_log = new wxFileOutputStream(dialog.GetPath());
-        }
-
-        m_status_bar->SetStatusText("Ready.");
-    } else if (m_conv_result_log) {
-        m_conv_result_log->Close();
-        delete m_conv_result_log;
-        m_conv_result_log = nullptr;
-    }
-}
-
-void MainFrame::onRunEditBSize(wxCommandEvent&)
-{
-    wxTextEntryDialog dialog (this, "Enter new buffer size (100-4096)", "Set Buffer Size");
-    if (dialog.ShowModal() == wxID_OK) {
-        if (wxString value = dialog.GetValue(); !value.IsEmpty()) {
-            if (unsigned long n; value.ToULong(&n)) {
-                if (n >= 100 && n <= stmdsp::SAMPLES_MAX) {
-                    m_device->continuous_set_buffer_size(n);
-                } else {
-                    m_status_bar->SetStatusText("Error: Invalid buffer size.");
-                }
-            } else {
-                m_status_bar->SetStatusText("Error: Invalid buffer size.");
-            }
-        } else {
-            m_status_bar->SetStatusText("Ready.");
-        }
-    } else {
-        m_status_bar->SetStatusText("Ready.");
-    }
-}
-
 void MainFrame::onToolbarSampleRate(wxCommandEvent& ce)
 {
     auto combo = dynamic_cast<wxComboBox *>(ce.GetEventUserData());
@@ -762,99 +375,6 @@ void MainFrame::onToolbarSampleRate(wxCommandEvent& ce)
     m_status_bar->SetStatusText("Ready.");
 }
 
-void MainFrame::onRunGenUpload(wxCommandEvent&)
-{
-    wxTextEntryDialog dialog (this, "Enter up to 8000 generator values below. "
-                                    "Values must be whole numbers between zero and 4095.",
-                                    "Enter Generator Values");
-    if (dialog.ShowModal() == wxID_OK) {
-        if (wxString values = dialog.GetValue(); !values.IsEmpty()) {
-            if (values[0] == '/') {
-                m_wav_clip = new wav::clip(values.Mid(1));
-                if (m_wav_clip->valid()) {
-                    m_status_bar->SetStatusText("Generator ready.");
-                } else {
-                    delete m_wav_clip;
-                    m_wav_clip = nullptr;
-                    m_status_bar->SetStatusText("Error: Bad WAV file.");
-                }
-            } else {
-                std::vector<stmdsp::dacsample_t> samples;
-                while (!values.IsEmpty() && samples.size() <= stmdsp::SAMPLES_MAX * 2) {
-                    if (auto number_end = values.find_first_not_of("0123456789");
-                        number_end != wxString::npos && number_end > 0)
-                    {
-                        auto number = values.Left(number_end);
-                        if (unsigned long n; number.ToULong(&n))
-                            samples.push_back(n & 4095);
-
-                        if (auto next = values.find_first_of("0123456789", number_end + 1);
-                            next != wxString::npos)
-                        {
-                            values = values.Mid(next);
-                        } else {
-                            break;
-                        }
-                    } else {
-                        break;
-                    }
-                }
-
-                if (samples.size() <= stmdsp::SAMPLES_MAX) {
-                    m_device->siggen_upload(&samples[0], samples.size());
-                    m_status_bar->SetStatusText("Generator ready.");
-                } else {
-                    m_status_bar->SetStatusText("Error: Too many samples (max is 8000).");
-                }
-            }
-        } else {
-            m_status_bar->SetStatusText("Error: No samples given.");
-        }
-    } else {
-        m_status_bar->SetStatusText("Ready.");
-    }
-}
-
-void MainFrame::onRunGenStart(wxCommandEvent& ce)
-{
-    auto menuItem = dynamic_cast<wxMenuItem *>(ce.GetEventUserData());
-    if (menuItem->IsChecked()) {
-        m_device->siggen_start();
-        menuItem->SetItemLabel("Stop &generator");
-        m_status_bar->SetStatusText("Generator running.");
-    } else {
-        m_device->siggen_stop();
-        menuItem->SetItemLabel("Start &generator");
-        m_status_bar->SetStatusText("Ready.");
-    }
-}
-
-void MainFrame::onRunUpload(wxCommandEvent&)
-{
-    if (auto file = compileEditorCode(); !file.IsEmpty()) {
-        if (wxFileInputStream file_stream (file); file_stream.IsOk()) {
-            auto size = file_stream.GetSize();
-            auto buffer = new unsigned char[size];
-            file_stream.ReadAll(buffer, size);
-            m_device->upload_filter(buffer, size);
-            m_status_bar->SetStatusText("Code uploaded.");
-        } else {
-             m_status_bar->SetStatusText("Couldn't load compiled code.");
-        }
-    }
-}
-
-void MainFrame::onRunUnload(wxCommandEvent&)
-{
-    m_device->unload_filter();
-    m_status_bar->SetStatusText("Unloaded code.");
-}
-
-void MainFrame::onRunCompile(wxCommandEvent&)
-{
-    compileEditorCode();
-}
-
 void MainFrame::onCodeDisassemble(wxCommandEvent&)
 {
     if (!m_temp_file_name.IsEmpty()) {
diff --git a/gui/wxmain_devdata.h b/gui/wxmain_devdata.h
new file mode 100644 (file)
index 0000000..4ca1eb2
--- /dev/null
@@ -0,0 +1,165 @@
+static const std::array<wxString, 6> srateValues {
+    "8 kS/s",
+    "16 kS/s",
+    "20 kS/s",
+    "32 kS/s",
+    "48 kS/s",
+    "96 kS/s"
+};
+static const std::array<unsigned int, 6> srateNums {
+    8000,
+    16000,
+    20000,
+    32000,
+    48000,
+    96000
+};
+
+static const char *makefile_text_h7 = R"make(
+all:
+       @arm-none-eabi-g++ -x c++ -Os -fno-exceptions -fno-rtti \
+                       -mcpu=cortex-m7 -mthumb -mfloat-abi=hard -mfpu=fpv5-d16 -mtune=cortex-m7 \
+                          -nostartfiles \
+                          -Wl,-Ttext-segment=0x00000000 -Wl,-zmax-page-size=512 -Wl,-eprocess_data_entry \
+                          $0 -o $0.o
+       @cp $0.o $0.orig.o
+       @arm-none-eabi-strip -s -S --strip-unneeded $0.o
+       @arm-none-eabi-objcopy --remove-section .ARM.attributes \
+                           --remove-section .comment \
+                           --remove-section .noinit \
+                           $0.o
+       arm-none-eabi-size $0.o
+)make";
+static const char *makefile_text_l4 = R"make(
+all:
+       @arm-none-eabi-g++ -x c++ -Os -fno-exceptions -fno-rtti \
+                          -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -mtune=cortex-m4 \
+                          -nostartfiles \
+                          -Wl,-Ttext-segment=0x10000000 -Wl,-zmax-page-size=512 -Wl,-eprocess_data_entry \
+                          $0 -o $0.o
+       @cp $0.o $0.orig.o
+       @arm-none-eabi-strip -s -S --strip-unneeded $0.o
+       @arm-none-eabi-objcopy --remove-section .ARM.attributes \
+                           --remove-section .comment \
+                           --remove-section .noinit \
+                           $0.o
+       arm-none-eabi-size $0.o
+)make";
+
+static const char *file_header_h7 = R"cpp(
+#include <cstdint>
+
+using adcsample_t = uint16_t;
+constexpr unsigned int SIZE = $0;
+adcsample_t *process_data(adcsample_t *samples, unsigned int size);
+extern "C" void process_data_entry()
+{
+    ((void (*)())process_data)();
+}
+
+constexpr double PI = 3.14159265358979323846L;
+__attribute__((naked))
+auto sin(double x) {
+asm("vmov.f64 r1, r2, d0;"
+    "eor r0, r0;"
+    "svc 1;"
+    "vmov.f64 d0, r1, r2;"
+    "bx lr");
+return 0;
+}
+__attribute__((naked))
+auto cos(double x) {
+asm("vmov.f64 r1, r2, d0;"
+       "mov r0, #1;"
+       "svc 1;"
+       "vmov.f64 d0, r1, r2;"
+       "bx lr");
+return 0;
+}
+__attribute__((naked))
+auto tan(double x) {
+asm("vmov.f64 r1, r2, d0;"
+       "mov r0, #2;"
+       "svc 1;"
+       "vmov.f64 d0, r1, r2;"
+       "bx lr");
+return 0;
+}
+__attribute__((naked))
+auto sqrt(double x) {
+asm("vsqrt.f64 d0, d0; bx lr");
+return 0;
+}
+
+auto readalt() {
+adcsample_t s;
+asm("svc 3; mov %0, r0" : "=&r"(s));
+return s;
+}
+
+// End stmdspgui header code
+
+)cpp";
+static const char *file_header_l4 = R"cpp(
+#include <cstdint>
+
+using adcsample_t = uint16_t;
+constexpr unsigned int SIZE = $0;
+adcsample_t *process_data(adcsample_t *samples, unsigned int size);
+extern "C" void process_data_entry()
+{
+    ((void (*)())process_data)();
+}
+
+constexpr float PI = 3.14159265358979L;
+__attribute__((naked))
+auto sin(float x) {
+asm("vmov.f32 r1, s0;"
+    "eor r0, r0;"
+    "svc 1;"
+    "vmov.f32 s0, r1;"
+    "bx lr");
+return 0;
+}
+__attribute__((naked))
+auto cos(float x) {
+asm("vmov.f32 r1, s0;"
+       "mov r0, #1;"
+       "svc 1;"
+       "vmov.f32 s0, r1;"
+       "bx lr");
+return 0;
+}
+__attribute__((naked))
+auto tan(float x) {
+asm("vmov.f32 r1, s0;"
+       "mov r0, #2;"
+       "svc 1;"
+       "vmov.f32 s0, r1;"
+       "bx lr");
+return 0;
+}
+__attribute__((naked))
+auto sqrt(float) {
+asm("vsqrt.f32 s0, s0; bx lr");
+return 0;
+}
+
+auto readalt() {
+adcsample_t s;
+asm("push {r4-r6}; svc 3; mov %0, r0; pop {r4-r6}" : "=&r"(s));
+return s;
+}
+
+// End stmdspgui header code
+
+)cpp";
+
+
+static const char *file_content = 
+R"cpp(adcsample_t *process_data(adcsample_t *samples, unsigned int size)
+{
+    return samples;
+}
+)cpp";
+
diff --git a/gui/wxmain_mfile.h b/gui/wxmain_mfile.h
new file mode 100644 (file)
index 0000000..7cc9177
--- /dev/null
@@ -0,0 +1,108 @@
+void MainFrame::onFileNew(wxCommandEvent&)
+{
+    m_open_file_path = "";
+    m_text_editor->SetText(file_content);
+    m_text_editor->DiscardEdits();
+    m_status_bar->SetStatusText("Ready.");
+}
+
+void MainFrame::onFileOpen(wxCommandEvent&)
+{
+    wxFileDialog openDialog(this, "Open filter file", "", "",
+                            "C++ source file (*.cpp)|*.cpp",
+                            wxFD_OPEN | wxFD_FILE_MUST_EXIST);
+
+    if (openDialog.ShowModal() != wxID_CANCEL) {
+        if (wxFileInputStream file_stream (openDialog.GetPath()); file_stream.IsOk()) {
+            auto size = file_stream.GetSize();
+            auto buffer = new char[size + 1];
+            buffer[size] = '\0';
+            if (file_stream.ReadAll(buffer, size)) {
+                m_open_file_path = openDialog.GetPath();
+                m_text_editor->SetText(buffer);
+                m_text_editor->DiscardEdits();
+                m_compile_output->ChangeValue("");
+                m_status_bar->SetStatusText("Ready.");
+            } else {
+                m_status_bar->SetStatusText("Failed to read file contents.");
+            }
+            delete[] buffer;
+        } else {
+            m_status_bar->SetStatusText("Failed to open file.");
+        }
+    } else {
+        m_status_bar->SetStatusText("Ready.");
+    }
+}
+
+void MainFrame::onFileOpenTemplate(wxCommandEvent& event)
+{
+    auto file_path = wxGetCwd() + "/templates/" + m_menu_bar->GetLabel(event.GetId());
+
+    if (wxFileInputStream file_stream (file_path); file_stream.IsOk()) {
+        auto size = file_stream.GetSize();
+        auto buffer = new char[size + 1];
+        buffer[size] = '\0';
+        if (file_stream.ReadAll(buffer, size)) {
+            m_open_file_path = "";
+            m_text_editor->SetText(buffer);
+            //m_text_editor->DiscardEdits();
+            m_status_bar->SetStatusText("Ready.");
+        } else {
+            m_status_bar->SetStatusText("Failed to read file contents.");
+        }
+        delete[] buffer;
+    } else {
+        m_status_bar->SetStatusText("Ready.");
+    }
+}
+
+
+void MainFrame::onFileSave(wxCommandEvent& ce)
+{
+    if (m_text_editor->IsModified()) {
+        if (m_open_file_path.IsEmpty()) {
+            onFileSaveAs(ce);
+        } else {
+            if (wxFile file (m_open_file_path, wxFile::write); file.IsOpened()) {
+                file.Write(m_text_editor->GetText());
+                file.Close();
+                m_text_editor->DiscardEdits();
+                m_status_bar->SetStatusText("Saved.");
+            } else {
+                m_status_bar->SetStatusText("Save failed: couldn't open file.");
+            }
+        }
+    } else {
+        m_status_bar->SetStatusText("No modifications to save.");
+    }
+}
+
+void MainFrame::onFileSaveAs(wxCommandEvent&)
+{
+    if (m_text_editor->IsModified()) {
+        wxFileDialog saveDialog(this, "Save filter file", "", "",
+                                "C++ source file (*.cpp)|*.cpp",
+                                wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
+
+        if (saveDialog.ShowModal() != wxID_CANCEL) {
+            if (wxFile file (saveDialog.GetPath(), wxFile::write); file.IsOpened()) {
+                file.Write(m_text_editor->GetText());
+                file.Close();
+                m_text_editor->DiscardEdits();
+                m_open_file_path = saveDialog.GetPath();
+                m_status_bar->SetStatusText("Saved.");
+            } else {
+                m_status_bar->SetStatusText("Save failed: couldn't open file.");
+            }
+        }
+    } else {
+        m_status_bar->SetStatusText("No modifications to save.");
+    }
+}
+
+void MainFrame::onFileQuit(wxCommandEvent&)
+{
+    Close(true);
+}
+
diff --git a/gui/wxmain_mrun.h b/gui/wxmain_mrun.h
new file mode 100644 (file)
index 0000000..b00898a
--- /dev/null
@@ -0,0 +1,228 @@
+void MainFrame::onRunConnect(wxCommandEvent& ce)
+{
+    auto menuItem = dynamic_cast<wxMenuItem *>(ce.GetEventUserData());
+
+    if (!m_device) {
+        stmdsp::scanner scanner;
+        if (auto devices = scanner.scan(); devices.size() > 0) {
+            m_device = new stmdsp::device(devices.front());
+            if (m_device->connected()) {
+                auto rate = m_device->get_sample_rate();
+                m_rate_select->SetSelection(rate);
+
+                updateMenuOptions();
+                menuItem->SetItemLabel("&Disconnect");
+                m_status_bar->SetStatusText("Connected.");
+            } else {
+                delete m_device;
+                m_device = nullptr;
+
+                menuItem->SetItemLabel("&Connect");
+                m_status_bar->SetStatusText("Failed to connect.");
+            }
+        } else {
+            m_status_bar->SetStatusText("No devices found.");
+        }
+    } else {
+        delete m_device;
+        m_device = nullptr;
+        updateMenuOptions();
+        menuItem->SetItemLabel("&Connect");
+        m_status_bar->SetStatusText("Disconnected.");
+    }
+}
+
+void MainFrame::onRunStart(wxCommandEvent& ce)
+{
+    auto menuItem = dynamic_cast<wxMenuItem *>(ce.GetEventUserData());
+
+    if (!m_is_running) {
+        if (m_run_measure->IsChecked()) {
+            m_device->continuous_start_measure();
+            m_measure_timer->StartOnce(1000);
+        } else {
+            if (m_device->is_siggening() && m_wav_clip) {
+                m_measure_timer->Start(m_device->get_buffer_size() * 500 / 
+                                       srateNums[m_rate_select->GetSelection()]);
+            } else if (m_conv_result_log) {
+                m_measure_timer->Start(15);
+            } else if (m_run_draw_samples->IsChecked()) {
+                m_measure_timer->Start(300);
+            }
+
+            m_device->continuous_start();
+        }
+
+        m_rate_select->Enable(false);
+        menuItem->SetItemLabel("&Stop");
+        m_status_bar->SetStatusText("Running.");
+        m_is_running = true;
+    } else {
+        m_device->continuous_stop();
+        m_measure_timer->Stop();
+
+        m_rate_select->Enable(true);
+        menuItem->SetItemLabel("&Start");
+        m_status_bar->SetStatusText("Ready.");
+        m_is_running = false;
+
+        if (m_run_draw_samples->IsChecked())
+            m_compile_output->Refresh();
+    }
+}
+
+void MainFrame::onRunLogResults(wxCommandEvent& ce)
+{
+    auto menuItem = dynamic_cast<wxMenuItem *>(ce.GetEventUserData());
+    if (menuItem->IsChecked()) {
+        wxFileDialog dialog (this, "Choose log file", "", "", "*.csv",
+                            wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
+
+        if (dialog.ShowModal() != wxID_CANCEL) {
+            if (m_conv_result_log) {
+                m_conv_result_log->Close();
+                delete m_conv_result_log;
+                m_conv_result_log = nullptr;
+            }
+
+            m_conv_result_log = new wxFileOutputStream(dialog.GetPath());
+        }
+
+        m_status_bar->SetStatusText("Ready.");
+    } else if (m_conv_result_log) {
+        m_conv_result_log->Close();
+        delete m_conv_result_log;
+        m_conv_result_log = nullptr;
+    }
+}
+
+void MainFrame::onRunEditBSize(wxCommandEvent&)
+{
+    wxTextEntryDialog dialog (this, "Enter new buffer size (100-4096)", "Set Buffer Size");
+    if (dialog.ShowModal() == wxID_OK) {
+        if (wxString value = dialog.GetValue(); !value.IsEmpty()) {
+            if (unsigned long n; value.ToULong(&n)) {
+                if (n >= 100 && n <= stmdsp::SAMPLES_MAX) {
+                    m_device->continuous_set_buffer_size(n);
+                } else {
+                    m_status_bar->SetStatusText("Error: Invalid buffer size.");
+                }
+            } else {
+                m_status_bar->SetStatusText("Error: Invalid buffer size.");
+            }
+        } else {
+            m_status_bar->SetStatusText("Ready.");
+        }
+    } else {
+        m_status_bar->SetStatusText("Ready.");
+    }
+}
+
+void MainFrame::onRunGenUpload(wxCommandEvent&)
+{
+    if (SiggenDialog dialog (this); dialog.ShowModal() == wxID_OK) {
+        auto result = dialog.Result();
+        if (result.Find(".wav") != wxNOT_FOUND) {
+            // Audio
+            m_wav_clip = new wav::clip(result/*.Mid(1)*/);
+            if (m_wav_clip->valid()) {
+                m_status_bar->SetStatusText("Generator ready.");
+            } else {
+                delete m_wav_clip;
+                m_wav_clip = nullptr;
+                m_status_bar->SetStatusText("Error: Bad WAV file.");
+            }
+        } else if (result.find_first_not_of("0123456789 \t\r\n") == wxString::npos) {
+            // List
+            std::vector<stmdsp::dacsample_t> samples;
+            while (!result.IsEmpty() && samples.size() <= stmdsp::SAMPLES_MAX * 2) {
+                if (auto number_end = result.find_first_not_of("0123456789");
+                    number_end != wxString::npos && number_end > 0)
+                {
+                    auto number = result.Left(number_end);
+                    if (unsigned long n; number.ToULong(&n))
+                        samples.push_back(n & 4095);
+
+                    if (auto next = result.find_first_of("0123456789", number_end + 1);
+                        next != wxString::npos)
+                    {
+                        result = result.Mid(next);
+                    } else {
+                        break;
+                    }
+                } else {
+                    break;
+                }
+            }
+
+            if (samples.size() <= stmdsp::SAMPLES_MAX * 2) {
+                m_device->siggen_upload(&samples[0], samples.size());
+                m_status_bar->SetStatusText("Generator ready.");
+            } else {
+                m_status_bar->SetStatusText(wxString::Format("Error: Too many samples (max is %u).",
+                                                             stmdsp::SAMPLES_MAX * 2));
+            }
+        } else {
+            // Formula
+            m_status_bar->SetStatusText("Sorry, formulas not supported yet.");
+        }
+    //    if (wxString values = dialog.GetValue(); !values.IsEmpty()) {
+    //        if (values[0] == '/') {
+    //            m_wav_clip = new wav::clip(values.Mid(1));
+    //            if (m_wav_clip->valid()) {
+    //                m_status_bar->SetStatusText("Generator ready.");
+    //            } else {
+    //                delete m_wav_clip;
+    //                m_wav_clip = nullptr;
+    //                m_status_bar->SetStatusText("Error: Bad WAV file.");
+    //            }
+    //        } else {
+    //        }
+    //    } else {
+    //        m_status_bar->SetStatusText("Error: No samples given.");
+    //    }
+    } else {
+        m_status_bar->SetStatusText("Ready.");
+    }
+}
+
+void MainFrame::onRunGenStart(wxCommandEvent& ce)
+{
+    auto menuItem = dynamic_cast<wxMenuItem *>(ce.GetEventUserData());
+    if (menuItem->IsChecked()) {
+        m_device->siggen_start();
+        menuItem->SetItemLabel("Stop &generator");
+        m_status_bar->SetStatusText("Generator running.");
+    } else {
+        m_device->siggen_stop();
+        menuItem->SetItemLabel("Start &generator");
+        m_status_bar->SetStatusText("Ready.");
+    }
+}
+
+void MainFrame::onRunUpload(wxCommandEvent&)
+{
+    if (auto file = compileEditorCode(); !file.IsEmpty()) {
+        if (wxFileInputStream file_stream (file); file_stream.IsOk()) {
+            auto size = file_stream.GetSize();
+            auto buffer = new unsigned char[size];
+            file_stream.ReadAll(buffer, size);
+            m_device->upload_filter(buffer, size);
+            m_status_bar->SetStatusText("Code uploaded.");
+        } else {
+             m_status_bar->SetStatusText("Couldn't load compiled code.");
+        }
+    }
+}
+
+void MainFrame::onRunUnload(wxCommandEvent&)
+{
+    m_device->unload_filter();
+    m_status_bar->SetStatusText("Unloaded code.");
+}
+
+void MainFrame::onRunCompile(wxCommandEvent&)
+{
+    compileEditorCode();
+}
+
diff --git a/gui/wxsiggen.cpp b/gui/wxsiggen.cpp
new file mode 100644 (file)
index 0000000..f6abce8
--- /dev/null
@@ -0,0 +1,89 @@
+/**
+ * @file wxsiggen.cpp
+ * @brief Dialog prompt for providing signal generator input.
+ *
+ * Copyright (C) 2021 Clyne Sullivan
+ *
+ * Distributed under the GNU GPL v3 or later. 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 "wxsiggen.hpp"
+
+#include <wx/filedlg.h>
+#include <wx/radiobox.h>
+
+static const std::array<wxString, 3> Sources {{
+    "List",
+    "Formula",
+    "WAV audio"
+}};
+static const std::array<wxString, 3> Instructions {{
+    "Enter a list of numbers:",
+    "Enter a formula. f(x) = ",
+    wxEmptyString
+}};
+
+SiggenDialog::SiggenDialog(wxWindow *parent) :
+    wxDialog(parent, wxID_ANY, "stmdspgui signal generator", wxDefaultPosition, wxSize(300, 200))
+{
+    m_instruction = new wxStaticText(this, wxID_ANY, wxEmptyString, wxPoint(10, 70));
+    m_source_list = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxPoint(10, 100), wxSize(280, 30));
+    m_source_math = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxPoint(10, 100), wxSize(280, 30));
+    m_source_file = new wxButton(this, 42, "Choose file...", wxPoint(10, 75), wxSize(280, 50));
+
+    auto radio = new wxRadioBox(this, wxID_ANY, "Source", wxPoint(10, 10), wxSize(280, 50),
+                                Sources.size(), Sources.data());
+    auto save = new wxButton(this, 43, "Save", wxPoint(200, 150));
+
+    m_instruction->SetLabel(Instructions[0]);
+    m_source_math->Hide();
+    m_source_file->Hide();
+
+    Bind(wxEVT_RADIOBOX, &SiggenDialog::onSourceChange, this, wxID_ANY, wxID_ANY, radio);
+    Bind(wxEVT_BUTTON, &SiggenDialog::onSourceFile, this, 42, 42, m_source_file);
+    Bind(wxEVT_BUTTON, &SiggenDialog::onSave, this, 43, 43, save);
+}
+
+SiggenDialog::~SiggenDialog()
+{
+    Unbind(wxEVT_BUTTON, &SiggenDialog::onSave, this, 43, 43);
+    Unbind(wxEVT_BUTTON, &SiggenDialog::onSourceFile, this, 42, 42);
+    Unbind(wxEVT_RADIOBOX, &SiggenDialog::onSourceChange, this, wxID_ANY, wxID_ANY);
+}
+
+void SiggenDialog::onSourceFile(wxCommandEvent&)
+{
+    wxFileDialog dialog (this, "Open audio file", "", "",
+                         "Audio file (*.wav)|*.wav",
+                         wxFD_OPEN | wxFD_FILE_MUST_EXIST);
+    if (dialog.ShowModal() != wxID_CANCEL)
+        m_result = dialog.GetPath();
+}
+
+void SiggenDialog::onSave(wxCommandEvent&)
+{
+    if (m_source_list->IsShown())
+        m_result = m_source_list->GetValue();
+    else if (m_source_math->IsShown())
+        m_result = m_source_math->GetValue();
+
+    EndModal(!m_result.IsEmpty() ? wxID_OK : wxID_CANCEL);
+}
+
+void SiggenDialog::onSourceChange(wxCommandEvent& ce)
+{
+    auto radio = dynamic_cast<wxRadioBox*>(ce.GetEventObject());
+    if (radio == nullptr)
+        return;
+
+    m_result.Clear();
+    if (int selection = radio->GetSelection(); selection >= 0 && selection < Sources.size()) {
+        m_instruction->SetLabel(Instructions[selection]);
+        m_source_list->Show(selection == 0);
+        m_source_math->Show(selection == 1);
+        m_source_file->Show(selection == 2);
+    }
+}
+
diff --git a/gui/wxsiggen.hpp b/gui/wxsiggen.hpp
new file mode 100644 (file)
index 0000000..acf479e
--- /dev/null
@@ -0,0 +1,42 @@
+/**
+ * @file wxsiggen.hpp
+ * @brief Dialog prompt for providing signal generator input.
+ *
+ * Copyright (C) 2021 Clyne Sullivan
+ *
+ * Distributed under the GNU GPL v3 or later. You should have received a copy of
+ * the GNU General Public License along with this program.
+ * If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef WXSIGGEN_HPP_
+#define WXSIGGEN_HPP_
+
+#include <wx/button.h>
+#include <wx/dialog.h>
+#include <wx/stattext.h>
+#include <wx/textctrl.h>
+
+class SiggenDialog : public wxDialog {
+public:
+    SiggenDialog(wxWindow *parent);
+    ~SiggenDialog();
+
+    auto Result() const {
+        return m_result;
+    }
+
+    void onSourceChange(wxCommandEvent&);
+    void onSourceFile(wxCommandEvent&);
+    void onSave(wxCommandEvent&);
+
+private:
+    wxStaticText *m_instruction = nullptr;
+    wxTextCtrl *m_source_list = nullptr;
+    wxTextCtrl *m_source_math = nullptr;
+    wxButton *m_source_file = nullptr;
+    wxString m_result;
+};
+
+#endif // WXSIGGEN_HPP_
+