]> code.bitgloo.com Git - clyne/stmdspgui.git/commitdiff
wip: major code refactor, split most gui from logic
authorClyne Sullivan <clyne@bitgloo.com>
Sun, 21 Nov 2021 16:30:41 +0000 (11:30 -0500)
committerClyne Sullivan <clyne@bitgloo.com>
Sun, 21 Nov 2021 16:30:41 +0000 (11:30 -0500)
Makefile
source/code.cpp
source/device.cpp
source/gui_code.cpp [new file with mode: 0644]
source/gui_device.cpp [new file with mode: 0644]
source/stmdsp/stmdsp.cpp
source/stmdsp/stmdsp.hpp
source/wav.hpp

index 7aa95dbb89c54c01bebb8c333d395a408ff6e89b..26e0208e4f9062f0f9ab52cd0fc5275509b556e2 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -13,7 +13,8 @@ OUTPUT := stmdspgui
 #CXXFLAGS := -std=c++20 -O2 \
 #            -Isource -Isource/imgui -Isource/stmdsp -Isource/serial/include
 CXXFLAGS := -std=c++20 -ggdb -O0 -g3 \
-            -Isource -Isource/imgui -Isource/stmdsp -Isource/serial/include
+            -Isource -Isource/imgui -Isource/stmdsp -Isource/serial/include \
+            -Wall -Wextra -pedantic
 
 all: $(OUTPUT)
 
index 163d6e5397c7554f9ab3d386f96a24944911f500..7a9afaaf61e0d46b2d5843a7580cf618cb8d8afc 100644 (file)
@@ -1,20 +1,3 @@
-/**
- * @file code.cpp
- * @brief Contains code for algorithm-code-related UI elements and logic.
- *
- * 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 "imgui.h"
-#include "backends/imgui_impl_sdl.h"
-#include "backends/imgui_impl_opengl2.h"
-#include "TextEditor.h"
-
-#include "config.h"
 #include "stmdsp.hpp"
 #include "stmdsp_code.hpp"
 
 #include <filesystem>
 #include <fstream>
 #include <iostream>
+#include <memory>
 #include <string>
 
 extern std::shared_ptr<stmdsp::device> m_device;
-
 extern void log(const std::string& str);
 
-TextEditor editor; // file.cpp
 std::string tempFileName; // device.cpp
-static std::string editorCompiled;
 
 static std::string newTempFileName();
-static bool codeExecuteCommand(const std::string& command, const std::string& file);
-static void stringReplaceAll(std::string& str, const std::string& what, const std::string& with);
-static void compileEditorCode();
-static void disassembleCode();
-
-void codeEditorInit()
-{
-    editor.SetLanguageDefinition(TextEditor::LanguageDefinition::CPlusPlus());
-    editor.SetPalette(TextEditor::GetLightPalette());
-}
-
-void codeRenderMenu()
-{
-    if (ImGui::BeginMenu("Code")) {
-        if (ImGui::MenuItem("Compile code"))
-            compileEditorCode();
-        if (ImGui::MenuItem("Show disassembly"))
-            disassembleCode();
-        ImGui::EndMenu();
-    }
-}
-
-void codeRenderToolbar()
-{
-    if (ImGui::Button("Compile"))
-        compileEditorCode();
-}
-
-void codeRenderWidgets()
-{
-    editor.Render("code", {WINDOW_WIDTH - 15, 450}, true);
-}
-
-std::string newTempFileName()
-{
-    const auto path = std::filesystem::temp_directory_path() / "stmdspgui_build";
-    return path.string();
-}
-
-bool codeExecuteCommand(const std::string& command, const std::string& file)
-{
-    if (system(command.c_str()) == 0) {
-        if (std::ifstream output (file); output.good()) {
-            std::ostringstream sstr;
-            sstr << output.rdbuf();
-            log(sstr.str().c_str());
-        } else {
-            log("Could not read command output!");
-        }
-
-        std::filesystem::remove(file);
-        return true;
-    } else {
-        return false;
-    }
-}
-
-void stringReplaceAll(std::string& str, const std::string& what, const std::string& with)
-{
-    std::size_t i;
-    while ((i = str.find(what)) != std::string::npos) {
-        str.replace(i, what.size(), with);
-        i += what.size();
-    }
-};
-
-void compileEditorCode()
+static bool codeExecuteCommand(
+    const std::string& command,
+    const std::string& file);
+static void stringReplaceAll(
+    std::string& str,
+    const std::string& what,
+    const std::string& with);
+
+void compileEditorCode(const std::string& code)
 {
     log("Compiling...");
 
-    // Scrap cached build if there are changes
-    if (editor.GetText().compare(editorCompiled) != 0) {
+    if (tempFileName.empty()) {
+        tempFileName = newTempFileName();
+    } else {
         std::filesystem::remove(tempFileName + ".o");
         std::filesystem::remove(tempFileName + ".orig.o");
     }
@@ -112,10 +36,6 @@ void compileEditorCode()
     const auto platform = m_device ? m_device->get_platform()
                                    : stmdsp::platform::L4;
 
-    if (tempFileName.empty())
-        tempFileName = newTempFileName();
-
-
     {
         std::ofstream file (tempFileName, std::ios::trunc | std::ios::binary);
 
@@ -127,7 +47,7 @@ void compileEditorCode()
 
         stringReplaceAll(file_text, "$0", std::to_string(buffer_size));
 
-        file << file_text << '\n' << editor.GetText();
+        file << file_text << '\n' << code;
     }
 
     const auto scriptFile = tempFileName +
@@ -156,12 +76,10 @@ void compileEditorCode()
 
     const auto makeOutput = scriptFile + ".log";
     const auto makeCommand = scriptFile + " > " + makeOutput + " 2>&1";
-    if (codeExecuteCommand(makeCommand, makeOutput)) {
-        editorCompiled = editor.GetText();
+    if (codeExecuteCommand(makeCommand, makeOutput))
         log("Compilation succeeded.");
-    } else {
+    else
         log("Compilation failed.");
-    }
 
     std::filesystem::remove(tempFileName);
     std::filesystem::remove(scriptFile);
@@ -171,19 +89,50 @@ void disassembleCode()
 {
     log("Disassembling...");
 
-    if (tempFileName.size() == 0 || editor.GetText().compare(editorCompiled) != 0) {
-        compileEditorCode();
-    }
+    //if (tempFileName.empty())
+    //    compileEditorCode();
 
     const auto output = tempFileName + ".asm.log";
     const auto command =
         std::string("arm-none-eabi-objdump -d --no-show-raw-insn ") +
         tempFileName + ".orig.o > " + output + " 2>&1";
 
-    if (codeExecuteCommand(command, output)) {
+    if (codeExecuteCommand(command, output))
         log("Ready.");
-    } else {
+    else
         log("Failed to load disassembly.");
+}
+
+std::string newTempFileName()
+{
+    const auto path = std::filesystem::temp_directory_path() / "stmdspgui_build";
+    return path.string();
+}
+
+bool codeExecuteCommand(const std::string& command, const std::string& file)
+{
+    bool success = system(command.c_str()) == 0;
+    if (success) {
+        if (std::ifstream output (file); output.good()) {
+            std::ostringstream sstr;
+            sstr << output.rdbuf();
+            log(sstr.str().c_str());
+        } else {
+            log("Could not read command output!");
+        }
+
+        std::filesystem::remove(file);
     }
+
+    return success;
 }
 
+void stringReplaceAll(std::string& str, const std::string& what, const std::string& with)
+{
+    std::size_t i;
+    while ((i = str.find(what)) != std::string::npos) {
+        str.replace(i, what.size(), with);
+        i += what.size();
+    }
+};
+
index 39bc746254330046ea622fdd310c494e26d9a7fb..df3f3614728bd92ab68eadec47986286edf3be5e 100644 (file)
@@ -12,8 +12,6 @@
 #include "stmdsp.hpp"
 
 #include "imgui.h"
-#include "imgui_internal.h"
-#include "ImGuiFileDialog.h"
 #include "wav.hpp"
 
 #include <array>
 #include <cmath>
 #include <deque>
 #include <fstream>
+#include <functional>
 #include <iostream>
 #include <memory>
 #include <mutex>
+#include <string>
+#include <string_view>
 #include <thread>
+#include <vector>
 
 extern std::string tempFileName;
 extern void log(const std::string& str);
-
 extern std::vector<stmdsp::dacsample_t> deviceGenLoadFormulaEval(const std::string_view);
 
 std::shared_ptr<stmdsp::device> m_device;
 
-static const std::array<const char *, 6> sampleRateList {{
-    "8 kHz",
-    "16 kHz",
-    "20 kHz",
-    "32 kHz",
-    "48 kHz",
-    "96 kHz"
-}};
-static const char *sampleRatePreview = sampleRateList[0];
-static const std::array<unsigned int, 6> sampleRateInts {{
-    8'000,
-    16'000,
-    20'000,
-    32'000,
-    48'000,
-    96'000
-}};
-
-static bool measureCodeTime = false;
-static bool drawSamples = false;
-static bool logResults = false;
-static bool genRunning = false;
-static bool drawSamplesInput = false;
-
-static bool popupRequestBuffer = false;
-static bool popupRequestSiggen = false;
-static bool popupRequestDraw = false;
-static bool popupRequestLog = false;
-
 static std::timed_mutex mutexDrawSamples;
 static std::timed_mutex mutexDeviceLoad;
-
 static std::ofstream logSamplesFile;
 static wav::clip wavOutput;
-
 static std::deque<stmdsp::dacsample_t> drawSamplesQueue;
 static std::deque<stmdsp::dacsample_t> drawSamplesInputQueue;
-static double drawSamplesTimeframe = 1.0; // seconds
+static bool drawSamplesInput = false;
 static unsigned int drawSamplesBufferSize = 1;
 
+void deviceSetInputDrawing(bool enabled)
+{
+    drawSamplesInput = enabled;
+}
+
 static void measureCodeTask(std::shared_ptr<stmdsp::device> device)
 {
     std::this_thread::sleep_for(std::chrono::seconds(1));
@@ -105,7 +80,7 @@ static std::chrono::duration<double> getBufferPeriod(
 {
     if (device) {
         const double bufferSize = device->get_buffer_size();
-        const double sampleRate = sampleRateInts[device->get_sample_rate()];
+        const double sampleRate = device->get_sample_rate();
         return std::chrono::duration<double>(bufferSize / sampleRate * factor);
     } else {
         return {};
@@ -117,7 +92,6 @@ static void drawSamplesTask(std::shared_ptr<stmdsp::device> device)
     if (!device)
         return;
 
-    const bool doLogger = logResults && logSamplesFile.good();
     const auto bufferTime = getBufferPeriod(device);
 
     std::unique_lock<std::timed_mutex> lockDraw (mutexDrawSamples, std::defer_lock);
@@ -138,7 +112,7 @@ static void drawSamplesTask(std::shared_ptr<stmdsp::device> device)
             lockDevice.unlock();
 
             addToQueue(drawSamplesQueue, chunk);
-            if (doLogger) {
+            if (logSamplesFile.good()) {
                 for (const auto& s : chunk)
                     logSamplesFile << s << '\n';
             }
@@ -147,7 +121,7 @@ static void drawSamplesTask(std::shared_ptr<stmdsp::device> device)
             std::this_thread::sleep_for(std::chrono::milliseconds(500));
         }
 
-        if (drawSamplesInput && popupRequestDraw) {
+        if (drawSamplesInput) {
             if (lockDevice.try_lock_for(std::chrono::milliseconds(1))) {
                 const auto chunk2 = tryReceiveChunk(device,
                     std::mem_fn(&stmdsp::device::continuous_read_input));
@@ -182,7 +156,7 @@ static void feedSigGenTask(std::shared_ptr<stmdsp::device> device)
 
     std::vector<int16_t> wavIntBuf (wavBuf.size());
 
-    while (genRunning) {
+    while (device->is_siggening()) {
         const auto next = std::chrono::high_resolution_clock::now() + delay;
 
         wavOutput.next(wavIntBuf.data(), wavIntBuf.size());
@@ -228,346 +202,60 @@ static void statusTask(std::shared_ptr<stmdsp::device> device)
     }
 }
 
-static void deviceConnect();
-static void deviceStart();
-static void deviceAlgorithmUpload();
-static void deviceAlgorithmUnload();
-static void deviceGenLoadList(std::string_view list);
-static void deviceGenLoadFormula(std::string_view list);
-
-void deviceRenderWidgets()
+void deviceLoadAudioFile(const std::string& file)
 {
-    static char *siggenBuffer = nullptr;
-    static int siggenOption = 0;
-
-    if (popupRequestSiggen) {
-        siggenBuffer = new char[65536];
-        *siggenBuffer = '\0';
-        ImGui::OpenPopup("siggen");
-        popupRequestSiggen = false;
-    } else if (popupRequestBuffer) {
-        ImGui::OpenPopup("buffer");
-        popupRequestBuffer = false;
-    } else if (popupRequestLog) {
-        ImGuiFileDialog::Instance()->OpenModal(
-            "ChooseFileLogGen", "Choose File", ".csv", ".");
-        popupRequestLog = false;
-    }
-
-    if (ImGui::BeginPopup("siggen")) {
-        if (ImGui::RadioButton("List", &siggenOption, 0))
-            siggenBuffer[0] = '\0';
-        ImGui::SameLine();
-        if (ImGui::RadioButton("Formula", &siggenOption, 1))
-            siggenBuffer[0] = '\0';
-        ImGui::SameLine();
-        if (ImGui::RadioButton("Audio File", &siggenOption, 2))
-            siggenBuffer[0] = '\0';
-
-        switch (siggenOption) {
-        case 0:
-            ImGui::Text("Enter a list of numbers:");
-            ImGui::PushStyleColor(ImGuiCol_FrameBg, {.8, .8, .8, 1});
-            ImGui::InputText("", siggenBuffer, 65536);
-            ImGui::PopStyleColor();
-            break;
-        case 1:
-            ImGui::Text("Enter a formula. f(x) = ");
-            ImGui::PushStyleColor(ImGuiCol_FrameBg, {.8, .8, .8, 1});
-            ImGui::InputText("", siggenBuffer, 65536);
-            ImGui::PopStyleColor();
-            break;
-        case 2:
-            if (ImGui::Button("Choose File")) {
-                // This dialog will override the siggen popup, closing it.
-                ImGuiFileDialog::Instance()->OpenModal(
-                    "ChooseFileLogGen", "Choose File", ".wav", ".");
-            }
-            break;
-        }
-
-        if (ImGui::Button("Cancel")) {
-            delete[] siggenBuffer;
-            ImGui::CloseCurrentPopup();
-        }
-
-        if (ImGui::Button("Save")) {
-            switch (siggenOption) {
-            case 0:
-                deviceGenLoadList(siggenBuffer);
-                break;
-            case 1:
-                deviceGenLoadFormula(siggenBuffer);
-                break;
-            case 2:
-                break;
-            }
-
-            delete[] siggenBuffer;
-            ImGui::CloseCurrentPopup();
-        }
-
-        ImGui::EndPopup();
-    }
-
-    if (ImGui::BeginPopup("buffer")) {
-        static char bufferSizeStr[5] = "4096";
-        ImGui::Text("Please enter a new sample buffer size (100-4096):");
-        ImGui::PushStyleColor(ImGuiCol_FrameBg, {.8, .8, .8, 1});
-        ImGui::InputText("", bufferSizeStr, sizeof(bufferSizeStr), ImGuiInputTextFlags_CharsDecimal);
-        ImGui::PopStyleColor();
-        if (ImGui::Button("Save")) {
-            if (m_device) {
-                int n = std::clamp(std::stoi(bufferSizeStr), 100, 4096);
-                m_device->continuous_set_buffer_size(n);
-            }
-            ImGui::CloseCurrentPopup();
-        }
-        ImGui::SameLine();
-        if (ImGui::Button("Cancel"))
-            ImGui::CloseCurrentPopup();
-        ImGui::EndPopup();
-    }
-
-    if (ImGuiFileDialog::Instance()->Display("ChooseFileLogGen",
-                                             ImGuiWindowFlags_NoCollapse,
-                                             ImVec2(460, 540)))
-    {
-        if (ImGuiFileDialog::Instance()->IsOk()) {
-            auto filePathName = ImGuiFileDialog::Instance()->GetFilePathName();
-            auto ext = filePathName.substr(filePathName.size() - 4);
-
-            if (ext.compare(".wav") == 0) {
-                wavOutput = wav::clip(filePathName.c_str());
-                if (wavOutput.valid())
-                    log("Audio file loaded.");
-                else
-                    log("Error: Bad WAV audio file.");
-
-                delete[] siggenBuffer;
-            } else if (ext.compare(".csv") == 0) {
-                logSamplesFile = std::ofstream(filePathName);
-                if (logSamplesFile.good())
-                    log("Log file ready.");
-            }
-        }
-
-        ImGuiFileDialog::Instance()->Close();
-    }
+    wavOutput = wav::clip(file);
+    if (wavOutput.valid())
+        log("Audio file loaded.");
+    else
+        log("Error: Bad WAV audio file.");
 }
 
-void deviceRenderDraw()
+void deviceLoadLogFile(const std::string& file)
 {
-    if (popupRequestDraw) {
-        static std::vector<stmdsp::dacsample_t> buffer;
-        static decltype(buffer.begin()) bufferCursor;
-        static std::vector<stmdsp::dacsample_t> bufferInput;
-        static decltype(bufferInput.begin()) bufferInputCursor;
-        static unsigned int yMinMax = 4095;
-
-        ImGui::Begin("draw", &popupRequestDraw);
-        ImGui::Text("Draw input ");
-        ImGui::SameLine();
-        ImGui::Checkbox("", &drawSamplesInput);
-        ImGui::SameLine();
-        ImGui::Text("Time: %0.3f sec", drawSamplesTimeframe);
-        ImGui::SameLine();
-        if (ImGui::Button("-", {30, 0})) {
-            drawSamplesTimeframe = std::max(drawSamplesTimeframe / 2., 0.0078125);
-            auto sr = sampleRateInts[m_device->get_sample_rate()];
-            auto tf = drawSamplesTimeframe;
-            drawSamplesBufferSize = std::round(sr * tf);
-        }
-        ImGui::SameLine();
-        if (ImGui::Button("+", {30, 0})) {
-            drawSamplesTimeframe = std::min(drawSamplesTimeframe * 2, 32.);
-            auto sr = sampleRateInts[m_device->get_sample_rate()];
-            auto tf = drawSamplesTimeframe;
-            drawSamplesBufferSize = std::round(sr * tf);
-        }
-        ImGui::SameLine();
-        ImGui::Text("Y: +/-%1.2fV", 3.3f * (static_cast<float>(yMinMax) / 4095.f));
-        ImGui::SameLine();
-        if (ImGui::Button(" - ", {30, 0})) {
-            yMinMax = std::max(63u, yMinMax >> 1);
-        }
-        ImGui::SameLine();
-        if (ImGui::Button(" + ", {30, 0})) {
-            yMinMax = std::min(4095u, (yMinMax << 1) | 1);
-        }
-
-        static unsigned long csize = 0;
-        if (buffer.size() != drawSamplesBufferSize) {
-            buffer.resize(drawSamplesBufferSize);
-            bufferInput.resize(drawSamplesBufferSize);
-            bufferCursor = buffer.begin();
-            bufferInputCursor = bufferInput.begin();
-            csize = drawSamplesBufferSize / (60. * drawSamplesTimeframe) * 1.025;
-        }
-
-        {
-            std::scoped_lock lock (mutexDrawSamples);
-            auto count = std::min(drawSamplesQueue.size(), csize);
-            for (auto i = count; i; --i) {
-                *bufferCursor = drawSamplesQueue.front();
-                drawSamplesQueue.pop_front();
-                if (++bufferCursor == buffer.end())
-                    bufferCursor = buffer.begin();
-            }
-            
-            if (drawSamplesInput) {
-                auto count = std::min(drawSamplesInputQueue.size(), csize);
-                for (auto i = count; i; --i) {
-                    *bufferInputCursor = drawSamplesInputQueue.front();
-                    drawSamplesInputQueue.pop_front();
-                    if (++bufferInputCursor == bufferInput.end())
-                        bufferInputCursor = bufferInput.begin();
-                }
-            }
-        }
-
-        auto drawList = ImGui::GetWindowDrawList();
-        ImVec2 p0 = ImGui::GetWindowPos();
-        auto size = ImGui::GetWindowSize();
-        p0.y += 65;
-        size.y -= 70;
-        drawList->AddRectFilled(p0, {p0.x + size.x, p0.y + size.y}, IM_COL32(0, 0, 0, 255));
-
-        const float di = static_cast<float>(buffer.size()) / size.x;
-        const float dx = std::ceil(size.x / static_cast<float>(buffer.size()));
-        ImVec2 pp = p0;
-        float i = 0;
-        while (pp.x < p0.x + size.x) {
-            unsigned int idx = i;
-            float n = std::clamp((buffer[idx] - 2048.) / yMinMax, -0.5, 0.5);
-            i += di;
-
-            ImVec2 next (pp.x + dx, p0.y + size.y * (0.5 - n));
-            drawList->AddLine(pp, next, ImGui::GetColorU32(IM_COL32(255, 0, 0, 255)));
-            pp = next;
-        }
-
-        if (drawSamplesInput) {
-            ImVec2 pp = p0;
-            float i = 0;
-            while (pp.x < p0.x + size.x) {
-                unsigned int idx = i;
-                float n = std::clamp((bufferInput[idx] - 2048.) / yMinMax, -0.5, 0.5);
-                i += di;
-
-                ImVec2 next (pp.x + dx, p0.y + size.y * (0.5 - n));
-                drawList->AddLine(pp, next, ImGui::GetColorU32(IM_COL32(0, 0, 255, 255)));
-                pp = next;
-            }
-        }
-
-        ImGui::End();
-    }
+    logSamplesFile = std::ofstream(file);
+    if (logSamplesFile.good())
+        log("Log file ready.");
+    else
+        log("Error: Could not open log file.");
 }
 
-void deviceRenderMenu()
+bool deviceGenStartToggle()
 {
-    if (ImGui::BeginMenu("Run")) {
-        bool isConnected = m_device ? true : false;
-        bool isRunning = isConnected && m_device->is_running();
-
-        static const char *connectLabel = "Connect";
-        if (ImGui::MenuItem(connectLabel, nullptr, false, !isConnected || (isConnected && !isRunning))) {
-            deviceConnect();
-            isConnected = m_device ? true : false;
-            connectLabel = isConnected ? "Disconnect" : "Connect";
-        }
-
-        ImGui::Separator();
-        static const char *startLabel = "Start";
-        if (ImGui::MenuItem(startLabel, nullptr, false, isConnected)) {
-            startLabel = isRunning ? "Start" : "Stop";
-            deviceStart();
+    if (m_device) {
+        bool running = m_device->is_siggening();
+        if (!running) {
+            if (wavOutput.valid())
+                std::thread(feedSigGenTask, m_device).detach();
+            else
+                m_device->siggen_start();
+            log("Generator started.");
+        } else {
+            m_device->siggen_stop();
+            log("Generator stopped.");
         }
 
-        if (ImGui::MenuItem("Upload algorithm", nullptr, false, isConnected && !isRunning))
-            deviceAlgorithmUpload();
-        if (ImGui::MenuItem("Unload algorithm", nullptr, false, isConnected && !isRunning))
-            deviceAlgorithmUnload();
-
-        ImGui::Separator();
-        if (!isConnected || isRunning)
-            ImGui::PushDisabled();
-        ImGui::Checkbox("Measure Code Time", &measureCodeTime);
-        if (ImGui::Checkbox("Draw samples", &drawSamples)) {
-            if (drawSamples)
-                popupRequestDraw = true;
-        }
-        if (ImGui::Checkbox("Log results...", &logResults)) {
-            if (logResults)
-                popupRequestLog = true;
-            else if (logSamplesFile.is_open())
-                logSamplesFile.close();
-        }
-        if (!isConnected || isRunning)
-            ImGui::PopDisabled();
+        return !running;
+    }
 
-        if (ImGui::MenuItem("Set buffer size...", nullptr, false, isConnected && !isRunning)) {
-            popupRequestBuffer = true;
-        }
-        ImGui::Separator();
-        if (ImGui::MenuItem("Load signal generator", nullptr, false, isConnected && !m_device->is_siggening())) {
-            popupRequestSiggen = true;
-        }
-        static const char *startSiggenLabel = "Start signal generator";
-        if (ImGui::MenuItem(startSiggenLabel, nullptr, false, isConnected)) {
-            if (m_device) {
-                if (!genRunning) {
-                    genRunning = true;
-                    if (wavOutput.valid())
-                        std::thread(feedSigGenTask, m_device).detach();
-                    else
-                        m_device->siggen_start();
-                    log("Generator started.");
-                    startSiggenLabel = "Stop signal generator";
-                } else {
-                    genRunning = false;
-                    m_device->siggen_stop();
-                    log("Generator stopped.");
-                    startSiggenLabel = "Start signal generator";
-                }
-            }
-        }
+    return false;
+}
 
-        ImGui::EndMenu();
-    }
+void deviceUpdateDrawBufferSize(double timeframe)
+{
+    drawSamplesBufferSize = std::round(
+        m_device->get_sample_rate() * timeframe);
 }
 
-void deviceRenderToolbar()
+void deviceSetSampleRate(unsigned int rate)
 {
-    ImGui::SameLine();
-    if (ImGui::Button("Upload"))
-        deviceAlgorithmUpload();
-    ImGui::SameLine();
-    ImGui::SetNextItemWidth(100);
-
-    const bool enable = m_device && !m_device->is_running() && !m_device->is_siggening();
-    if (!enable)
-        ImGui::PushDisabled();
-    if (ImGui::BeginCombo("", sampleRatePreview)) {
-        for (unsigned int i = 0; i < sampleRateList.size(); ++i) {
-            if (ImGui::Selectable(sampleRateList[i])) {
-                sampleRatePreview = sampleRateList[i];
-                do {
-                    m_device->set_sample_rate(i);
-                    std::this_thread::sleep_for(std::chrono::milliseconds(10));
-                } while (m_device->get_sample_rate() != i);
-
-                drawSamplesBufferSize = std::round(sampleRateInts[i] * drawSamplesTimeframe);
-            }
-        }
-        ImGui::EndCombo();
-    }
-    if (!enable)
-        ImGui::PopDisabled();
+    do {
+        m_device->set_sample_rate(rate);
+        std::this_thread::sleep_for(std::chrono::milliseconds(10));
+    } while (m_device->get_sample_rate() != rate);
 }
 
-void deviceConnect()
+bool deviceConnect()
 {
     static std::thread statusThread;
 
@@ -583,12 +271,10 @@ void deviceConnect()
 
             if (m_device) {
                 if (m_device->connected()) {
-                    auto sri = m_device->get_sample_rate();
-                    sampleRatePreview = sampleRateList[sri];
-                    drawSamplesBufferSize = std::round(sampleRateInts[sri] * drawSamplesTimeframe);
                     log("Connected!");
                     statusThread = std::thread(statusTask, m_device);
                     statusThread.detach();
+                    return true;
                 } else {
                     m_device.reset();
                     log("Failed to connect.");
@@ -604,9 +290,11 @@ void deviceConnect()
         m_device.reset();
         log("Disconnected.");
     }
+
+    return false;
 }
 
-void deviceStart()
+void deviceStart(bool measureCodeTime, bool logResults, bool drawSamples)
 {
     if (!m_device) {
         log("No device connected.");
@@ -619,9 +307,8 @@ void deviceStart()
             std::this_thread::sleep_for(std::chrono::microseconds(150));
             m_device->continuous_stop();
         }
-        if (logResults) {
+        if (logSamplesFile.good()) {
             logSamplesFile.close();
-            logResults = false;
             log("Log file saved and closed.");
         }
         log("Ready.");
@@ -715,3 +402,44 @@ void deviceGenLoadFormula(std::string_view formula)
     }
 }
 
+void pullFromQueue(
+    std::deque<stmdsp::dacsample_t>& queue,
+    std::vector<stmdsp::dacsample_t>& buffer,
+    decltype(buffer.begin())& bufferCursor,
+    double timeframe)
+{
+    if (buffer.size() != drawSamplesBufferSize) {
+        buffer.resize(drawSamplesBufferSize);
+        bufferCursor = buffer.begin();
+    }
+
+    std::scoped_lock lock (mutexDrawSamples);
+
+    auto count = drawSamplesBufferSize / (60. * timeframe) * 1.025;
+    count = std::min(drawSamplesInputQueue.size(),
+        static_cast<std::size_t>(count));
+    for (auto i = count; i; --i) {
+        *bufferCursor = queue.front();
+        queue.pop_front();
+
+        if (++bufferCursor == buffer.end())
+            bufferCursor = buffer.begin();
+    }
+}
+
+void pullFromDrawQueue(
+    std::vector<stmdsp::dacsample_t>& buffer,
+    decltype(buffer.begin())& bufferCursor,
+    double timeframe)
+{
+    pullFromQueue(drawSamplesQueue, buffer, bufferCursor, timeframe);
+}
+
+void pullFromInputDrawQueue(
+    std::vector<stmdsp::dacsample_t>& buffer,
+    decltype(buffer.begin())& bufferCursor,
+    double timeframe)
+{
+    pullFromQueue(drawSamplesInputQueue, buffer, bufferCursor, timeframe);
+}
+
diff --git a/source/gui_code.cpp b/source/gui_code.cpp
new file mode 100644 (file)
index 0000000..6917c72
--- /dev/null
@@ -0,0 +1,72 @@
+/**
+ * @file code.cpp
+ * @brief Contains code for algorithm-code-related UI elements and logic.
+ *
+ * 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 "imgui.h"
+#include "backends/imgui_impl_sdl.h"
+#include "backends/imgui_impl_opengl2.h"
+#include "TextEditor.h"
+
+#include "config.h"
+
+#include <string>
+
+extern void compileEditorCode(const std::string& code);
+extern void disassembleCode();
+
+TextEditor editor; // file.cpp
+
+static std::string editorCompiled;
+
+static void codeCompile();
+static void codeDisassemble();
+
+void codeEditorInit()
+{
+    editor.SetLanguageDefinition(TextEditor::LanguageDefinition::CPlusPlus());
+    editor.SetPalette(TextEditor::GetLightPalette());
+}
+
+void codeRenderMenu()
+{
+    if (ImGui::BeginMenu("Code")) {
+        if (ImGui::MenuItem("Compile code"))
+            codeCompile();
+        if (ImGui::MenuItem("Show disassembly"))
+            codeDisassemble();
+
+        ImGui::EndMenu();
+    }
+}
+
+void codeRenderToolbar()
+{
+    if (ImGui::Button("Compile"))
+        codeCompile();
+}
+
+void codeRenderWidgets()
+{
+    editor.Render("code", {WINDOW_WIDTH - 15, 450}, true);
+}
+
+static void codeCompile()
+{
+    compileEditorCode(editor.GetText());
+    editorCompiled = editor.GetText().compare(editorCompiled);
+}
+
+static void codeDisassemble()
+{
+    if (editor.GetText().compare(editorCompiled) != 0)
+        codeCompile();
+    disassembleCode();
+}
+
diff --git a/source/gui_device.cpp b/source/gui_device.cpp
new file mode 100644 (file)
index 0000000..5399c8b
--- /dev/null
@@ -0,0 +1,340 @@
+#include "imgui.h"
+#include "imgui_internal.h"
+#include "ImGuiFileDialog.h"
+
+#include "stmdsp.hpp"
+
+#include <array>
+#include <memory>
+#include <string>
+#include <string_view>
+
+// Used for status queries and buffer size configuration.
+extern std::shared_ptr<stmdsp::device> m_device;
+
+void deviceAlgorithmUnload();
+void deviceAlgorithmUpload();
+bool deviceConnect();
+void deviceGenLoadFormula(std::string_view list);
+void deviceGenLoadList(std::string_view list);
+bool deviceGenStartToggle();
+void deviceLoadAudioFile(const std::string& file);
+void deviceLoadLogFile(const std::string& file);
+void deviceSetSampleRate(unsigned int index);
+void deviceSetInputDrawing(bool enabled);
+void deviceStart(bool measureCodeTime, bool logResults, bool drawSamples);
+void deviceUpdateDrawBufferSize(double timeframe);
+void pullFromDrawQueue(
+    std::vector<stmdsp::dacsample_t>& buffer,
+    decltype(buffer.begin())& bufferCursor,
+    double timeframe);
+void pullFromInputDrawQueue(
+    std::vector<stmdsp::dacsample_t>& buffer,
+    decltype(buffer.begin())& bufferCursor,
+    double timeframe);
+
+static std::string sampleRatePreview = "?";
+static bool measureCodeTime = false;
+static bool logResults = false;
+static bool drawSamples = false;
+static bool popupRequestBuffer = false;
+static bool popupRequestSiggen = false;
+static bool popupRequestLog = false;
+static double drawSamplesTimeframe = 1.0; // seconds
+
+static std::string getSampleRatePreview(unsigned int rate)
+{
+    return std::to_string(rate / 1000) + " kHz";
+}
+
+void deviceRenderMenu()
+{
+    auto addMenuItem = [](const std::string& label, bool enable, auto action) {
+        if (ImGui::MenuItem(label.c_str(), nullptr, false, enable)) {
+            action();
+        }
+    };
+
+    if (ImGui::BeginMenu("Run")) {
+        const bool isConnected = m_device ? true : false;
+        const bool isRunning = isConnected && m_device->is_running();
+
+        static std::string connectLabel ("Connect");
+        addMenuItem(connectLabel, !isConnected || !isRunning, [&] {
+                if (deviceConnect()) {
+                    connectLabel = "Disconnect";
+                    sampleRatePreview =
+                        getSampleRatePreview(m_device->get_sample_rate());
+                    deviceUpdateDrawBufferSize(drawSamplesTimeframe);
+                } else {
+                    connectLabel = "Connect";
+                }
+            });
+
+        ImGui::Separator();
+
+        static std::string startLabel ("Start");
+        addMenuItem(startLabel, isConnected, [&] {
+                startLabel = isRunning ? "Start" : "Stop";
+                deviceStart(measureCodeTime, logResults, drawSamples);
+                if (logResults && isRunning)
+                    logResults = false;
+            });
+        addMenuItem("Upload algorithm", isConnected && !isRunning,
+            deviceAlgorithmUpload);
+        addMenuItem("Unload algorithm", isConnected && !isRunning,
+            deviceAlgorithmUnload);
+
+        ImGui::Separator();
+        if (!isConnected || isRunning)
+            ImGui::PushDisabled();
+
+        ImGui::Checkbox("Measure Code Time", &measureCodeTime);
+        ImGui::Checkbox("Draw samples", &drawSamples);
+        if (ImGui::Checkbox("Log results...", &logResults)) {
+            if (logResults)
+                popupRequestLog = true;
+        }
+
+        if (!isConnected || isRunning)
+            ImGui::PopDisabled();
+
+        addMenuItem("Set buffer size...", isConnected && !isRunning,
+            [] { popupRequestBuffer = true; });
+
+        ImGui::Separator();
+
+        addMenuItem("Load signal generator",
+            isConnected && !m_device->is_siggening(),
+            [] { popupRequestSiggen = true; });
+
+        static std::string startSiggenLabel ("Start signal generator");
+        addMenuItem(startSiggenLabel, isConnected, [&] {
+                const bool running = deviceGenStartToggle();
+                startSiggenLabel = running ? "Stop signal generator"
+                                           : "Start signal generator";
+            });
+
+        ImGui::EndMenu();
+    }
+}
+
+void deviceRenderToolbar()
+{
+    ImGui::SameLine();
+    if (ImGui::Button("Upload"))
+        deviceAlgorithmUpload();
+    ImGui::SameLine();
+    ImGui::SetNextItemWidth(100);
+
+    const bool enable =
+        m_device && !m_device->is_running() && !m_device->is_siggening();
+    if (!enable)
+        ImGui::PushDisabled();
+
+    if (ImGui::BeginCombo("", sampleRatePreview.c_str())) {
+        extern std::array<unsigned int, 6> sampleRateInts;
+
+        for (const auto& r : sampleRateInts) {
+            const auto s = getSampleRatePreview(r);
+            if (ImGui::Selectable(s.c_str())) {
+                sampleRatePreview = s;
+                deviceSetSampleRate(r);
+                deviceUpdateDrawBufferSize(drawSamplesTimeframe);
+            }
+        }
+
+        ImGui::EndCombo();
+    }
+
+    if (!enable)
+        ImGui::PopDisabled();
+}
+
+void deviceRenderWidgets()
+{
+    static std::string siggenInput;
+    static int siggenOption = 0;
+
+    if (popupRequestSiggen) {
+        popupRequestSiggen = false;
+        siggenInput.clear();
+        ImGui::OpenPopup("siggen");
+    } else if (popupRequestBuffer) {
+        popupRequestBuffer = false;
+        ImGui::OpenPopup("buffer");
+    } else if (popupRequestLog) {
+        popupRequestLog = false;
+        ImGuiFileDialog::Instance()->OpenModal(
+            "ChooseFileLogGen", "Choose File", ".csv", ".");
+    }
+
+    if (ImGui::BeginPopup("siggen")) {
+        if (ImGui::RadioButton("List", &siggenOption, 0))
+            siggenInput.clear();
+        ImGui::SameLine();
+        if (ImGui::RadioButton("Formula", &siggenOption, 1))
+            siggenInput.clear();
+        ImGui::SameLine();
+        if (ImGui::RadioButton("Audio File", &siggenOption, 2))
+            siggenInput.clear();
+
+        if (siggenOption == 2) {
+            if (ImGui::Button("Choose File")) {
+                // This dialog will override the siggen popup, closing it.
+                ImGuiFileDialog::Instance()->OpenModal(
+                    "ChooseFileLogGen", "Choose File", ".wav", ".");
+            }
+        } else {
+            ImGui::Text(siggenOption == 0 ? "Enter a list of numbers:"
+                                          : "Enter a formula. f(x) = ");
+            ImGui::PushStyleColor(ImGuiCol_FrameBg, {.8, .8, .8, 1});
+            ImGui::InputText("", siggenInput.data(), siggenInput.size());
+            ImGui::PopStyleColor();
+        }
+
+        if (ImGui::Button("Cancel")) {
+            siggenInput.clear();
+            ImGui::CloseCurrentPopup();
+        }
+
+        if (ImGui::Button("Save")) {
+            switch (siggenOption) {
+            case 0:
+                deviceGenLoadList(siggenInput);
+                break;
+            case 1:
+                deviceGenLoadFormula(siggenInput);
+                break;
+            case 2:
+                break;
+            }
+
+            ImGui::CloseCurrentPopup();
+        }
+
+        ImGui::EndPopup();
+    }
+
+    if (ImGui::BeginPopup("buffer")) {
+        static std::string bufferSizeInput ("4096");
+        ImGui::Text("Please enter a new sample buffer size (100-4096):");
+        ImGui::PushStyleColor(ImGuiCol_FrameBg, {.8, .8, .8, 1});
+        ImGui::InputText("",
+            bufferSizeInput.data(),
+            bufferSizeInput.size(),
+            ImGuiInputTextFlags_CharsDecimal);
+        ImGui::PopStyleColor();
+        if (ImGui::Button("Save")) {
+            if (m_device) {
+                int n = std::clamp(std::stoi(bufferSizeInput), 100, 4096);
+                m_device->continuous_set_buffer_size(n);
+            }
+            ImGui::CloseCurrentPopup();
+        }
+        ImGui::SameLine();
+        if (ImGui::Button("Cancel"))
+            ImGui::CloseCurrentPopup();
+        ImGui::EndPopup();
+    }
+
+    if (ImGuiFileDialog::Instance()->Display("ChooseFileLogGen",
+                                             ImGuiWindowFlags_NoCollapse,
+                                             ImVec2(460, 540)))
+    {
+        if (ImGuiFileDialog::Instance()->IsOk()) {
+            auto filePathName = ImGuiFileDialog::Instance()->GetFilePathName();
+            auto ext = filePathName.substr(filePathName.size() - 4);
+
+            if (ext.compare(".wav") == 0)
+                deviceLoadAudioFile(filePathName);
+            else if (ext.compare(".csv") == 0)
+                deviceLoadLogFile(filePathName);
+        }
+
+        ImGuiFileDialog::Instance()->Close();
+    }
+}
+
+void deviceRenderDraw()
+{
+    if (drawSamples) {
+        static std::vector<stmdsp::dacsample_t> buffer;
+        static decltype(buffer.begin()) bufferCursor;
+        static std::vector<stmdsp::dacsample_t> bufferInput;
+        static decltype(bufferInput.begin()) bufferInputCursor;
+
+        static bool drawSamplesInput = false;
+        static unsigned int yMinMax = 4095;
+
+        ImGui::Begin("draw", &drawSamples);
+        ImGui::Text("Draw input ");
+        ImGui::SameLine();
+        if (ImGui::Checkbox("", &drawSamplesInput))
+            deviceSetInputDrawing(drawSamplesInput);
+        ImGui::SameLine();
+        ImGui::Text("Time: %0.3f sec", drawSamplesTimeframe);
+        ImGui::SameLine();
+        if (ImGui::Button("-", {30, 0})) {
+            drawSamplesTimeframe = std::max(drawSamplesTimeframe / 2., 0.0078125);
+            deviceUpdateDrawBufferSize(drawSamplesTimeframe);
+        }
+        ImGui::SameLine();
+        if (ImGui::Button("+", {30, 0})) {
+            drawSamplesTimeframe = std::min(drawSamplesTimeframe * 2, 32.);
+            deviceUpdateDrawBufferSize(drawSamplesTimeframe);
+        }
+        ImGui::SameLine();
+        ImGui::Text("Y: +/-%1.2fV", 3.3f * (static_cast<float>(yMinMax) / 4095.f));
+        ImGui::SameLine();
+        if (ImGui::Button(" - ", {30, 0})) {
+            yMinMax = std::max(63u, yMinMax >> 1);
+        }
+        ImGui::SameLine();
+        if (ImGui::Button(" + ", {30, 0})) {
+            yMinMax = std::min(4095u, (yMinMax << 1) | 1);
+        }
+
+        pullFromDrawQueue(buffer, bufferCursor, drawSamplesTimeframe);
+        if (drawSamplesInput)
+            pullFromInputDrawQueue(bufferInput, bufferInputCursor, drawSamplesTimeframe);
+
+        auto drawList = ImGui::GetWindowDrawList();
+        ImVec2 p0 = ImGui::GetWindowPos();
+        auto size = ImGui::GetWindowSize();
+        p0.y += 65;
+        size.y -= 70;
+        drawList->AddRectFilled(p0, {p0.x + size.x, p0.y + size.y}, IM_COL32(0, 0, 0, 255));
+
+        const float di = static_cast<float>(buffer.size()) / size.x;
+        const float dx = std::ceil(size.x / static_cast<float>(buffer.size()));
+        ImVec2 pp = p0;
+        float i = 0;
+        while (pp.x < p0.x + size.x) {
+            unsigned int idx = i;
+            float n = std::clamp((buffer[idx] - 2048.) / yMinMax, -0.5, 0.5);
+            i += di;
+
+            ImVec2 next (pp.x + dx, p0.y + size.y * (0.5 - n));
+            drawList->AddLine(pp, next, ImGui::GetColorU32(IM_COL32(255, 0, 0, 255)));
+            pp = next;
+        }
+
+        if (drawSamplesInput) {
+            ImVec2 pp = p0;
+            float i = 0;
+            while (pp.x < p0.x + size.x) {
+                unsigned int idx = i;
+                float n = std::clamp((bufferInput[idx] - 2048.) / yMinMax, -0.5, 0.5);
+                i += di;
+
+                ImVec2 next (pp.x + dx, p0.y + size.y * (0.5 - n));
+                drawList->AddLine(pp, next, ImGui::GetColorU32(IM_COL32(0, 0, 255, 255)));
+                pp = next;
+            }
+        }
+
+        ImGui::End();
+    }
+}
+
index 2bbb92b8ed4d1ae6b059fedbac4689fcd1a2c5df..22523647aacc8736ba7a0fd740423d00b5ab3a8a 100644 (file)
 
 extern void log(const std::string& str);
 
+std::array<unsigned int, 6> sampleRateInts {{
+    8'000,
+    16'000,
+    20'000,
+    32'000,
+    48'000,
+    96'000
+}};
+
 namespace stmdsp
 {
     const std::forward_list<std::string>& scanner::scan()
@@ -117,11 +126,19 @@ namespace stmdsp
         }
     }
 
-    void device::set_sample_rate(unsigned int id) {
-        try_command({
-            'r',
-            static_cast<uint8_t>(id)
-        });
+    void device::set_sample_rate(unsigned int rate) {
+        auto it = std::find(
+            sampleRateInts.cbegin(),
+            sampleRateInts.cend(),
+            rate);
+
+        if (it != sampleRateInts.cend()) {
+            const auto i = std::distance(sampleRateInts.cbegin(), it);
+            try_command({
+                'r',
+                static_cast<uint8_t>(i)
+            });
+        }
     }
 
     unsigned int device::get_sample_rate() {
@@ -130,7 +147,10 @@ namespace stmdsp
             if (try_read({'r', 0xFF}, &result, 1))
                 m_sample_rate = result;
         }
-        return m_sample_rate;
+
+        return m_sample_rate < sampleRateInts.size() ?
+            sampleRateInts[m_sample_rate] :
+            0;
     }
 
     void device::continuous_start() {
index 64f5aff6f7eafc5ad0be7da57d55c718c817b6ce..e0fca90d818e563f32b6d12fe849b95a3c794215 100644 (file)
@@ -117,7 +117,7 @@ namespace stmdsp
         void continuous_set_buffer_size(unsigned int size);
         unsigned int get_buffer_size() const { return m_buffer_size; }
 
-        void set_sample_rate(unsigned int id);
+        void set_sample_rate(unsigned int rate);
         unsigned int get_sample_rate();
 
         void continuous_start();
index 71842bd70b14bacd9e4d8112c9686cb67d509710..e20776abf6fb74630737a0181f652a8d98392411 100644 (file)
@@ -4,6 +4,7 @@
 #include <cstdint>
 #include <cstring>
 #include <fstream>
+#include <string>
 #include <vector>
 
 namespace wav
@@ -44,7 +45,7 @@ namespace wav
 
     class clip {
     public:
-        clip(const char *path) {
+        clip(const std::string& path) {
             std::ifstream file (path);
             if (!file.good())
                 return;