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