Merge pull request 'devel: Ready for pre-release' (#1) from devel into master

Reviewed-on: #1
master v0.1
Clyne 2 years ago
commit 5902a67796

4
.gitignore vendored

@ -1,5 +1,9 @@
build/
openocd/
imgui.ini
stmdspgui
stmdspgui.exe
perf.data*
*.o
*.dll
.*

@ -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)

@ -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 $@

@ -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];

@ -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;
}

@ -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;
}

@ -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)

@ -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;
}

@ -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;
}

@ -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;
}

@ -0,0 +1,2 @@
source [find interface/stlink.cfg]
source [find target/stm32l4x.cfg]

@ -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 <https://www.gnu.org/licenses/>.
*/
#ifndef CIRCULAR_HPP
#define CIRCULAR_HPP
#include <iterator>
template<template<typename> class Container, typename T>
class CircularBuffer
{
public:
CircularBuffer(Container<T>& 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<T>::iterator m_begin;
Container<T>::iterator m_end;
Container<T>::iterator m_current;
};
#endif // CIRCULAR_HPP

@ -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 <https://www.gnu.org/licenses/>.
*/
#include "imgui.h"
#include "backends/imgui_impl_sdl.h"
#include "backends/imgui_impl_opengl2.h"
#include "TextEditor.h"
#include "config.h"
#include "stmdsp.hpp"
#include "stmdsp_code.hpp"
@ -22,87 +16,63 @@
#include <filesystem>
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
extern stmdsp::device *m_device;
extern void log(const std::string& str);
extern std::shared_ptr<stmdsp::device> 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();
}
};

@ -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

@ -11,455 +11,353 @@
#include "stmdsp.hpp"
#include "circular.hpp"
#include "imgui.h"
#include "ImGuiFileDialog.h"
#include "wav.hpp"
#include <array>
#include <cctype>
#include <charconv>
#include <cmath>
#include <deque>
#include <fstream>
#include <functional>
#include <iostream>
#include <memory>
#include <mutex>
#include <string>
#include <string_view>
#include <thread>
#include <vector>
extern std::string tempFileName;
extern stmdsp::device *m_device;
extern void log(const std::string& str);
extern std::vector<stmdsp::dacsample_t> deviceGenLoadFormulaEval(const std::string&);
extern std::ifstream compileOpenBinaryFile();
extern void deviceRenderDisconnect();
extern std::vector<stmdsp::dacsample_t> 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<stmdsp::device> m_device;
static std::mutex mutexDrawSamples;
static std::vector<stmdsp::dacsample_t> drawSamplesBuf;
static std::vector<stmdsp::dacsample_t> drawSamplesBuf2;
static std::timed_mutex mutexDrawSamples;
static std::timed_mutex mutexDeviceLoad;
static std::ofstream logSamplesFile;
static wav::clip wavOutput;
static std::deque<stmdsp::dacsample_t> drawSamplesQueue;
static std::deque<stmdsp::dacsample_t> drawSamplesInputQueue;
static 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<stmdsp::device> 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<stmdsp::dacsample_t> tryReceiveChunk(
std::shared_ptr<stmdsp::device> 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<double> getBufferPeriod(
std::shared_ptr<stmdsp::device> 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<double>(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<stmdsp::device> 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<uint16_t *>(wavOutput.next(bsize));
for (auto i = 0u; i < bsize; ++i)
*dst++ = *src++ / 16 + 2048;
m_device->siggen_upload(wavBuf, bsize);
}
std::unique_lock<std::timed_mutex> 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<uint16_t *>(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<stmdsp::dacsample_t> 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<stmdsp::device> device)
{
if (!device)
return;
delete[] siggenBuffer;
ImGui::CloseCurrentPopup();
}
const auto delay = getBufferPeriod(device);
const auto uploadDelay = getBufferPeriod(device, 0.001);
ImGui::EndPopup();
}
std::vector<stmdsp::dacsample_t> 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<int16_t> 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<stmdsp::dacsample_t>(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<stmdsp::device> 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<float>(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<stmdsp::dacsample_t> 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<stmdsp::dacsample_t>& queue,
CircularBuffer<std::vector, stmdsp::dacsample_t>& 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<std::size_t>(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<std::vector, stmdsp::dacsample_t>& circ)
{
return pullFromQueue(drawSamplesQueue, circ);
}
std::size_t pullFromInputDrawQueue(
CircularBuffer<std::vector, stmdsp::dacsample_t>& circ)
{
return pullFromQueue(drawSamplesInputQueue, circ);
}

@ -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 <https://www.gnu.org/licenses/>.
*/
#include "stmdsp.hpp"
#include "exprtk.hpp"
#include <algorithm>
#include <random>
#include <string_view>
#include <vector>
std::vector<stmdsp::dacsample_t> 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<stmdsp::dacsample_t> deviceGenLoadFormulaEval(const std::string& formulaString)
{
double x = 0;
exprtk::symbol_table<double> symbol_table;
exprtk::function_compositor<double> compositor (symbol_table);
exprtk::expression<double> expression;
exprtk::parser<double> 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<double>(l, h)(randomDevice);
});
compositor.add(exprtk::function_compositor<double>::function()
.name("square")
.var("X")
.expression("ceil(sin(pi*X))"));
compositor.add(exprtk::function_compositor<double>::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<double>::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<stmdsp::dacsample_t>(std::min(s, 4095.));
};
std::vector<stmdsp::dacsample_t> samples (stmdsp::SAMPLES_MAX);
std::generate(samples.begin(), samples.end(), genFun);
return samples;
}
std::generate(samples.begin(), samples.end(),
[&] { ++x; return static_cast<stmdsp::dacsample_t>(expression.value()); });
#else // no formula support
return samples;
std::vector<stmdsp::dacsample_t> deviceGenLoadFormulaEval(const std::string&)
{
return {};
}
#endif // STMDSP_DISABLE_FORMULAS

@ -17,6 +17,7 @@
#include "stmdsp_code.hpp"
#include <algorithm>
#include <cstdlib>
#include <filesystem>
#include <fstream>
@ -25,6 +26,8 @@
#include <string>
#include <vector>
#include <SDL2/SDL.h>
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<std::filesystem::path> fileTemplateList;
static std::vector<std::filesystem::path> 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<std::filesystem::path> fileScanExamples()
{
const auto path = std::filesystem::current_path() / "examples";
const std::filesystem::recursive_directory_iterator rdi (path);
std::vector<std::filesystem::path> 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;
}
}

@ -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 <SDL2/SDL.h>
#include <SDL2/SDL_opengl.h>
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<int>(displaySize.x);
const int sizeY = static_cast<int>(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()

@ -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 <https://www.gnu.org/licenses/>.
*/
#include "imgui.h"
#include "backends/imgui_impl_sdl.h"
#include "backends/imgui_impl_opengl2.h"
#include "TextEditor.h"
#include <string>
extern void compileEditorCode(const std::string& code);
extern void disassembleCode();
TextEditor editor; // file.cpp
static std::string editorCompiled;
static void codeCompile();
static void codeDisassemble();
void codeEditorInit()
{
editor.SetLanguageDefinition(TextEditor::LanguageDefinition::CPlusPlus());
editor.SetPalette(TextEditor::GetLightPalette());
}
void codeRenderMenu()
{
if (ImGui::BeginMenu("Code")) {
if (ImGui::MenuItem("Compile"))
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();
}

@ -0,0 +1,418 @@
#include "circular.hpp"
#include "imgui.h"
#include "imgui_internal.h"
#include "ImGuiFileDialog.h"
#include "stmdsp.hpp"
#include <array>
#include <cstdio>
#include <memory>
#include <string>
#include <string_view>
// Used for status queries and buffer size configuration.
extern std::shared_ptr<stmdsp::device> m_device;
void deviceAlgorithmUnload();
void deviceAlgorithmUpload();
bool deviceConnect();
void deviceGenLoadFormula(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<std::vector, stmdsp::dacsample_t>& circ);
std::size_t pullFromInputDrawQueue(
CircularBuffer<std::vector, stmdsp::dacsample_t>& 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<unsigned int, 6> sampleRateInts;
for (const auto& r : sampleRateInts) {
const auto s = getSampleRatePreview(r);
if (ImGui::Selectable(s.c_str())) {
sampleRatePreview = s;
deviceSetSampleRate(r);
deviceUpdateDrawBufferSize(drawSamplesTimeframe);
}
}
ImGui::EndCombo();
}
if (!enable)
ImGui::PopDisabled();
}
void deviceRenderWidgets()
{
static std::string siggenInput (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<stmdsp::dacsample_t> buffer;
static std::vector<stmdsp::dacsample_t> 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<float>(yMinMax) / 4095.f));
ImGui::SameLine();
if (ImGui::Button(" - ", {30, 0})) {
yMinMax = std::max(63u, yMinMax >> 1);
}
ImGui::SameLine();
if (ImGui::Button(" + ", {30, 0})) {
yMinMax = std::min(4095u, (yMinMax << 1) | 1);
}
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<float>(buffer.size()) / size.x;
const float dx = std::ceil(size.x / static_cast<float>(buffer.size()));
ImVec2 pp = p0;
float i = 0;
while (pp.x < p0.x + size.x) {
unsigned int idx = i;
float n = std::clamp((buffer[idx] - 2048.) / yMinMax, -0.5, 0.5);
i += di;
ImVec2 next (pp.x + dx, p0.y + size.y * (0.5 - n));
drawList->AddLine(pp, next, ImGui::GetColorU32(IM_COL32(255, 0, 0, 255)));
pp = next;
}
if (drawSamplesInput) {
ImVec2 pp = p0;
float i = 0;
while (pp.x < p0.x + size.x) {
unsigned int idx = i;
float n = std::clamp((bufferInput[idx] - 2048.) / yMinMax, -0.5, 0.5);
i += di;
ImVec2 next (pp.x + dx, p0.y + size.y * (0.5 - n));
drawList->AddLine(pp, next, ImGui::GetColorU32(IM_COL32(0, 0, 255, 255)));
pp = next;
}
}
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();
}
}

@ -0,0 +1,113 @@
#include "imgui.h"
#include "ImGuiFileDialog.h"
#include <cstdlib>
#include <iostream>
#include <string>
#include <thread>
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();
}

@ -46,11 +46,11 @@ TextEditor::TextEditor()
, mHandleKeyboardInputs(true)
, mHandleMouseInputs(true)
, mIgnoreImGuiChild(false)
, mShowWhitespaces(true)
, mShowWhitespaces(false)
, mStartTime(std::chrono::duration_cast<std::chrono::milliseconds>(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<std::string, PaletteIndex>("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", 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<std::string, PaletteIndex>("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", 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<std::string, PaletteIndex>("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("\\\'[^\\\']*\\\'", PaletteIndex::String));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", 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<std::string, PaletteIndex>("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("\\'\\\\?[^\\']\\'", PaletteIndex::String));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", 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<std::string, PaletteIndex>("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("\\\'[^\\\']*\\\'", PaletteIndex::String));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier));
langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation));
langDef.mCommentStart = "--[[";
langDef.mCommentEnd = "]]";
langDef.mSingleLineComment = "--";
langDef.mCaseSensitive = true;
langDef.mAutoIndentation = false;
langDef.mName = "Lua";
inited = true;
}
return langDef;
}

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

@ -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

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

@ -3,87 +3,23 @@
#include <string>
#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<int> LineOffsets; // Index to lines offset. We maintain this with AddLog() calls.
bool updated;
};
#endif // LOGVIEW_H

@ -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 <chrono>
#include <cmath>
#include <iostream>
#include <string>
#include <thread>
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<bool first = false>
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<true>();
static LogView logView;
while (1) {
constexpr std::chrono::duration<double> 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<bool first>
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();
}

@ -13,120 +13,186 @@
#include <serial/serial.h>
#include <algorithm>
extern void log(const std::string& str);
std::array<unsigned int, 6> sampleRateInts {{
8'000,
16'000,
20'000,
32'000,
48'000,
96'000
}};
namespace stmdsp
{
std::list<std::string>& scanner::scan()
const std::forward_list<std::string>& 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<uint8_t> 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<uint8_t> 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<uint8_t>(size),
static_cast<uint8_t>(size >> 8)
};
m_serial.write(request, 3);
static_cast<uint8_t>(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<uint8_t>(id)
};
m_serial.write(request, 2);
static_cast<uint8_t>(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<uint8_t *>(&count), sizeof(uint32_t));
}
try_read({'m'}, reinterpret_cast<uint8_t *>(&count), sizeof(uint32_t));
return count / 2;
}
std::vector<adcsample_t> 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<adcsample_t> data (size);
unsigned int total = size * sizeof(adcsample_t);
unsigned int offset = 0;
while (total > 512) {
m_serial.read(reinterpret_cast<uint8_t *>(&data[0]) + offset, 512);
m_serial.write("n");
offset += 512;
total -= 512;
}
m_serial.read(reinterpret_cast<uint8_t *>(&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<adcsample_t> data (size);
unsigned int total = size * sizeof(adcsample_t);
unsigned int offset = 0;
while (total > 512) {
m_serial->read(reinterpret_cast<uint8_t *>(&data[0]) + offset, 512);
m_serial->write("n");
offset += 512;
total -= 512;
}
m_serial->read(reinterpret_cast<uint8_t *>(&data[0]) + offset, total);
m_serial->write("n");
return data;
}
} catch (...) {
handle_disconnect();
}
}
@ -135,25 +201,29 @@ namespace stmdsp
std::vector<adcsample_t> 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<adcsample_t> data (size);
unsigned int total = size * sizeof(adcsample_t);
unsigned int offset = 0;
while (total > 512) {
m_serial.read(reinterpret_cast<uint8_t *>(&data[0]) + offset, 512);
m_serial.write("n");
offset += 512;
total -= 512;
}
m_serial.read(reinterpret_cast<uint8_t *>(&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<adcsample_t> data (size);
unsigned int total = size * sizeof(adcsample_t);
unsigned int offset = 0;
while (total > 512) {
m_serial->read(reinterpret_cast<uint8_t *>(&data[0]) + offset, 512);
m_serial->write("n");
offset += 512;
total -= 512;
}
m_serial->read(reinterpret_cast<uint8_t *>(&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<uint8_t>(size),
static_cast<uint8_t>(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<uint8_t>(size),
static_cast<uint8_t>(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<RunStatus, Error> device::get_status() {
std::pair<RunStatus, Error> ret;
unsigned char buf[2];
if (try_read({'I'}, buf, 2)) {
ret = {
static_cast<RunStatus>(buf[0]),
static_cast<Error>(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

@ -12,17 +12,89 @@
#ifndef STMDSP_HPP_
#define STMDSP_HPP_
#include <cstdint>
#include <list>
#include <serial/serial.h>
#include <cstdint>
#include <forward_list>
#include <memory>
#include <mutex>
#include <string>
#include <tuple>
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<std::string>& scan();
/**
* Retrieves the results of the last scan().
*/
const std::forward_list<std::string>& 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<std::string>& scan();
auto& devices() {
return m_available_devices;
}
private:
std::list<std::string> m_available_devices;
};
using adcsample_t = uint16_t;
using dacsample_t = uint16_t;
enum class platform {
Unknown,
H7,
L4,
G4
std::forward_list<std::string> 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<adcsample_t> continuous_read();
std::vector<adcsample_t> 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<RunStatus, Error> get_status();
private:
serial::Serial m_serial;
std::unique_ptr<serial::Serial> 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<uint8_t> data);
bool try_read(std::basic_string<uint8_t> cmd, uint8_t *dest, unsigned int dest_size);
void handle_disconnect();
};
}

@ -118,67 +118,67 @@ return s;
)cpp";
static std::string file_header_l4 = R"cpp(
#include <cstdint>
#include <span>
using Sample = uint16_t;
using Samples = std::span<Sample, $0>;
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";

@ -4,6 +4,8 @@
#include <cstdint>
#include <cstring>
#include <fstream>
#include <string>
#include <vector>
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<char *>(&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<char *>(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<int16_t> m_data;
decltype(m_data.begin()) m_next;
};
}

@ -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();
}
Loading…
Cancel
Save