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();
-}