From c76ba69fc933a86e526855b097907a728e24568b Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Sun, 21 Nov 2021 11:30:41 -0500 Subject: [PATCH] wip: major code refactor, split most gui from logic --- Makefile | 3 +- source/code.cpp | 155 +++++-------- source/device.cpp | 468 ++++++++------------------------------- source/gui_code.cpp | 72 ++++++ source/gui_device.cpp | 340 ++++++++++++++++++++++++++++ source/stmdsp/stmdsp.cpp | 32 ++- source/stmdsp/stmdsp.hpp | 2 +- source/wav.hpp | 3 +- 8 files changed, 593 insertions(+), 482 deletions(-) create mode 100644 source/gui_code.cpp create mode 100644 source/gui_device.cpp diff --git a/Makefile b/Makefile index 7aa95db..26e0208 100644 --- 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) diff --git a/source/code.cpp b/source/code.cpp index 163d6e5..7a9afaa 100644 --- a/source/code.cpp +++ b/source/code.cpp @@ -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 . - */ - -#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" @@ -22,89 +5,30 @@ #include #include #include +#include #include extern std::shared_ptr 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(); + } +}; + diff --git a/source/device.cpp b/source/device.cpp index 39bc746..df3f361 100644 --- a/source/device.cpp +++ b/source/device.cpp @@ -12,8 +12,6 @@ #include "stmdsp.hpp" #include "imgui.h" -#include "imgui_internal.h" -#include "ImGuiFileDialog.h" #include "wav.hpp" #include @@ -21,58 +19,35 @@ #include #include #include +#include #include #include #include +#include +#include #include +#include extern std::string tempFileName; extern void log(const std::string& str); - extern std::vector deviceGenLoadFormulaEval(const std::string_view); std::shared_ptr m_device; -static const std::array sampleRateList {{ - "8 kHz", - "16 kHz", - "20 kHz", - "32 kHz", - "48 kHz", - "96 kHz" -}}; -static const char *sampleRatePreview = sampleRateList[0]; -static const std::array 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 drawSamplesQueue; static std::deque 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 device) { std::this_thread::sleep_for(std::chrono::seconds(1)); @@ -105,7 +80,7 @@ static std::chrono::duration 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(bufferSize / sampleRate * factor); } else { return {}; @@ -117,7 +92,6 @@ static void drawSamplesTask(std::shared_ptr device) if (!device) return; - const bool doLogger = logResults && logSamplesFile.good(); const auto bufferTime = getBufferPeriod(device); std::unique_lock lockDraw (mutexDrawSamples, std::defer_lock); @@ -138,7 +112,7 @@ static void drawSamplesTask(std::shared_ptr 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 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 device) std::vector 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 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 buffer; - static decltype(buffer.begin()) bufferCursor; - static std::vector 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(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(buffer.size()) / size.x; - const float dx = std::ceil(size.x / static_cast(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& queue, + std::vector& 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(count)); + for (auto i = count; i; --i) { + *bufferCursor = queue.front(); + queue.pop_front(); + + if (++bufferCursor == buffer.end()) + bufferCursor = buffer.begin(); + } +} + +void pullFromDrawQueue( + std::vector& buffer, + decltype(buffer.begin())& bufferCursor, + double timeframe) +{ + pullFromQueue(drawSamplesQueue, buffer, bufferCursor, timeframe); +} + +void pullFromInputDrawQueue( + std::vector& 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 index 0000000..6917c72 --- /dev/null +++ b/source/gui_code.cpp @@ -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 . + */ + +#include "imgui.h" +#include "backends/imgui_impl_sdl.h" +#include "backends/imgui_impl_opengl2.h" +#include "TextEditor.h" + +#include "config.h" + +#include + +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 index 0000000..5399c8b --- /dev/null +++ b/source/gui_device.cpp @@ -0,0 +1,340 @@ +#include "imgui.h" +#include "imgui_internal.h" +#include "ImGuiFileDialog.h" + +#include "stmdsp.hpp" + +#include +#include +#include +#include + +// Used for status queries and buffer size configuration. +extern std::shared_ptr 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& buffer, + decltype(buffer.begin())& bufferCursor, + double timeframe); +void pullFromInputDrawQueue( + std::vector& 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 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 buffer; + static decltype(buffer.begin()) bufferCursor; + static std::vector 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(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(buffer.size()) / size.x; + const float dx = std::ceil(size.x / static_cast(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(); + } +} + diff --git a/source/stmdsp/stmdsp.cpp b/source/stmdsp/stmdsp.cpp index 2bbb92b..2252364 100644 --- a/source/stmdsp/stmdsp.cpp +++ b/source/stmdsp/stmdsp.cpp @@ -17,6 +17,15 @@ extern void log(const std::string& str); +std::array sampleRateInts {{ + 8'000, + 16'000, + 20'000, + 32'000, + 48'000, + 96'000 +}}; + namespace stmdsp { const std::forward_list& scanner::scan() @@ -117,11 +126,19 @@ namespace stmdsp } } - void device::set_sample_rate(unsigned int id) { - try_command({ - 'r', - static_cast(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(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() { diff --git a/source/stmdsp/stmdsp.hpp b/source/stmdsp/stmdsp.hpp index 64f5aff..e0fca90 100644 --- a/source/stmdsp/stmdsp.hpp +++ b/source/stmdsp/stmdsp.hpp @@ -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(); diff --git a/source/wav.hpp b/source/wav.hpp index 71842bd..e20776a 100644 --- a/source/wav.hpp +++ b/source/wav.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include 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;