wip: major code refactor, split most gui from logic

pull/1/head
Clyne 3 years ago
parent f6318e3284
commit c76ba69fc9

@ -13,7 +13,8 @@ OUTPUT := stmdspgui
#CXXFLAGS := -std=c++20 -O2 \ #CXXFLAGS := -std=c++20 -O2 \
# -Isource -Isource/imgui -Isource/stmdsp -Isource/serial/include # -Isource -Isource/imgui -Isource/stmdsp -Isource/serial/include
CXXFLAGS := -std=c++20 -ggdb -O0 -g3 \ 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) all: $(OUTPUT)

@ -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 <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.hpp"
#include "stmdsp_code.hpp" #include "stmdsp_code.hpp"
@ -22,89 +5,30 @@
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <memory>
#include <string> #include <string>
extern std::shared_ptr<stmdsp::device> m_device; extern std::shared_ptr<stmdsp::device> m_device;
extern void log(const std::string& str); extern void log(const std::string& str);
TextEditor editor; // file.cpp
std::string tempFileName; // device.cpp std::string tempFileName; // device.cpp
static std::string editorCompiled;
static std::string newTempFileName(); static std::string newTempFileName();
static bool codeExecuteCommand(const std::string& command, const std::string& file); static bool codeExecuteCommand(
static void stringReplaceAll(std::string& str, const std::string& what, const std::string& with); const std::string& command,
static void compileEditorCode(); const std::string& file);
static void disassembleCode(); static void stringReplaceAll(
std::string& str,
void codeEditorInit() const std::string& what,
{ const std::string& with);
editor.SetLanguageDefinition(TextEditor::LanguageDefinition::CPlusPlus());
editor.SetPalette(TextEditor::GetLightPalette()); void compileEditorCode(const std::string& code)
}
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()
{ {
log("Compiling..."); log("Compiling...");
// Scrap cached build if there are changes if (tempFileName.empty()) {
if (editor.GetText().compare(editorCompiled) != 0) { tempFileName = newTempFileName();
} else {
std::filesystem::remove(tempFileName + ".o"); std::filesystem::remove(tempFileName + ".o");
std::filesystem::remove(tempFileName + ".orig.o"); std::filesystem::remove(tempFileName + ".orig.o");
} }
@ -112,10 +36,6 @@ void compileEditorCode()
const auto platform = m_device ? m_device->get_platform() const auto platform = m_device ? m_device->get_platform()
: stmdsp::platform::L4; : stmdsp::platform::L4;
if (tempFileName.empty())
tempFileName = newTempFileName();
{ {
std::ofstream file (tempFileName, std::ios::trunc | std::ios::binary); 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)); stringReplaceAll(file_text, "$0", std::to_string(buffer_size));
file << file_text << '\n' << editor.GetText(); file << file_text << '\n' << code;
} }
const auto scriptFile = tempFileName + const auto scriptFile = tempFileName +
@ -156,12 +76,10 @@ void compileEditorCode()
const auto makeOutput = scriptFile + ".log"; const auto makeOutput = scriptFile + ".log";
const auto makeCommand = scriptFile + " > " + makeOutput + " 2>&1"; const auto makeCommand = scriptFile + " > " + makeOutput + " 2>&1";
if (codeExecuteCommand(makeCommand, makeOutput)) { if (codeExecuteCommand(makeCommand, makeOutput))
editorCompiled = editor.GetText();
log("Compilation succeeded."); log("Compilation succeeded.");
} else { else
log("Compilation failed."); log("Compilation failed.");
}
std::filesystem::remove(tempFileName); std::filesystem::remove(tempFileName);
std::filesystem::remove(scriptFile); std::filesystem::remove(scriptFile);
@ -171,19 +89,50 @@ void disassembleCode()
{ {
log("Disassembling..."); log("Disassembling...");
if (tempFileName.size() == 0 || editor.GetText().compare(editorCompiled) != 0) { //if (tempFileName.empty())
compileEditorCode(); // compileEditorCode();
}
const auto output = tempFileName + ".asm.log"; const auto output = tempFileName + ".asm.log";
const auto command = const auto command =
std::string("arm-none-eabi-objdump -d --no-show-raw-insn ") + std::string("arm-none-eabi-objdump -d --no-show-raw-insn ") +
tempFileName + ".orig.o > " + output + " 2>&1"; tempFileName + ".orig.o > " + output + " 2>&1";
if (codeExecuteCommand(command, output)) { if (codeExecuteCommand(command, output))
log("Ready."); log("Ready.");
} else { else
log("Failed to load disassembly."); 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();
}
};

@ -12,8 +12,6 @@
#include "stmdsp.hpp" #include "stmdsp.hpp"
#include "imgui.h" #include "imgui.h"
#include "imgui_internal.h"
#include "ImGuiFileDialog.h"
#include "wav.hpp" #include "wav.hpp"
#include <array> #include <array>
@ -21,58 +19,35 @@
#include <cmath> #include <cmath>
#include <deque> #include <deque>
#include <fstream> #include <fstream>
#include <functional>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <string>
#include <string_view>
#include <thread> #include <thread>
#include <vector>
extern std::string tempFileName; extern std::string tempFileName;
extern void log(const std::string& str); extern void log(const std::string& str);
extern std::vector<stmdsp::dacsample_t> deviceGenLoadFormulaEval(const std::string_view); extern std::vector<stmdsp::dacsample_t> deviceGenLoadFormulaEval(const std::string_view);
std::shared_ptr<stmdsp::device> m_device; std::shared_ptr<stmdsp::device> m_device;
static const std::array<const char *, 6> sampleRateList {{
"8 kHz",
"16 kHz",
"20 kHz",
"32 kHz",
"48 kHz",
"96 kHz"
}};
static const char *sampleRatePreview = sampleRateList[0];
static const std::array<unsigned int, 6> 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 mutexDrawSamples;
static std::timed_mutex mutexDeviceLoad; static std::timed_mutex mutexDeviceLoad;
static std::ofstream logSamplesFile; static std::ofstream logSamplesFile;
static wav::clip wavOutput; static wav::clip wavOutput;
static std::deque<stmdsp::dacsample_t> drawSamplesQueue; static std::deque<stmdsp::dacsample_t> drawSamplesQueue;
static std::deque<stmdsp::dacsample_t> drawSamplesInputQueue; static std::deque<stmdsp::dacsample_t> drawSamplesInputQueue;
static double drawSamplesTimeframe = 1.0; // seconds static bool drawSamplesInput = false;
static unsigned int drawSamplesBufferSize = 1; static unsigned int drawSamplesBufferSize = 1;
void deviceSetInputDrawing(bool enabled)
{
drawSamplesInput = enabled;
}
static void measureCodeTask(std::shared_ptr<stmdsp::device> device) static void measureCodeTask(std::shared_ptr<stmdsp::device> device)
{ {
std::this_thread::sleep_for(std::chrono::seconds(1)); std::this_thread::sleep_for(std::chrono::seconds(1));
@ -105,7 +80,7 @@ static std::chrono::duration<double> getBufferPeriod(
{ {
if (device) { if (device) {
const double bufferSize = device->get_buffer_size(); 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<double>(bufferSize / sampleRate * factor); return std::chrono::duration<double>(bufferSize / sampleRate * factor);
} else { } else {
return {}; return {};
@ -117,7 +92,6 @@ static void drawSamplesTask(std::shared_ptr<stmdsp::device> device)
if (!device) if (!device)
return; return;
const bool doLogger = logResults && logSamplesFile.good();
const auto bufferTime = getBufferPeriod(device); const auto bufferTime = getBufferPeriod(device);
std::unique_lock<std::timed_mutex> lockDraw (mutexDrawSamples, std::defer_lock); std::unique_lock<std::timed_mutex> lockDraw (mutexDrawSamples, std::defer_lock);
@ -138,7 +112,7 @@ static void drawSamplesTask(std::shared_ptr<stmdsp::device> device)
lockDevice.unlock(); lockDevice.unlock();
addToQueue(drawSamplesQueue, chunk); addToQueue(drawSamplesQueue, chunk);
if (doLogger) { if (logSamplesFile.good()) {
for (const auto& s : chunk) for (const auto& s : chunk)
logSamplesFile << s << '\n'; logSamplesFile << s << '\n';
} }
@ -147,7 +121,7 @@ static void drawSamplesTask(std::shared_ptr<stmdsp::device> device)
std::this_thread::sleep_for(std::chrono::milliseconds(500)); std::this_thread::sleep_for(std::chrono::milliseconds(500));
} }
if (drawSamplesInput && popupRequestDraw) { if (drawSamplesInput) {
if (lockDevice.try_lock_for(std::chrono::milliseconds(1))) { if (lockDevice.try_lock_for(std::chrono::milliseconds(1))) {
const auto chunk2 = tryReceiveChunk(device, const auto chunk2 = tryReceiveChunk(device,
std::mem_fn(&stmdsp::device::continuous_read_input)); std::mem_fn(&stmdsp::device::continuous_read_input));
@ -182,7 +156,7 @@ static void feedSigGenTask(std::shared_ptr<stmdsp::device> device)
std::vector<int16_t> wavIntBuf (wavBuf.size()); std::vector<int16_t> wavIntBuf (wavBuf.size());
while (genRunning) { while (device->is_siggening()) {
const auto next = std::chrono::high_resolution_clock::now() + delay; const auto next = std::chrono::high_resolution_clock::now() + delay;
wavOutput.next(wavIntBuf.data(), wavIntBuf.size()); wavOutput.next(wavIntBuf.data(), wavIntBuf.size());
@ -228,346 +202,60 @@ static void statusTask(std::shared_ptr<stmdsp::device> device)
} }
} }
static void deviceConnect(); void deviceLoadAudioFile(const std::string& file)
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()
{ {
static char *siggenBuffer = nullptr; wavOutput = wav::clip(file);
static int siggenOption = 0; if (wavOutput.valid())
log("Audio file loaded.");
if (popupRequestSiggen) { else
siggenBuffer = new char[65536]; log("Error: Bad WAV audio file.");
*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();
}
} }
void deviceRenderDraw() void deviceLoadLogFile(const std::string& file)
{ {
if (popupRequestDraw) { logSamplesFile = std::ofstream(file);
static std::vector<stmdsp::dacsample_t> buffer; if (logSamplesFile.good())
static decltype(buffer.begin()) bufferCursor; log("Log file ready.");
static std::vector<stmdsp::dacsample_t> bufferInput; else
static decltype(bufferInput.begin()) bufferInputCursor; log("Error: Could not open log file.");
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<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);
}
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<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;
}
}
ImGui::End();
}
} }
void deviceRenderMenu() bool deviceGenStartToggle()
{ {
if (ImGui::BeginMenu("Run")) { if (m_device) {
bool isConnected = m_device ? true : false; bool running = m_device->is_siggening();
bool isRunning = isConnected && m_device->is_running(); if (!running) {
if (wavOutput.valid())
static const char *connectLabel = "Connect"; std::thread(feedSigGenTask, m_device).detach();
if (ImGui::MenuItem(connectLabel, nullptr, false, !isConnected || (isConnected && !isRunning))) { else
deviceConnect(); m_device->siggen_start();
isConnected = m_device ? true : false; log("Generator started.");
connectLabel = isConnected ? "Disconnect" : "Connect"; } else {
} m_device->siggen_stop();
log("Generator stopped.");
ImGui::Separator();
static const char *startLabel = "Start";
if (ImGui::MenuItem(startLabel, nullptr, false, isConnected)) {
startLabel = isRunning ? "Start" : "Stop";
deviceStart();
} }
if (ImGui::MenuItem("Upload algorithm", nullptr, false, isConnected && !isRunning)) return !running;
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();
if (ImGui::MenuItem("Set buffer size...", nullptr, false, isConnected && !isRunning)) { return false;
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";
}
}
}
ImGui::EndMenu(); void deviceUpdateDrawBufferSize(double timeframe)
} {
drawSamplesBufferSize = std::round(
m_device->get_sample_rate() * timeframe);
} }
void deviceRenderToolbar() void deviceSetSampleRate(unsigned int rate)
{ {
ImGui::SameLine(); do {
if (ImGui::Button("Upload")) m_device->set_sample_rate(rate);
deviceAlgorithmUpload(); std::this_thread::sleep_for(std::chrono::milliseconds(10));
ImGui::SameLine(); } while (m_device->get_sample_rate() != rate);
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();
} }
void deviceConnect() bool deviceConnect()
{ {
static std::thread statusThread; static std::thread statusThread;
@ -583,12 +271,10 @@ void deviceConnect()
if (m_device) { if (m_device) {
if (m_device->connected()) { if (m_device->connected()) {
auto sri = m_device->get_sample_rate();
sampleRatePreview = sampleRateList[sri];
drawSamplesBufferSize = std::round(sampleRateInts[sri] * drawSamplesTimeframe);
log("Connected!"); log("Connected!");
statusThread = std::thread(statusTask, m_device); statusThread = std::thread(statusTask, m_device);
statusThread.detach(); statusThread.detach();
return true;
} else { } else {
m_device.reset(); m_device.reset();
log("Failed to connect."); log("Failed to connect.");
@ -604,9 +290,11 @@ void deviceConnect()
m_device.reset(); m_device.reset();
log("Disconnected."); log("Disconnected.");
} }
return false;
} }
void deviceStart() void deviceStart(bool measureCodeTime, bool logResults, bool drawSamples)
{ {
if (!m_device) { if (!m_device) {
log("No device connected."); log("No device connected.");
@ -619,9 +307,8 @@ void deviceStart()
std::this_thread::sleep_for(std::chrono::microseconds(150)); std::this_thread::sleep_for(std::chrono::microseconds(150));
m_device->continuous_stop(); m_device->continuous_stop();
} }
if (logResults) { if (logSamplesFile.good()) {
logSamplesFile.close(); logSamplesFile.close();
logResults = false;
log("Log file saved and closed."); log("Log file saved and closed.");
} }
log("Ready."); log("Ready.");
@ -715,3 +402,44 @@ void deviceGenLoadFormula(std::string_view formula)
} }
} }
void pullFromQueue(
std::deque<stmdsp::dacsample_t>& queue,
std::vector<stmdsp::dacsample_t>& 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<std::size_t>(count));
for (auto i = count; i; --i) {
*bufferCursor = queue.front();
queue.pop_front();
if (++bufferCursor == buffer.end())
bufferCursor = buffer.begin();
}
}
void pullFromDrawQueue(
std::vector<stmdsp::dacsample_t>& buffer,
decltype(buffer.begin())& bufferCursor,
double timeframe)
{
pullFromQueue(drawSamplesQueue, buffer, bufferCursor, timeframe);
}
void pullFromInputDrawQueue(
std::vector<stmdsp::dacsample_t>& buffer,
decltype(buffer.begin())& bufferCursor,
double timeframe)
{
pullFromQueue(drawSamplesInputQueue, buffer, bufferCursor, timeframe);
}

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

@ -0,0 +1,340 @@
#include "imgui.h"
#include "imgui_internal.h"
#include "ImGuiFileDialog.h"
#include "stmdsp.hpp"
#include <array>
#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(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<stmdsp::dacsample_t>& buffer,
decltype(buffer.begin())& bufferCursor,
double timeframe);
void pullFromInputDrawQueue(
std::vector<stmdsp::dacsample_t>& 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<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;
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<stmdsp::dacsample_t> buffer;
static decltype(buffer.begin()) bufferCursor;
static std::vector<stmdsp::dacsample_t> 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<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);
}
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<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;
}
}
ImGui::End();
}
}

@ -17,6 +17,15 @@
extern void log(const std::string& str); 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 namespace stmdsp
{ {
const std::forward_list<std::string>& scanner::scan() const std::forward_list<std::string>& scanner::scan()
@ -117,11 +126,19 @@ namespace stmdsp
} }
} }
void device::set_sample_rate(unsigned int id) { void device::set_sample_rate(unsigned int rate) {
try_command({ auto it = std::find(
'r', sampleRateInts.cbegin(),
static_cast<uint8_t>(id) sampleRateInts.cend(),
}); rate);
if (it != sampleRateInts.cend()) {
const auto i = std::distance(sampleRateInts.cbegin(), it);
try_command({
'r',
static_cast<uint8_t>(i)
});
}
} }
unsigned int device::get_sample_rate() { unsigned int device::get_sample_rate() {
@ -130,7 +147,10 @@ namespace stmdsp
if (try_read({'r', 0xFF}, &result, 1)) if (try_read({'r', 0xFF}, &result, 1))
m_sample_rate = result; m_sample_rate = result;
} }
return m_sample_rate;
return m_sample_rate < sampleRateInts.size() ?
sampleRateInts[m_sample_rate] :
0;
} }
void device::continuous_start() { void device::continuous_start() {

@ -117,7 +117,7 @@ namespace stmdsp
void continuous_set_buffer_size(unsigned int size); void continuous_set_buffer_size(unsigned int size);
unsigned int get_buffer_size() const { return m_buffer_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(); unsigned int get_sample_rate();
void continuous_start(); void continuous_start();

@ -4,6 +4,7 @@
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <fstream> #include <fstream>
#include <string>
#include <vector> #include <vector>
namespace wav namespace wav
@ -44,7 +45,7 @@ namespace wav
class clip { class clip {
public: public:
clip(const char *path) { clip(const std::string& path) {
std::ifstream file (path); std::ifstream file (path);
if (!file.good()) if (!file.good())
return; return;

Loading…
Cancel
Save