You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
419 lines
14 KiB
C++
419 lines
14 KiB
C++
#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();
|
|
}
|
|
}
|
|
|