aboutsummaryrefslogtreecommitdiffstats
path: root/source/gui_device.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/gui_device.cpp')
-rw-r--r--source/gui_device.cpp418
1 files changed, 418 insertions, 0 deletions
diff --git a/source/gui_device.cpp b/source/gui_device.cpp
new file mode 100644
index 0000000..689ef54
--- /dev/null
+++ b/source/gui_device.cpp
@@ -0,0 +1,418 @@
+#include "circular.hpp"
+#include "imgui.h"
+#include "imgui_internal.h"
+#include "ImGuiFileDialog.h"
+
+#include "stmdsp.hpp"
+
+#include <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();
+ }
+}
+