diff --git a/.gitignore b/.gitignore index 5ef6f49..b23a6a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ +build/ +openocd/ imgui.ini stmdspgui stmdspgui.exe +perf.data* *.o +*.dll .* diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..54f7086 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.10) + +project(stmdspgui VERSION 0.5) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +add_compile_options(-O0 -ggdb -g3) + +file(GLOB SRC_IMGUI_BACKENDS "${CMAKE_SOURCE_DIR}/source/imgui/backends/*.cpp") +file(GLOB SRC_IMGUI "${CMAKE_SOURCE_DIR}/source/imgui/*.cpp") +file(GLOB SRC_STMDSP "${CMAKE_SOURCE_DIR}/source/stmdsp/*.cpp") +file(GLOB SRC_STMDSPGUI "${CMAKE_SOURCE_DIR}/source/*.cpp") + +set_property(SOURCE ${SRC_STMDSPGUI} PROPERTY COMPILE_FLAGS "-Wall -Wextra -Wpedantic") + +add_executable(stmdspgui + source/serial/src/serial.cc + source/serial/src/impl/unix.cc + source/serial/src/impl/list_ports/list_ports_linux.cc + ${SRC_IMGUI_BACKENDS} + ${SRC_IMGUI} + ${SRC_STMDSP} + ${SRC_STMDSPGUI}) + +target_include_directories(stmdspgui PUBLIC + ${CMAKE_SOURCE_DIR}/source + ${CMAKE_SOURCE_DIR}/source/imgui + ${CMAKE_SOURCE_DIR}/source/stmdsp + ${CMAKE_SOURCE_DIR}/source/serial/include) + +target_link_libraries(stmdspgui PRIVATE SDL2 GL pthread) + diff --git a/Makefile b/Makefile index 7aa95db..ee9b224 100644 --- a/Makefile +++ b/Makefile @@ -1,35 +1,46 @@ +CXX = g++ + CXXFILES := \ source/serial/src/serial.cc \ - source/serial/src/impl/unix.cc \ - source/serial/src/impl/list_ports/list_ports_linux.cc \ $(wildcard source/imgui/backends/*.cpp) \ $(wildcard source/imgui/*.cpp) \ $(wildcard source/stmdsp/*.cpp) \ $(wildcard source/*.cpp) -OFILES := $(patsubst %.cc, %.o, $(patsubst %.cpp, %.o, $(CXXFILES))) +CXXFLAGS := -std=c++20 -O2 \ + -Isource -Isource/imgui -Isource/stmdsp -Isource/serial/include \ + -Wall -Wextra -pedantic #-DSTMDSP_DISABLE_FORMULAS + +ifeq ($(OS),Windows_NT) +CXXFILES += source/serial/src/impl/win.cc \ + source/serial/src/impl/list_ports/list_ports_win.cc +CXXFLAGS += -DSTMDSP_WIN32 -Wa,-mbig-obj +LDFLAGS = -mwindows -lSDL2 -lopengl32 -lsetupapi -lole32 +OUTPUT := stmdspgui.exe +else +CXXFILES += source/serial/src/impl/unix.cc \ + source/serial/src/impl/list_ports/list_ports_linux.cc +LDFLAGS = -lSDL2 -lGL -lpthread OUTPUT := stmdspgui +endif -#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 +OFILES := $(patsubst %.cc, %.o, $(patsubst %.cpp, %.o, $(CXXFILES))) all: $(OUTPUT) $(OUTPUT): $(OFILES) @echo " LD " $(OUTPUT) - @g++ $(OFILES) -o $(OUTPUT) -lSDL2 -lGL -lpthread + @$(CXX) $(OFILES) -o $(OUTPUT) $(LDFLAGS) clean: @echo " CLEAN" - @rm $(OFILES) $(OUTPUT) + @rm -f $(OFILES) $(OUTPUT) %.o: %.cpp @echo " CXX " $< - @g++ $(CXXFLAGS) -c $< -o $@ + @$(CXX) $(CXXFLAGS) -c $< -o $@ %.o: %.cc @echo " CXX " $< - @g++ $(CXXFLAGS) -c $< -o $@ + @$(CXX) $(CXXFLAGS) -c $< -o $@ diff --git a/templates/1_convolve_simple.cpp b/examples/1_convolve_simple.cpp similarity index 72% rename from templates/1_convolve_simple.cpp rename to examples/1_convolve_simple.cpp index 8de05d3..95877f1 100644 --- a/templates/1_convolve_simple.cpp +++ b/examples/1_convolve_simple.cpp @@ -7,10 +7,10 @@ * transient response is not calculated. */ -Sample *process_data(Samples samples) +Sample* process_data(Samples samples) { - // Define our output buffer. SIZE is the largest size of the 'samples' buffer. - static Sample buffer[samples.size()]; + // Define our output buffer. + static Samples buffer; // Define our filter constexpr unsigned int filter_size = 3; @@ -19,7 +19,8 @@ Sample *process_data(Samples samples) }; // Begin convolving: - for (int n = 0; n < samples.size() - (filter_size - 1); n++) { + // SIZE is the size of the sample buffer. + for (int n = 0; n < SIZE - (filter_size - 1); n++) { buffer[n] = 0; for (int k = 0; k < filter_size; k++) buffer[n] += samples[n + k] * filter[k]; diff --git a/templates/2_convolve_overlap_save.cpp b/examples/2_convolve_overlap_save.cpp similarity index 87% rename from templates/2_convolve_overlap_save.cpp rename to examples/2_convolve_overlap_save.cpp index 57c020a..5651f3e 100644 --- a/templates/2_convolve_overlap_save.cpp +++ b/examples/2_convolve_overlap_save.cpp @@ -11,9 +11,9 @@ * computation. */ -Sample *process_data(Samples samples) +Sample* process_data(Samples samples) { - static Sample buffer[samples.size()]; + static Samples buffer; constexpr unsigned int filter_size = 3; float filter[filter_size] = { @@ -23,7 +23,7 @@ Sample *process_data(Samples samples) // Keep a buffer of extra samples for overlap-save static Sample prev[filter_size]; - for (int n = 0; n < samples.size(); n++) { + for (int n = 0; n < SIZE; n++) { buffer[n] = 0; for (int k = 0; k < filter_size; k++) { @@ -40,7 +40,7 @@ Sample *process_data(Samples samples) // Save samples for the next convolution run for (int i = 0; i < filter_size; i++) - prev[i] = samples[samples.size() - filter_size + i]; + prev[i] = samples[SIZE - filter_size + i]; return buffer; } diff --git a/templates/3_fir.cpp b/examples/3_fir.cpp similarity index 87% rename from templates/3_fir.cpp rename to examples/3_fir.cpp index 3a68500..b6d8751 100644 --- a/templates/3_fir.cpp +++ b/examples/3_fir.cpp @@ -7,9 +7,9 @@ * within the available execution time. Samples are also normalized so that they center around zero. */ -Sample *process_data(Samples samples) +Sample* process_data(Samples samples) { - static Sample buffer[samples.size()]; + static Samples buffer; // Define the filter: constexpr unsigned int filter_size = 3; @@ -21,7 +21,7 @@ Sample *process_data(Samples samples) // Do an overlap-save convolution static Sample prev[filter_size]; - for (int n = 0; n < samples.size(); n++) { + for (int n = 0; n < SIZE; n++) { // Using a float variable for accumulation allows for better code optimization float v = 0; @@ -40,7 +40,7 @@ Sample *process_data(Samples samples) // Save samples for next convolution for (int i = 0; i < filter_size; i++) - prev[i] = samples[samples.size() - filter_size + i]; + prev[i] = samples[SIZE - filter_size + i]; return buffer; } diff --git a/templates/4_fir_pro.cpp b/examples/4_fir_pro.cpp similarity index 98% rename from templates/4_fir_pro.cpp rename to examples/4_fir_pro.cpp index b1a6832..1771cd5 100644 --- a/templates/4_fir_pro.cpp +++ b/examples/4_fir_pro.cpp @@ -10,7 +10,7 @@ typedef struct static void arm_fir_f32(const arm_fir_instance_f32 * S, float32_t * pSrc, float32_t * pDst, uint32_t blockSize); -Sample *process_data(Samples samples) +Sample* process_data(Samples samples) { // 1. Define our array sizes (Be sure to set Run > Set buffer size... to below value!) constexpr unsigned int buffer_size = 500; @@ -34,18 +34,18 @@ Sample *process_data(Samples samples) static float working[buffer_size + filter_size]; // 3. Scale 0-4095 interger sample values to +/- 1.0 floats - for (unsigned int i = 0; i < samples.size(); i++) + for (unsigned int i = 0; i < SIZE; i++) input[i] = (samples[i] - 2048) / 2048.f; // 4. Compute the FIR arm_fir_instance_f32 fir { filter_size, working, filter }; - arm_fir_f32(&fir, input, output, samples.size()); + arm_fir_f32(&fir, input, output, SIZE); // 5. Convert float results back to 0-4095 range for output - for (unsigned int i = 0; i < samples.size(); i++) + for (unsigned int i = 0; i < SIZE; i++) samples[i] = output[i] * 2048.f + 2048; - return samples.data(); + return samples; } // Below taken from the CMSIS DSP Library (find it on GitHub) diff --git a/templates/5_fir_differentiator.cpp b/examples/5_fir_differentiator.cpp similarity index 81% rename from templates/5_fir_differentiator.cpp rename to examples/5_fir_differentiator.cpp index 72415c6..1500dee 100644 --- a/templates/5_fir_differentiator.cpp +++ b/examples/5_fir_differentiator.cpp @@ -7,23 +7,23 @@ * A scaling factor is applied so that the output's form is more clearly visible. */ -Sample *process_data(Samples samples) +Sample* process_data(Samples samples) { constexpr int scaling_factor = 4; - static Sample output[samples.size()]; + static Samples output; static Sample prev = 2048; // Compute the first output value using the saved sample. output[0] = 2048 + ((samples[0] - prev) * scaling_factor); - for (unsigned int i = 1; i < samples.size(); i++) { + for (unsigned int i = 1; i < SIZE; i++) { // Take the rate of change and scale it. // 2048 is added as the output should be centered in the voltage range. output[i] = 2048 + ((samples[i] - samples[i - 1]) * scaling_factor); } // Save the last sample for the next iteration. - prev = samples[samples.size() - 1]; + prev = samples[SIZE - 1]; return output; } diff --git a/examples/6_iir_test.cpp b/examples/6_iir_test.cpp new file mode 100644 index 0000000..e0b266d --- /dev/null +++ b/examples/6_iir_test.cpp @@ -0,0 +1,23 @@ +/** + * 6_iir_test.cpp + * Written by Clyne Sullivan. + * + * Implements a simple infinite impulse response (IIR) filter using an alpha + * parameter. + * To build upon this example, try setting `alpha` with a parameter knob: + * alpha = param1() / 4095.0 + */ + +Sample* process_data(Samples samples) +{ + constexpr float alpha = 0.7; + + static Sample prev = 2048; + + samples[0] = (1 - alpha) * samples[0] + alpha * prev; + for (unsigned int i = 1; i < SIZE; i++) + samples[i] = (1 - alpha) * samples[i] + alpha * samples[i - 1]; + prev = samples[SIZE - 1]; + + return samples; +} diff --git a/templates/7_iir_echo.cpp b/examples/7_iir_echo.cpp similarity index 56% rename from templates/7_iir_echo.cpp rename to examples/7_iir_echo.cpp index 57e5605..75bf56e 100644 --- a/templates/7_iir_echo.cpp +++ b/examples/7_iir_echo.cpp @@ -1,9 +1,17 @@ -Sample *process_data(Samples samples) +/** + * 7_iir_echo.cpp + * Written by Clyne Sullivan. + * + * This filter produces an echo of the given input. There are two parameters: + * alpha controls the feedback gain, and D controls the echo/delay length. + */ + +Sample* process_data(Samples samples) { constexpr float alpha = 0.75; constexpr unsigned int D = 100; - static Sample output[samples.size()]; + static Samples output; static Sample prev[D]; // prev[0] = output[0 - D] // Do calculations with previous output @@ -11,12 +19,12 @@ Sample *process_data(Samples samples) output[i] = samples[i] + alpha * (prev[i] - 2048); // Do calculations with current samples - for (unsigned int i = D; i < samples.size(); i++) + for (unsigned int i = D; i < SIZE; i++) output[i] = samples[i] + alpha * (output[i - D] - 2048); // Save outputs for next computation for (unsigned int i = 0; i < D; i++) - prev[i] = output[samples.size() - (D - i)]; + prev[i] = output[SIZE - (D - i)]; return output; } diff --git a/openocd.cfg b/openocd.cfg new file mode 100644 index 0000000..33f2c52 --- /dev/null +++ b/openocd.cfg @@ -0,0 +1,2 @@ +source [find interface/stlink.cfg] +source [find target/stm32l4x.cfg] \ No newline at end of file diff --git a/source/circular.hpp b/source/circular.hpp new file mode 100644 index 0000000..4f49322 --- /dev/null +++ b/source/circular.hpp @@ -0,0 +1,48 @@ +/** + * @file circular.hpp + * @brief Small utility for filling a buffer in a circular manner. + * + * 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 . + */ + +#ifndef CIRCULAR_HPP +#define CIRCULAR_HPP + +#include + +template class Container, typename T> +class CircularBuffer +{ +public: + CircularBuffer(Container& container) : + m_begin(std::begin(container)), + m_end(std::end(container)), + m_current(m_begin) {} + + void put(const T& value) noexcept { + *m_current = value; + if (++m_current == m_end) + m_current = m_begin; + } + + std::size_t size() const noexcept { + return std::distance(m_begin, m_end); + } + + void reset(const T& fill) noexcept { + std::fill(m_begin, m_end, fill); + m_current = m_begin; + } + +private: + Container::iterator m_begin; + Container::iterator m_end; + Container::iterator m_current; +}; + +#endif // CIRCULAR_HPP + diff --git a/source/code.cpp b/source/code.cpp index 2f10b90..8e3bd6c 100644 --- a/source/code.cpp +++ b/source/code.cpp @@ -1,6 +1,6 @@ /** * @file code.cpp - * @brief Contains code for algorithm-code-related UI elements and logic. + * @brief Functionality for compiling and disassembling source code. * * Copyright (C) 2021 Clyne Sullivan * @@ -9,12 +9,6 @@ * 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,87 +16,63 @@ #include #include #include +#include #include -extern stmdsp::device *m_device; - -extern void log(const std::string& str); +extern std::shared_ptr m_device; +void log(const std::string& str); -TextEditor editor; // file.cpp -std::string tempFileName; // device.cpp -static std::string editorCompiled; +std::ifstream compileOpenBinaryFile(); +void compileEditorCode(const std::string& code); +void disassembleCode(); +static std::string tempFileName; static std::string newTempFileName(); -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() +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); + +std::ifstream compileOpenBinaryFile() { - editor.Render("code", {WINDOW_WIDTH - 15, 450}, true); + if (!tempFileName.empty()) + return std::ifstream(tempFileName + ".o"); + else + return std::ifstream(); } -void compileEditorCode() +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"); } - stmdsp::platform platform; - if (m_device != nullptr) { - platform = m_device->get_platform(); - } else { - // Assume a default. - platform = stmdsp::platform::L4; - } - - if (tempFileName.size() == 0) - tempFileName = newTempFileName(); + const auto platform = m_device ? m_device->get_platform() + : stmdsp::platform::L4; { std::ofstream file (tempFileName, std::ios::trunc | std::ios::binary); - auto file_text = platform == stmdsp::platform::L4 ? stmdsp::file_header_l4 - : stmdsp::file_header_h7; - auto samples_text = std::to_string(m_device ? m_device->get_buffer_size() - : stmdsp::SAMPLES_MAX); - for (std::size_t i = 0; (i = file_text.find("$0", i)) != std::string::npos;) { - file_text.replace(i, 2, samples_text); - i += 2; - } - - file << file_text; - file << "\n"; - file << editor.GetText(); + auto file_text = + platform == stmdsp::platform::L4 ? stmdsp::file_header_l4 + : stmdsp::file_header_h7; + const auto buffer_size = m_device ? m_device->get_buffer_size() + : stmdsp::SAMPLES_MAX; + + stringReplaceAll(file_text, "$0", std::to_string(buffer_size)); + + file << file_text << '\n' << code; } - constexpr const char *script_ext = + const auto scriptFile = tempFileName + #ifndef STMDSP_WIN32 ".sh"; #else @@ -110,79 +80,80 @@ void compileEditorCode() #endif { - std::ofstream makefile (tempFileName + script_ext, std::ios::binary); - auto make_text = platform == stmdsp::platform::L4 ? stmdsp::makefile_text_l4 - : stmdsp::makefile_text_h7; - auto cwd = std::filesystem::current_path().string(); - for (std::size_t i = 0; (i = make_text.find("$0", i)) != std::string::npos;) { - make_text.replace(i, 2, tempFileName); - i += 2; - } - for (std::size_t i = 0; (i = make_text.find("$1", i)) != std::string::npos;) { - make_text.replace(i, 2, cwd); - i += 2; - } + std::ofstream makefile (scriptFile, std::ios::binary); + auto make_text = + platform == stmdsp::platform::L4 ? stmdsp::makefile_text_l4 + : stmdsp::makefile_text_h7; + + stringReplaceAll(make_text, "$0", tempFileName); + stringReplaceAll(make_text, "$1", + std::filesystem::current_path().string()); makefile << make_text; } - auto makeOutput = tempFileName + script_ext + ".log"; - auto makeCommand = tempFileName + script_ext + " > " + makeOutput + " 2>&1"; - #ifndef STMDSP_WIN32 - system((std::string("chmod +x ") + tempFileName + script_ext).c_str()); + system((std::string("chmod +x ") + scriptFile).c_str()); #endif - int result = system(makeCommand.c_str()); - std::ifstream result_file (makeOutput); - std::ostringstream sstr; - sstr << result_file.rdbuf(); - log(sstr.str().c_str()); - - std::filesystem::remove(tempFileName); - std::filesystem::remove(tempFileName + script_ext); - std::filesystem::remove(makeOutput); - if (result == 0) { - editorCompiled = editor.GetText(); + const auto makeOutput = scriptFile + ".log"; + const auto makeCommand = scriptFile + " > " + makeOutput + " 2>&1"; + if (codeExecuteCommand(makeCommand, makeOutput)) log("Compilation succeeded."); - } else { + else log("Compilation failed."); - } + + std::filesystem::remove(tempFileName); + std::filesystem::remove(scriptFile); } void disassembleCode() { log("Disassembling..."); - if (tempFileName.size() == 0 || editor.GetText().compare(editorCompiled) != 0) { - compileEditorCode(); - } + //if (tempFileName.empty()) + // compileEditorCode(); - auto output = tempFileName + ".asm.log"; - auto command = std::string("arm-none-eabi-objdump -d --no-show-raw-insn ") + + 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 (system(command.c_str()) == 0) { - { - std::ifstream result_file (output); - std::ostringstream sstr; - sstr << result_file.rdbuf(); - log(sstr.str().c_str()); - } - - ImGui::OpenPopup("compile"); - std::filesystem::remove(output); + if (codeExecuteCommand(command, output)) log("Ready."); - } else { + else log("Failed to load disassembly."); - } } std::string newTempFileName() { - auto tempPath = std::filesystem::temp_directory_path(); - tempPath /= "stmdspgui_build"; - return tempPath.string(); + 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 (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/config.h b/source/config.h deleted file mode 100644 index 879a0ea..0000000 --- a/source/config.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef STMDSP_CONFIG_H -#define STMDSP_CONFIG_H - -constexpr unsigned int WINDOW_WIDTH = 640; -constexpr unsigned int WINDOW_HEIGHT = 720; - -#endif - diff --git a/source/device.cpp b/source/device.cpp index 196f5b4..9c50a0d 100644 --- a/source/device.cpp +++ b/source/device.cpp @@ -11,455 +11,353 @@ #include "stmdsp.hpp" +#include "circular.hpp" #include "imgui.h" -#include "ImGuiFileDialog.h" #include "wav.hpp" +#include +#include #include +#include +#include #include +#include +#include +#include #include +#include +#include #include +#include -extern std::string tempFileName; -extern stmdsp::device *m_device; extern void log(const std::string& str); +extern std::vector deviceGenLoadFormulaEval(const std::string&); +extern std::ifstream compileOpenBinaryFile(); +extern void deviceRenderDisconnect(); -extern std::vector deviceGenLoadFormulaEval(const std::string_view); - -static const char *sampleRateList[6] = { - "8 kHz", - "16 kHz", - "20 kHz", - "32 kHz", - "48 kHz", - "96 kHz" -}; -static const char *sampleRatePreview = sampleRateList[0]; -static const unsigned int sampleRateInts[6] = { - 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; +std::shared_ptr m_device; -static std::mutex mutexDrawSamples; -static std::vector drawSamplesBuf; -static std::vector drawSamplesBuf2; +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 bool drawSamplesInput = false; +static unsigned int drawSamplesBufferSize = 1; -static void measureCodeTask(stmdsp::device *device) +bool deviceConnect(); + +void deviceSetInputDrawing(bool enabled) { - if (device == nullptr) - return; - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - auto cycles = device->continuous_start_get_measurement(); - log(std::string("Execution time: ") + std::to_string(cycles) + " cycles."); + drawSamplesInput = enabled; + if (enabled) { + drawSamplesQueue.clear(); + drawSamplesInputQueue.clear(); + } } -static void drawSamplesTask(stmdsp::device *device) +static void measureCodeTask(std::shared_ptr device) { - if (device == nullptr) - return; + std::this_thread::sleep_for(std::chrono::seconds(1)); - const bool doLogger = logResults && logSamplesFile.good(); + if (device) { + const auto cycles = device->measurement_read(); + log(std::string("Execution time: ") + std::to_string(cycles) + " cycles."); + } +} - const auto bsize = m_device->get_buffer_size(); - const float srate = sampleRateInts[m_device->get_sample_rate()]; - const unsigned int delay = bsize / srate * 1000.f * 0.5f; +static std::vector tryReceiveChunk( + std::shared_ptr device, + auto readFunc) +{ + for (int tries = 0; tries < 100; ++tries) { + if (!device->is_running()) + break; - while (m_device->is_running()) { - { - std::scoped_lock lock (mutexDrawSamples); - drawSamplesBuf = m_device->continuous_read(); - if (drawSamplesInput && popupRequestDraw) - drawSamplesBuf2 = m_device->continuous_read_input(); - } + const auto chunk = readFunc(device.get()); + if (!chunk.empty()) + return chunk; + else + std::this_thread::sleep_for(std::chrono::microseconds(20)); + } - if (doLogger) { - for (const auto& s : drawSamplesBuf) - logSamplesFile << s << '\n'; - } + return {}; +} - std::this_thread::sleep_for(std::chrono::milliseconds(delay)); +static std::chrono::duration getBufferPeriod( + std::shared_ptr device, + const double factor = 0.975) +{ + if (device) { + const double bufferSize = device->get_buffer_size(); + const double sampleRate = device->get_sample_rate(); + return std::chrono::duration(bufferSize / sampleRate * factor); + } else { + return {}; } - - std::fill(drawSamplesBuf.begin(), drawSamplesBuf.end(), 2048); - std::fill(drawSamplesBuf2.begin(), drawSamplesBuf2.end(), 2048); } -static void feedSigGenTask(stmdsp::device *device) +static void drawSamplesTask(std::shared_ptr device) { - if (device == nullptr) + if (!device) return; - const auto bsize = m_device->get_buffer_size(); - const float srate = sampleRateInts[m_device->get_sample_rate()]; - const unsigned int delay = bsize / srate * 1000.f * 0.4f; + // This is the amount of time to wait between device reads. + const auto bufferTime = getBufferPeriod(device, 1); - auto wavBuf = new stmdsp::adcsample_t[bsize]; + // Adds the given chunk of samples to the given queue. + const auto addToQueue = [](auto& queue, const auto& chunk) { + std::scoped_lock lock (mutexDrawSamples); + std::copy(chunk.cbegin(), chunk.cend(), std::back_inserter(queue)); + }; - { - auto dst = wavBuf; - auto src = reinterpret_cast(wavOutput.next(bsize)); - for (auto i = 0u; i < bsize; ++i) - *dst++ = *src++ / 16 + 2048; - m_device->siggen_upload(wavBuf, bsize); - } + std::unique_lock lockDevice (mutexDeviceLoad, std::defer_lock); - m_device->siggen_start(); + while (device && device->is_running()) { + const auto next = std::chrono::high_resolution_clock::now() + bufferTime; - while (genRunning) { - auto dst = wavBuf; - auto src = reinterpret_cast(wavOutput.next(bsize)); - for (auto i = 0u; i < bsize; ++i) - *dst++ = *src++ / 16 + 2048; - m_device->siggen_upload(wavBuf, bsize); + if (lockDevice.try_lock_until(next)) { + std::vector chunk, chunk2; - std::this_thread::sleep_for(std::chrono::milliseconds(delay)); - } + chunk = tryReceiveChunk(device, + std::mem_fn(&stmdsp::device::continuous_read)); + if (drawSamplesInput) { + chunk2 = tryReceiveChunk(device, + std::mem_fn(&stmdsp::device::continuous_read_input)); + } - delete[] wavBuf; -} + lockDevice.unlock(); -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); + addToQueue(drawSamplesQueue, chunk); + if (drawSamplesInput) + addToQueue(drawSamplesInputQueue, chunk2); -void deviceRenderWidgets() -{ - 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", "."); + if (logSamplesFile.is_open()) { + for (const auto& s : chunk) + logSamplesFile << s << '\n'; } - break; + } else { + // Device must be busy, back off for a bit. + std::this_thread::sleep_for(std::chrono::milliseconds(50)); } - if (ImGui::Button("Cancel")) { - delete[] siggenBuffer; - ImGui::CloseCurrentPopup(); - } + std::this_thread::sleep_until(next); + } +} - if (ImGui::Button("Save")) { - switch (siggenOption) { - case 0: - deviceGenLoadList(siggenBuffer); - break; - case 1: - deviceGenLoadFormula(siggenBuffer); - break; - case 2: - break; - } +static void feedSigGenTask(std::shared_ptr device) +{ + if (!device) + return; - delete[] siggenBuffer; - ImGui::CloseCurrentPopup(); - } + const auto delay = getBufferPeriod(device); + const auto uploadDelay = getBufferPeriod(device, 0.001); - ImGui::EndPopup(); - } + std::vector wavBuf (device->get_buffer_size() * 2, 2048); - 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 != nullptr) { - 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(); + { + std::scoped_lock lock (mutexDeviceLoad); + device->siggen_upload(wavBuf.data(), wavBuf.size()); + device->siggen_start(); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); } - if (ImGuiFileDialog::Instance()->Display("ChooseFileLogGen")) { - 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."); - } + wavBuf.resize(wavBuf.size() / 2); + std::vector wavIntBuf (wavBuf.size()); + + while (device->is_siggening()) { + const auto next = std::chrono::high_resolution_clock::now() + delay; - ImGuiFileDialog::Instance()->Close(); + wavOutput.next(wavIntBuf.data(), wavIntBuf.size()); + std::transform(wavIntBuf.cbegin(), wavIntBuf.cend(), + wavBuf.begin(), + [](auto i) { return static_cast(i / 16 + 2048); }); + + { + std::scoped_lock lock (mutexDeviceLoad); + while (!device->siggen_upload(wavBuf.data(), wavBuf.size())) + std::this_thread::sleep_for(uploadDelay); } + + std::this_thread::sleep_until(next); } } -void deviceRenderDraw() +static void statusTask(std::shared_ptr device) { - if (popupRequestDraw) { - ImGui::Begin("draw", &popupRequestDraw); - ImGui::Checkbox("Draw input", &drawSamplesInput); - { - std::scoped_lock lock (mutexDrawSamples); - auto drawList = ImGui::GetWindowDrawList(); - const ImVec2 p0 = ImGui::GetWindowPos(); - const auto size = ImGui::GetWindowSize(); - //ImVec2 p1 (p0.x + size.x, p0.y + size.y); - //ImU32 col_a = ImGui::GetColorU32(IM_COL32(0, 0, 0, 255)); - //ImU32 col_b = ImGui::GetColorU32(IM_COL32(255, 255, 255, 255)); - //drawList->AddRectFilledMultiColor(p0, p1, col_a, col_b, col_b, col_a); - - const unsigned int didx = 1.f / (size.x / static_cast(drawSamplesBuf.size())); - ImVec2 pp = p0; - for (auto i = 0u; i < drawSamplesBuf.size(); i += didx) { - ImVec2 next (pp.x + 1, p0.y + (float)drawSamplesBuf[i] / 4095.f * size.y); - drawList->AddLine(pp, next, ImGui::GetColorU32(IM_COL32(128, 0, 0, 255))); - pp = next; - } + if (!device) + return; - if (drawSamplesInput) { - pp = p0; - for (auto i = 0u; i < drawSamplesBuf2.size(); i += didx) { - ImVec2 next (pp.x + 1, p0.y + (float)drawSamplesBuf2[i] / 4095.f * size.y); - drawList->AddLine(pp, next, ImGui::GetColorU32(IM_COL32(0, 0, 128, 255))); - pp = next; - } - } + while (device->connected()) { + mutexDeviceLoad.lock(); + const auto [status, error] = device->get_status(); + mutexDeviceLoad.unlock(); + + if (error != stmdsp::Error::None) { + switch (error) { + case stmdsp::Error::NotIdle: + log("Error: Device already running..."); + break; + case stmdsp::Error::ConversionAborted: + log("Error: Algorithm unloaded, a fault occurred!"); + break; + case stmdsp::Error::GUIDisconnect: + // Do GUI events for disconnect if device was lost. + deviceConnect(); + deviceRenderDisconnect(); + return; + break; + default: + log("Error: Device had an issue..."); + break; } - ImGui::End(); + } + + std::this_thread::sleep_for(std::chrono::seconds(1)); } } -void deviceRenderMenu() +void deviceLoadAudioFile(const std::string& file) { - if (ImGui::BeginMenu("Run")) { - bool isConnected = m_device != nullptr; - bool isRunning = isConnected && m_device->is_running(); - - static const char *connectLabel = "Connect"; - if (ImGui::MenuItem(connectLabel)) { - deviceConnect(); - connectLabel = isConnected ? "Disconnect" : "Connect"; - } + wavOutput = wav::clip(file); + if (wavOutput.valid()) + log("Audio file loaded."); + else + log("Error: Bad WAV audio file."); +} - ImGui::Separator(); - static const char *startLabel = "Start"; - if (ImGui::MenuItem(startLabel, nullptr, false, isConnected)) { - deviceStart(); - startLabel = isRunning ? "Stop" : "Start"; - } +void deviceLoadLogFile(const std::string& file) +{ + logSamplesFile = std::ofstream(file); + if (logSamplesFile.is_open()) + log("Log file ready."); + else + log("Error: Could not open log file."); +} -/** -TODO test siggen formula -TODO improve siggen audio streaming -TODO draw: smoothly chain captures - */ - if (ImGui::MenuItem("Upload algorithm", nullptr, false, isConnected)) - deviceAlgorithmUpload(); - if (ImGui::MenuItem("Unload algorithm", nullptr, false, isConnected)) - deviceAlgorithmUnload(); - ImGui::Separator(); - if (ImGui::Checkbox("Measure Code Time", &measureCodeTime)) { - if (!isConnected) - measureCodeTime = false; - } - if (ImGui::Checkbox("Draw samples", &drawSamples)) { - if (isConnected) { - if (drawSamples) - popupRequestDraw = true; - } else { - drawSamples = false; - } - } - if (ImGui::Checkbox("Log results...", &logResults)) { - if (isConnected) { - if (logResults) - popupRequestLog = true; - else if (logSamplesFile.is_open()) - logSamplesFile.close(); +bool deviceGenStartToggle() +{ + if (m_device) { + const bool running = m_device->is_siggening(); + + if (!running) { + if (wavOutput.valid()) { + std::thread(feedSigGenTask, m_device).detach(); } else { - logResults = false; + std::scoped_lock dlock (mutexDeviceLoad); + m_device->siggen_start(); } - } - if (ImGui::MenuItem("Set buffer size...", nullptr, false, isConnected)) { - popupRequestBuffer = true; - } - ImGui::Separator(); - if (ImGui::MenuItem("Load signal generator", nullptr, false, isConnected)) { - popupRequestSiggen = true; - } - static const char *startSiggenLabel = "Start signal generator"; - if (ImGui::MenuItem(startSiggenLabel, nullptr, false, isConnected)) { - if (m_device != nullptr) { - 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"; - } + log("Generator started."); + } else { + { + std::scoped_lock dlock (mutexDeviceLoad); + m_device->siggen_stop(); } + log("Generator stopped."); } - ImGui::EndMenu(); + return !running; } + + return false; } -void deviceRenderToolbar() +void deviceUpdateDrawBufferSize(double timeframe) { - ImGui::SameLine(); - if (ImGui::Button("Upload")) - deviceAlgorithmUpload(); - ImGui::SameLine(); - ImGui::SetNextItemWidth(100); - if (ImGui::BeginCombo("", sampleRatePreview)) { - for (int i = 0; i < 6; ++i) { - if (ImGui::Selectable(sampleRateList[i])) { - sampleRatePreview = sampleRateList[i]; - if (m_device != nullptr && !m_device->is_running()) - m_device->set_sample_rate(i); - } - } - ImGui::EndCombo(); - } + drawSamplesBufferSize = std::round( + m_device->get_sample_rate() * timeframe); } -void deviceConnect() +void deviceSetSampleRate(unsigned int rate) { - if (m_device == nullptr) { + do { + m_device->set_sample_rate(rate); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } while (m_device->get_sample_rate() != rate); +} + +bool deviceConnect() +{ + static std::thread statusThread; + + 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()) { - sampleRatePreview = sampleRateList[m_device->get_sample_rate()]; - log("Connected!"); - } else { - delete m_device; - m_device = nullptr; - log("Failed to connect."); + if (const auto devices = scanner.scan(); !devices.empty()) { + try { + m_device.reset(new stmdsp::device(devices.front())); + } catch (...) { + log("Failed to connect (check permissions?)."); + m_device.reset(); + } + + if (m_device) { + if (m_device->connected()) { + log("Connected!"); + statusThread = std::thread(statusTask, m_device); + statusThread.detach(); + return true; + } else { + m_device.reset(); + log("Failed to connect."); + } } } else { log("No devices found."); } } else { - delete m_device; - m_device = nullptr; + m_device->disconnect(); + if (statusThread.joinable()) + statusThread.join(); + m_device.reset(); log("Disconnected."); } + + return false; } -void deviceStart() +void deviceStart(bool logResults, bool drawSamples) { - if (m_device == nullptr) { + if (!m_device) { log("No device connected."); return; } if (m_device->is_running()) { - m_device->continuous_stop(); - if (logResults) { + { + std::scoped_lock lock (mutexDrawSamples, mutexDeviceLoad); + std::this_thread::sleep_for(std::chrono::microseconds(150)); + m_device->continuous_stop(); + } + if (logSamplesFile.is_open()) { logSamplesFile.close(); - logResults = false; log("Log file saved and closed."); } log("Ready."); } else { - if (measureCodeTime) { - m_device->continuous_start_measure(); - std::thread(measureCodeTask, m_device).detach(); - } else { - m_device->continuous_start(); - if (drawSamples || logResults || wavOutput.valid()) - std::thread(drawSamplesTask, m_device).detach(); - } + m_device->continuous_start(); + if (drawSamples || logResults || wavOutput.valid()) + std::thread(drawSamplesTask, m_device).detach(); + log("Running."); } } -void deviceAlgorithmUpload() +void deviceStartMeasurement() { - if (m_device == nullptr) { - log("No device connected."); - return; + if (m_device && m_device->is_running()) { + m_device->measurement_start(); + std::thread(measureCodeTask, m_device).detach(); } +} - if (m_device->is_running()) - return; - - if (std::ifstream algo (tempFileName + ".o"); algo.good()) { +void deviceAlgorithmUpload() +{ + if (!m_device) { + log("No device connected."); + } else if (m_device->is_running()) { + log("Cannot upload algorithm while running."); + } else if (auto algo = compileOpenBinaryFile(); algo.is_open()) { std::ostringstream sstr; sstr << algo.rdbuf(); auto str = sstr.str(); @@ -473,60 +371,111 @@ void deviceAlgorithmUpload() void deviceAlgorithmUnload() { - if (m_device == nullptr) { + if (!m_device) { log("No device connected."); - return; - } - - if (!m_device->is_running()) { + } else if (m_device->is_running()) { + log("Cannot unload algorithm while running."); + } else { m_device->unload_filter(); log("Algorithm unloaded."); } } -void deviceGenLoadList(std::string_view listStr) +void deviceGenLoadList(const std::string_view list) { std::vector samples; - while (listStr.size() > 0 && samples.size() <= stmdsp::SAMPLES_MAX * 2) { - auto numberEnd = listStr.find_first_not_of("0123456789"); + auto it = list.cbegin(); + while (it != list.cend()) { + const auto itend = std::find_if(it, list.cend(), + [](char c) { return !isdigit(c); }); unsigned long n; - auto end = numberEnd != std::string_view::npos ? listStr.begin() + numberEnd : listStr.end(); - auto [ptr, ec] = std::from_chars(listStr.begin(), end, n); - if (ec != std::errc()) + const auto ec = std::from_chars(it, itend, n).ec; + if (ec != std::errc()) { + log("Error: Bad data in sample list."); break; - - samples.push_back(n & 4095); - if (end == listStr.end()) + } else if (n > 4095) { + log("Error: Sample data value larger than max of 4095."); break; - listStr = listStr.substr(numberEnd + 1); + } else { + samples.push_back(n & 4095); + if (samples.size() >= stmdsp::SAMPLES_MAX * 2) { + log("Error: Too many samples for signal generator."); + break; + } + } + + it = std::find_if(itend, list.cend(), isdigit); } - if (samples.size() <= stmdsp::SAMPLES_MAX * 2) { + if (it == list.cend()) { // DAC buffer must be of even size - if ((samples.size() & 1) == 1) + if (samples.size() % 2 != 0) samples.push_back(samples.back()); - if (m_device != nullptr) - m_device->siggen_upload(&samples[0], samples.size()); + m_device->siggen_upload(samples.data(), samples.size()); log("Generator ready."); - } else { - log("Error: Too many samples for signal generator."); } } -void deviceGenLoadFormula(std::string_view formula) +void deviceGenLoadFormula(const std::string& formula) { auto samples = deviceGenLoadFormulaEval(formula); - if (samples.size() > 0) { - if (m_device != nullptr) - m_device->siggen_upload(&samples[0], samples.size()); - + if (!samples.empty()) { + m_device->siggen_upload(samples.data(), samples.size()); log("Generator ready."); } else { log("Error: Bad formula."); } } +std::size_t pullFromQueue( + std::deque& queue, + CircularBuffer& circ) +{ + // We know how big the circular buffer should be to hold enough samples to + // fill the current draw samples view. + // If the given buffer does not match this size, notify the caller. + // TODO this could be done better... drawSamplesBufferSize should be a GUI- + // only thing. + if (circ.size() != drawSamplesBufferSize) + return drawSamplesBufferSize; + + std::scoped_lock lock (mutexDrawSamples); + + // The render code will draw all of the new samples we add to the buffer. + // So, we must provide a certain amount of samples at a time to make the + // render appear smooth. + // The 1.025 factor keeps us on top of the stream; don't want to fall + // behind. + const double FPS = ImGui::GetIO().Framerate; + const auto desiredCount = m_device->get_sample_rate() / FPS; + + // Transfer from the queue to the render buffer. + auto count = std::min(queue.size(), static_cast(desiredCount)); + while (count--) { + circ.put(queue.front()); + queue.pop_front(); + } + + return 0; +} + +/** + * Pulls a render frame's worth of samples from the draw samples queue, adding + * the samples to the given buffer. + */ +std::size_t pullFromDrawQueue( + CircularBuffer& circ) +{ + return pullFromQueue(drawSamplesQueue, circ); +} + +std::size_t pullFromInputDrawQueue( + CircularBuffer& circ) +{ + return pullFromQueue(drawSamplesInputQueue, circ); +} + diff --git a/source/device_formula.cpp b/source/device_formula.cpp index 5c1ca16..e21d374 100644 --- a/source/device_formula.cpp +++ b/source/device_formula.cpp @@ -1,28 +1,86 @@ +/** + * @file device_formula.cpp + * @brief Function for filling generator buffer using a mathematical formula. + * This is kept in its own file as exprtk.hpp takes forever to compile. + * + * 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 "stmdsp.hpp" -#include "exprtk.hpp" #include +#include #include #include -std::vector deviceGenLoadFormulaEval(const std::string_view formulaString) +#ifndef STMDSP_DISABLE_FORMULAS + +#define exprtk_disable_comments +#define exprtk_disable_break_continue +#define exprtk_disable_sc_andor +#define exprtk_disable_return_statement +#define exprtk_disable_enhanced_features +//#define exprtk_disable_string_capabilities +#define exprtk_disable_superscalar_unroll +#define exprtk_disable_rtl_io_file +#define exprtk_disable_rtl_vecops +//#define exprtk_disable_caseinsensitivity +#include "exprtk.hpp" + +static std::random_device randomDevice; + +std::vector deviceGenLoadFormulaEval(const std::string& formulaString) { double x = 0; exprtk::symbol_table symbol_table; + exprtk::function_compositor compositor (symbol_table); exprtk::expression expression; exprtk::parser parser; - symbol_table.add_variable("x", x); symbol_table.add_constants(); + symbol_table.add_variable("x", x); + symbol_table.add_function("random", + [](double l, double h) -> double { + return std::uniform_real_distribution(l, h)(randomDevice); + }); + compositor.add(exprtk::function_compositor::function() + .name("square") + .var("X") + .expression("ceil(sin(pi*X))")); + compositor.add(exprtk::function_compositor::function() + .name("triangle") + .var("X") + .expression("ceil(sin(pi*X))*(X-floor(X))+ceil(-sin(pi*X))*(-X-floor(-X))")); + compositor.add(exprtk::function_compositor::function() + .name("pulse") + .var("L") + .var("X") + .expression("if(X<=L,1,0)")); expression.register_symbol_table(symbol_table); - parser.compile(std::string(formulaString), expression); + parser.compile(formulaString, expression); + + const auto genFun = [&x, &expression] { + const auto s = std::clamp(expression.value(), -1., 1.) * 2048. + 2048.; + ++x; + return static_cast(std::min(s, 4095.)); + }; std::vector samples (stmdsp::SAMPLES_MAX); + std::generate(samples.begin(), samples.end(), genFun); + return samples; +} - std::generate(samples.begin(), samples.end(), - [&] { ++x; return static_cast(expression.value()); }); +#else // no formula support - return samples; +std::vector deviceGenLoadFormulaEval(const std::string&) +{ + return {}; } +#endif // STMDSP_DISABLE_FORMULAS + diff --git a/source/file.cpp b/source/file.cpp index f6222fa..b4332f4 100644 --- a/source/file.cpp +++ b/source/file.cpp @@ -17,6 +17,7 @@ #include "stmdsp_code.hpp" +#include #include #include #include @@ -25,6 +26,8 @@ #include #include +#include + extern TextEditor editor; extern void log(const std::string& str); @@ -34,9 +37,10 @@ enum class FileAction { Save, SaveAs }; + static FileAction fileAction = FileAction::None; static std::string fileCurrentPath; -static std::vector fileTemplateList; +static std::vector fileExampleList; static void saveCurrentFile() { @@ -56,11 +60,31 @@ static void openCurrentFile() } } -void fileScanTemplates() +static void openNewFile() +{ + fileCurrentPath.clear(); + editor.SetText(stmdsp::file_content); +} + +static std::vector fileScanExamples() +{ + const auto path = std::filesystem::current_path() / "examples"; + const std::filesystem::recursive_directory_iterator rdi (path); + + std::vector list; + std::transform( + std::filesystem::begin(rdi), + std::filesystem::end(rdi), + std::back_inserter(list), + [](const auto& file) { return file.path(); }); + std::sort(list.begin(), list.end()); + return list; +} + +void fileInit() { - auto path = std::filesystem::current_path() / "templates"; - for (const auto& file : std::filesystem::recursive_directory_iterator{path}) - fileTemplateList.push_back(file.path()); + fileExampleList = fileScanExamples(); + openNewFile(); } void fileRenderMenu() @@ -68,8 +92,7 @@ void fileRenderMenu() if (ImGui::BeginMenu("File")) { if (ImGui::MenuItem("New")) { // TODO modified? - fileCurrentPath.clear(); - editor.SetText(stmdsp::file_content); + openNewFile(); log("Ready."); } @@ -79,9 +102,9 @@ void fileRenderMenu() "ChooseFileOpenSave", "Choose File", ".cpp", "."); } - if (ImGui::BeginMenu("Open Template")) { - for (const auto& file : fileTemplateList) { - if (ImGui::MenuItem(file.filename().c_str())) { + if (ImGui::BeginMenu("Open Example")) { + for (const auto& file : fileExampleList) { + if (ImGui::MenuItem(file.filename().string().c_str())) { fileCurrentPath = file.string(); openCurrentFile(); @@ -112,8 +135,8 @@ void fileRenderMenu() ImGui::Separator(); if (ImGui::MenuItem("Quit")) { - extern bool done; - done = true; + SDL_Event quitEvent (SDL_QUIT); + SDL_PushEvent(&quitEvent); } ImGui::EndMenu(); @@ -122,22 +145,20 @@ void fileRenderMenu() void fileRenderDialog() { - if (ImGuiFileDialog::Instance()->Display("ChooseFileOpenSave")) { + if (ImGuiFileDialog::Instance()->Display("ChooseFileOpenSave", + ImGuiWindowFlags_NoCollapse, + ImVec2(460, 540))) + { if (ImGuiFileDialog::Instance()->IsOk()) { std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName(); - switch (fileAction) { - case FileAction::None: - break; - case FileAction::Open: + if (fileAction == FileAction::Open) { fileCurrentPath = filePathName; openCurrentFile(); log("Ready."); - break; - case FileAction::SaveAs: + } else if (fileAction == FileAction::SaveAs) { fileCurrentPath = filePathName; saveCurrentFile(); - break; } } diff --git a/source/gui.cpp b/source/gui.cpp index fad451e..43c95df 100644 --- a/source/gui.cpp +++ b/source/gui.cpp @@ -10,23 +10,24 @@ */ #include "imgui.h" +#include "imgui_internal.h" #include "backends/imgui_impl_sdl.h" #include "backends/imgui_impl_opengl2.h" -#include "config.h" - #include #include -ImFont *fontSans = nullptr; -ImFont *fontMono = nullptr; -static ImGuiIO *io = nullptr; +bool guiInitialize(); +void guiRender(); +bool guiHandleEvents(); +void guiShutdown(); + static SDL_Window *window = nullptr; -static decltype(SDL_GL_CreateContext(nullptr)) gl_context; +static SDL_GLContext gl_context; bool guiInitialize() { - if (SDL_Init(SDL_INIT_VIDEO) != 0) { + if (SDL_Init(0) != 0) { printf("Error: %s\n", SDL_GetError()); return false; } @@ -39,8 +40,16 @@ bool guiInitialize() SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); window = SDL_CreateWindow("stmdsp gui", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, - WINDOW_WIDTH, WINDOW_HEIGHT, - SDL_WINDOW_OPENGL /*| SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI*/); + 640, 700, + SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE /*| SDL_WINDOW_ALLOW_HIGHDPI*/); + + if (window == nullptr) { + puts("Error: Could not create the window!"); + return false; + } + + SDL_SetWindowMinimumSize(window, 320, 320); + gl_context = SDL_GL_CreateContext(window); SDL_GL_MakeCurrent(window, gl_context); SDL_GL_SetSwapInterval(1); // Enable vsync @@ -48,37 +57,85 @@ bool guiInitialize() // Setup Dear ImGui context IMGUI_CHECKVERSION(); ImGui::CreateContext(); - io = &ImGui::GetIO(); - io->ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - fontSans = io->Fonts->AddFontFromFileTTF("fonts/Roboto-Regular.ttf", 20); - fontMono = io->Fonts->AddFontFromFileTTF("fonts/RobotoMono-Regular.ttf", 20); - ImGui::StyleColorsLight(); + //io->ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; ImGui_ImplSDL2_InitForOpenGL(window, gl_context); ImGui_ImplOpenGL2_Init(); + ImGui::StyleColorsLight(); + ImGuiStyle& style = ImGui::GetStyle(); + style.WindowRounding = 5; + style.FrameRounding = 3; + style.ScrollbarRounding = 1; + +//#define ACCENT1 0.26f, 0.59f, 0.98f +#define ACCENT1 0.6f, 0.6f, 0.6f +#define ACCENT2 0.4f, 0.4f, 0.4f + + style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(ACCENT1, 0.40f); + style.Colors[ImGuiCol_FrameBgActive] = ImVec4(ACCENT1, 0.67f); + style.Colors[ImGuiCol_CheckMark] = ImVec4(ACCENT1, 1.00f); + style.Colors[ImGuiCol_SliderGrab] = ImVec4(ACCENT1, 0.78f); + style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(0.46f, 0.54f, 0.80f, 0.60f); + style.Colors[ImGuiCol_Button] = ImVec4(ACCENT1, 0.40f); + style.Colors[ImGuiCol_ButtonHovered] = ImVec4(ACCENT1, 1.00f); + style.Colors[ImGuiCol_ButtonActive] = ImVec4(ACCENT2, 1.00f); + style.Colors[ImGuiCol_Header] = ImVec4(ACCENT1, 0.31f); + style.Colors[ImGuiCol_HeaderHovered] = ImVec4(ACCENT1, 0.80f); + style.Colors[ImGuiCol_HeaderActive] = ImVec4(ACCENT1, 1.00f); + style.Colors[ImGuiCol_Separator] = ImVec4(0.39f, 0.39f, 0.39f, 0.62f); + style.Colors[ImGuiCol_SeparatorHovered] = ImVec4(0.14f, 0.44f, 0.80f, 0.78f); + style.Colors[ImGuiCol_SeparatorActive] = ImVec4(0.14f, 0.44f, 0.80f, 1.00f); + style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(ACCENT1, 0.67f); + style.Colors[ImGuiCol_ResizeGripActive] = ImVec4(ACCENT1, 0.95f); + style.Colors[ImGuiCol_TableHeaderBg] = ImVec4(0.78f, 0.87f, 0.98f, 1.00f); + style.Colors[ImGuiCol_TableBorderStrong] = ImVec4(0.57f, 0.57f, 0.64f, 1.00f); + style.Colors[ImGuiCol_TableBorderLight] = ImVec4(0.68f, 0.68f, 0.74f, 1.00f); + style.Colors[ImGuiCol_TextSelectedBg] = ImVec4(ACCENT1, 0.35f); + style.Colors[ImGuiCol_DragDropTarget] = ImVec4(ACCENT1, 0.95f); + + style.Colors[ImGuiCol_Tab] = ImLerp(style.Colors[ImGuiCol_Header], style.Colors[ImGuiCol_TitleBgActive], 0.90f); + style.Colors[ImGuiCol_TabHovered] = style.Colors[ImGuiCol_HeaderHovered]; + style.Colors[ImGuiCol_TabActive] = ImLerp(style.Colors[ImGuiCol_HeaderActive], style.Colors[ImGuiCol_TitleBgActive], 0.60f); + style.Colors[ImGuiCol_TabUnfocused] = ImLerp(style.Colors[ImGuiCol_Tab], style.Colors[ImGuiCol_TitleBg], 0.80f); + style.Colors[ImGuiCol_TabUnfocusedActive] = ImLerp(style.Colors[ImGuiCol_TabActive], style.Colors[ImGuiCol_TitleBg], 0.40f); + style.Colors[ImGuiCol_NavHighlight] = style.Colors[ImGuiCol_HeaderHovered]; + return true; } -void guiRender(void (*func)()) +void guiRender() { - glViewport(0, 0, (int)io->DisplaySize.x, (int)io->DisplaySize.y); - glClearColor(1, 1, 1, 1); - glClear(GL_COLOR_BUFFER_BIT); - func(); - SDL_GL_SwapWindow(window); + ImGui::Render(); + + const auto& displaySize = ImGui::GetIO().DisplaySize; + const int sizeX = static_cast(displaySize.x); + const int sizeY = static_cast(displaySize.y); + + glViewport(0, 0, sizeX, sizeY); + glClearColor(1, 1, 1, 1); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData()); + SDL_GL_SwapWindow(window); } -void guiHandleEvents(bool& done) +bool guiHandleEvents() { - SDL_Event event; + bool done = false; + for (SDL_Event event; SDL_PollEvent(&event);) { ImGui_ImplSDL2_ProcessEvent(&event); - if (event.type == SDL_QUIT) - done = true; - if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)) + if (event.type == SDL_QUIT) { done = true; + } else if (event.type == SDL_WINDOWEVENT) { + const auto& ew = event.window; + const auto wid = SDL_GetWindowID(window); + if (ew.event == SDL_WINDOWEVENT_CLOSE && ew.windowID == wid) + done = true; + } } + + return done; } void guiShutdown() diff --git a/source/gui_code.cpp b/source/gui_code.cpp new file mode 100644 index 0000000..50fd0c8 --- /dev/null +++ b/source/gui_code.cpp @@ -0,0 +1,70 @@ +/** + * @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 + +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")) + codeCompile(); + if (ImGui::MenuItem("Disassemble")) + codeDisassemble(); + + ImGui::EndMenu(); + } +} + +void codeRenderToolbar() +{ + if (ImGui::Button("Compile")) + codeCompile(); +} + +void codeRenderWidgets(const ImVec2& size) +{ + editor.Render("code", size, 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..689ef54 --- /dev/null +++ b/source/gui_device.cpp @@ -0,0 +1,418 @@ +#include "circular.hpp" +#include "imgui.h" +#include "imgui_internal.h" +#include "ImGuiFileDialog.h" + +#include "stmdsp.hpp" + +#include +#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(const std::string& 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 logResults, bool drawSamples); +void deviceStartMeasurement(); +void deviceUpdateDrawBufferSize(double timeframe); +std::size_t pullFromDrawQueue( + CircularBuffer& circ); +std::size_t pullFromInputDrawQueue( + CircularBuffer& circ); + +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"; +} + +static std::string connectLabel ("Connect"); +void deviceRenderDisconnect() +{ + connectLabel = "Connect"; + measureCodeTime = false; + logResults = false; + drawSamples = false; +} + +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("Device")) { + addMenuItem(connectLabel, !m_device || !m_device->is_running(), [&] { + if (deviceConnect()) { + connectLabel = "Disconnect"; + sampleRatePreview = + getSampleRatePreview(m_device->get_sample_rate()); + deviceUpdateDrawBufferSize(drawSamplesTimeframe); + } else { + deviceRenderDisconnect(); + } + }); + + const bool isConnected = m_device ? true : false; + const bool isRunning = isConnected && m_device->is_running(); + + ImGui::Separator(); + + static std::string startLabel ("Start"); + addMenuItem(startLabel, isConnected, [&] { + startLabel = isRunning ? "Start" : "Stop"; + deviceStart(logResults, drawSamples); + if (logResults && isRunning) + logResults = false; + }); + addMenuItem("Upload algorithm", isConnected && !isRunning, + deviceAlgorithmUpload); + addMenuItem("Unload algorithm", isConnected && !isRunning, + deviceAlgorithmUnload); + addMenuItem("Measure Code Time", isRunning, deviceStartMeasurement); + + ImGui::Separator(); + if (!isConnected || isRunning) + ImGui::PushDisabled(); // Hey, pushing disabled! + + ImGui::Checkbox("Draw samples", &drawSamples); + if (ImGui::Checkbox("Log results...", &logResults)) { + if (logResults) + popupRequestLog = true; + } + addMenuItem("Set buffer size...", true, [] { popupRequestBuffer = true; }); + + if (!isConnected || isRunning) + ImGui::PopDisabled(); + ImGui::Separator(); + + addMenuItem("Load signal generator", + isConnected && !m_device->is_siggening() && !m_device->is_running(), + [] { 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 (32768, '\0'); + static int siggenOption = 0; + + if (popupRequestSiggen) { + popupRequestSiggen = false; + ImGui::OpenPopup("siggen"); + } else if (popupRequestBuffer) { + popupRequestBuffer = false; + ImGui::OpenPopup("buffer"); + } else if (popupRequestLog) { + popupRequestLog = false; + ImGuiFileDialog::Instance()->OpenModal( + "ChooseFileLog", "Choose File", ".csv", "."); + } + + if (ImGui::BeginPopup("siggen")) { + if (ImGui::RadioButton("List", &siggenOption, 0)) { + siggenInput.resize(32768); + siggenInput[0] = '\0'; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Formula", &siggenOption, 1)) { + siggenInput.resize(1024); + siggenInput[0] = '\0'; + } + 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( + "ChooseFileGen", "Choose File", ".wav", "."); + } + } else { + ImGui::Text(siggenOption == 0 ? "Enter a list of numbers:" + : "Enter a formula. x = sample #, y = -1 to 1.\nf(x) = "); + ImGui::PushStyleColor(ImGuiCol_FrameBg, {.8, .8, .8, 1}); + ImGui::InputText("", siggenInput.data(), siggenInput.size()); + ImGui::PopStyleColor(); + } + + if (ImGui::Button("Save")) { + switch (siggenOption) { + case 0: + deviceGenLoadList(siggenInput.substr(0, siggenInput.find('\0'))); + break; + case 1: + deviceGenLoadFormula(siggenInput.substr(0, siggenInput.find('\0'))); + break; + case 2: + break; + } + + ImGui::CloseCurrentPopup(); + } + + ImGui::SameLine(); + if (ImGui::Button("Cancel")) { + siggenInput.clear(); + 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("ChooseFileLog", + ImGuiWindowFlags_NoCollapse, + ImVec2(460, 540))) + { + if (ImGuiFileDialog::Instance()->IsOk()) { + const auto filePathName = ImGuiFileDialog::Instance()->GetFilePathName(); + deviceLoadLogFile(filePathName); + } else { + logResults = false; + } + + ImGuiFileDialog::Instance()->Close(); + } + + if (ImGuiFileDialog::Instance()->Display("ChooseFileGen", + ImGuiWindowFlags_NoCollapse, + ImVec2(460, 540))) + { + if (ImGuiFileDialog::Instance()->IsOk()) { + const auto filePathName = ImGuiFileDialog::Instance()->GetFilePathName(); + deviceLoadAudioFile(filePathName); + } + + ImGuiFileDialog::Instance()->Close(); + } +} + +void deviceRenderDraw() +{ + if (drawSamples) { + static std::vector buffer; + static std::vector bufferInput; + static auto bufferCirc = CircularBuffer(buffer); + static auto bufferInputCirc = CircularBuffer(bufferInput); + + 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); + if (drawSamplesInput) { + bufferCirc.reset(2048); + bufferInputCirc.reset(2048); + } + } + 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); + } + + auto newSize = pullFromDrawQueue(bufferCirc); + if (newSize > 0) { + buffer.resize(newSize); + bufferCirc = CircularBuffer(buffer); + pullFromDrawQueue(bufferCirc); + } + + if (drawSamplesInput) { + auto newSize = pullFromInputDrawQueue(bufferInputCirc); + if (newSize > 0) { + bufferInput.resize(newSize); + bufferInputCirc = CircularBuffer(bufferInput); + pullFromInputDrawQueue(bufferInputCirc); + } + } + + 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_BLACK); + + const auto lcMinor = ImGui::GetColorU32(IM_COL32(40, 40, 40, 255)); + const auto lcMajor = ImGui::GetColorU32(IM_COL32(140, 140, 140, 255)); + + { + const float yinc = (3. / 3.3) * size.y / 12.f; + const float center = p0.y + size.y / 2; + drawList->AddLine({p0.x, center}, {p0.x + size.x, center}, ImGui::GetColorU32(IM_COL32_WHITE)); + for (int i = 1; i < 7; ++i) { + drawList->AddLine({p0.x, center + i * yinc}, {p0.x + size.x, center + i * yinc}, (i % 2) ? lcMinor : lcMajor); + drawList->AddLine({p0.x, center - i * yinc}, {p0.x + size.x, center - i * yinc}, (i % 2) ? lcMinor : lcMajor); + } + } + { + const float xinc = size.x / 16.f; + const float center = p0.x + size.x / 2; + drawList->AddLine({center, p0.y}, {center, p0.y + size.y}, ImGui::GetColorU32(IM_COL32_WHITE)); + for (int i = 1; i < 8; ++i) { + drawList->AddLine({center + i * xinc, p0.y}, {center + i * xinc, p0.y + size.y}, (i % 2) ? lcMinor : lcMajor); + drawList->AddLine({center - i * xinc, p0.y}, {center - i * xinc, p0.y + size.y}, (i % 2) ? lcMinor : lcMajor); + } + } + + 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; + } + } + + const auto mouse = ImGui::GetMousePos(); + if (mouse.x > p0.x && mouse.x < p0.x + size.x && + mouse.y > p0.y && mouse.y < p0.y + size.y) + { + char buf[16]; + drawList->AddLine({mouse.x, p0.y}, {mouse.x, p0.y + size.y}, IM_COL32(255, 255, 0, 255)); + + { + const std::size_t si = (mouse.x - p0.x) / size.x * buffer.size(); + const float s = buffer[si] / 4095.f * 6.6f - 3.3f; + snprintf(buf, sizeof(buf), " %1.3fV", s); + drawList->AddText(mouse, IM_COL32(255, 0, 0, 255), buf); + } + + if (drawSamplesInput) { + const std::size_t si = (mouse.x - p0.x) / size.x * bufferInput.size(); + const float s = bufferInput[si] / 4095.f * 6.6f - 3.3f; + snprintf(buf, sizeof(buf), " %1.3fV", s); + drawList->AddText({mouse.x, mouse.y + 20}, IM_COL32(0, 0, 255, 255), buf); + } + } + + ImGui::End(); + } +} + diff --git a/source/gui_help.cpp b/source/gui_help.cpp new file mode 100644 index 0000000..a17d9fa --- /dev/null +++ b/source/gui_help.cpp @@ -0,0 +1,113 @@ +#include "imgui.h" +#include "ImGuiFileDialog.h" + +#include +#include +#include +#include + +void log(const std::string& str); + +static bool showDownloadFirmware = false; +static bool showHelp = false; +static std::string firmwareFile; + +static void helpDownloadThread(); + +void helpRenderMenu() +{ + if (ImGui::BeginMenu("Help")) { + if (ImGui::MenuItem("Open wiki...")) { +#ifdef STMDSP_WIN32 + system("start " +#else + system("xdg-open " +#endif + "https://code.bitgloo.com/clyne/stmdspgui/wiki"); + } + + if (ImGui::MenuItem("Download firmware...")) { + showDownloadFirmware = true; + } + + ImGui::Separator(); + if (ImGui::MenuItem("About")) { + showHelp = true; + } + + ImGui::EndMenu(); + } +} + +void helpRenderDialog() +{ + if (showDownloadFirmware) { + showDownloadFirmware = false; + ImGuiFileDialog::Instance()->OpenModal( + "ChooseFileFW", "Choose Firmware File", ".hex", "."); + } + + if (ImGuiFileDialog::Instance()->Display("ChooseFileFW", + ImGuiWindowFlags_NoCollapse, + ImVec2(460, 540))) + { + if (ImGuiFileDialog::Instance()->IsOk()) { + firmwareFile = ImGuiFileDialog::Instance()->GetFilePathName(); +#ifdef STMDSP_WIN32 + size_t i = 0; + while ((i = firmwareFile.find('\\', i)) != std::string::npos) { + firmwareFile.replace(i, 1, "\\\\"); + i += 2; + } +#endif + + std::thread(helpDownloadThread).detach(); + } + + ImGuiFileDialog::Instance()->Close(); + } + + if (showHelp) { + ImGui::Begin("help"); + + ImGui::Text("stmdspgui\nCompiled on " __DATE__ ".\n\nWritten by Clyne Sullivan.\n"); + + if (ImGui::Button("Close")) { + showHelp = false; + } + + ImGui::End(); + } + + if (!firmwareFile.empty()) { + ImGui::Begin("Downloading"); + + ImGui::Text("Downloading firmware to device..."); + + ImGui::End(); + } +} + +void helpDownloadThread() +{ + std::string command ( +#ifdef STMDSP_WIN32 + "openocd\\bin\\openocd.exe" +#else + "openocd" +#endif + " -f openocd.cfg -c \"program $0 reset exit\""); + + command.replace(command.find("$0"), 2, firmwareFile); + + std::cout << "Run: " << command << std::endl; + + if (system(command.c_str()) == 0) { + log("Programming finished."); + } else { + log("Error while programming device!"); + } + + firmwareFile.clear(); +} + diff --git a/source/imgui/TextEditor.cpp b/source/imgui/TextEditor.cpp index 02966f0..85bff74 100644 --- a/source/imgui/TextEditor.cpp +++ b/source/imgui/TextEditor.cpp @@ -46,11 +46,11 @@ TextEditor::TextEditor() , mHandleKeyboardInputs(true) , mHandleMouseInputs(true) , mIgnoreImGuiChild(false) - , mShowWhitespaces(true) + , mShowWhitespaces(false) , mStartTime(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) { SetPalette(GetDarkPalette()); - SetLanguageDefinition(LanguageDefinition::HLSL()); + SetLanguageDefinition(LanguageDefinition::CPlusPlus()); mLines.push_back(Line()); } @@ -2804,357 +2804,3 @@ const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::CPlusPlus( return langDef; } -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::HLSL() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) - { - static const char* const keywords[] = { - "AppendStructuredBuffer", "asm", "asm_fragment", "BlendState", "bool", "break", "Buffer", "ByteAddressBuffer", "case", "cbuffer", "centroid", "class", "column_major", "compile", "compile_fragment", - "CompileShader", "const", "continue", "ComputeShader", "ConsumeStructuredBuffer", "default", "DepthStencilState", "DepthStencilView", "discard", "do", "double", "DomainShader", "dword", "else", - "export", "extern", "false", "float", "for", "fxgroup", "GeometryShader", "groupshared", "half", "Hullshader", "if", "in", "inline", "inout", "InputPatch", "int", "interface", "line", "lineadj", - "linear", "LineStream", "matrix", "min16float", "min10float", "min16int", "min12int", "min16uint", "namespace", "nointerpolation", "noperspective", "NULL", "out", "OutputPatch", "packoffset", - "pass", "pixelfragment", "PixelShader", "point", "PointStream", "precise", "RasterizerState", "RenderTargetView", "return", "register", "row_major", "RWBuffer", "RWByteAddressBuffer", "RWStructuredBuffer", - "RWTexture1D", "RWTexture1DArray", "RWTexture2D", "RWTexture2DArray", "RWTexture3D", "sample", "sampler", "SamplerState", "SamplerComparisonState", "shared", "snorm", "stateblock", "stateblock_state", - "static", "string", "struct", "switch", "StructuredBuffer", "tbuffer", "technique", "technique10", "technique11", "texture", "Texture1D", "Texture1DArray", "Texture2D", "Texture2DArray", "Texture2DMS", - "Texture2DMSArray", "Texture3D", "TextureCube", "TextureCubeArray", "true", "typedef", "triangle", "triangleadj", "TriangleStream", "uint", "uniform", "unorm", "unsigned", "vector", "vertexfragment", - "VertexShader", "void", "volatile", "while", - "bool1","bool2","bool3","bool4","double1","double2","double3","double4", "float1", "float2", "float3", "float4", "int1", "int2", "int3", "int4", "in", "out", "inout", - "uint1", "uint2", "uint3", "uint4", "dword1", "dword2", "dword3", "dword4", "half1", "half2", "half3", "half4", - "float1x1","float2x1","float3x1","float4x1","float1x2","float2x2","float3x2","float4x2", - "float1x3","float2x3","float3x3","float4x3","float1x4","float2x4","float3x4","float4x4", - "half1x1","half2x1","half3x1","half4x1","half1x2","half2x2","half3x2","half4x2", - "half1x3","half2x3","half3x3","half4x3","half1x4","half2x4","half3x4","half4x4", - }; - for (auto& k : keywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "abort", "abs", "acos", "all", "AllMemoryBarrier", "AllMemoryBarrierWithGroupSync", "any", "asdouble", "asfloat", "asin", "asint", "asint", "asuint", - "asuint", "atan", "atan2", "ceil", "CheckAccessFullyMapped", "clamp", "clip", "cos", "cosh", "countbits", "cross", "D3DCOLORtoUBYTE4", "ddx", - "ddx_coarse", "ddx_fine", "ddy", "ddy_coarse", "ddy_fine", "degrees", "determinant", "DeviceMemoryBarrier", "DeviceMemoryBarrierWithGroupSync", - "distance", "dot", "dst", "errorf", "EvaluateAttributeAtCentroid", "EvaluateAttributeAtSample", "EvaluateAttributeSnapped", "exp", "exp2", - "f16tof32", "f32tof16", "faceforward", "firstbithigh", "firstbitlow", "floor", "fma", "fmod", "frac", "frexp", "fwidth", "GetRenderTargetSampleCount", - "GetRenderTargetSamplePosition", "GroupMemoryBarrier", "GroupMemoryBarrierWithGroupSync", "InterlockedAdd", "InterlockedAnd", "InterlockedCompareExchange", - "InterlockedCompareStore", "InterlockedExchange", "InterlockedMax", "InterlockedMin", "InterlockedOr", "InterlockedXor", "isfinite", "isinf", "isnan", - "ldexp", "length", "lerp", "lit", "log", "log10", "log2", "mad", "max", "min", "modf", "msad4", "mul", "noise", "normalize", "pow", "printf", - "Process2DQuadTessFactorsAvg", "Process2DQuadTessFactorsMax", "Process2DQuadTessFactorsMin", "ProcessIsolineTessFactors", "ProcessQuadTessFactorsAvg", - "ProcessQuadTessFactorsMax", "ProcessQuadTessFactorsMin", "ProcessTriTessFactorsAvg", "ProcessTriTessFactorsMax", "ProcessTriTessFactorsMin", - "radians", "rcp", "reflect", "refract", "reversebits", "round", "rsqrt", "saturate", "sign", "sin", "sincos", "sinh", "smoothstep", "sqrt", "step", - "tan", "tanh", "tex1D", "tex1D", "tex1Dbias", "tex1Dgrad", "tex1Dlod", "tex1Dproj", "tex2D", "tex2D", "tex2Dbias", "tex2Dgrad", "tex2Dlod", "tex2Dproj", - "tex3D", "tex3D", "tex3Dbias", "tex3Dgrad", "tex3Dlod", "tex3Dproj", "texCUBE", "texCUBE", "texCUBEbias", "texCUBEgrad", "texCUBElod", "texCUBEproj", "transpose", "trunc" - }; - for (auto& k : identifiers) - { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } - - langDef.mTokenRegexStrings.push_back(std::make_pair("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor)); - langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); - - langDef.mCommentStart = "/*"; - langDef.mCommentEnd = "*/"; - langDef.mSingleLineComment = "//"; - - langDef.mCaseSensitive = true; - langDef.mAutoIndentation = true; - - langDef.mName = "HLSL"; - - inited = true; - } - return langDef; -} - -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::GLSL() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) - { - static const char* const keywords[] = { - "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "inline", "int", "long", "register", "restrict", "return", "short", - "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while", "_Alignas", "_Alignof", "_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary", - "_Noreturn", "_Static_assert", "_Thread_local" - }; - for (auto& k : keywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph", - "ispunct", "isspace", "isupper", "kbhit", "log10", "log2", "log", "memcmp", "modf", "pow", "putchar", "putenv", "puts", "rand", "remove", "rename", "sinh", "sqrt", "srand", "strcat", "strcmp", "strerror", "time", "tolower", "toupper" - }; - for (auto& k : identifiers) - { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } - - langDef.mTokenRegexStrings.push_back(std::make_pair("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor)); - langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); - - langDef.mCommentStart = "/*"; - langDef.mCommentEnd = "*/"; - langDef.mSingleLineComment = "//"; - - langDef.mCaseSensitive = true; - langDef.mAutoIndentation = true; - - langDef.mName = "GLSL"; - - inited = true; - } - return langDef; -} - -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::C() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) - { - static const char* const keywords[] = { - "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "inline", "int", "long", "register", "restrict", "return", "short", - "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while", "_Alignas", "_Alignof", "_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary", - "_Noreturn", "_Static_assert", "_Thread_local" - }; - for (auto& k : keywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph", - "ispunct", "isspace", "isupper", "kbhit", "log10", "log2", "log", "memcmp", "modf", "pow", "putchar", "putenv", "puts", "rand", "remove", "rename", "sinh", "sqrt", "srand", "strcat", "strcmp", "strerror", "time", "tolower", "toupper" - }; - for (auto& k : identifiers) - { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } - - langDef.mTokenize = [](const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end, PaletteIndex & paletteIndex) -> bool - { - paletteIndex = PaletteIndex::Max; - - while (in_begin < in_end && isascii(*in_begin) && isblank(*in_begin)) - in_begin++; - - if (in_begin == in_end) - { - out_begin = in_end; - out_end = in_end; - paletteIndex = PaletteIndex::Default; - } - else if (TokenizeCStyleString(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::String; - else if (TokenizeCStyleCharacterLiteral(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::CharLiteral; - else if (TokenizeCStyleIdentifier(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::Identifier; - else if (TokenizeCStyleNumber(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::Number; - else if (TokenizeCStylePunctuation(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::Punctuation; - - return paletteIndex != PaletteIndex::Max; - }; - - langDef.mCommentStart = "/*"; - langDef.mCommentEnd = "*/"; - langDef.mSingleLineComment = "//"; - - langDef.mCaseSensitive = true; - langDef.mAutoIndentation = true; - - langDef.mName = "C"; - - inited = true; - } - return langDef; -} - -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::SQL() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) - { - static const char* const keywords[] = { - "ADD", "EXCEPT", "PERCENT", "ALL", "EXEC", "PLAN", "ALTER", "EXECUTE", "PRECISION", "AND", "EXISTS", "PRIMARY", "ANY", "EXIT", "PRINT", "AS", "FETCH", "PROC", "ASC", "FILE", "PROCEDURE", - "AUTHORIZATION", "FILLFACTOR", "PUBLIC", "BACKUP", "FOR", "RAISERROR", "BEGIN", "FOREIGN", "READ", "BETWEEN", "FREETEXT", "READTEXT", "BREAK", "FREETEXTTABLE", "RECONFIGURE", - "BROWSE", "FROM", "REFERENCES", "BULK", "FULL", "REPLICATION", "BY", "FUNCTION", "RESTORE", "CASCADE", "GOTO", "RESTRICT", "CASE", "GRANT", "RETURN", "CHECK", "GROUP", "REVOKE", - "CHECKPOINT", "HAVING", "RIGHT", "CLOSE", "HOLDLOCK", "ROLLBACK", "CLUSTERED", "IDENTITY", "ROWCOUNT", "COALESCE", "IDENTITY_INSERT", "ROWGUIDCOL", "COLLATE", "IDENTITYCOL", "RULE", - "COLUMN", "IF", "SAVE", "COMMIT", "IN", "SCHEMA", "COMPUTE", "INDEX", "SELECT", "CONSTRAINT", "INNER", "SESSION_USER", "CONTAINS", "INSERT", "SET", "CONTAINSTABLE", "INTERSECT", "SETUSER", - "CONTINUE", "INTO", "SHUTDOWN", "CONVERT", "IS", "SOME", "CREATE", "JOIN", "STATISTICS", "CROSS", "KEY", "SYSTEM_USER", "CURRENT", "KILL", "TABLE", "CURRENT_DATE", "LEFT", "TEXTSIZE", - "CURRENT_TIME", "LIKE", "THEN", "CURRENT_TIMESTAMP", "LINENO", "TO", "CURRENT_USER", "LOAD", "TOP", "CURSOR", "NATIONAL", "TRAN", "DATABASE", "NOCHECK", "TRANSACTION", - "DBCC", "NONCLUSTERED", "TRIGGER", "DEALLOCATE", "NOT", "TRUNCATE", "DECLARE", "NULL", "TSEQUAL", "DEFAULT", "NULLIF", "UNION", "DELETE", "OF", "UNIQUE", "DENY", "OFF", "UPDATE", - "DESC", "OFFSETS", "UPDATETEXT", "DISK", "ON", "USE", "DISTINCT", "OPEN", "USER", "DISTRIBUTED", "OPENDATASOURCE", "VALUES", "DOUBLE", "OPENQUERY", "VARYING","DROP", "OPENROWSET", "VIEW", - "DUMMY", "OPENXML", "WAITFOR", "DUMP", "OPTION", "WHEN", "ELSE", "OR", "WHERE", "END", "ORDER", "WHILE", "ERRLVL", "OUTER", "WITH", "ESCAPE", "OVER", "WRITETEXT" - }; - - for (auto& k : keywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "ABS", "ACOS", "ADD_MONTHS", "ASCII", "ASCIISTR", "ASIN", "ATAN", "ATAN2", "AVG", "BFILENAME", "BIN_TO_NUM", "BITAND", "CARDINALITY", "CASE", "CAST", "CEIL", - "CHARTOROWID", "CHR", "COALESCE", "COMPOSE", "CONCAT", "CONVERT", "CORR", "COS", "COSH", "COUNT", "COVAR_POP", "COVAR_SAMP", "CUME_DIST", "CURRENT_DATE", - "CURRENT_TIMESTAMP", "DBTIMEZONE", "DECODE", "DECOMPOSE", "DENSE_RANK", "DUMP", "EMPTY_BLOB", "EMPTY_CLOB", "EXP", "EXTRACT", "FIRST_VALUE", "FLOOR", "FROM_TZ", "GREATEST", - "GROUP_ID", "HEXTORAW", "INITCAP", "INSTR", "INSTR2", "INSTR4", "INSTRB", "INSTRC", "LAG", "LAST_DAY", "LAST_VALUE", "LEAD", "LEAST", "LENGTH", "LENGTH2", "LENGTH4", - "LENGTHB", "LENGTHC", "LISTAGG", "LN", "LNNVL", "LOCALTIMESTAMP", "LOG", "LOWER", "LPAD", "LTRIM", "MAX", "MEDIAN", "MIN", "MOD", "MONTHS_BETWEEN", "NANVL", "NCHR", - "NEW_TIME", "NEXT_DAY", "NTH_VALUE", "NULLIF", "NUMTODSINTERVAL", "NUMTOYMINTERVAL", "NVL", "NVL2", "POWER", "RANK", "RAWTOHEX", "REGEXP_COUNT", "REGEXP_INSTR", - "REGEXP_REPLACE", "REGEXP_SUBSTR", "REMAINDER", "REPLACE", "ROUND", "ROWNUM", "RPAD", "RTRIM", "SESSIONTIMEZONE", "SIGN", "SIN", "SINH", - "SOUNDEX", "SQRT", "STDDEV", "SUBSTR", "SUM", "SYS_CONTEXT", "SYSDATE", "SYSTIMESTAMP", "TAN", "TANH", "TO_CHAR", "TO_CLOB", "TO_DATE", "TO_DSINTERVAL", "TO_LOB", - "TO_MULTI_BYTE", "TO_NCLOB", "TO_NUMBER", "TO_SINGLE_BYTE", "TO_TIMESTAMP", "TO_TIMESTAMP_TZ", "TO_YMINTERVAL", "TRANSLATE", "TRIM", "TRUNC", "TZ_OFFSET", "UID", "UPPER", - "USER", "USERENV", "VAR_POP", "VAR_SAMP", "VARIANCE", "VSIZE " - }; - for (auto& k : identifiers) - { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } - - langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("\\\'[^\\\']*\\\'", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); - - langDef.mCommentStart = "/*"; - langDef.mCommentEnd = "*/"; - langDef.mSingleLineComment = "//"; - - langDef.mCaseSensitive = false; - langDef.mAutoIndentation = false; - - langDef.mName = "SQL"; - - inited = true; - } - return langDef; -} - -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::AngelScript() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) - { - static const char* const keywords[] = { - "and", "abstract", "auto", "bool", "break", "case", "cast", "class", "const", "continue", "default", "do", "double", "else", "enum", "false", "final", "float", "for", - "from", "funcdef", "function", "get", "if", "import", "in", "inout", "int", "interface", "int8", "int16", "int32", "int64", "is", "mixin", "namespace", "not", - "null", "or", "out", "override", "private", "protected", "return", "set", "shared", "super", "switch", "this ", "true", "typedef", "uint", "uint8", "uint16", "uint32", - "uint64", "void", "while", "xor" - }; - - for (auto& k : keywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "cos", "sin", "tab", "acos", "asin", "atan", "atan2", "cosh", "sinh", "tanh", "log", "log10", "pow", "sqrt", "abs", "ceil", "floor", "fraction", "closeTo", "fpFromIEEE", "fpToIEEE", - "complex", "opEquals", "opAddAssign", "opSubAssign", "opMulAssign", "opDivAssign", "opAdd", "opSub", "opMul", "opDiv" - }; - for (auto& k : identifiers) - { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } - - langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("\\'\\\\?[^\\']\\'", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); - - langDef.mCommentStart = "/*"; - langDef.mCommentEnd = "*/"; - langDef.mSingleLineComment = "//"; - - langDef.mCaseSensitive = true; - langDef.mAutoIndentation = true; - - langDef.mName = "AngelScript"; - - inited = true; - } - return langDef; -} - -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Lua() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) - { - static const char* const keywords[] = { - "and", "break", "do", "", "else", "elseif", "end", "false", "for", "function", "if", "in", "", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while" - }; - - for (auto& k : keywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "assert", "collectgarbage", "dofile", "error", "getmetatable", "ipairs", "loadfile", "load", "loadstring", "next", "pairs", "pcall", "print", "rawequal", "rawlen", "rawget", "rawset", - "select", "setmetatable", "tonumber", "tostring", "type", "xpcall", "_G", "_VERSION","arshift", "band", "bnot", "bor", "bxor", "btest", "extract", "lrotate", "lshift", "replace", - "rrotate", "rshift", "create", "resume", "running", "status", "wrap", "yield", "isyieldable", "debug","getuservalue", "gethook", "getinfo", "getlocal", "getregistry", "getmetatable", - "getupvalue", "upvaluejoin", "upvalueid", "setuservalue", "sethook", "setlocal", "setmetatable", "setupvalue", "traceback", "close", "flush", "input", "lines", "open", "output", "popen", - "read", "tmpfile", "type", "write", "close", "flush", "lines", "read", "seek", "setvbuf", "write", "__gc", "__tostring", "abs", "acos", "asin", "atan", "ceil", "cos", "deg", "exp", "tointeger", - "floor", "fmod", "ult", "log", "max", "min", "modf", "rad", "random", "randomseed", "sin", "sqrt", "string", "tan", "type", "atan2", "cosh", "sinh", "tanh", - "pow", "frexp", "ldexp", "log10", "pi", "huge", "maxinteger", "mininteger", "loadlib", "searchpath", "seeall", "preload", "cpath", "path", "searchers", "loaded", "module", "require", "clock", - "date", "difftime", "execute", "exit", "getenv", "remove", "rename", "setlocale", "time", "tmpname", "byte", "char", "dump", "find", "format", "gmatch", "gsub", "len", "lower", "match", "rep", - "reverse", "sub", "upper", "pack", "packsize", "unpack", "concat", "maxn", "insert", "pack", "unpack", "remove", "move", "sort", "offset", "codepoint", "char", "len", "codes", "charpattern", - "coroutine", "table", "io", "os", "string", "utf8", "bit32", "math", "debug", "package" - }; - for (auto& k : identifiers) - { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } - - langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("\\\'[^\\\']*\\\'", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); - - langDef.mCommentStart = "--[["; - langDef.mCommentEnd = "]]"; - langDef.mSingleLineComment = "--"; - - langDef.mCaseSensitive = true; - langDef.mAutoIndentation = false; - - langDef.mName = "Lua"; - - inited = true; - } - return langDef; -} diff --git a/source/imgui/TextEditor.h b/source/imgui/TextEditor.h index bd52e13..e0e86cd 100644 --- a/source/imgui/TextEditor.h +++ b/source/imgui/TextEditor.h @@ -174,12 +174,6 @@ public: } static const LanguageDefinition& CPlusPlus(); - static const LanguageDefinition& HLSL(); - static const LanguageDefinition& GLSL(); - static const LanguageDefinition& C(); - static const LanguageDefinition& SQL(); - static const LanguageDefinition& AngelScript(); - static const LanguageDefinition& Lua(); }; TextEditor(); diff --git a/source/imgui/imgui.h b/source/imgui/imgui.h index 182c46a..8d2b376 100644 --- a/source/imgui/imgui.h +++ b/source/imgui/imgui.h @@ -243,9 +243,9 @@ typedef unsigned long long ImU64; // 64-bit unsigned integer (post C++11) IM_MSVC_RUNTIME_CHECKS_OFF struct ImVec2 { - float x, y; - ImVec2() { x = y = 0.0f; } - ImVec2(float _x, float _y) { x = _x; y = _y; } + float x = 0.f, y = 0.f; + constexpr ImVec2() = default; + constexpr ImVec2(float _x, float _y) : x(_x), y(_y) {} float operator[] (size_t idx) const { IM_ASSERT(idx <= 1); return (&x)[idx]; } // We very rarely use this [] operator, the assert overhead is fine. float& operator[] (size_t idx) { IM_ASSERT(idx <= 1); return (&x)[idx]; } // We very rarely use this [] operator, the assert overhead is fine. #ifdef IM_VEC2_CLASS_EXTRA diff --git a/source/logview.cpp b/source/logview.cpp new file mode 100644 index 0000000..5a771bf --- /dev/null +++ b/source/logview.cpp @@ -0,0 +1,82 @@ +#include "logview.h" + +LogView::LogView() +{ + Clear(); +} + +void LogView::Clear() +{ + Buf.clear(); + LineOffsets.clear(); + LineOffsets.push_back(0); + updated = false; +} + +void LogView::AddLog(const std::string& str) +{ + AddLog(str.c_str()); +} + +void LogView::AddLog(const char* fmt, ...) +{ + int old_size = Buf.size(); + va_list args; + va_start(args, fmt); + Buf.appendfv(fmt, args); + Buf.appendfv("\n", args); + va_end(args); + for (int new_size = Buf.size(); old_size < new_size; old_size++) + if (Buf[old_size] == '\n') + LineOffsets.push_back(old_size + 1); + updated = true; +} + +void LogView::Draw(const char* title, bool* p_open, ImGuiWindowFlags flags) +{ + if (!ImGui::Begin(title, p_open, flags)) + { + ImGui::End(); + return; + } + + ImGui::Text("Log "); + ImGui::SameLine(ImGui::GetWindowWidth() - 120); + if (ImGui::Button("Clear")) + Clear(); + ImGui::SameLine(); + if (ImGui::Button("Copy")) + ImGui::LogToClipboard(); + ImGui::Separator(); + ImGui::BeginChild("scrolling", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar); + + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + const char* buf = Buf.begin(); + const char* buf_end = Buf.end(); + + ImGuiListClipper clipper; + clipper.Begin(LineOffsets.Size); + + while (clipper.Step()) + { + for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++) + { + const char* line_start = buf + LineOffsets[line_no]; + const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end; + ImGui::TextUnformatted(line_start, line_end); + } + } + clipper.End(); + + ImGui::PopStyleVar(); + + if (updated) { + ImGui::SetScrollHereY(); + updated = false; + } + + ImGui::EndChild(); + ImGui::End(); +} + diff --git a/source/logview.h b/source/logview.h index 4ab2c94..3c6acf1 100644 --- a/source/logview.h +++ b/source/logview.h @@ -3,87 +3,23 @@ #include +#include "imgui.h" + // Adapted from ExampleAppLog from imgui_demo.cpp class LogView { public: - LogView() - { - Clear(); - } - - void Clear() - { - Buf.clear(); - LineOffsets.clear(); - LineOffsets.push_back(0); - } - - void AddLog(const std::string& str) - { - AddLog(str.c_str()); - } - - void AddLog(const char* fmt, ...) IM_FMTARGS(2) - { - int old_size = Buf.size(); - va_list args; - va_start(args, fmt); - Buf.appendfv(fmt, args); - Buf.appendfv("\n", args); - va_end(args); - for (int new_size = Buf.size(); old_size < new_size; old_size++) - if (Buf[old_size] == '\n') - LineOffsets.push_back(old_size + 1); - } - - void Draw(const char* title, bool* p_open = NULL, ImGuiWindowFlags flags = 0) - { - if (!ImGui::Begin(title, p_open, flags)) - { - ImGui::End(); - return; - } - - ImGui::Text("Log "); - ImGui::SameLine(); - if (ImGui::Button("Clear")) - Clear(); - ImGui::SameLine(); - if (ImGui::Button("Copy")) - ImGui::LogToClipboard(); - ImGui::Separator(); - ImGui::BeginChild("scrolling", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar); - - - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); - const char* buf = Buf.begin(); - const char* buf_end = Buf.end(); - ImGuiListClipper clipper; - clipper.Begin(LineOffsets.Size); - while (clipper.Step()) - { - for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++) - { - const char* line_start = buf + LineOffsets[line_no]; - const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end; - ImGui::TextUnformatted(line_start, line_end); - } - } - clipper.End(); - - ImGui::PopStyleVar(); - - if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) - ImGui::SetScrollHereY(1.0f); + LogView(); - ImGui::EndChild(); - ImGui::End(); - } + void Clear(); + void AddLog(const std::string& str); + void AddLog(const char* fmt, ...) IM_FMTARGS(2); + void Draw(const char* title, bool* p_open = NULL, ImGuiWindowFlags flags = 0); private: ImGuiTextBuffer Buf; ImVector LineOffsets; // Index to lines offset. We maintain this with AddLog() calls. + bool updated; }; #endif // LOGVIEW_H diff --git a/source/main.cpp b/source/main.cpp index ce1ea20..aa9f7c4 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -13,104 +13,138 @@ #include "backends/imgui_impl_sdl.h" #include "backends/imgui_impl_opengl2.h" -#include "config.h" #include "logview.h" #include "stmdsp.hpp" +#include +#include +#include #include +#include + +void codeEditorInit(); +void codeRenderMenu(); +void codeRenderToolbar(); +void codeRenderWidgets(const ImVec2& size); +void deviceRenderDraw(); +void deviceRenderMenu(); +void deviceRenderToolbar(); +void deviceRenderWidgets(); +void fileRenderMenu(); +void fileRenderDialog(); +void fileInit(); +bool guiInitialize(); +bool guiHandleEvents(); +void guiShutdown(); +void guiRender(); +void helpRenderMenu(); +void helpRenderDialog(); + +void log(const std::string& str); -// Externs -extern ImFont *fontSans; -extern ImFont *fontMono; +static LogView logView; +static ImFont *fontSans = nullptr; +static ImFont *fontMono = nullptr; -extern bool guiInitialize(); -extern void guiHandleEvents(bool& done); -extern void guiShutdown(); -extern void guiRender(void (*func)()); +template +static void renderWindow(); -extern void fileRenderMenu(); -extern void fileRenderDialog(); -extern void fileScanTemplates(); +int main(int, char **) +{ + if (!guiInitialize()) + return -1; -extern void codeEditorInit(); -extern void codeRenderMenu(); -extern void codeRenderToolbar(); -extern void codeRenderWidgets(); + auto& io = ImGui::GetIO(); + fontSans = io.Fonts->AddFontFromFileTTF("fonts/Roboto-Regular.ttf", 20); + fontMono = io.Fonts->AddFontFromFileTTF("fonts/RobotoMono-Regular.ttf", 20); + if (fontSans == nullptr || fontMono == nullptr) { + std::cout << "Failed to load fonts!" << std::endl; + return -1; + } -extern void deviceRenderDraw(); -extern void deviceRenderMenu(); -extern void deviceRenderToolbar(); -extern void deviceRenderWidgets(); + codeEditorInit(); + fileInit(); -// Globals that live here -bool done = false; -stmdsp::device *m_device = nullptr; + renderWindow(); -static LogView logView; + while (1) { + constexpr std::chrono::duration fpsDelay (1. / 60.); + const auto endTime = std::chrono::steady_clock::now() + fpsDelay; + + const bool isDone = guiHandleEvents(); + if (!isDone) { + renderWindow(); + std::this_thread::sleep_until(endTime); + } else { + break; + } + } + + guiShutdown(); + return 0; +} void log(const std::string& str) { logView.AddLog(str); } -int main(int, char **) +template +void renderWindow() { - if (!guiInitialize()) - return -1; + // Start the new window frame and render the menu bar. + ImGui_ImplOpenGL2_NewFrame(); + ImGui_ImplSDL2_NewFrame(); + ImGui::NewFrame(); + + if (ImGui::BeginMainMenuBar()) { + fileRenderMenu(); + deviceRenderMenu(); + codeRenderMenu(); + helpRenderMenu(); + + ImGui::EndMainMenuBar(); + } - fileScanTemplates(); - codeEditorInit(); + if constexpr (first) { + ImGui::SetNextWindowSize({550, 440}); + } - while (!done) { - guiHandleEvents(done); + constexpr int MainTopMargin = 22; + const auto& displaySize = ImGui::GetIO().DisplaySize; - // Start the new window frame and render the menu bar. - ImGui_ImplOpenGL2_NewFrame(); - ImGui_ImplSDL2_NewFrame(); - ImGui::NewFrame(); + ImGui::SetNextWindowPos({0, MainTopMargin}); + ImGui::SetNextWindowSizeConstraints({displaySize.x, 150}, {displaySize.x, displaySize.y - 150}); + ImGui::Begin("main", nullptr, + ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoBringToFrontOnFocus); - if (ImGui::BeginMainMenuBar()) { - fileRenderMenu(); - deviceRenderMenu(); - codeRenderMenu(); - ImGui::EndMainMenuBar(); - } + const float mainWindowHeight = ImGui::GetWindowHeight(); - // Begin the main view which the controls will be drawn onto. - ImGui::SetNextWindowPos({0, 22}); - ImGui::SetNextWindowSize({WINDOW_WIDTH, WINDOW_HEIGHT - 22 - 200}); - ImGui::Begin("main", nullptr, - ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration | - ImGuiWindowFlags_NoBringToFrontOnFocus); - - // Render main controls (order is important). - ImGui::PushFont(fontSans); - codeRenderToolbar(); - deviceRenderToolbar(); - fileRenderDialog(); - deviceRenderWidgets(); - ImGui::PopFont(); - - ImGui::PushFont(fontMono); - codeRenderWidgets(); - ImGui::SetNextWindowPos({0, WINDOW_HEIGHT - 200}); - ImGui::SetNextWindowSize({WINDOW_WIDTH, 200}); - logView.Draw("log", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBringToFrontOnFocus); - ImGui::PopFont(); - - // Finish main view rendering. - ImGui::End(); - - deviceRenderDraw(); - - // Draw everything to the screen. - ImGui::Render(); - guiRender([] { - ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData()); - }); - } + ImGui::PushFont(fontSans); + codeRenderToolbar(); + deviceRenderToolbar(); + fileRenderDialog(); + helpRenderDialog(); + deviceRenderWidgets(); + ImGui::PopFont(); - guiShutdown(); - return 0; + ImGui::PushFont(fontMono); + codeRenderWidgets({displaySize.x - 16, mainWindowHeight - MainTopMargin - 24}); + ImGui::PopFont(); + + ImGui::End(); + + // The log window is kept separate from "main" to support panel resizing. + ImGui::PushFont(fontMono); + ImGui::SetNextWindowPos({0, mainWindowHeight + MainTopMargin}); + ImGui::SetNextWindowSize({displaySize.x, displaySize.y - mainWindowHeight - MainTopMargin}); + logView.Draw("log", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBringToFrontOnFocus); + ImGui::PopFont(); + + deviceRenderDraw(); + + // Draw everything to the screen. + guiRender(); } diff --git a/source/stmdsp/stmdsp.cpp b/source/stmdsp/stmdsp.cpp index b3fc8c3..294e98f 100644 --- a/source/stmdsp/stmdsp.cpp +++ b/source/stmdsp/stmdsp.cpp @@ -13,120 +13,186 @@ #include +#include + +extern void log(const std::string& str); + +std::array sampleRateInts {{ + 8'000, + 16'000, + 20'000, + 32'000, + 48'000, + 96'000 +}}; + namespace stmdsp { - std::list& scanner::scan() + const std::forward_list& scanner::scan() { auto devices = serial::list_ports(); - for (auto& device : devices) { - if (device.hardware_id.find(STMDSP_USB_ID) != std::string::npos) - m_available_devices.emplace_front(device.port); - } - + auto foundDevicesEnd = std::remove_if( + devices.begin(), devices.end(), + [](const auto& dev) { + return dev.hardware_id.find(STMDSP_USB_ID) == std::string::npos; + }); + std::transform(devices.begin(), foundDevicesEnd, + std::front_inserter(m_available_devices), + [](const auto& dev) { return dev.port; }); return m_available_devices; } - device::device(const std::string& file) : - m_serial(file, 1000000/*230400*/, serial::Timeout::simpleTimeout(50)) + device::device(const std::string& file) { - if (m_serial.isOpen()) { - m_serial.flush(); - m_serial.write("i"); - if (auto id = m_serial.read(7); id.starts_with("stmdsp")) { - if (id.back() == 'h') - m_platform = platform::H7; - else if (id.back() == 'l') - m_platform = platform::L4; - else - m_serial.close(); - } else { - m_serial.close(); - } + // This could throw! + // Note: Windows needs a not-simple, positive timeout like this to + // ensure that reads block. + m_serial.reset(new serial::Serial(file, 921'600 /*8'000'000*/, serial::Timeout(1000, 1000, 1, 1000, 1))); + + // Test the ID command. + m_serial->flush(); + m_serial->write("i"); + auto id = m_serial->read(7); + + if (id.starts_with("stmdsp")) { + if (id.back() == 'h') + m_platform = platform::H7; + else if (id.back() == 'l') + m_platform = platform::L4; + else + m_serial.release(); + } else { + m_serial.release(); } } - void device::continuous_set_buffer_size(unsigned int size) { + device::~device() + { + disconnect(); + } + + bool device::connected() { + if (m_serial && !m_serial->isOpen()) + m_serial.release(); + + return m_serial ? true : false; + } + + void device::disconnect() { + if (m_serial) + m_serial.release(); + } + + bool device::try_command(std::basic_string cmd) { + bool success = false; + if (connected()) { - m_buffer_size = size; + try { + std::scoped_lock lock (m_lock); + m_serial->write(cmd.data(), cmd.size()); + success = true; + } catch (...) { + handle_disconnect(); + } + } - uint8_t request[3] = { + return success; + } + + bool device::try_read(std::basic_string cmd, uint8_t *dest, unsigned int dest_size) { + bool success = false; + + if (connected() && dest && dest_size > 0) { + try { + std::scoped_lock lock (m_lock); + m_serial->write(cmd.data(), cmd.size()); + m_serial->read(dest, dest_size); + success = true; + } catch (...) { + handle_disconnect(); + } + } + + return success; + } + + void device::continuous_set_buffer_size(unsigned int size) { + if (try_command({ 'B', static_cast(size), - static_cast(size >> 8) - }; - m_serial.write(request, 3); + static_cast(size >> 8)})) + { + m_buffer_size = size; } } - void device::set_sample_rate(unsigned int id) { - if (connected()) { - uint8_t request[2] = { + 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(id) - }; - m_serial.write(request, 2); + static_cast(i) + }); } } unsigned int device::get_sample_rate() { - unsigned char result = 0xFF; - - if (connected()) { - uint8_t request[2] = { - 'r', 0xFF - }; - m_serial.write(request, 2); - m_serial.read(&result, 1); + if (!is_running()) { + uint8_t result = 0xFF; + if (try_read({'r', 0xFF}, &result, 1)) + m_sample_rate = result; } - return result; + return m_sample_rate < sampleRateInts.size() ? + sampleRateInts[m_sample_rate] : + 0; } void device::continuous_start() { - if (connected()) { - m_serial.write("R"); + if (try_command({'R'})) m_is_running = true; - } } - void device::continuous_start_measure() { - if (connected()) { - m_serial.write("M"); - m_is_running = true; - } + void device::measurement_start() { + try_command({'M'}); } - uint32_t device::continuous_start_get_measurement() { + uint32_t device::measurement_read() { uint32_t count = 0; - if (connected()) { - m_serial.write("m"); - m_serial.read(reinterpret_cast(&count), sizeof(uint32_t)); - } - + try_read({'m'}, reinterpret_cast(&count), sizeof(uint32_t)); return count / 2; } std::vector device::continuous_read() { if (connected()) { - m_serial.write("s"); - unsigned char sizebytes[2]; - m_serial.read(sizebytes, 2); - unsigned int size = sizebytes[0] | (sizebytes[1] << 8); - if (size > 0) { - std::vector data (size); - unsigned int total = size * sizeof(adcsample_t); - unsigned int offset = 0; - - while (total > 512) { - m_serial.read(reinterpret_cast(&data[0]) + offset, 512); - m_serial.write("n"); - offset += 512; - total -= 512; - } - m_serial.read(reinterpret_cast(&data[0]) + offset, total); - m_serial.write("n"); - return data; + try { + m_serial->write("s"); + unsigned char sizebytes[2]; + m_serial->read(sizebytes, 2); + unsigned int size = sizebytes[0] | (sizebytes[1] << 8); + if (size > 0) { + std::vector data (size); + unsigned int total = size * sizeof(adcsample_t); + unsigned int offset = 0; + while (total > 512) { + m_serial->read(reinterpret_cast(&data[0]) + offset, 512); + m_serial->write("n"); + offset += 512; + total -= 512; + } + m_serial->read(reinterpret_cast(&data[0]) + offset, total); + m_serial->write("n"); + return data; + + } + } catch (...) { + handle_disconnect(); } } @@ -135,25 +201,29 @@ namespace stmdsp std::vector device::continuous_read_input() { if (connected()) { - m_serial.write("t"); - unsigned char sizebytes[2]; - m_serial.read(sizebytes, 2); - unsigned int size = sizebytes[0] | (sizebytes[1] << 8); - if (size > 0) { - std::vector data (size); - unsigned int total = size * sizeof(adcsample_t); - unsigned int offset = 0; - - while (total > 512) { - m_serial.read(reinterpret_cast(&data[0]) + offset, 512); - m_serial.write("n"); - offset += 512; - total -= 512; - } - m_serial.read(reinterpret_cast(&data[0]) + offset, total); - m_serial.write("n"); - return data; + try { + m_serial->write("t"); + unsigned char sizebytes[2]; + m_serial->read(sizebytes, 2); + unsigned int size = sizebytes[0] | (sizebytes[1] << 8); + if (size > 0) { + std::vector data (size); + unsigned int total = size * sizeof(adcsample_t); + unsigned int offset = 0; + while (total > 512) { + m_serial->read(reinterpret_cast(&data[0]) + offset, 512); + m_serial->write("n"); + offset += 512; + total -= 512; + } + m_serial->read(reinterpret_cast(&data[0]) + offset, total); + m_serial->write("n"); + return data; + + } + } catch (...) { + handle_disconnect(); } } @@ -161,37 +231,51 @@ namespace stmdsp } void device::continuous_stop() { - if (connected()) { - m_serial.write("S"); + if (try_command({'S'})) m_is_running = false; - } } - void device::siggen_upload(dacsample_t *buffer, unsigned int size) { + bool device::siggen_upload(dacsample_t *buffer, unsigned int size) { if (connected()) { uint8_t request[3] = { 'D', static_cast(size), static_cast(size >> 8) }; - m_serial.write(request, 3); - m_serial.write((uint8_t *)buffer, size * sizeof(dacsample_t)); + if (!m_is_siggening) { + try { + m_serial->write(request, 3); + m_serial->write((uint8_t *)buffer, size * sizeof(dacsample_t)); + } catch (...) { + handle_disconnect(); + } + } else { + try { + m_serial->write(request, 3); + if (m_serial->read(1)[0] == 0) + return false; + else + m_serial->write((uint8_t *)buffer, size * sizeof(dacsample_t)); + } catch (...) { + handle_disconnect(); + } + } + + return true; + } else { + return false; } } void device::siggen_start() { - if (connected()) { + if (try_command({'W'})) m_is_siggening = true; - m_serial.write("W"); - } } void device::siggen_stop() { - if (connected()) { + if (try_command({'w'})) m_is_siggening = false; - m_serial.write("w"); - } } void device::upload_filter(unsigned char *buffer, size_t size) { @@ -201,14 +285,46 @@ namespace stmdsp static_cast(size), static_cast(size >> 8) }; - m_serial.write(request, 3); - m_serial.write(buffer, size); + try { + m_serial->write(request, 3); + m_serial->write(buffer, size); + } catch (...) { + handle_disconnect(); + } } } void device::unload_filter() { - if (connected()) - m_serial.write("e"); + try_command({'e'}); + } + + std::pair device::get_status() { + std::pair ret; + + unsigned char buf[2]; + if (try_read({'I'}, buf, 2)) { + ret = { + static_cast(buf[0]), + static_cast(buf[1]) + }; + + bool running = ret.first == RunStatus::Running; + if (m_is_running != running) + m_is_running = running; + } else if (m_disconnect_error_flag) { + m_disconnect_error_flag = false; + return {RunStatus::Idle, Error::GUIDisconnect}; + } + + return ret; } -} + + void device::handle_disconnect() + { + m_disconnect_error_flag = true; + m_serial.release(); + log("Lost connection!"); + } +} // namespace stmdsp + diff --git a/source/stmdsp/stmdsp.hpp b/source/stmdsp/stmdsp.hpp index 8da98f2..efed8a3 100644 --- a/source/stmdsp/stmdsp.hpp +++ b/source/stmdsp/stmdsp.hpp @@ -12,17 +12,89 @@ #ifndef STMDSP_HPP_ #define STMDSP_HPP_ -#include -#include #include + +#include +#include +#include +#include #include +#include namespace stmdsp { + /** + * The largest possible size of an ADC or DAC sample buffer, as a sample count. + * Maximum byte size would be `SAMPLES_MAX * sizeof(XXXsample_t)`. + */ constexpr unsigned int SAMPLES_MAX = 4096; + /** + * ADC samples on all platforms are stored as 16-bit unsigned integers. + */ + using adcsample_t = uint16_t; + /** + * DAC samples on all platforms are stored as 16-bit unsigned integers. + */ + using dacsample_t = uint16_t; + + /** + * List of all available platforms. + * Note that some platforms in this list may not have complete support. + */ + enum class platform { + Unknown, + H7, /* Some feature support */ + L4, /* Complete feature support */ + G4 /* Unsupported, but planned */ + }; + + /** + * Run status states, valued to match what the stmdsp firmware reports. + */ + enum class RunStatus : char { + Idle = '1', /* Device ready for commands or execution. */ + Running, /* Device currently executing its algorithm. */ + Recovering /* Device recovering from fault caused by algorithm. */ + }; + + /** + * Error messages that are reported by the firmware. + */ + enum class Error : char { + None = 0, + BadParam, /* An invalid parameter was passed for a command. */ + BadParamSize, /* An invaild param. size was given for a command. */ + BadUserCodeLoad, /* Device failed to load the given algorithm. */ + BadUserCodeSize, /* The given algorithm is too large for the device. */ + NotIdle, /* An idle-only command was received while not Idle. */ + ConversionAborted, /* A conversion was aborted due to a fault. */ + NotRunning, /* A running-only command was received while not Running. */ + + GUIDisconnect = 100 /* The GUI lost connection with the device. */ + }; + + /** + * Provides functionality to scan the system for stmdsp devices. + * A list of devices is returned, though the GUI only interacts with one + * device at a time. + */ class scanner { + public: + /** + * Scans for connected devices, returning a list of ports with + * connected stmdsp devices. + */ + const std::forward_list& scan(); + + /** + * Retrieves the results of the last scan(). + */ + const std::forward_list& devices() const noexcept { + return m_available_devices; + } + private: constexpr static const char *STMDSP_USB_ID = #ifndef STMDSP_WIN32 @@ -31,54 +103,39 @@ namespace stmdsp "USB\\VID_0483&PID_5740"; #endif - public: - std::list& scan(); - auto& devices() { - return m_available_devices; - } - - private: - std::list m_available_devices; - }; - - using adcsample_t = uint16_t; - using dacsample_t = uint16_t; - - enum class platform { - Unknown, - H7, - L4, - G4 + std::forward_list m_available_devices; }; class device { public: device(const std::string& file); + ~device(); - ~device() { - m_serial.close(); - } - - bool connected() { - return m_serial.isOpen(); - } + bool connected(); + void disconnect(); auto get_platform() const { return m_platform; } + 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(); - void continuous_start_measure(); - uint32_t continuous_start_get_measurement(); + void continuous_stop(); + + void measurement_start(); + uint32_t measurement_read(); + std::vector continuous_read(); std::vector continuous_read_input(); - void continuous_stop(); - void siggen_upload(dacsample_t *buffer, unsigned int size); + bool siggen_upload(dacsample_t *buffer, unsigned int size); void siggen_start(); void siggen_stop(); + bool is_siggening() const { return m_is_siggening; } bool is_running() const { return m_is_running; } @@ -86,12 +143,22 @@ namespace stmdsp void upload_filter(unsigned char *buffer, size_t size); void unload_filter(); + std::pair get_status(); + private: - serial::Serial m_serial; + std::unique_ptr m_serial; platform m_platform = platform::Unknown; unsigned int m_buffer_size = SAMPLES_MAX; + unsigned int m_sample_rate = 0; bool m_is_siggening = false; bool m_is_running = false; + bool m_disconnect_error_flag = false; + + std::mutex m_lock; + + bool try_command(std::basic_string data); + bool try_read(std::basic_string cmd, uint8_t *dest, unsigned int dest_size); + void handle_disconnect(); }; } diff --git a/source/stmdsp/stmdsp_code.hpp b/source/stmdsp/stmdsp_code.hpp index 6850459..7ba0ed2 100644 --- a/source/stmdsp/stmdsp_code.hpp +++ b/source/stmdsp/stmdsp_code.hpp @@ -118,67 +118,67 @@ return s; )cpp"; static std::string file_header_l4 = R"cpp( #include -#include using Sample = uint16_t; -using Samples = std::span; +using Samples = Sample[$0]; +constexpr unsigned int SIZE = $0; Sample *process_data(Samples samples); extern "C" void process_data_entry() { Sample *samples; asm("mov %0, r0" : "=r" (samples)); - process_data(Samples(samples, $0)); + process_data(samples); } -static float PI = 3.14159265358979L; +static inline float PI = 3.14159265358979L; __attribute__((naked)) -auto sin(float x) { -asm("vmov.f32 r1, s0;" +static inline auto sin(float x) { + asm("vmov.f32 r1, s0;" "eor r0, r0;" "svc 1;" "vmov.f32 s0, r1;" "bx lr"); -return 0; + return 0; } __attribute__((naked)) -auto cos(float x) { -asm("vmov.f32 r1, s0;" +static inline auto cos(float x) { + asm("vmov.f32 r1, s0;" "mov r0, #1;" "svc 1;" "vmov.f32 s0, r1;" "bx lr"); -return 0; + return 0; } __attribute__((naked)) -auto tan(float x) { -asm("vmov.f32 r1, s0;" +static inline auto tan(float x) { + asm("vmov.f32 r1, s0;" "mov r0, #2;" "svc 1;" "vmov.f32 s0, r1;" "bx lr"); -return 0; + return 0; } __attribute__((naked)) -auto sqrt(float) { -asm("vsqrt.f32 s0, s0; bx lr"); -return 0; +static inline auto sqrt(float) { + asm("vsqrt.f32 s0, s0; bx lr"); + return 0; } -auto readpot1() { -Sample s; -asm("push {r4-r11}; eor r0, r0; svc 3; mov %0, r0; pop {r4-r11}" : "=r"(s)); -return s; +static inline auto param1() { + Sample s; + asm("eor r0, r0; svc 3; mov %0, r0" : "=r" (s) :: "r0"); + return s; } -auto readpot2() { -Sample s; -asm("push {r4-r11}; mov r0, #1; svc 3; mov %0, r0; pop {r4-r11}" : "=r"(s)); -return s; +static inline auto param2() { + Sample s; + asm("mov r0, #1; svc 3; mov %0, r0" : "=r" (s) :: "r0"); + return s; } -//void puts(const char *s) { -// 's' will already be in r0. -//asm("push {r4-r6}; svc 4; pop {r4-r6}"); +//static inline void puts(const char *s) { +// // 's' will already be in r0. +// asm("push {r4-r6}; svc 4; pop {r4-r6}"); //} // End stmdspgui header code @@ -187,9 +187,9 @@ return s; static std::string file_content = -R"cpp(Sample *process_data(Samples samples) +R"cpp(Sample* process_data(Samples samples) { - return samples.data(); + return samples; } )cpp"; diff --git a/source/wav.hpp b/source/wav.hpp index 9ff06e5..e20776a 100644 --- a/source/wav.hpp +++ b/source/wav.hpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include namespace wav { @@ -43,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; @@ -64,31 +66,30 @@ namespace wav file.read(reinterpret_cast(&d), sizeof(wav::data)); if (!d.valid()) return; - m_data = new char[d.size + 4096 - (d.size % 4096)]; - m_size = d.size; - file.read(m_data, d.size); + m_data.resize(d.size / sizeof(int16_t)); + m_next = m_data.begin(); + file.read(reinterpret_cast(m_data.data()), d.size); } } clip() = default; bool valid() const { - return m_data != nullptr && m_size > 0; + return !m_data.empty(); } - auto data() const { - return m_data; + const int16_t *data() const { + return m_data.data(); } - auto next(unsigned int chunksize = 3000) { - if (m_pos == m_size) { - m_pos = 0; + void next(int16_t *buf, unsigned int size) { + for (unsigned int i = 0; i < size; ++i) { + if (m_next == m_data.end()) + m_next = m_data.begin(); + else + *buf++ = *m_next++; } - auto ret = m_data + m_pos; - m_pos = std::min(m_pos + chunksize, m_size); - return ret; } private: - char *m_data = nullptr; - uint32_t m_size = 0; - uint32_t m_pos = 0; + std::vector m_data; + decltype(m_data.begin()) m_next; }; } diff --git a/templates/6_iir_test.cpp b/templates/6_iir_test.cpp deleted file mode 100644 index 116a680..0000000 --- a/templates/6_iir_test.cpp +++ /dev/null @@ -1,13 +0,0 @@ -Sample *process_data(Samples samples) -{ - constexpr float alpha = 0.7; - - static Sample prev = 2048; - - samples[0] = (1 - alpha) * samples[0] + alpha * prev; - for (unsigned int i = 1; i < samples.size(); i++) - samples[i] = (1 - alpha) * samples[i] + alpha * samples[i - 1]; - prev = samples[samples.size() - 1]; - - return samples.data(); -}