From 9f480396204e8ccdd89779860cb61d4e4aded8b4 Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Tue, 5 Oct 2021 13:56:33 -0400 Subject: [PATCH 01/21] buffered draw samples; increased read rate --- source/device.cpp | 220 ++++++++++++++++++++++++++++++--------- source/main.cpp | 1 - source/stmdsp/stmdsp.cpp | 2 +- 3 files changed, 171 insertions(+), 52 deletions(-) diff --git a/source/device.cpp b/source/device.cpp index 196f5b4..469fcb1 100644 --- a/source/device.cpp +++ b/source/device.cpp @@ -16,6 +16,9 @@ #include "wav.hpp" #include +#include +#include +#include #include #include #include @@ -43,6 +46,14 @@ static const unsigned int sampleRateInts[6] = { 48'000, 96'000 }; +static const unsigned int sampleRateBSizes[6] = { + 8000, + 16000, + 20000, + 32000, + 48000, + 96000, +}; static bool measureCodeTime = false; static bool drawSamples = false; @@ -56,11 +67,14 @@ static bool popupRequestDraw = false; static bool popupRequestLog = false; static std::mutex mutexDrawSamples; -static std::vector drawSamplesBuf; -static std::vector drawSamplesBuf2; +//static std::vector drawSamplesBuf; +//static std::vector drawSamplesBuf2; static std::ofstream logSamplesFile; static wav::clip wavOutput; +static std::deque drawSamplesQueue; +static unsigned int drawSamplesBufferSize = 4096; + static void measureCodeTask(stmdsp::device *device) { if (device == nullptr) @@ -77,28 +91,94 @@ static void drawSamplesTask(stmdsp::device *device) const bool doLogger = logResults && logSamplesFile.good(); - const auto bsize = m_device->get_buffer_size(); - const float srate = sampleRateInts[m_device->get_sample_rate()]; - const unsigned int delay = bsize / srate * 1000.f * 0.5f; + const double bufferSize = m_device->get_buffer_size(); + const double sampleRate = sampleRateInts[m_device->get_sample_rate()]; + double samplesToPush = sampleRate * 0.0007 * 2; - while (m_device->is_running()) { - { - std::scoped_lock lock (mutexDrawSamples); - drawSamplesBuf = m_device->continuous_read(); - if (drawSamplesInput && popupRequestDraw) - drawSamplesBuf2 = m_device->continuous_read_input(); + std::vector lastReadBuffer (bufferSize, 2048); + double lastReadIndexD = 0; + double desiredTime = bufferSize / sampleRate * 1.01; + + auto bufferTime = std::chrono::high_resolution_clock::now(); + while (m_device && m_device->is_running()) { + if (samplesToPush < bufferSize) { + if (lastReadIndexD >= bufferSize - 1) + lastReadIndexD -= bufferSize - 1; + + unsigned int lastReadIndex = std::floor(lastReadIndexD); + unsigned int end = lastReadIndexD + samplesToPush; + + { + std::scoped_lock lock (mutexDrawSamples); + for (; lastReadIndexD < end; lastReadIndexD += 1) { + if (lastReadIndex >= lastReadBuffer.size()) { + auto old = samplesToPush; + + auto now = std::chrono::high_resolution_clock::now(); + std::chrono::duration diff = now - bufferTime; + + lastReadBuffer = m_device->continuous_read(); + if (drawSamplesInput && popupRequestDraw) + drawSamplesBuf2 = m_device->continuous_read_input(); + if (lastReadBuffer.empty()) { + do { + std::this_thread::sleep_for(std::chrono::microseconds(2)); + lastReadBuffer = m_device->continuous_read(); + } while (lastReadBuffer.empty() && m_device->is_running()); + } + + bufferTime = now; + + if (double c = diff.count(); c > desiredTime + 0.001) { + // Too slow + samplesToPush = (samplesToPush * c / desiredTime) * 0.98; + if (samplesToPush > bufferSize) + samplesToPush = bufferSize; + } else if (c < desiredTime - 0.001) { + // Too fast + samplesToPush = (samplesToPush * c / desiredTime) * 0.98; + } + + lastReadIndex = 0; + } + + drawSamplesQueue.push_back(lastReadBuffer[lastReadIndex++]); + } + } + + std::this_thread::sleep_for(std::chrono::microseconds(700)); } + // Full blast + else { + lastReadBuffer = m_device->continuous_read(); + while (lastReadBuffer.empty() && m_device->is_running()) { + std::this_thread::sleep_for(std::chrono::microseconds(1)); + lastReadBuffer = m_device->continuous_read(); + } + + { + std::scoped_lock lock (mutexDrawSamples); + for (const auto& s : lastReadBuffer) + drawSamplesQueue.push_back(s); + } + + auto now = std::chrono::high_resolution_clock::now(); + std::chrono::duration diff = now - bufferTime; + bufferTime = now; + + double c = diff.count(); + if (c < desiredTime) { + samplesToPush = (samplesToPush * c / desiredTime) * 0.98; + } - if (doLogger) { - for (const auto& s : drawSamplesBuf) - logSamplesFile << s << '\n'; + std::this_thread::sleep_for(std::chrono::microseconds(10)); } - std::this_thread::sleep_for(std::chrono::milliseconds(delay)); + //if (doLogger) { + // for (const auto& s : drawSamplesBuf) + // logSamplesFile << s << '\n'; + //} } - - std::fill(drawSamplesBuf.begin(), drawSamplesBuf.end(), 2048); - std::fill(drawSamplesBuf2.begin(), drawSamplesBuf2.end(), 2048); } static void feedSigGenTask(stmdsp::device *device) @@ -264,34 +344,62 @@ void deviceRenderDraw() { if (popupRequestDraw) { ImGui::Begin("draw", &popupRequestDraw); - ImGui::Checkbox("Draw input", &drawSamplesInput); - { - std::scoped_lock lock (mutexDrawSamples); - auto drawList = ImGui::GetWindowDrawList(); - const ImVec2 p0 = ImGui::GetWindowPos(); - const auto size = ImGui::GetWindowSize(); - //ImVec2 p1 (p0.x + size.x, p0.y + size.y); - //ImU32 col_a = ImGui::GetColorU32(IM_COL32(0, 0, 0, 255)); - //ImU32 col_b = ImGui::GetColorU32(IM_COL32(255, 255, 255, 255)); - //drawList->AddRectFilledMultiColor(p0, p1, col_a, col_b, col_b, col_a); - - const unsigned int didx = 1.f / (size.x / static_cast(drawSamplesBuf.size())); - ImVec2 pp = p0; - for (auto i = 0u; i < drawSamplesBuf.size(); i += didx) { - ImVec2 next (pp.x + 1, p0.y + (float)drawSamplesBuf[i] / 4095.f * size.y); - drawList->AddLine(pp, next, ImGui::GetColorU32(IM_COL32(128, 0, 0, 255))); - pp = next; - } + ImGui::Checkbox("Draw input", &drawSamplesInput); - if (drawSamplesInput) { - pp = p0; - for (auto i = 0u; i < drawSamplesBuf2.size(); i += didx) { - ImVec2 next (pp.x + 1, p0.y + (float)drawSamplesBuf2[i] / 4095.f * size.y); - drawList->AddLine(pp, next, ImGui::GetColorU32(IM_COL32(0, 0, 128, 255))); - pp = next; - } + static std::vector buffer; + static decltype(buffer.begin()) bufferCursor; + static long unsigned int drawChunkSize = 0; + static std::chrono::time_point timee; + + if (buffer.size() != drawSamplesBufferSize) { + buffer.resize(drawSamplesBufferSize); + bufferCursor = buffer.begin(); + } + + { + std::scoped_lock lock (mutexDrawSamples); + auto qsize = drawSamplesQueue.size(); + auto count = qsize;//std::min(qsize, drawChunkSize); + //if (count > 0) + // printf("draw: %9llu/%9lu\n", qsize, count); + for (auto i = count; i; --i) { + *bufferCursor = drawSamplesQueue.front(); + drawSamplesQueue.pop_front(); + if (++bufferCursor == buffer.end()) { + bufferCursor = buffer.begin(); + + //auto now = std::chrono::high_resolution_clock::now(); + //std::chrono::duration diff = now - timee; + //timee = now; + //printf("\rtime: %.9lf", diff); + //fflush(stdout); } } + } + + 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 unsigned int didx = 1.f / (size.x / static_cast(buffer.size())); + ImVec2 pp = p0; + for (auto i = 0u; i < buffer.size(); i += didx) { + ImVec2 next (pp.x + 1, p0.y + size.y - (float)buffer[i] / 4095.f * size.y); + drawList->AddLine(pp, next, ImGui::GetColorU32(IM_COL32(255, 0, 0, 255))); + pp = next; + } + + if (drawSamplesInput) { + pp = p0; + for (auto i = 0u; i < drawSamplesBuf2.size(); i += didx) { + ImVec2 next (pp.x + 1, p0.y + size.y - (float)buffer[i] / 4095.f * size.y); + drawList->AddLine(pp, next, ImGui::GetColorU32(IM_COL32(0, 0, 255, 255))); + pp = next; + } + } ImGui::End(); } } @@ -304,21 +412,21 @@ void deviceRenderMenu() static const char *connectLabel = "Connect"; if (ImGui::MenuItem(connectLabel)) { + connectLabel = isConnected ? "Connect" : "Disconnect"; deviceConnect(); - connectLabel = isConnected ? "Disconnect" : "Connect"; } ImGui::Separator(); static const char *startLabel = "Start"; if (ImGui::MenuItem(startLabel, nullptr, false, isConnected)) { + startLabel = isRunning ? "Start" : "Stop"; deviceStart(); - startLabel = isRunning ? "Stop" : "Start"; } /** TODO test siggen formula TODO improve siggen audio streaming -TODO draw: smoothly chain captures +TODO draw: smoothly chain captures for 96kHz */ if (ImGui::MenuItem("Upload algorithm", nullptr, false, isConnected)) deviceAlgorithmUpload(); @@ -389,8 +497,14 @@ void deviceRenderToolbar() for (int i = 0; i < 6; ++i) { if (ImGui::Selectable(sampleRateList[i])) { sampleRatePreview = sampleRateList[i]; - if (m_device != nullptr && !m_device->is_running()) - m_device->set_sample_rate(i); + if (m_device != nullptr && !m_device->is_running()) { + do { + m_device->set_sample_rate(i); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } while (m_device->get_sample_rate() != i); + + drawSamplesBufferSize = sampleRateBSizes[i]; + } } } ImGui::EndCombo(); @@ -404,7 +518,9 @@ void deviceConnect() if (auto devices = scanner.scan(); devices.size() > 0) { m_device = new stmdsp::device(devices.front()); if (m_device->connected()) { - sampleRatePreview = sampleRateList[m_device->get_sample_rate()]; + auto sri = m_device->get_sample_rate(); + sampleRatePreview = sampleRateList[sri]; + drawSamplesBufferSize = sampleRateBSizes[sri]; log("Connected!"); } else { delete m_device; @@ -429,7 +545,11 @@ void deviceStart() } if (m_device->is_running()) { - m_device->continuous_stop(); + { + std::scoped_lock lock (mutexDrawSamples); + std::this_thread::sleep_for(std::chrono::microseconds(150)); + m_device->continuous_stop(); + } if (logResults) { logSamplesFile.close(); logResults = false; diff --git a/source/main.cpp b/source/main.cpp index ce1ea20..36e8c6e 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -19,7 +19,6 @@ #include -// Externs extern ImFont *fontSans; extern ImFont *fontMono; diff --git a/source/stmdsp/stmdsp.cpp b/source/stmdsp/stmdsp.cpp index b3fc8c3..bb417ca 100644 --- a/source/stmdsp/stmdsp.cpp +++ b/source/stmdsp/stmdsp.cpp @@ -27,7 +27,7 @@ namespace stmdsp } device::device(const std::string& file) : - m_serial(file, 1000000/*230400*/, serial::Timeout::simpleTimeout(50)) + m_serial(file, 8'000'000/*230400*/, serial::Timeout::simpleTimeout(50)) { if (m_serial.isOpen()) { m_serial.flush(); From a4e2375f049d3949c1a4b9a044d3d1efc6fa7d62 Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Tue, 5 Oct 2021 19:59:20 -0400 Subject: [PATCH 02/21] draw samples: set time frame --- source/device.cpp | 48 ++++++++++++++++++++++++++-------------- source/stmdsp/stmdsp.cpp | 21 +++++++++--------- source/stmdsp/stmdsp.hpp | 1 + 3 files changed, 43 insertions(+), 27 deletions(-) diff --git a/source/device.cpp b/source/device.cpp index 469fcb1..6cdec13 100644 --- a/source/device.cpp +++ b/source/device.cpp @@ -9,6 +9,16 @@ * If not, see . */ +/** + * TODO list: + * - Test loading the signal generator with a formula. + * - Improve signal generator audio streaming. + * - Decide how to handle drawing samples at 96kS/s. + * - May not be possible: USB streaming maxing out at ~80kS/s (1.25MB/s) + * - Draw input samples + * - Log samples + */ + #include "stmdsp.hpp" #include "imgui.h" @@ -46,14 +56,6 @@ static const unsigned int sampleRateInts[6] = { 48'000, 96'000 }; -static const unsigned int sampleRateBSizes[6] = { - 8000, - 16000, - 20000, - 32000, - 48000, - 96000, -}; static bool measureCodeTime = false; static bool drawSamples = false; @@ -68,12 +70,13 @@ static bool popupRequestLog = false; static std::mutex mutexDrawSamples; //static std::vector drawSamplesBuf; -//static std::vector drawSamplesBuf2; +static std::vector drawSamplesBuf2; static std::ofstream logSamplesFile; static wav::clip wavOutput; static std::deque drawSamplesQueue; -static unsigned int drawSamplesBufferSize = 4096; +static double drawSamplesTimeframe = 1.0; // seconds +static unsigned int drawSamplesBufferSize = 1; static void measureCodeTask(stmdsp::device *device) { @@ -345,6 +348,22 @@ void deviceRenderDraw() if (popupRequestDraw) { ImGui::Begin("draw", &popupRequestDraw); ImGui::Checkbox("Draw input", &drawSamplesInput); + ImGui::SameLine(); + ImGui::Text("| time: %0.3f sec", drawSamplesTimeframe); + ImGui::SameLine(); + if (ImGui::Button("-", {30, 0})) { + drawSamplesTimeframe = std::max(drawSamplesTimeframe - 0.25, 0.5); + 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 + 0.25, 30.); + auto sr = sampleRateInts[m_device->get_sample_rate()]; + auto tf = drawSamplesTimeframe; + drawSamplesBufferSize = std::round(sr * tf); + } static std::vector buffer; static decltype(buffer.begin()) bufferCursor; @@ -423,11 +442,6 @@ void deviceRenderMenu() deviceStart(); } -/** -TODO test siggen formula -TODO improve siggen audio streaming -TODO draw: smoothly chain captures for 96kHz - */ if (ImGui::MenuItem("Upload algorithm", nullptr, false, isConnected)) deviceAlgorithmUpload(); if (ImGui::MenuItem("Unload algorithm", nullptr, false, isConnected)) @@ -503,7 +517,7 @@ void deviceRenderToolbar() std::this_thread::sleep_for(std::chrono::milliseconds(10)); } while (m_device->get_sample_rate() != i); - drawSamplesBufferSize = sampleRateBSizes[i]; + drawSamplesBufferSize = std::round(sampleRateInts[i] * drawSamplesTimeframe); } } } @@ -520,7 +534,7 @@ void deviceConnect() if (m_device->connected()) { auto sri = m_device->get_sample_rate(); sampleRatePreview = sampleRateList[sri]; - drawSamplesBufferSize = sampleRateBSizes[sri]; + drawSamplesBufferSize = std::round(sampleRateInts[sri] * drawSamplesTimeframe); log("Connected!"); } else { delete m_device; diff --git a/source/stmdsp/stmdsp.cpp b/source/stmdsp/stmdsp.cpp index bb417ca..93c52dd 100644 --- a/source/stmdsp/stmdsp.cpp +++ b/source/stmdsp/stmdsp.cpp @@ -30,18 +30,18 @@ namespace stmdsp m_serial(file, 8'000'000/*230400*/, serial::Timeout::simpleTimeout(50)) { if (m_serial.isOpen()) { - m_serial.flush(); - m_serial.write("i"); - if (auto id = m_serial.read(7); id.starts_with("stmdsp")) { + m_serial.flush(); + m_serial.write("i"); + if (auto id = m_serial.read(7); id.starts_with("stmdsp")) { if (id.back() == 'h') m_platform = platform::H7; else if (id.back() == 'l') m_platform = platform::L4; else m_serial.close(); - } else { - m_serial.close(); - } + } else { + m_serial.close(); + } } } @@ -69,17 +69,18 @@ namespace stmdsp } unsigned int device::get_sample_rate() { - unsigned char result = 0xFF; - - if (connected()) { + if (connected() && !is_running()) { uint8_t request[2] = { 'r', 0xFF }; m_serial.write(request, 2); + + unsigned char result = 0xFF; m_serial.read(&result, 1); + m_sample_rate = result; } - return result; + return m_sample_rate; } void device::continuous_start() { diff --git a/source/stmdsp/stmdsp.hpp b/source/stmdsp/stmdsp.hpp index 8da98f2..0b9398e 100644 --- a/source/stmdsp/stmdsp.hpp +++ b/source/stmdsp/stmdsp.hpp @@ -90,6 +90,7 @@ namespace stmdsp serial::Serial m_serial; platform m_platform = platform::Unknown; unsigned int m_buffer_size = SAMPLES_MAX; + unsigned int m_sample_rate = 0; bool m_is_siggening = false; bool m_is_running = false; }; From e976308b6f36a9480671ce39dfce86a4aabfb7d2 Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Thu, 7 Oct 2021 14:22:20 -0400 Subject: [PATCH 03/21] draw samples: x/y scale progress --- source/device.cpp | 112 +++++++++++++++++++++++++++++++--------------- 1 file changed, 77 insertions(+), 35 deletions(-) diff --git a/source/device.cpp b/source/device.cpp index 6cdec13..6d55ee4 100644 --- a/source/device.cpp +++ b/source/device.cpp @@ -25,6 +25,7 @@ #include "ImGuiFileDialog.h" #include "wav.hpp" +#include #include #include #include @@ -39,23 +40,23 @@ extern void log(const std::string& str); extern std::vector deviceGenLoadFormulaEval(const std::string_view); -static const char *sampleRateList[6] = { +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 unsigned int sampleRateInts[6] = { +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; @@ -104,7 +105,7 @@ static void drawSamplesTask(stmdsp::device *device) auto bufferTime = std::chrono::high_resolution_clock::now(); while (m_device && m_device->is_running()) { - if (samplesToPush < bufferSize) { + if (samplesToPush < bufferSize || drawSamplesTimeframe < 0.25) { if (lastReadIndexD >= bufferSize - 1) lastReadIndexD -= bufferSize - 1; @@ -115,7 +116,7 @@ static void drawSamplesTask(stmdsp::device *device) std::scoped_lock lock (mutexDrawSamples); for (; lastReadIndexD < end; lastReadIndexD += 1) { if (lastReadIndex >= lastReadBuffer.size()) { - auto old = samplesToPush; + //auto old = samplesToPush; auto now = std::chrono::high_resolution_clock::now(); std::chrono::duration diff = now - bufferTime; @@ -125,7 +126,7 @@ static void drawSamplesTask(stmdsp::device *device) drawSamplesBuf2 = m_device->continuous_read_input(); if (lastReadBuffer.empty()) { do { - std::this_thread::sleep_for(std::chrono::microseconds(2)); + std::this_thread::sleep_for(std::chrono::microseconds(20)); lastReadBuffer = m_device->continuous_read(); } while (lastReadBuffer.empty() && m_device->is_running()); } @@ -142,6 +143,11 @@ static void drawSamplesTask(stmdsp::device *device) samplesToPush = (samplesToPush * c / desiredTime) * 0.98; } + //if (std::abs(old - samplesToPush) > 4) { + // printf("\r%0.2lf", samplesToPush); + // fflush(stdout); + //} + lastReadIndex = 0; } @@ -169,12 +175,16 @@ static void drawSamplesTask(stmdsp::device *device) std::chrono::duration diff = now - bufferTime; bufferTime = now; - double c = diff.count(); - if (c < desiredTime) { - samplesToPush = (samplesToPush * c / desiredTime) * 0.98; - } + if (drawSamplesTimeframe >= 0.25) { + double c = diff.count(); + if (c < desiredTime) { + samplesToPush = (samplesToPush * c / desiredTime) * 0.98; + } - std::this_thread::sleep_for(std::chrono::microseconds(10)); + std::this_thread::sleep_for(std::chrono::microseconds(10)); + } else { + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + } } //if (doLogger) { @@ -346,29 +356,42 @@ void deviceRenderWidgets() void deviceRenderDraw() { if (popupRequestDraw) { + static std::vector buffer; + static decltype(buffer.begin()) bufferCursor; + static unsigned int yMinMax = 4095; + //static long unsigned int drawChunkSize = 0; + //static std::chrono::time_point timee; + ImGui::Begin("draw", &popupRequestDraw); - ImGui::Checkbox("Draw input", &drawSamplesInput); + ImGui::Text("Draw input "); + ImGui::SameLine(); + ImGui::Checkbox("", &drawSamplesInput); ImGui::SameLine(); - ImGui::Text("| time: %0.3f sec", drawSamplesTimeframe); + ImGui::Text("Time: %0.3f sec", drawSamplesTimeframe); ImGui::SameLine(); if (ImGui::Button("-", {30, 0})) { - drawSamplesTimeframe = std::max(drawSamplesTimeframe - 0.25, 0.5); + 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 + 0.25, 30.); + drawSamplesTimeframe = std::min(drawSamplesTimeframe * 2, 32.); auto sr = sampleRateInts[m_device->get_sample_rate()]; auto tf = drawSamplesTimeframe; drawSamplesBufferSize = std::round(sr * tf); } - - static std::vector buffer; - static decltype(buffer.begin()) bufferCursor; - static long unsigned int drawChunkSize = 0; - static std::chrono::time_point timee; + ImGui::SameLine(); + ImGui::Text("Y-minmax: %u", yMinMax); + 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); + } if (buffer.size() != drawSamplesBufferSize) { buffer.resize(drawSamplesBufferSize); @@ -403,22 +426,41 @@ void deviceRenderDraw() size.y -= 70; drawList->AddRectFilled(p0, {p0.x + size.x, p0.y + size.y}, IM_COL32(0, 0, 0, 255)); - const unsigned int didx = 1.f / (size.x / static_cast(buffer.size())); - ImVec2 pp = p0; - for (auto i = 0u; i < buffer.size(); i += didx) { - ImVec2 next (pp.x + 1, p0.y + size.y - (float)buffer[i] / 4095.f * size.y); + 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) { - pp = p0; - for (auto i = 0u; i < drawSamplesBuf2.size(); i += didx) { - ImVec2 next (pp.x + 1, p0.y + size.y - (float)buffer[i] / 4095.f * size.y); - drawList->AddLine(pp, next, ImGui::GetColorU32(IM_COL32(0, 0, 255, 255))); - pp = next; - } - } + //const unsigned int didx = std::ceil(1.f / (size.x / static_cast(buffer.size()))); + //const auto dx = size.x / buffer.size(); + //ImVec2 pp = p0; + //for (auto i = 0u; i < buffer.size(); i += didx) { + // float n = std::clamp((buffer[i] - 2048.) / yMinMax, -0.5, 0.5); + + // ImVec2 next (pp.x + 1, p0.y + size.y * (0.5 - n)); + // drawList->AddLine(pp, next, ImGui::GetColorU32(IM_COL32(255, 0, 0, 255))); + // pp = next; + //} + + //if (drawSamplesInput) { + // pp = p0; + // for (auto i = 0u; i < drawSamplesBuf2.size(); i += didx) { + // ImVec2 next (pp.x + 1, + // p0.y + size.y - + // (static_cast(buffer[i]) / yMinMax) * size.y); + // drawList->AddLine(pp, next, ImGui::GetColorU32(IM_COL32(0, 0, 255, 255))); + // pp = next; + // } + //} ImGui::End(); } } @@ -431,8 +473,8 @@ void deviceRenderMenu() static const char *connectLabel = "Connect"; if (ImGui::MenuItem(connectLabel)) { - connectLabel = isConnected ? "Connect" : "Disconnect"; deviceConnect(); + connectLabel = m_device == nullptr ? "Connect" : "Disconnect"; } ImGui::Separator(); @@ -508,7 +550,7 @@ void deviceRenderToolbar() ImGui::SameLine(); ImGui::SetNextItemWidth(100); if (ImGui::BeginCombo("", sampleRatePreview)) { - for (int i = 0; i < 6; ++i) { + for (int i = 0; i < sampleRateList.size() - 1; ++i) { if (ImGui::Selectable(sampleRateList[i])) { sampleRatePreview = sampleRateList[i]; if (m_device != nullptr && !m_device->is_running()) { From a1700f3ca6456a3215165c7d59564c594e22cafd Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Sun, 10 Oct 2021 20:17:52 -0400 Subject: [PATCH 04/21] finished draw samples, can draw input too --- .gitignore | 1 + source/device.cpp | 185 ++++++++++++++++------------------------------ source/gui.cpp | 2 +- source/main.cpp | 8 ++ 4 files changed, 73 insertions(+), 123 deletions(-) diff --git a/.gitignore b/.gitignore index 5ef6f49..debda6d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ imgui.ini stmdspgui stmdspgui.exe +perf.data* *.o .* diff --git a/source/device.cpp b/source/device.cpp index 6d55ee4..e5d7839 100644 --- a/source/device.cpp +++ b/source/device.cpp @@ -13,10 +13,7 @@ * TODO list: * - Test loading the signal generator with a formula. * - Improve signal generator audio streaming. - * - Decide how to handle drawing samples at 96kS/s. - * - May not be possible: USB streaming maxing out at ~80kS/s (1.25MB/s) - * - Draw input samples - * - Log samples + * - Log samples (should be good..?) */ #include "stmdsp.hpp" @@ -76,6 +73,7 @@ static std::ofstream logSamplesFile; static wav::clip wavOutput; static std::deque drawSamplesQueue; +static std::deque drawSamplesInputQueue; static double drawSamplesTimeframe = 1.0; // seconds static unsigned int drawSamplesBufferSize = 1; @@ -97,100 +95,46 @@ static void drawSamplesTask(stmdsp::device *device) const double bufferSize = m_device->get_buffer_size(); const double sampleRate = sampleRateInts[m_device->get_sample_rate()]; - double samplesToPush = sampleRate * 0.0007 * 2; + unsigned long bufferTime = bufferSize / sampleRate * 0.975 * 1e6; - std::vector lastReadBuffer (bufferSize, 2048); - double lastReadIndexD = 0; - double desiredTime = bufferSize / sampleRate * 1.01; - - auto bufferTime = std::chrono::high_resolution_clock::now(); while (m_device && m_device->is_running()) { - if (samplesToPush < bufferSize || drawSamplesTimeframe < 0.25) { - if (lastReadIndexD >= bufferSize - 1) - lastReadIndexD -= bufferSize - 1; - - unsigned int lastReadIndex = std::floor(lastReadIndexD); - unsigned int end = lastReadIndexD + samplesToPush; - - { - std::scoped_lock lock (mutexDrawSamples); - for (; lastReadIndexD < end; lastReadIndexD += 1) { - if (lastReadIndex >= lastReadBuffer.size()) { - //auto old = samplesToPush; - - auto now = std::chrono::high_resolution_clock::now(); - std::chrono::duration diff = now - bufferTime; - - lastReadBuffer = m_device->continuous_read(); - if (drawSamplesInput && popupRequestDraw) - drawSamplesBuf2 = m_device->continuous_read_input(); - if (lastReadBuffer.empty()) { - do { - std::this_thread::sleep_for(std::chrono::microseconds(20)); - lastReadBuffer = m_device->continuous_read(); - } while (lastReadBuffer.empty() && m_device->is_running()); - } - - bufferTime = now; - - if (double c = diff.count(); c > desiredTime + 0.001) { - // Too slow - samplesToPush = (samplesToPush * c / desiredTime) * 0.98; - if (samplesToPush > bufferSize) - samplesToPush = bufferSize; - } else if (c < desiredTime - 0.001) { - // Too fast - samplesToPush = (samplesToPush * c / desiredTime) * 0.98; - } - - //if (std::abs(old - samplesToPush) > 4) { - // printf("\r%0.2lf", samplesToPush); - // fflush(stdout); - //} - - lastReadIndex = 0; - } - - drawSamplesQueue.push_back(lastReadBuffer[lastReadIndex++]); - } - } + auto next = std::chrono::high_resolution_clock::now() + + std::chrono::microseconds(bufferTime); - std::this_thread::sleep_for(std::chrono::microseconds(700)); + auto chunk = m_device->continuous_read(); + while (chunk.empty() && m_device->is_running()) { + std::this_thread::sleep_for(std::chrono::microseconds(20)); + chunk = m_device->continuous_read(); } - // Full blast - else { - lastReadBuffer = m_device->continuous_read(); - while (lastReadBuffer.empty() && m_device->is_running()) { - std::this_thread::sleep_for(std::chrono::microseconds(1)); - lastReadBuffer = m_device->continuous_read(); + + if (drawSamplesInput && popupRequestDraw) { + auto chunk2 = m_device->continuous_read_input(); + while (chunk2.empty() && m_device->is_running()) { + std::this_thread::sleep_for(std::chrono::microseconds(20)); + chunk2 = m_device->continuous_read_input(); } { std::scoped_lock lock (mutexDrawSamples); - for (const auto& s : lastReadBuffer) + auto i = chunk2.cbegin(); + for (const auto& s : chunk) { drawSamplesQueue.push_back(s); - } - - auto now = std::chrono::high_resolution_clock::now(); - std::chrono::duration diff = now - bufferTime; - bufferTime = now; - - if (drawSamplesTimeframe >= 0.25) { - double c = diff.count(); - if (c < desiredTime) { - samplesToPush = (samplesToPush * c / desiredTime) * 0.98; + drawSamplesInputQueue.push_back(*i++); } - - std::this_thread::sleep_for(std::chrono::microseconds(10)); - } else { - std::this_thread::sleep_for(std::chrono::milliseconds(300)); + } + } else if (!doLogger) { + std::scoped_lock lock (mutexDrawSamples); + for (const auto& s : chunk) + drawSamplesQueue.push_back(s); + } else { + std::scoped_lock lock (mutexDrawSamples); + for (const auto& s : chunk) { + drawSamplesQueue.push_back(s); + logSamplesFile << s << '\n'; } } - - //if (doLogger) { - // for (const auto& s : drawSamplesBuf) - // logSamplesFile << s << '\n'; - //} + + std::this_thread::sleep_until(next); } } @@ -358,9 +302,9 @@ void deviceRenderDraw() 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; - //static long unsigned int drawChunkSize = 0; - //static std::chrono::time_point timee; ImGui::Begin("draw", &popupRequestDraw); ImGui::Text("Draw input "); @@ -393,28 +337,32 @@ void deviceRenderDraw() 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 qsize = drawSamplesQueue.size(); - auto count = qsize;//std::min(qsize, drawChunkSize); - //if (count > 0) - // printf("draw: %9llu/%9lu\n", qsize, count); + auto count = std::min(drawSamplesQueue.size(), csize); for (auto i = count; i; --i) { *bufferCursor = drawSamplesQueue.front(); drawSamplesQueue.pop_front(); - if (++bufferCursor == buffer.end()) { + if (++bufferCursor == buffer.end()) bufferCursor = buffer.begin(); - - //auto now = std::chrono::high_resolution_clock::now(); - //std::chrono::duration diff = now - timee; - //timee = now; - //printf("\rtime: %.9lf", diff); - //fflush(stdout); + } + + 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(); } } } @@ -428,7 +376,7 @@ void deviceRenderDraw() const float di = static_cast(buffer.size()) / size.x; const float dx = std::ceil(size.x / static_cast(buffer.size())); - ImVec2 pp = p0; + ImVec2 pp = p0; float i = 0; while (pp.x < p0.x + size.x) { unsigned int idx = i; @@ -440,27 +388,20 @@ void deviceRenderDraw() pp = next; } - //const unsigned int didx = std::ceil(1.f / (size.x / static_cast(buffer.size()))); - //const auto dx = size.x / buffer.size(); - //ImVec2 pp = p0; - //for (auto i = 0u; i < buffer.size(); i += didx) { - // float n = std::clamp((buffer[i] - 2048.) / yMinMax, -0.5, 0.5); - - // ImVec2 next (pp.x + 1, p0.y + size.y * (0.5 - n)); - // drawList->AddLine(pp, next, ImGui::GetColorU32(IM_COL32(255, 0, 0, 255))); - // pp = next; - //} - - //if (drawSamplesInput) { - // pp = p0; - // for (auto i = 0u; i < drawSamplesBuf2.size(); i += didx) { - // ImVec2 next (pp.x + 1, - // p0.y + size.y - - // (static_cast(buffer[i]) / yMinMax) * size.y); - // drawList->AddLine(pp, next, ImGui::GetColorU32(IM_COL32(0, 0, 255, 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(); } } @@ -550,7 +491,7 @@ void deviceRenderToolbar() ImGui::SameLine(); ImGui::SetNextItemWidth(100); if (ImGui::BeginCombo("", sampleRatePreview)) { - for (int i = 0; i < sampleRateList.size() - 1; ++i) { + for (int i = 0; i < sampleRateList.size(); ++i) { if (ImGui::Selectable(sampleRateList[i])) { sampleRatePreview = sampleRateList[i]; if (m_device != nullptr && !m_device->is_running()) { diff --git a/source/gui.cpp b/source/gui.cpp index fad451e..47291c6 100644 --- a/source/gui.cpp +++ b/source/gui.cpp @@ -26,7 +26,7 @@ static decltype(SDL_GL_CreateContext(nullptr)) gl_context; bool guiInitialize() { - if (SDL_Init(SDL_INIT_VIDEO) != 0) { + if (SDL_Init(/*SDL_INIT_VIDEO*/0) != 0) { printf("Error: %s\n", SDL_GetError()); return false; } diff --git a/source/main.cpp b/source/main.cpp index 36e8c6e..bea1a21 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -17,7 +17,10 @@ #include "logview.h" #include "stmdsp.hpp" +#include +#include #include +#include extern ImFont *fontSans; extern ImFont *fontMono; @@ -61,6 +64,9 @@ int main(int, char **) codeEditorInit(); while (!done) { + auto endTime = std::chrono::steady_clock::now() + + std::chrono::milliseconds(static_cast(std::floor(1000. / 60.))); + guiHandleEvents(done); // Start the new window frame and render the menu bar. @@ -107,6 +113,8 @@ int main(int, char **) guiRender([] { ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData()); }); + + std::this_thread::sleep_until(endTime); } guiShutdown(); From 41ae9b3b1b6a75d12c39f1186e6d6e644f834c6f Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Thu, 28 Oct 2021 20:09:39 -0400 Subject: [PATCH 05/21] added cmake support --- CMakeLists.txt | 31 +++++++++ source/device.cpp | 145 ++++++++++++++++++++++++--------------- source/file.cpp | 5 +- source/stmdsp/stmdsp.cpp | 5 +- 4 files changed, 129 insertions(+), 57 deletions(-) create mode 100644 CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d042f12 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.10) + +project(stmdspgui VERSION 0.5) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +add_compile_options(-O0 -ggdb -g3 -Wall -Wextra -pedantic) + +file(GLOB SRC_IMGUI_BACKENDS "${CMAKE_SOURCE_DIR}/source/imgui/backends/*.cpp") +file(GLOB SRC_IMGUI "${CMAKE_SOURCE_DIR}/source/imgui/*.cpp") +file(GLOB SRC_STMDSP "${CMAKE_SOURCE_DIR}/source/stmdsp/*.cpp") +file(GLOB SRC_STMDSPGUI "${CMAKE_SOURCE_DIR}/source/*.cpp") + +add_executable(stmdspgui + source/serial/src/serial.cc + source/serial/src/impl/unix.cc + source/serial/src/impl/list_ports/list_ports_linux.cc + ${SRC_IMGUI_BACKENDS} + ${SRC_IMGUI} + ${SRC_STMDSP} + ${SRC_STMDSPGUI}) + +target_include_directories(stmdspgui PUBLIC + ${CMAKE_SOURCE_DIR}/source + ${CMAKE_SOURCE_DIR}/source/imgui + ${CMAKE_SOURCE_DIR}/source/stmdsp + ${CMAKE_SOURCE_DIR}/source/serial/include) + +target_link_libraries(stmdspgui PRIVATE SDL2 GL pthread) + diff --git a/source/device.cpp b/source/device.cpp index e5d7839..333fd81 100644 --- a/source/device.cpp +++ b/source/device.cpp @@ -11,9 +11,7 @@ /** * TODO list: - * - Test loading the signal generator with a formula. * - Improve signal generator audio streaming. - * - Log samples (should be good..?) */ #include "stmdsp.hpp" @@ -25,9 +23,9 @@ #include #include #include -#include #include #include +#include #include #include @@ -66,9 +64,9 @@ static bool popupRequestSiggen = false; static bool popupRequestDraw = false; static bool popupRequestLog = false; -static std::mutex mutexDrawSamples; -//static std::vector drawSamplesBuf; -static std::vector drawSamplesBuf2; +static std::timed_mutex mutexDrawSamples; +static std::timed_mutex mutexDeviceLoad; + static std::ofstream logSamplesFile; static wav::clip wavOutput; @@ -97,41 +95,64 @@ static void drawSamplesTask(stmdsp::device *device) const double sampleRate = sampleRateInts[m_device->get_sample_rate()]; unsigned long bufferTime = bufferSize / sampleRate * 0.975 * 1e6; + std::unique_lock lockDraw (mutexDrawSamples, std::defer_lock); + std::unique_lock lockDevice (mutexDeviceLoad, std::defer_lock); + while (m_device && m_device->is_running()) { auto next = std::chrono::high_resolution_clock::now() + std::chrono::microseconds(bufferTime); - auto chunk = m_device->continuous_read(); - while (chunk.empty() && m_device->is_running()) { - std::this_thread::sleep_for(std::chrono::microseconds(20)); + std::vector chunk; + + if (lockDevice.try_lock_until(next)) { chunk = m_device->continuous_read(); - } + int tries = -1; + while (chunk.empty() && m_device->is_running()) { + if (++tries == 100) + break; + std::this_thread::sleep_for(std::chrono::microseconds(20)); + chunk = m_device->continuous_read(); + } + lockDevice.unlock(); + } else { + // Cooldown. + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } if (drawSamplesInput && popupRequestDraw) { - auto chunk2 = m_device->continuous_read_input(); - while (chunk2.empty() && m_device->is_running()) { - std::this_thread::sleep_for(std::chrono::microseconds(20)); + std::vector chunk2; + + if (lockDevice.try_lock_for(std::chrono::milliseconds(1))) { chunk2 = m_device->continuous_read_input(); + int tries = -1; + while (chunk2.empty() && m_device->is_running()) { + if (++tries == 100) + break; + std::this_thread::sleep_for(std::chrono::microseconds(20)); + chunk2 = m_device->continuous_read_input(); + } + lockDevice.unlock(); } - { - std::scoped_lock lock (mutexDrawSamples); - auto i = chunk2.cbegin(); - for (const auto& s : chunk) { - drawSamplesQueue.push_back(s); - drawSamplesInputQueue.push_back(*i++); - } + lockDraw.lock(); + auto i = chunk2.cbegin(); + for (const auto& s : chunk) { + drawSamplesQueue.push_back(s); + drawSamplesInputQueue.push_back(*i++); } + lockDraw.unlock(); } else if (!doLogger) { - std::scoped_lock lock (mutexDrawSamples); + lockDraw.lock(); for (const auto& s : chunk) drawSamplesQueue.push_back(s); + lockDraw.unlock(); } else { - std::scoped_lock lock (mutexDrawSamples); + lockDraw.lock(); for (const auto& s : chunk) { drawSamplesQueue.push_back(s); logSamplesFile << s << '\n'; } + lockDraw.unlock(); } std::this_thread::sleep_until(next); @@ -143,33 +164,37 @@ static void feedSigGenTask(stmdsp::device *device) if (device == nullptr) return; - const auto bsize = m_device->get_buffer_size(); - const float srate = sampleRateInts[m_device->get_sample_rate()]; - const unsigned int delay = bsize / srate * 1000.f * 0.4f; + const auto bufferSize = m_device->get_buffer_size(); + const double sampleRate = sampleRateInts[m_device->get_sample_rate()]; + const unsigned long delay = bufferSize / sampleRate * 0.975 * 1e6; + + std::vector wavBuf (bufferSize, 2048); - auto wavBuf = new stmdsp::adcsample_t[bsize]; + std::unique_lock lockDevice (mutexDeviceLoad, std::defer_lock); - { - auto dst = wavBuf; - auto src = reinterpret_cast(wavOutput.next(bsize)); - for (auto i = 0u; i < bsize; ++i) - *dst++ = *src++ / 16 + 2048; - m_device->siggen_upload(wavBuf, bsize); - } + lockDevice.lock(); + // One (or both) of these freezes the device... + m_device->siggen_upload(wavBuf.data(), wavBuf.size()); + //m_device->siggen_start(); + lockDevice.unlock(); - m_device->siggen_start(); + std::this_thread::sleep_for(std::chrono::microseconds(delay)); + return; while (genRunning) { - auto dst = wavBuf; - auto src = reinterpret_cast(wavOutput.next(bsize)); - for (auto i = 0u; i < bsize; ++i) - *dst++ = *src++ / 16 + 2048; - m_device->siggen_upload(wavBuf, bsize); + auto next = std::chrono::high_resolution_clock::now() + + std::chrono::microseconds(delay); - std::this_thread::sleep_for(std::chrono::milliseconds(delay)); - } + auto src = reinterpret_cast(wavOutput.next(bufferSize)); + for (auto& w : wavBuf) + w = *src++ / 16 + 2048; - delete[] wavBuf; + if (lockDevice.try_lock_until(next)) { + m_device->siggen_upload(wavBuf.data(), wavBuf.size()); + lockDevice.unlock(); + std::this_thread::sleep_until(next); + } + } } static void deviceConnect(); @@ -273,7 +298,10 @@ void deviceRenderWidgets() ImGui::EndPopup(); } - if (ImGuiFileDialog::Instance()->Display("ChooseFileLogGen")) { + 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); @@ -291,9 +319,9 @@ void deviceRenderWidgets() if (logSamplesFile.good()) log("Log file ready."); } - - ImGuiFileDialog::Instance()->Close(); } + + ImGuiFileDialog::Instance()->Close(); } } @@ -513,16 +541,23 @@ void deviceConnect() if (m_device == nullptr) { stmdsp::scanner scanner; if (auto devices = scanner.scan(); devices.size() > 0) { - m_device = new stmdsp::device(devices.front()); - if (m_device->connected()) { - auto sri = m_device->get_sample_rate(); - sampleRatePreview = sampleRateList[sri]; - drawSamplesBufferSize = std::round(sampleRateInts[sri] * drawSamplesTimeframe); - log("Connected!"); - } else { - delete m_device; - m_device = nullptr; - log("Failed to connect."); + try { + m_device = new stmdsp::device(devices.front()); + } catch (...) { + log("Failed to connect (check permissions?)."); + m_device = nullptr; + } + if (m_device != nullptr) { + if (m_device->connected()) { + auto sri = m_device->get_sample_rate(); + sampleRatePreview = sampleRateList[sri]; + drawSamplesBufferSize = std::round(sampleRateInts[sri] * drawSamplesTimeframe); + log("Connected!"); + } else { + delete m_device; + m_device = nullptr; + log("Failed to connect."); + } } } else { log("No devices found."); diff --git a/source/file.cpp b/source/file.cpp index f6222fa..96dac0a 100644 --- a/source/file.cpp +++ b/source/file.cpp @@ -122,7 +122,10 @@ void fileRenderMenu() void fileRenderDialog() { - if (ImGuiFileDialog::Instance()->Display("ChooseFileOpenSave")) { + if (ImGuiFileDialog::Instance()->Display("ChooseFileOpenSave", + ImGuiWindowFlags_NoCollapse, + ImVec2(460, 540))) + { if (ImGuiFileDialog::Instance()->IsOk()) { std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName(); diff --git a/source/stmdsp/stmdsp.cpp b/source/stmdsp/stmdsp.cpp index 93c52dd..d219d38 100644 --- a/source/stmdsp/stmdsp.cpp +++ b/source/stmdsp/stmdsp.cpp @@ -27,7 +27,7 @@ namespace stmdsp } device::device(const std::string& file) : - m_serial(file, 8'000'000/*230400*/, serial::Timeout::simpleTimeout(50)) + m_serial(file, 8'000'000, serial::Timeout::simpleTimeout(50)) { if (m_serial.isOpen()) { m_serial.flush(); @@ -178,6 +178,9 @@ namespace stmdsp m_serial.write(request, 3); m_serial.write((uint8_t *)buffer, size * sizeof(dacsample_t)); + // TODO + if (!m_is_running) + m_serial.write((uint8_t *)buffer, size * sizeof(dacsample_t)); } } From 12440b673f55eb4bfac5f553923de7ce9508a2a7 Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Sat, 30 Oct 2021 10:08:11 -0400 Subject: [PATCH 06/21] new file on startup; styling; bug fixes --- .gitignore | 1 + CMakeLists.txt | 4 +++- source/device.cpp | 2 +- source/file.cpp | 18 +++++++++--------- source/gui.cpp | 6 +++++- source/main.cpp | 2 ++ 6 files changed, 21 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index debda6d..d7ed0eb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +build/ imgui.ini stmdspgui stmdspgui.exe diff --git a/CMakeLists.txt b/CMakeLists.txt index d042f12..54f7086 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,13 +5,15 @@ project(stmdspgui VERSION 0.5) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED True) -add_compile_options(-O0 -ggdb -g3 -Wall -Wextra -pedantic) +add_compile_options(-O0 -ggdb -g3) file(GLOB SRC_IMGUI_BACKENDS "${CMAKE_SOURCE_DIR}/source/imgui/backends/*.cpp") file(GLOB SRC_IMGUI "${CMAKE_SOURCE_DIR}/source/imgui/*.cpp") file(GLOB SRC_STMDSP "${CMAKE_SOURCE_DIR}/source/stmdsp/*.cpp") file(GLOB SRC_STMDSPGUI "${CMAKE_SOURCE_DIR}/source/*.cpp") +set_property(SOURCE ${SRC_STMDSPGUI} PROPERTY COMPILE_FLAGS "-Wall -Wextra -Wpedantic") + add_executable(stmdspgui source/serial/src/serial.cc source/serial/src/impl/unix.cc diff --git a/source/device.cpp b/source/device.cpp index 333fd81..6f052d7 100644 --- a/source/device.cpp +++ b/source/device.cpp @@ -519,7 +519,7 @@ void deviceRenderToolbar() ImGui::SameLine(); ImGui::SetNextItemWidth(100); if (ImGui::BeginCombo("", sampleRatePreview)) { - for (int i = 0; i < sampleRateList.size(); ++i) { + for (unsigned int i = 0; i < sampleRateList.size(); ++i) { if (ImGui::Selectable(sampleRateList[i])) { sampleRatePreview = sampleRateList[i]; if (m_device != nullptr && !m_device->is_running()) { diff --git a/source/file.cpp b/source/file.cpp index 96dac0a..0f0015c 100644 --- a/source/file.cpp +++ b/source/file.cpp @@ -56,6 +56,12 @@ static void openCurrentFile() } } +void openNewFile() +{ + fileCurrentPath.clear(); + editor.SetText(stmdsp::file_content); +} + void fileScanTemplates() { auto path = std::filesystem::current_path() / "templates"; @@ -68,8 +74,7 @@ void fileRenderMenu() if (ImGui::BeginMenu("File")) { if (ImGui::MenuItem("New")) { // TODO modified? - fileCurrentPath.clear(); - editor.SetText(stmdsp::file_content); + openNewFile(); log("Ready."); } @@ -129,18 +134,13 @@ void fileRenderDialog() if (ImGuiFileDialog::Instance()->IsOk()) { std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName(); - switch (fileAction) { - case FileAction::None: - break; - case FileAction::Open: + if (fileAction == FileAction::Open) { fileCurrentPath = filePathName; openCurrentFile(); log("Ready."); - break; - case FileAction::SaveAs: + } else if (fileAction == FileAction::SaveAs) { fileCurrentPath = filePathName; saveCurrentFile(); - break; } } diff --git a/source/gui.cpp b/source/gui.cpp index 47291c6..2ecf8ee 100644 --- a/source/gui.cpp +++ b/source/gui.cpp @@ -57,6 +57,11 @@ bool guiInitialize() ImGui_ImplSDL2_InitForOpenGL(window, gl_context); ImGui_ImplOpenGL2_Init(); + ImGuiStyle& style = ImGui::GetStyle(); + style.WindowRounding = 5; + style.FrameRounding = 3; + style.ScrollbarRounding = 1; + return true; } @@ -71,7 +76,6 @@ void guiRender(void (*func)()) void guiHandleEvents(bool& done) { - SDL_Event event; for (SDL_Event event; SDL_PollEvent(&event);) { ImGui_ImplSDL2_ProcessEvent(&event); if (event.type == SDL_QUIT) diff --git a/source/main.cpp b/source/main.cpp index bea1a21..e112118 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -33,6 +33,7 @@ extern void guiRender(void (*func)()); extern void fileRenderMenu(); extern void fileRenderDialog(); extern void fileScanTemplates(); +extern void openNewFile(); extern void codeEditorInit(); extern void codeRenderMenu(); @@ -62,6 +63,7 @@ int main(int, char **) fileScanTemplates(); codeEditorInit(); + openNewFile(); while (!done) { auto endTime = std::chrono::steady_clock::now() + From 79032a73d586df88e4438c1d3809b726d9d69600 Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Sat, 30 Oct 2021 16:50:03 -0400 Subject: [PATCH 07/21] status monitoring; improve connection safety --- source/code.cpp | 4 +- source/device.cpp | 95 +++++++++----- source/main.cpp | 1 - source/stmdsp/stmdsp.cpp | 268 ++++++++++++++++++++++++++++----------- source/stmdsp/stmdsp.hpp | 49 +++++-- 5 files changed, 295 insertions(+), 122 deletions(-) diff --git a/source/code.cpp b/source/code.cpp index 2f10b90..e290440 100644 --- a/source/code.cpp +++ b/source/code.cpp @@ -24,7 +24,7 @@ #include #include -extern stmdsp::device *m_device; +extern std::shared_ptr m_device; extern void log(const std::string& str); @@ -75,7 +75,7 @@ void compileEditorCode() } stmdsp::platform platform; - if (m_device != nullptr) { + if (m_device) { platform = m_device->get_platform(); } else { // Assume a default. diff --git a/source/device.cpp b/source/device.cpp index 6f052d7..cfadd6e 100644 --- a/source/device.cpp +++ b/source/device.cpp @@ -26,15 +26,17 @@ #include #include #include +#include #include #include extern std::string tempFileName; -extern stmdsp::device *m_device; 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", @@ -75,18 +77,18 @@ static std::deque drawSamplesInputQueue; static double drawSamplesTimeframe = 1.0; // seconds static unsigned int drawSamplesBufferSize = 1; -static void measureCodeTask(stmdsp::device *device) +static void measureCodeTask(std::shared_ptr device) { - if (device == nullptr) + if (!device) return; std::this_thread::sleep_for(std::chrono::milliseconds(1000)); auto cycles = device->continuous_start_get_measurement(); log(std::string("Execution time: ") + std::to_string(cycles) + " cycles."); } -static void drawSamplesTask(stmdsp::device *device) +static void drawSamplesTask(std::shared_ptr device) { - if (device == nullptr) + if (!device) return; const bool doLogger = logResults && logSamplesFile.good(); @@ -159,9 +161,9 @@ static void drawSamplesTask(stmdsp::device *device) } } -static void feedSigGenTask(stmdsp::device *device) +static void feedSigGenTask(std::shared_ptr device) { - if (device == nullptr) + if (!device) return; const auto bufferSize = m_device->get_buffer_size(); @@ -197,6 +199,31 @@ static void feedSigGenTask(stmdsp::device *device) } } +static void statusTask(std::shared_ptr device) +{ + if (!device) + return; + + while (device->connected()) { + std::unique_lock lockDevice (mutexDeviceLoad, std::defer_lock); + lockDevice.lock(); + auto [status, error] = device->get_status(); + lockDevice.unlock(); + + if (error != stmdsp::Error::None) { + if (error == stmdsp::Error::NotIdle) { + log("Error: Device already running..."); + } else if (error == stmdsp::Error::ConversionAborted) { + log("Error: Algorithm unloaded, a fault occurred!"); + } else { + log("Error: Device had an issue..."); + } + } + + std::this_thread::sleep_for(std::chrono::seconds(1)); + } +} + static void deviceConnect(); static void deviceStart(); static void deviceAlgorithmUpload(); @@ -286,7 +313,7 @@ void deviceRenderWidgets() ImGui::InputText("", bufferSizeStr, sizeof(bufferSizeStr), ImGuiInputTextFlags_CharsDecimal); ImGui::PopStyleColor(); if (ImGui::Button("Save")) { - if (m_device != nullptr) { + if (m_device) { int n = std::clamp(std::stoi(bufferSizeStr), 100, 4096); m_device->continuous_set_buffer_size(n); } @@ -437,13 +464,14 @@ void deviceRenderDraw() void deviceRenderMenu() { if (ImGui::BeginMenu("Run")) { - bool isConnected = m_device != nullptr; + bool isConnected = m_device ? true : false; bool isRunning = isConnected && m_device->is_running(); static const char *connectLabel = "Connect"; - if (ImGui::MenuItem(connectLabel)) { + if (ImGui::MenuItem(connectLabel, nullptr, false, !isConnected || (isConnected && !isRunning))) { deviceConnect(); - connectLabel = m_device == nullptr ? "Connect" : "Disconnect"; + isConnected = m_device ? true : false; + connectLabel = isConnected ? "Disconnect" : "Connect"; } ImGui::Separator(); @@ -453,9 +481,9 @@ void deviceRenderMenu() deviceStart(); } - if (ImGui::MenuItem("Upload algorithm", nullptr, false, isConnected)) + if (ImGui::MenuItem("Upload algorithm", nullptr, false, isConnected && !isRunning)) deviceAlgorithmUpload(); - if (ImGui::MenuItem("Unload algorithm", nullptr, false, isConnected)) + if (ImGui::MenuItem("Unload algorithm", nullptr, false, isConnected && !isRunning)) deviceAlgorithmUnload(); ImGui::Separator(); if (ImGui::Checkbox("Measure Code Time", &measureCodeTime)) { @@ -480,16 +508,16 @@ void deviceRenderMenu() logResults = false; } } - if (ImGui::MenuItem("Set buffer size...", nullptr, false, isConnected)) { + if (ImGui::MenuItem("Set buffer size...", nullptr, false, isConnected && !isRunning)) { popupRequestBuffer = true; } ImGui::Separator(); - if (ImGui::MenuItem("Load signal generator", nullptr, false, isConnected)) { + if (ImGui::MenuItem("Load signal generator", nullptr, false, isConnected && !isRunning)) { popupRequestSiggen = true; } static const char *startSiggenLabel = "Start signal generator"; if (ImGui::MenuItem(startSiggenLabel, nullptr, false, isConnected)) { - if (m_device != nullptr) { + if (m_device) { if (!genRunning) { genRunning = true; if (wavOutput.valid()) @@ -522,7 +550,7 @@ void deviceRenderToolbar() for (unsigned int i = 0; i < sampleRateList.size(); ++i) { if (ImGui::Selectable(sampleRateList[i])) { sampleRatePreview = sampleRateList[i]; - if (m_device != nullptr && !m_device->is_running()) { + if (m_device && !m_device->is_running()) { do { m_device->set_sample_rate(i); std::this_thread::sleep_for(std::chrono::milliseconds(10)); @@ -538,24 +566,28 @@ void deviceRenderToolbar() void deviceConnect() { - if (m_device == nullptr) { + static std::thread statusThread; + + if (!m_device) { stmdsp::scanner scanner; if (auto devices = scanner.scan(); devices.size() > 0) { try { - m_device = new stmdsp::device(devices.front()); + m_device.reset(new stmdsp::device(devices.front())); } catch (...) { log("Failed to connect (check permissions?)."); - m_device = nullptr; + m_device.reset(); } - if (m_device != nullptr) { + + 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(); } else { - delete m_device; - m_device = nullptr; + m_device.reset(); log("Failed to connect."); } } @@ -563,15 +595,17 @@ void deviceConnect() log("No devices found."); } } else { - delete m_device; - m_device = nullptr; + m_device->disconnect(); + if (statusThread.joinable()) + statusThread.join(); + m_device.reset(); log("Disconnected."); } } void deviceStart() { - if (m_device == nullptr) { + if (!m_device) { log("No device connected."); return; } @@ -579,6 +613,7 @@ void deviceStart() if (m_device->is_running()) { { std::scoped_lock lock (mutexDrawSamples); + std::scoped_lock lock2 (mutexDeviceLoad); std::this_thread::sleep_for(std::chrono::microseconds(150)); m_device->continuous_stop(); } @@ -603,7 +638,7 @@ void deviceStart() void deviceAlgorithmUpload() { - if (m_device == nullptr) { + if (!m_device) { log("No device connected."); return; } @@ -625,7 +660,7 @@ void deviceAlgorithmUpload() void deviceAlgorithmUnload() { - if (m_device == nullptr) { + if (!m_device) { log("No device connected."); return; } @@ -660,7 +695,7 @@ void deviceGenLoadList(std::string_view listStr) if ((samples.size() & 1) == 1) samples.push_back(samples.back()); - if (m_device != nullptr) + if (m_device) m_device->siggen_upload(&samples[0], samples.size()); log("Generator ready."); } else { @@ -673,7 +708,7 @@ void deviceGenLoadFormula(std::string_view formula) auto samples = deviceGenLoadFormulaEval(formula); if (samples.size() > 0) { - if (m_device != nullptr) + if (m_device) m_device->siggen_upload(&samples[0], samples.size()); log("Generator ready."); diff --git a/source/main.cpp b/source/main.cpp index e112118..b98ec2b 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -47,7 +47,6 @@ extern void deviceRenderWidgets(); // Globals that live here bool done = false; -stmdsp::device *m_device = nullptr; static LogView logView; diff --git a/source/stmdsp/stmdsp.cpp b/source/stmdsp/stmdsp.cpp index d219d38..650dbc4 100644 --- a/source/stmdsp/stmdsp.cpp +++ b/source/stmdsp/stmdsp.cpp @@ -13,6 +13,8 @@ #include +extern void log(const std::string& str); + namespace stmdsp { std::list& scanner::scan() @@ -26,25 +28,44 @@ namespace stmdsp return m_available_devices; } - device::device(const std::string& file) : - m_serial(file, 8'000'000, serial::Timeout::simpleTimeout(50)) + device::device(const std::string& file) { - if (m_serial.isOpen()) { - m_serial.flush(); - m_serial.write("i"); - if (auto id = m_serial.read(7); id.starts_with("stmdsp")) { - if (id.back() == 'h') - m_platform = platform::H7; - else if (id.back() == 'l') - m_platform = platform::L4; - else - m_serial.close(); - } else { - m_serial.close(); - } + // This could throw! + m_serial.reset(new serial::Serial(file, 8'000'000, serial::Timeout::simpleTimeout(50))); + + m_serial->flush(); + m_serial->write("i"); + auto id = m_serial->read(7); + + if (id.starts_with("stmdsp")) { + if (id.back() == 'h') + m_platform = platform::H7; + else if (id.back() == 'l') + m_platform = platform::L4; + else + m_serial.release(); + } else { + m_serial.release(); } } + device::~device() + { + disconnect(); + } + + bool device::connected() { + if (m_serial && !m_serial->isOpen()) + m_serial.release(); + + return m_serial ? true : false; + } + + void device::disconnect() { + if (m_serial) + m_serial.release(); + } + void device::continuous_set_buffer_size(unsigned int size) { if (connected()) { m_buffer_size = size; @@ -54,7 +75,13 @@ namespace stmdsp static_cast(size), static_cast(size >> 8) }; - m_serial.write(request, 3); + + try { + m_serial->write(request, 3); + } catch (...) { + m_serial.release(); + log("Lost connection!"); + } } } @@ -64,7 +91,13 @@ namespace stmdsp 'r', static_cast(id) }; - m_serial.write(request, 2); + + try { + m_serial->write(request, 2); + } catch (...) { + m_serial.release(); + log("Lost connection!"); + } } } @@ -73,10 +106,16 @@ namespace stmdsp uint8_t request[2] = { 'r', 0xFF }; - m_serial.write(request, 2); unsigned char result = 0xFF; - m_serial.read(&result, 1); + try { + m_serial->write(request, 2); + m_serial->read(&result, 1); + } catch (...) { + m_serial.release(); + log("Lost connection!"); + } + m_sample_rate = result; } @@ -85,23 +124,38 @@ namespace stmdsp void device::continuous_start() { if (connected()) { - m_serial.write("R"); - m_is_running = true; + try { + m_serial->write("R"); + m_is_running = true; + } catch (...) { + m_serial.release(); + log("Lost connection!"); + } } } void device::continuous_start_measure() { if (connected()) { - m_serial.write("M"); - m_is_running = true; + try { + m_serial->write("M"); + m_is_running = true; + } catch (...) { + m_serial.release(); + log("Lost connection!"); + } } } uint32_t device::continuous_start_get_measurement() { uint32_t count = 0; if (connected()) { - m_serial.write("m"); - m_serial.read(reinterpret_cast(&count), sizeof(uint32_t)); + try { + m_serial->write("m"); + m_serial->read(reinterpret_cast(&count), sizeof(uint32_t)); + } catch (...) { + m_serial.release(); + log("Lost connection!"); + } } return count / 2; @@ -109,25 +163,30 @@ namespace stmdsp std::vector device::continuous_read() { if (connected()) { - m_serial.write("s"); - unsigned char sizebytes[2]; - m_serial.read(sizebytes, 2); - unsigned int size = sizebytes[0] | (sizebytes[1] << 8); - if (size > 0) { - std::vector data (size); - unsigned int total = size * sizeof(adcsample_t); - unsigned int offset = 0; - - while (total > 512) { - m_serial.read(reinterpret_cast(&data[0]) + offset, 512); - m_serial.write("n"); - offset += 512; - total -= 512; - } - m_serial.read(reinterpret_cast(&data[0]) + offset, total); - m_serial.write("n"); - return data; + try { + m_serial->write("s"); + unsigned char sizebytes[2]; + m_serial->read(sizebytes, 2); + unsigned int size = sizebytes[0] | (sizebytes[1] << 8); + if (size > 0) { + std::vector data (size); + unsigned int total = size * sizeof(adcsample_t); + unsigned int offset = 0; + while (total > 512) { + m_serial->read(reinterpret_cast(&data[0]) + offset, 512); + m_serial->write("n"); + offset += 512; + total -= 512; + } + m_serial->read(reinterpret_cast(&data[0]) + offset, total); + m_serial->write("n"); + return data; + + } + } catch (...) { + m_serial.release(); + log("Lost connection!"); } } @@ -136,25 +195,30 @@ namespace stmdsp std::vector device::continuous_read_input() { if (connected()) { - m_serial.write("t"); - unsigned char sizebytes[2]; - m_serial.read(sizebytes, 2); - unsigned int size = sizebytes[0] | (sizebytes[1] << 8); - if (size > 0) { - std::vector data (size); - unsigned int total = size * sizeof(adcsample_t); - unsigned int offset = 0; - - while (total > 512) { - m_serial.read(reinterpret_cast(&data[0]) + offset, 512); - m_serial.write("n"); - offset += 512; - total -= 512; - } - m_serial.read(reinterpret_cast(&data[0]) + offset, total); - m_serial.write("n"); - return data; + try { + m_serial->write("t"); + unsigned char sizebytes[2]; + m_serial->read(sizebytes, 2); + unsigned int size = sizebytes[0] | (sizebytes[1] << 8); + if (size > 0) { + std::vector data (size); + unsigned int total = size * sizeof(adcsample_t); + unsigned int offset = 0; + while (total > 512) { + m_serial->read(reinterpret_cast(&data[0]) + offset, 512); + m_serial->write("n"); + offset += 512; + total -= 512; + } + m_serial->read(reinterpret_cast(&data[0]) + offset, total); + m_serial->write("n"); + return data; + + } + } catch (...) { + m_serial.release(); + log("Lost connection!"); } } @@ -163,8 +227,13 @@ namespace stmdsp void device::continuous_stop() { if (connected()) { - m_serial.write("S"); - m_is_running = false; + try { + m_serial->write("S"); + m_is_running = false; + } catch (...) { + m_serial.release(); + log("Lost connection!"); + } } } @@ -175,26 +244,39 @@ namespace stmdsp static_cast(size), static_cast(size >> 8) }; - m_serial.write(request, 3); - m_serial.write((uint8_t *)buffer, size * sizeof(dacsample_t)); - // TODO - if (!m_is_running) - m_serial.write((uint8_t *)buffer, size * sizeof(dacsample_t)); + try { + m_serial->write(request, 3); + // TODO different write size if feeding audio? + m_serial->write((uint8_t *)buffer, size * sizeof(dacsample_t)); + } catch (...) { + m_serial.release(); + log("Lost connection!"); + } } } void device::siggen_start() { if (connected()) { - m_is_siggening = true; - m_serial.write("W"); + try { + m_serial->write("W"); + m_is_siggening = true; + } catch (...) { + m_serial.release(); + log("Lost connection!"); + } } } void device::siggen_stop() { if (connected()) { - m_is_siggening = false; - m_serial.write("w"); + try { + m_serial->write("w"); + m_is_siggening = false; + } catch (...) { + m_serial.release(); + log("Lost connection!"); + } } } @@ -205,14 +287,48 @@ namespace stmdsp static_cast(size), static_cast(size >> 8) }; - m_serial.write(request, 3); - m_serial.write(buffer, size); + try { + m_serial->write(request, 3); + m_serial->write(buffer, size); + } catch (...) { + m_serial.release(); + log("Lost connection!"); + } } } void device::unload_filter() { - if (connected()) - m_serial.write("e"); + if (connected()) { + try { + m_serial->write("e"); + } catch (...) { + m_serial.release(); + log("Lost connection!"); + } + } + } + + std::pair device::get_status() { + std::pair ret; + + if (connected()) { + try { + m_serial->write("I"); + auto result = m_serial->read(2); + ret = {static_cast(result[0]), + static_cast(result[1])}; + + bool running = ret.first == RunStatus::Running; + if (m_is_running != running) + m_is_running = running; + } catch (...) { + m_serial.release(); + log("Lost connection!"); + } + } + + return ret; } } + diff --git a/source/stmdsp/stmdsp.hpp b/source/stmdsp/stmdsp.hpp index 0b9398e..76ca94e 100644 --- a/source/stmdsp/stmdsp.hpp +++ b/source/stmdsp/stmdsp.hpp @@ -12,15 +12,34 @@ #ifndef STMDSP_HPP_ #define STMDSP_HPP_ +#include + #include #include -#include +#include #include +#include namespace stmdsp { constexpr unsigned int SAMPLES_MAX = 4096; + enum class RunStatus : char { + Idle = '1', + Running, + Recovering + }; + + enum class Error : char { + None = 0, + BadParam, + BadParamSize, + BadUserCodeLoad, + BadUserCodeSize, + NotIdle, + ConversionAborted + }; + class scanner { private: @@ -46,39 +65,41 @@ namespace stmdsp enum class platform { Unknown, - H7, - L4, - G4 + H7, /* Behind in feature support */ + L4, /* Complete feature support */ + G4 /* Currently unsupported */ }; class device { public: device(const std::string& file); + ~device(); - ~device() { - m_serial.close(); - } - - bool connected() { - return m_serial.isOpen(); - } + bool connected(); + void disconnect(); auto get_platform() const { return m_platform; } + void continuous_set_buffer_size(unsigned int size); unsigned int get_buffer_size() const { return m_buffer_size; } + void set_sample_rate(unsigned int id); unsigned int get_sample_rate(); + void continuous_start(); + void continuous_stop(); + void continuous_start_measure(); uint32_t continuous_start_get_measurement(); + std::vector continuous_read(); std::vector continuous_read_input(); - void continuous_stop(); void siggen_upload(dacsample_t *buffer, unsigned int size); void siggen_start(); void siggen_stop(); + bool is_siggening() const { return m_is_siggening; } bool is_running() const { return m_is_running; } @@ -86,8 +107,10 @@ namespace stmdsp void upload_filter(unsigned char *buffer, size_t size); void unload_filter(); + std::pair get_status(); + private: - serial::Serial m_serial; + std::unique_ptr m_serial; platform m_platform = platform::Unknown; unsigned int m_buffer_size = SAMPLES_MAX; unsigned int m_sample_rate = 0; From 4306970fd3b7c13d1a1a69ed48111a738f2e80ba Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Sun, 31 Oct 2021 08:49:30 -0400 Subject: [PATCH 08/21] proper checkbox disable; basic themeing --- source/device.cpp | 60 ++++++++++++++++++++++------------------------- source/gui.cpp | 38 ++++++++++++++++++++++++++++-- 2 files changed, 64 insertions(+), 34 deletions(-) diff --git a/source/device.cpp b/source/device.cpp index cfadd6e..399bf7d 100644 --- a/source/device.cpp +++ b/source/device.cpp @@ -17,6 +17,7 @@ #include "stmdsp.hpp" #include "imgui.h" +#include "imgui_internal.h" #include "ImGuiFileDialog.h" #include "wav.hpp" @@ -106,7 +107,7 @@ static void drawSamplesTask(std::shared_ptr device) std::vector chunk; - if (lockDevice.try_lock_until(next)) { + if (lockDevice.try_lock_until(next)) { chunk = m_device->continuous_read(); int tries = -1; while (chunk.empty() && m_device->is_running()) { @@ -115,16 +116,16 @@ static void drawSamplesTask(std::shared_ptr device) std::this_thread::sleep_for(std::chrono::microseconds(20)); chunk = m_device->continuous_read(); } - lockDevice.unlock(); - } else { + lockDevice.unlock(); + } else { // Cooldown. std::this_thread::sleep_for(std::chrono::milliseconds(500)); - } + } if (drawSamplesInput && popupRequestDraw) { std::vector chunk2; - if (lockDevice.try_lock_for(std::chrono::milliseconds(1))) { + if (lockDevice.try_lock_for(std::chrono::milliseconds(1))) { chunk2 = m_device->continuous_read_input(); int tries = -1; while (chunk2.empty() && m_device->is_running()) { @@ -133,28 +134,28 @@ static void drawSamplesTask(std::shared_ptr device) std::this_thread::sleep_for(std::chrono::microseconds(20)); chunk2 = m_device->continuous_read_input(); } - lockDevice.unlock(); + lockDevice.unlock(); } - lockDraw.lock(); + lockDraw.lock(); auto i = chunk2.cbegin(); for (const auto& s : chunk) { drawSamplesQueue.push_back(s); drawSamplesInputQueue.push_back(*i++); } - lockDraw.unlock(); + lockDraw.unlock(); } else if (!doLogger) { - lockDraw.lock(); + lockDraw.lock(); for (const auto& s : chunk) drawSamplesQueue.push_back(s); - lockDraw.unlock(); + lockDraw.unlock(); } else { - lockDraw.lock(); + lockDraw.lock(); for (const auto& s : chunk) { drawSamplesQueue.push_back(s); logSamplesFile << s << '\n'; } - lockDraw.unlock(); + lockDraw.unlock(); } std::this_thread::sleep_until(next); @@ -193,7 +194,7 @@ static void feedSigGenTask(std::shared_ptr device) if (lockDevice.try_lock_until(next)) { m_device->siggen_upload(wavBuf.data(), wavBuf.size()); - lockDevice.unlock(); + lockDevice.unlock(); std::this_thread::sleep_until(next); } } @@ -485,29 +486,24 @@ void deviceRenderMenu() deviceAlgorithmUpload(); if (ImGui::MenuItem("Unload algorithm", nullptr, false, isConnected && !isRunning)) deviceAlgorithmUnload(); + ImGui::Separator(); - if (ImGui::Checkbox("Measure Code Time", &measureCodeTime)) { - if (!isConnected) - measureCodeTime = false; - } + if (!isConnected || isRunning) + ImGui::PushDisabled(); + ImGui::Checkbox("Measure Code Time", &measureCodeTime); if (ImGui::Checkbox("Draw samples", &drawSamples)) { - if (isConnected) { - if (drawSamples) - popupRequestDraw = true; - } else { - drawSamples = false; - } + if (drawSamples) + popupRequestDraw = true; } if (ImGui::Checkbox("Log results...", &logResults)) { - if (isConnected) { - if (logResults) - popupRequestLog = true; - else if (logSamplesFile.is_open()) - logSamplesFile.close(); - } else { - logResults = false; - } + 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)) { popupRequestBuffer = true; } @@ -575,7 +571,7 @@ void deviceConnect() m_device.reset(new stmdsp::device(devices.front())); } catch (...) { log("Failed to connect (check permissions?)."); - m_device.reset(); + m_device.reset(); } if (m_device) { diff --git a/source/gui.cpp b/source/gui.cpp index 2ecf8ee..9720442 100644 --- a/source/gui.cpp +++ b/source/gui.cpp @@ -10,6 +10,7 @@ */ #include "imgui.h" +#include "imgui_internal.h" #include "backends/imgui_impl_sdl.h" #include "backends/imgui_impl_opengl2.h" @@ -49,19 +50,52 @@ bool guiInitialize() IMGUI_CHECKVERSION(); ImGui::CreateContext(); io = &ImGui::GetIO(); - io->ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + //io->ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; fontSans = io->Fonts->AddFontFromFileTTF("fonts/Roboto-Regular.ttf", 20); fontMono = io->Fonts->AddFontFromFileTTF("fonts/RobotoMono-Regular.ttf", 20); - ImGui::StyleColorsLight(); ImGui_ImplSDL2_InitForOpenGL(window, gl_context); ImGui_ImplOpenGL2_Init(); + ImGui::StyleColorsLight(); ImGuiStyle& style = ImGui::GetStyle(); style.WindowRounding = 5; style.FrameRounding = 3; style.ScrollbarRounding = 1; +//#define ACCENT1 0.26f, 0.59f, 0.98f +#define ACCENT1 0.6f, 0.6f, 0.6f +#define ACCENT2 0.4f, 0.4f, 0.4f + + style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(ACCENT1, 0.40f); + style.Colors[ImGuiCol_FrameBgActive] = ImVec4(ACCENT1, 0.67f); + style.Colors[ImGuiCol_CheckMark] = ImVec4(ACCENT1, 1.00f); + style.Colors[ImGuiCol_SliderGrab] = ImVec4(ACCENT1, 0.78f); + style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(0.46f, 0.54f, 0.80f, 0.60f); + style.Colors[ImGuiCol_Button] = ImVec4(ACCENT1, 0.40f); + style.Colors[ImGuiCol_ButtonHovered] = ImVec4(ACCENT1, 1.00f); + style.Colors[ImGuiCol_ButtonActive] = ImVec4(ACCENT2, 1.00f); + style.Colors[ImGuiCol_Header] = ImVec4(ACCENT1, 0.31f); + style.Colors[ImGuiCol_HeaderHovered] = ImVec4(ACCENT1, 0.80f); + style.Colors[ImGuiCol_HeaderActive] = ImVec4(ACCENT1, 1.00f); + style.Colors[ImGuiCol_Separator] = ImVec4(0.39f, 0.39f, 0.39f, 0.62f); + style.Colors[ImGuiCol_SeparatorHovered] = ImVec4(0.14f, 0.44f, 0.80f, 0.78f); + style.Colors[ImGuiCol_SeparatorActive] = ImVec4(0.14f, 0.44f, 0.80f, 1.00f); + style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(ACCENT1, 0.67f); + style.Colors[ImGuiCol_ResizeGripActive] = ImVec4(ACCENT1, 0.95f); + style.Colors[ImGuiCol_TableHeaderBg] = ImVec4(0.78f, 0.87f, 0.98f, 1.00f); + style.Colors[ImGuiCol_TableBorderStrong] = ImVec4(0.57f, 0.57f, 0.64f, 1.00f); + style.Colors[ImGuiCol_TableBorderLight] = ImVec4(0.68f, 0.68f, 0.74f, 1.00f); + style.Colors[ImGuiCol_TextSelectedBg] = ImVec4(ACCENT1, 0.35f); + style.Colors[ImGuiCol_DragDropTarget] = ImVec4(ACCENT1, 0.95f); + + style.Colors[ImGuiCol_Tab] = ImLerp(style.Colors[ImGuiCol_Header], style.Colors[ImGuiCol_TitleBgActive], 0.90f); + style.Colors[ImGuiCol_TabHovered] = style.Colors[ImGuiCol_HeaderHovered]; + style.Colors[ImGuiCol_TabActive] = ImLerp(style.Colors[ImGuiCol_HeaderActive], style.Colors[ImGuiCol_TitleBgActive], 0.60f); + style.Colors[ImGuiCol_TabUnfocused] = ImLerp(style.Colors[ImGuiCol_Tab], style.Colors[ImGuiCol_TitleBg], 0.80f); + style.Colors[ImGuiCol_TabUnfocusedActive] = ImLerp(style.Colors[ImGuiCol_TabActive], style.Colors[ImGuiCol_TitleBg], 0.40f); + style.Colors[ImGuiCol_NavHighlight] = style.Colors[ImGuiCol_HeaderHovered]; + return true; } From f6318e328402a5e839d24e7d52f5bb13062bccdc Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Sat, 20 Nov 2021 16:55:26 -0500 Subject: [PATCH 09/21] code cleanup; fix audio streaming --- source/code.cpp | 133 +++++++++++----------- source/device.cpp | 230 ++++++++++++++++++++------------------- source/file.cpp | 26 ++++- source/main.cpp | 37 +++---- source/stmdsp/stmdsp.cpp | 218 ++++++++++++++++--------------------- source/stmdsp/stmdsp.hpp | 96 +++++++++++----- source/wav.hpp | 30 ++--- 7 files changed, 399 insertions(+), 371 deletions(-) diff --git a/source/code.cpp b/source/code.cpp index e290440..163d6e5 100644 --- a/source/code.cpp +++ b/source/code.cpp @@ -33,6 +33,8 @@ 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(); @@ -64,6 +66,39 @@ 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..."); @@ -74,35 +109,28 @@ void compileEditorCode() std::filesystem::remove(tempFileName + ".orig.o"); } - stmdsp::platform platform; - if (m_device) { - platform = m_device->get_platform(); - } else { - // Assume a default. - platform = stmdsp::platform::L4; - } + const auto platform = m_device ? m_device->get_platform() + : stmdsp::platform::L4; - if (tempFileName.size() == 0) + if (tempFileName.empty()) tempFileName = newTempFileName(); + { std::ofstream file (tempFileName, std::ios::trunc | std::ios::binary); - auto file_text = platform == stmdsp::platform::L4 ? stmdsp::file_header_l4 - : stmdsp::file_header_h7; - auto samples_text = std::to_string(m_device ? m_device->get_buffer_size() - : stmdsp::SAMPLES_MAX); - for (std::size_t i = 0; (i = file_text.find("$0", i)) != std::string::npos;) { - file_text.replace(i, 2, samples_text); - i += 2; - } + auto file_text = + platform == stmdsp::platform::L4 ? stmdsp::file_header_l4 + : stmdsp::file_header_h7; + const auto buffer_size = m_device ? m_device->get_buffer_size() + : stmdsp::SAMPLES_MAX; + + stringReplaceAll(file_text, "$0", std::to_string(buffer_size)); - file << file_text; - file << "\n"; - file << editor.GetText(); + file << file_text << '\n' << editor.GetText(); } - constexpr const char *script_ext = + const auto scriptFile = tempFileName + #ifndef STMDSP_WIN32 ".sh"; #else @@ -110,44 +138,33 @@ void compileEditorCode() #endif { - std::ofstream makefile (tempFileName + script_ext, std::ios::binary); - auto make_text = platform == stmdsp::platform::L4 ? stmdsp::makefile_text_l4 - : stmdsp::makefile_text_h7; - auto cwd = std::filesystem::current_path().string(); - for (std::size_t i = 0; (i = make_text.find("$0", i)) != std::string::npos;) { - make_text.replace(i, 2, tempFileName); - i += 2; - } - for (std::size_t i = 0; (i = make_text.find("$1", i)) != std::string::npos;) { - make_text.replace(i, 2, cwd); - i += 2; - } + std::ofstream makefile (scriptFile, std::ios::binary); + auto make_text = + platform == stmdsp::platform::L4 ? stmdsp::makefile_text_l4 + : stmdsp::makefile_text_h7; + + stringReplaceAll(make_text, "$0", tempFileName); + stringReplaceAll(make_text, "$1", + std::filesystem::current_path().string()); makefile << make_text; } - auto makeOutput = tempFileName + script_ext + ".log"; - auto makeCommand = tempFileName + script_ext + " > " + makeOutput + " 2>&1"; - #ifndef STMDSP_WIN32 - system((std::string("chmod +x ") + tempFileName + script_ext).c_str()); + system((std::string("chmod +x ") + scriptFile).c_str()); #endif - int result = system(makeCommand.c_str()); - std::ifstream result_file (makeOutput); - std::ostringstream sstr; - sstr << result_file.rdbuf(); - log(sstr.str().c_str()); - - std::filesystem::remove(tempFileName); - std::filesystem::remove(tempFileName + script_ext); - std::filesystem::remove(makeOutput); - if (result == 0) { + const auto makeOutput = scriptFile + ".log"; + const auto makeCommand = scriptFile + " > " + makeOutput + " 2>&1"; + if (codeExecuteCommand(makeCommand, makeOutput)) { editorCompiled = editor.GetText(); log("Compilation succeeded."); } else { log("Compilation failed."); } + + std::filesystem::remove(tempFileName); + std::filesystem::remove(scriptFile); } void disassembleCode() @@ -158,31 +175,15 @@ void disassembleCode() compileEditorCode(); } - auto output = tempFileName + ".asm.log"; - auto command = std::string("arm-none-eabi-objdump -d --no-show-raw-insn ") + + const auto output = tempFileName + ".asm.log"; + const auto command = + std::string("arm-none-eabi-objdump -d --no-show-raw-insn ") + tempFileName + ".orig.o > " + output + " 2>&1"; - - if (system(command.c_str()) == 0) { - { - std::ifstream result_file (output); - std::ostringstream sstr; - sstr << result_file.rdbuf(); - log(sstr.str().c_str()); - } - - ImGui::OpenPopup("compile"); - std::filesystem::remove(output); + if (codeExecuteCommand(command, output)) { log("Ready."); } else { log("Failed to load disassembly."); } } -std::string newTempFileName() -{ - auto tempPath = std::filesystem::temp_directory_path(); - tempPath /= "stmdspgui_build"; - return tempPath.string(); -} - diff --git a/source/device.cpp b/source/device.cpp index 399bf7d..39bc746 100644 --- a/source/device.cpp +++ b/source/device.cpp @@ -9,11 +9,6 @@ * If not, see . */ -/** - * TODO list: - * - Improve signal generator audio streaming. - */ - #include "stmdsp.hpp" #include "imgui.h" @@ -80,11 +75,41 @@ static unsigned int drawSamplesBufferSize = 1; static void measureCodeTask(std::shared_ptr device) { - if (!device) - return; - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - auto cycles = device->continuous_start_get_measurement(); - log(std::string("Execution time: ") + std::to_string(cycles) + " cycles."); + std::this_thread::sleep_for(std::chrono::seconds(1)); + + if (device) { + const auto cycles = device->continuous_start_get_measurement(); + log(std::string("Execution time: ") + std::to_string(cycles) + " cycles."); + } +} + +static std::vector tryReceiveChunk( + std::shared_ptr device, + auto readFunc) +{ + int tries = -1; + do { + const auto chunk = readFunc(device.get()); + if (!chunk.empty()) + return chunk; + else + std::this_thread::sleep_for(std::chrono::microseconds(20)); + } while (++tries < 100 && device->is_running()); + + return {}; +} + +static std::chrono::duration getBufferPeriod( + std::shared_ptr device, + const double factor = 0.975) +{ + if (device) { + const double bufferSize = device->get_buffer_size(); + const double sampleRate = sampleRateInts[device->get_sample_rate()]; + return std::chrono::duration(bufferSize / sampleRate * factor); + } else { + return {}; + } } static void drawSamplesTask(std::shared_ptr device) @@ -93,71 +118,45 @@ static void drawSamplesTask(std::shared_ptr device) return; const bool doLogger = logResults && logSamplesFile.good(); - - const double bufferSize = m_device->get_buffer_size(); - const double sampleRate = sampleRateInts[m_device->get_sample_rate()]; - unsigned long bufferTime = bufferSize / sampleRate * 0.975 * 1e6; + const auto bufferTime = getBufferPeriod(device); std::unique_lock lockDraw (mutexDrawSamples, std::defer_lock); std::unique_lock lockDevice (mutexDeviceLoad, std::defer_lock); - while (m_device && m_device->is_running()) { - auto next = std::chrono::high_resolution_clock::now() + - std::chrono::microseconds(bufferTime); + auto addToQueue = [&lockDraw](auto& queue, const auto& chunk) { + lockDraw.lock(); + std::copy(chunk.cbegin(), chunk.cend(), std::back_inserter(queue)); + lockDraw.unlock(); + }; - std::vector chunk; + while (device && device->is_running()) { + const auto next = std::chrono::high_resolution_clock::now() + bufferTime; if (lockDevice.try_lock_until(next)) { - chunk = m_device->continuous_read(); - int tries = -1; - while (chunk.empty() && m_device->is_running()) { - if (++tries == 100) - break; - std::this_thread::sleep_for(std::chrono::microseconds(20)); - chunk = m_device->continuous_read(); - } + const auto chunk = tryReceiveChunk(device, + std::mem_fn(&stmdsp::device::continuous_read)); lockDevice.unlock(); + + addToQueue(drawSamplesQueue, chunk); + if (doLogger) { + for (const auto& s : chunk) + logSamplesFile << s << '\n'; + } } else { - // Cooldown. + // Device must be busy, cooldown. std::this_thread::sleep_for(std::chrono::milliseconds(500)); } if (drawSamplesInput && popupRequestDraw) { - std::vector chunk2; - if (lockDevice.try_lock_for(std::chrono::milliseconds(1))) { - chunk2 = m_device->continuous_read_input(); - int tries = -1; - while (chunk2.empty() && m_device->is_running()) { - if (++tries == 100) - break; - std::this_thread::sleep_for(std::chrono::microseconds(20)); - chunk2 = m_device->continuous_read_input(); - } + const auto chunk2 = tryReceiveChunk(device, + std::mem_fn(&stmdsp::device::continuous_read_input)); lockDevice.unlock(); - } - lockDraw.lock(); - auto i = chunk2.cbegin(); - for (const auto& s : chunk) { - drawSamplesQueue.push_back(s); - drawSamplesInputQueue.push_back(*i++); - } - lockDraw.unlock(); - } else if (!doLogger) { - lockDraw.lock(); - for (const auto& s : chunk) - drawSamplesQueue.push_back(s); - lockDraw.unlock(); - } else { - lockDraw.lock(); - for (const auto& s : chunk) { - drawSamplesQueue.push_back(s); - logSamplesFile << s << '\n'; + addToQueue(drawSamplesInputQueue, chunk2); } - lockDraw.unlock(); } - + std::this_thread::sleep_until(next); } } @@ -167,36 +166,36 @@ static void feedSigGenTask(std::shared_ptr device) if (!device) return; - const auto bufferSize = m_device->get_buffer_size(); - const double sampleRate = sampleRateInts[m_device->get_sample_rate()]; - const unsigned long delay = bufferSize / sampleRate * 0.975 * 1e6; + const auto delay = getBufferPeriod(device); + const auto uploadDelay = getBufferPeriod(device, 0.001); - std::vector wavBuf (bufferSize, 2048); + std::vector wavBuf (device->get_buffer_size() * 2, 2048); std::unique_lock lockDevice (mutexDeviceLoad, std::defer_lock); lockDevice.lock(); - // One (or both) of these freezes the device... - m_device->siggen_upload(wavBuf.data(), wavBuf.size()); - //m_device->siggen_start(); + device->siggen_upload(wavBuf.data(), wavBuf.size()); + wavBuf.resize(wavBuf.size() / 2); + device->siggen_start(); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); lockDevice.unlock(); - std::this_thread::sleep_for(std::chrono::microseconds(delay)); + std::vector wavIntBuf (wavBuf.size()); - return; while (genRunning) { - auto next = std::chrono::high_resolution_clock::now() + - std::chrono::microseconds(delay); + const auto next = std::chrono::high_resolution_clock::now() + delay; - auto src = reinterpret_cast(wavOutput.next(bufferSize)); - for (auto& w : wavBuf) - w = *src++ / 16 + 2048; + wavOutput.next(wavIntBuf.data(), wavIntBuf.size()); + auto src = wavIntBuf.cbegin(); + std::generate(wavBuf.begin(), wavBuf.end(), + [&src] { return static_cast(*src++ / 16 + 2048); }); - if (lockDevice.try_lock_until(next)) { - m_device->siggen_upload(wavBuf.data(), wavBuf.size()); - lockDevice.unlock(); - std::this_thread::sleep_until(next); - } + lockDevice.lock(); + while (!device->siggen_upload(wavBuf.data(), wavBuf.size())) + std::this_thread::sleep_for(uploadDelay); + lockDevice.unlock(); + + std::this_thread::sleep_until(next); } } @@ -208,16 +207,20 @@ static void statusTask(std::shared_ptr device) while (device->connected()) { std::unique_lock lockDevice (mutexDeviceLoad, std::defer_lock); lockDevice.lock(); - auto [status, error] = device->get_status(); + const auto [status, error] = device->get_status(); lockDevice.unlock(); if (error != stmdsp::Error::None) { - if (error == stmdsp::Error::NotIdle) { + switch (error) { + case stmdsp::Error::NotIdle: log("Error: Device already running..."); - } else if (error == stmdsp::Error::ConversionAborted) { + break; + case stmdsp::Error::ConversionAborted: log("Error: Algorithm unloaded, a fault occurred!"); - } else { + break; + default: log("Error: Device had an issue..."); + break; } } @@ -383,13 +386,13 @@ void deviceRenderDraw() drawSamplesBufferSize = std::round(sr * tf); } ImGui::SameLine(); - ImGui::Text("Y-minmax: %u", yMinMax); + ImGui::Text("Y: +/-%1.2fV", 3.3f * (static_cast(yMinMax) / 4095.f)); ImGui::SameLine(); - if (ImGui::Button("--", {30, 0})) { + if (ImGui::Button(" - ", {30, 0})) { yMinMax = std::max(63u, yMinMax >> 1); } ImGui::SameLine(); - if (ImGui::Button("++", {30, 0})) { + if (ImGui::Button(" + ", {30, 0})) { yMinMax = std::min(4095u, (yMinMax << 1) | 1); } @@ -508,7 +511,7 @@ void deviceRenderMenu() popupRequestBuffer = true; } ImGui::Separator(); - if (ImGui::MenuItem("Load signal generator", nullptr, false, isConnected && !isRunning)) { + if (ImGui::MenuItem("Load signal generator", nullptr, false, isConnected && !m_device->is_siggening())) { popupRequestSiggen = true; } static const char *startSiggenLabel = "Start signal generator"; @@ -542,22 +545,26 @@ void deviceRenderToolbar() 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]; - if (m_device && !m_device->is_running()) { - do { - m_device->set_sample_rate(i); - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } while (m_device->get_sample_rate() != 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); - } + drawSamplesBufferSize = std::round(sampleRateInts[i] * drawSamplesTimeframe); } } ImGui::EndCombo(); } + if (!enable) + ImGui::PopDisabled(); } void deviceConnect() @@ -566,7 +573,7 @@ void deviceConnect() if (!m_device) { stmdsp::scanner scanner; - if (auto devices = scanner.scan(); devices.size() > 0) { + if (auto devices = scanner.scan(); !devices.empty()) { try { m_device.reset(new stmdsp::device(devices.front())); } catch (...) { @@ -608,8 +615,7 @@ void deviceStart() if (m_device->is_running()) { { - std::scoped_lock lock (mutexDrawSamples); - std::scoped_lock lock2 (mutexDeviceLoad); + std::scoped_lock lock (mutexDrawSamples, mutexDeviceLoad); std::this_thread::sleep_for(std::chrono::microseconds(150)); m_device->continuous_stop(); } @@ -637,10 +643,9 @@ void deviceAlgorithmUpload() if (!m_device) { log("No device connected."); return; - } - - if (m_device->is_running()) + } else if (m_device->is_running()) { return; + } if (std::ifstream algo (tempFileName + ".o"); algo.good()) { std::ostringstream sstr; @@ -658,41 +663,38 @@ void deviceAlgorithmUnload() { if (!m_device) { log("No device connected."); - return; - } - - if (!m_device->is_running()) { + } else if (!m_device->is_running()) { m_device->unload_filter(); log("Algorithm unloaded."); } } -void deviceGenLoadList(std::string_view listStr) +void deviceGenLoadList(const std::string_view list) { std::vector samples; - while (listStr.size() > 0 && samples.size() <= stmdsp::SAMPLES_MAX * 2) { - auto numberEnd = listStr.find_first_not_of("0123456789"); - + auto it = list.cbegin(); + while (it != list.cend() && samples.size() < stmdsp::SAMPLES_MAX * 2) { + const auto end = list.find_first_not_of("0123456789", + std::distance(list.cbegin(), it)); + const auto itend = end != std::string_view::npos ? list.cbegin() + end + : list.cend(); unsigned long n; - auto end = numberEnd != std::string_view::npos ? listStr.begin() + numberEnd : listStr.end(); - auto [ptr, ec] = std::from_chars(listStr.begin(), end, n); + const auto [ptr, ec] = std::from_chars(it, itend, n); if (ec != std::errc()) break; samples.push_back(n & 4095); - if (end == listStr.end()) - break; - listStr = listStr.substr(numberEnd + 1); + it = itend; } if (samples.size() <= stmdsp::SAMPLES_MAX * 2) { // DAC buffer must be of even size - if ((samples.size() & 1) == 1) + if (samples.size() % 2 != 0) samples.push_back(samples.back()); if (m_device) - m_device->siggen_upload(&samples[0], samples.size()); + m_device->siggen_upload(samples.data(), samples.size()); log("Generator ready."); } else { log("Error: Too many samples for signal generator."); @@ -703,9 +705,9 @@ void deviceGenLoadFormula(std::string_view formula) { auto samples = deviceGenLoadFormulaEval(formula); - if (samples.size() > 0) { + if (!samples.empty()) { if (m_device) - m_device->siggen_upload(&samples[0], samples.size()); + m_device->siggen_upload(samples.data(), samples.size()); log("Generator ready."); } else { diff --git a/source/file.cpp b/source/file.cpp index 0f0015c..dfd9148 100644 --- a/source/file.cpp +++ b/source/file.cpp @@ -17,6 +17,7 @@ #include "stmdsp_code.hpp" +#include #include #include #include @@ -34,6 +35,7 @@ enum class FileAction { Save, SaveAs }; + static FileAction fileAction = FileAction::None; static std::string fileCurrentPath; static std::vector fileTemplateList; @@ -56,17 +58,31 @@ static void openCurrentFile() } } -void openNewFile() +static void openNewFile() { fileCurrentPath.clear(); editor.SetText(stmdsp::file_content); } -void fileScanTemplates() +static std::vector fileScanTemplates() +{ + const auto path = std::filesystem::current_path() / "templates"; + const std::filesystem::recursive_directory_iterator rdi (path); + + std::vector list; + std::transform( + std::filesystem::begin(rdi), + std::filesystem::end(rdi), + std::back_inserter(list), + [](const auto& file) { return file.path(); }); + std::sort(list.begin(), list.end()); + return list; +} + +void fileInit() { - auto path = std::filesystem::current_path() / "templates"; - for (const auto& file : std::filesystem::recursive_directory_iterator{path}) - fileTemplateList.push_back(file.path()); + fileTemplateList = fileScanTemplates(); + openNewFile(); } void fileRenderMenu() diff --git a/source/main.cpp b/source/main.cpp index b98ec2b..20c8ea1 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -32,8 +32,7 @@ extern void guiRender(void (*func)()); extern void fileRenderMenu(); extern void fileRenderDialog(); -extern void fileScanTemplates(); -extern void openNewFile(); +extern void fileInit(); extern void codeEditorInit(); extern void codeRenderMenu(); @@ -60,9 +59,8 @@ int main(int, char **) if (!guiInitialize()) return -1; - fileScanTemplates(); codeEditorInit(); - openNewFile(); + fileInit(); while (!done) { auto endTime = std::chrono::steady_clock::now() + @@ -90,21 +88,22 @@ int main(int, char **) ImGuiWindowFlags_NoBringToFrontOnFocus); // Render main controls (order is important). - ImGui::PushFont(fontSans); - codeRenderToolbar(); - deviceRenderToolbar(); - fileRenderDialog(); - deviceRenderWidgets(); - ImGui::PopFont(); - - ImGui::PushFont(fontMono); - codeRenderWidgets(); - ImGui::SetNextWindowPos({0, WINDOW_HEIGHT - 200}); - ImGui::SetNextWindowSize({WINDOW_WIDTH, 200}); - logView.Draw("log", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBringToFrontOnFocus); - ImGui::PopFont(); - - // Finish main view rendering. + { + ImGui::PushFont(fontSans); + codeRenderToolbar(); + deviceRenderToolbar(); + fileRenderDialog(); + deviceRenderWidgets(); + ImGui::PopFont(); + + ImGui::PushFont(fontMono); + codeRenderWidgets(); + ImGui::SetNextWindowPos({0, WINDOW_HEIGHT - 200}); + ImGui::SetNextWindowSize({WINDOW_WIDTH, 200}); + logView.Draw("log", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBringToFrontOnFocus); + ImGui::PopFont(); + } + ImGui::End(); deviceRenderDraw(); diff --git a/source/stmdsp/stmdsp.cpp b/source/stmdsp/stmdsp.cpp index 650dbc4..2bbb92b 100644 --- a/source/stmdsp/stmdsp.cpp +++ b/source/stmdsp/stmdsp.cpp @@ -13,18 +13,23 @@ #include +#include + extern void log(const std::string& str); namespace stmdsp { - std::list& scanner::scan() + const std::forward_list& scanner::scan() { auto devices = serial::list_ports(); - for (auto& device : devices) { - if (device.hardware_id.find(STMDSP_USB_ID) != std::string::npos) - m_available_devices.emplace_front(device.port); - } - + auto foundDevicesEnd = std::remove_if( + devices.begin(), devices.end(), + [](const auto& dev) { + return dev.hardware_id.find(STMDSP_USB_ID) == std::string::npos; + }); + std::transform(devices.begin(), foundDevicesEnd, + std::front_inserter(m_available_devices), + [](const auto& dev) { return dev.port; }); return m_available_devices; } @@ -33,6 +38,7 @@ namespace stmdsp // This could throw! m_serial.reset(new serial::Serial(file, 8'000'000, serial::Timeout::simpleTimeout(50))); + // Test the ID command. m_serial->flush(); m_serial->write("i"); auto id = m_serial->read(7); @@ -66,98 +72,80 @@ namespace stmdsp m_serial.release(); } - void device::continuous_set_buffer_size(unsigned int size) { - if (connected()) { - m_buffer_size = size; - - uint8_t request[3] = { - 'B', - static_cast(size), - static_cast(size >> 8) - }; + bool device::try_command(std::basic_string cmd) { + bool success = false; + if (connected()) { try { - m_serial->write(request, 3); + std::scoped_lock lock (m_lock); + m_serial->write(cmd.data(), cmd.size()); + success = true; } catch (...) { m_serial.release(); log("Lost connection!"); } } + + return success; } - void device::set_sample_rate(unsigned int id) { - if (connected()) { - uint8_t request[2] = { - 'r', - static_cast(id) - }; + bool device::try_read(std::basic_string cmd, uint8_t *dest, unsigned int dest_size) { + bool success = false; + if (connected() && dest && dest_size > 0) { try { - m_serial->write(request, 2); + std::scoped_lock lock (m_lock); + m_serial->write(cmd.data(), cmd.size()); + m_serial->read(dest, dest_size); + success = true; } catch (...) { m_serial.release(); log("Lost connection!"); } } + + return success; } - unsigned int device::get_sample_rate() { - if (connected() && !is_running()) { - uint8_t request[2] = { - 'r', 0xFF - }; + void device::continuous_set_buffer_size(unsigned int size) { + if (try_command({ + 'B', + static_cast(size), + static_cast(size >> 8)})) + { + m_buffer_size = size; + } + } - unsigned char result = 0xFF; - try { - m_serial->write(request, 2); - m_serial->read(&result, 1); - } catch (...) { - m_serial.release(); - log("Lost connection!"); - } + void device::set_sample_rate(unsigned int id) { + try_command({ + 'r', + static_cast(id) + }); + } - m_sample_rate = result; + unsigned int device::get_sample_rate() { + if (!is_running()) { + uint8_t result = 0xFF; + if (try_read({'r', 0xFF}, &result, 1)) + m_sample_rate = result; } - return m_sample_rate; } void device::continuous_start() { - if (connected()) { - try { - m_serial->write("R"); - m_is_running = true; - } catch (...) { - m_serial.release(); - log("Lost connection!"); - } - } + if (try_command({'R'})) + m_is_running = true; } void device::continuous_start_measure() { - if (connected()) { - try { - m_serial->write("M"); - m_is_running = true; - } catch (...) { - m_serial.release(); - log("Lost connection!"); - } - } + if (try_command({'M'})) + m_is_running = true; } uint32_t device::continuous_start_get_measurement() { uint32_t count = 0; - if (connected()) { - try { - m_serial->write("m"); - m_serial->read(reinterpret_cast(&count), sizeof(uint32_t)); - } catch (...) { - m_serial.release(); - log("Lost connection!"); - } - } - + try_read({'m'}, reinterpret_cast(&count), sizeof(uint32_t)); return count / 2; } @@ -226,18 +214,11 @@ namespace stmdsp } void device::continuous_stop() { - if (connected()) { - try { - m_serial->write("S"); - m_is_running = false; - } catch (...) { - m_serial.release(); - log("Lost connection!"); - } - } + if (try_command({'S'})) + m_is_running = false; } - void device::siggen_upload(dacsample_t *buffer, unsigned int size) { + bool device::siggen_upload(dacsample_t *buffer, unsigned int size) { if (connected()) { uint8_t request[3] = { 'D', @@ -245,39 +226,41 @@ namespace stmdsp static_cast(size >> 8) }; - try { - m_serial->write(request, 3); - // TODO different write size if feeding audio? - m_serial->write((uint8_t *)buffer, size * sizeof(dacsample_t)); - } catch (...) { - m_serial.release(); - log("Lost connection!"); + if (!m_is_siggening) { + try { + m_serial->write(request, 3); + m_serial->write((uint8_t *)buffer, size * sizeof(dacsample_t)); + } catch (...) { + m_serial.release(); + log("Lost connection!"); + } + } else { + try { + m_serial->write(request, 3); + if (m_serial->read(1)[0] == 0) + return false; + else + m_serial->write((uint8_t *)buffer, size * sizeof(dacsample_t)); + } catch (...) { + m_serial.release(); + log("Lost connection!"); + } } + + return true; + } else { + return false; } } void device::siggen_start() { - if (connected()) { - try { - m_serial->write("W"); - m_is_siggening = true; - } catch (...) { - m_serial.release(); - log("Lost connection!"); - } - } + if (try_command({'W'})) + m_is_siggening = true; } void device::siggen_stop() { - if (connected()) { - try { - m_serial->write("w"); - m_is_siggening = false; - } catch (...) { - m_serial.release(); - log("Lost connection!"); - } - } + if (try_command({'w'})) + m_is_siggening = false; } void device::upload_filter(unsigned char *buffer, size_t size) { @@ -299,33 +282,22 @@ namespace stmdsp } void device::unload_filter() { - if (connected()) { - try { - m_serial->write("e"); - } catch (...) { - m_serial.release(); - log("Lost connection!"); - } - } + try_command({'e'}); } std::pair device::get_status() { std::pair ret; - if (connected()) { - try { - m_serial->write("I"); - auto result = m_serial->read(2); - ret = {static_cast(result[0]), - static_cast(result[1])}; - - bool running = ret.first == RunStatus::Running; - if (m_is_running != running) - m_is_running = running; - } catch (...) { - m_serial.release(); - log("Lost connection!"); - } + unsigned char buf[2]; + if (try_read({'I'}, buf, 2)) { + ret = { + static_cast(buf[0]), + static_cast(buf[1]) + }; + + bool running = ret.first == RunStatus::Running; + if (m_is_running != running) + m_is_running = running; } return ret; diff --git a/source/stmdsp/stmdsp.hpp b/source/stmdsp/stmdsp.hpp index 76ca94e..64f5aff 100644 --- a/source/stmdsp/stmdsp.hpp +++ b/source/stmdsp/stmdsp.hpp @@ -15,33 +15,83 @@ #include #include -#include +#include #include +#include #include #include namespace stmdsp { + /** + * The largest possible size of an ADC or DAC sample buffer, as a sample count. + * Maximum byte size would be `SAMPLES_MAX * sizeof(XXXsample_t)`. + */ constexpr unsigned int SAMPLES_MAX = 4096; + /** + * ADC samples on all platforms are stored as 16-bit unsigned integers. + */ + using adcsample_t = uint16_t; + /** + * DAC samples on all platforms are stored as 16-bit unsigned integers. + */ + using dacsample_t = uint16_t; + + /** + * List of all available platforms. + * Note that some platforms in this list may not have complete support. + */ + enum class platform { + Unknown, + H7, /* Some feature support */ + L4, /* Complete feature support */ + G4 /* Unsupported, but planned */ + }; + + /** + * Run status states, valued to match what the stmdsp firmware reports. + */ enum class RunStatus : char { - Idle = '1', - Running, - Recovering + Idle = '1', /* Device ready for commands or execution. */ + Running, /* Device currently executing its algorithm. */ + Recovering /* Device recovering from fault caused by algorithm. */ }; + /** + * Error messages that are reported by the firmware. + */ enum class Error : char { None = 0, - BadParam, - BadParamSize, - BadUserCodeLoad, - BadUserCodeSize, - NotIdle, - ConversionAborted + BadParam, /* An invalid parameter was passed for a command. */ + BadParamSize, /* An invaild param. size was given for a command. */ + BadUserCodeLoad, /* Device failed to load the given algorithm. */ + BadUserCodeSize, /* The given algorithm is too large for the device. */ + NotIdle, /* An idle-only command was received while not Idle. */ + ConversionAborted /* A conversion was aborted due to a fault. */ }; + /** + * Provides functionality to scan the system for stmdsp devices. + * A list of devices is returned, though the GUI only interacts with one + * device at a time. + */ class scanner { + public: + /** + * Scans for connected devices, returning a list of ports with + * connected stmdsp devices. + */ + const std::forward_list& scan(); + + /** + * Retrieves the results of the last scan(). + */ + const std::forward_list& devices() const noexcept { + return m_available_devices; + } + private: constexpr static const char *STMDSP_USB_ID = #ifndef STMDSP_WIN32 @@ -50,24 +100,7 @@ namespace stmdsp "USB\\VID_0483&PID_5740"; #endif - public: - std::list& scan(); - auto& devices() { - return m_available_devices; - } - - private: - std::list m_available_devices; - }; - - using adcsample_t = uint16_t; - using dacsample_t = uint16_t; - - enum class platform { - Unknown, - H7, /* Behind in feature support */ - L4, /* Complete feature support */ - G4 /* Currently unsupported */ + std::forward_list m_available_devices; }; class device @@ -96,7 +129,7 @@ namespace stmdsp std::vector continuous_read(); std::vector continuous_read_input(); - void siggen_upload(dacsample_t *buffer, unsigned int size); + bool siggen_upload(dacsample_t *buffer, unsigned int size); void siggen_start(); void siggen_stop(); @@ -116,6 +149,11 @@ namespace stmdsp unsigned int m_sample_rate = 0; bool m_is_siggening = false; bool m_is_running = false; + + std::mutex m_lock; + + bool try_command(std::basic_string data); + bool try_read(std::basic_string cmd, uint8_t *dest, unsigned int dest_size); }; } diff --git a/source/wav.hpp b/source/wav.hpp index 9ff06e5..71842bd 100644 --- a/source/wav.hpp +++ b/source/wav.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace wav { @@ -64,31 +65,30 @@ namespace wav file.read(reinterpret_cast(&d), sizeof(wav::data)); if (!d.valid()) return; - m_data = new char[d.size + 4096 - (d.size % 4096)]; - m_size = d.size; - file.read(m_data, d.size); + m_data.resize(d.size / sizeof(int16_t)); + m_next = m_data.begin(); + file.read(reinterpret_cast(m_data.data()), d.size); } } clip() = default; bool valid() const { - return m_data != nullptr && m_size > 0; + return !m_data.empty(); } - auto data() const { - return m_data; + const int16_t *data() const { + return m_data.data(); } - auto next(unsigned int chunksize = 3000) { - if (m_pos == m_size) { - m_pos = 0; + void next(int16_t *buf, unsigned int size) { + for (unsigned int i = 0; i < size; ++i) { + if (m_next == m_data.end()) + m_next = m_data.begin(); + else + *buf++ = *m_next++; } - auto ret = m_data + m_pos; - m_pos = std::min(m_pos + chunksize, m_size); - return ret; } private: - char *m_data = nullptr; - uint32_t m_size = 0; - uint32_t m_pos = 0; + std::vector m_data; + decltype(m_data.begin()) m_next; }; } From c76ba69fc933a86e526855b097907a728e24568b Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Sun, 21 Nov 2021 11:30:41 -0500 Subject: [PATCH 10/21] wip: major code refactor, split most gui from logic --- Makefile | 3 +- source/code.cpp | 155 +++++-------- source/device.cpp | 468 ++++++++------------------------------- source/gui_code.cpp | 72 ++++++ source/gui_device.cpp | 340 ++++++++++++++++++++++++++++ source/stmdsp/stmdsp.cpp | 32 ++- source/stmdsp/stmdsp.hpp | 2 +- source/wav.hpp | 3 +- 8 files changed, 593 insertions(+), 482 deletions(-) create mode 100644 source/gui_code.cpp create mode 100644 source/gui_device.cpp 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; From fde531e7c44ea917f745a9f800178fbe83fa19b5 Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Mon, 22 Nov 2021 19:44:48 -0500 Subject: [PATCH 11/21] more refactor; draw samples grid and cursor; gen load fixes --- Makefile | 2 +- source/circular.hpp | 32 ++++ source/device.cpp | 151 ++++++++------- source/device_formula.cpp | 12 +- source/file.cpp | 6 +- source/gui.cpp | 6 +- source/gui_device.cpp | 129 +++++++++---- source/imgui/TextEditor.cpp | 356 +----------------------------------- source/imgui/TextEditor.h | 6 - source/imgui/imgui.h | 6 +- source/logview.cpp | 82 +++++++++ source/logview.h | 80 +------- source/main.cpp | 143 ++++++++------- 13 files changed, 389 insertions(+), 622 deletions(-) create mode 100644 source/circular.hpp create mode 100644 source/logview.cpp diff --git a/Makefile b/Makefile index 26e0208..81e580d 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ $(OUTPUT): $(OFILES) clean: @echo " CLEAN" - @rm $(OFILES) $(OUTPUT) + @rm -f $(OFILES) $(OUTPUT) %.o: %.cpp @echo " CXX " $< diff --git a/source/circular.hpp b/source/circular.hpp new file mode 100644 index 0000000..33b8ee0 --- /dev/null +++ b/source/circular.hpp @@ -0,0 +1,32 @@ +#ifndef CIRCULAR_HPP +#define CIRCULAR_HPP + +#include + +template class Container, typename T> +class CircularBuffer +{ +public: + CircularBuffer(Container& container) : + m_begin(std::begin(container)), + m_end(std::end(container)), + m_current(std::begin(container)) {} + + void put(const T& value) noexcept { + *m_current = value; + if (++m_current == m_end) + m_current = m_begin; + } + + std::size_t size() const noexcept { + return std::distance(m_begin, m_end); + } + +private: + Container::iterator m_begin; + Container::iterator m_end; + Container::iterator m_current; +}; + +#endif // CIRCULAR_HPP + diff --git a/source/device.cpp b/source/device.cpp index df3f361..abcc88a 100644 --- a/source/device.cpp +++ b/source/device.cpp @@ -11,10 +11,12 @@ #include "stmdsp.hpp" +#include "circular.hpp" #include "imgui.h" #include "wav.hpp" #include +#include #include #include #include @@ -30,7 +32,7 @@ extern std::string tempFileName; extern void log(const std::string& str); -extern std::vector deviceGenLoadFormulaEval(const std::string_view); +extern std::vector deviceGenLoadFormulaEval(const std::string&); std::shared_ptr m_device; @@ -62,14 +64,16 @@ static std::vector tryReceiveChunk( std::shared_ptr device, auto readFunc) { - int tries = -1; - do { + for (int tries = 0; tries < 100; ++tries) { + if (!device->is_running()) + break; + const auto chunk = readFunc(device.get()); if (!chunk.empty()) return chunk; else std::this_thread::sleep_for(std::chrono::microseconds(20)); - } while (++tries < 100 && device->is_running()); + } return {}; } @@ -94,15 +98,13 @@ static void drawSamplesTask(std::shared_ptr device) const auto bufferTime = getBufferPeriod(device); - std::unique_lock lockDraw (mutexDrawSamples, std::defer_lock); - std::unique_lock lockDevice (mutexDeviceLoad, std::defer_lock); - - auto addToQueue = [&lockDraw](auto& queue, const auto& chunk) { - lockDraw.lock(); + const auto addToQueue = [](auto& queue, const auto& chunk) { + std::scoped_lock lock (mutexDrawSamples); std::copy(chunk.cbegin(), chunk.cend(), std::back_inserter(queue)); - lockDraw.unlock(); }; + std::unique_lock lockDevice (mutexDeviceLoad, std::defer_lock); + while (device && device->is_running()) { const auto next = std::chrono::high_resolution_clock::now() + bufferTime; @@ -112,7 +114,7 @@ static void drawSamplesTask(std::shared_ptr device) lockDevice.unlock(); addToQueue(drawSamplesQueue, chunk); - if (logSamplesFile.good()) { + if (logSamplesFile.is_open()) { for (const auto& s : chunk) logSamplesFile << s << '\n'; } @@ -145,29 +147,29 @@ static void feedSigGenTask(std::shared_ptr device) std::vector wavBuf (device->get_buffer_size() * 2, 2048); - std::unique_lock lockDevice (mutexDeviceLoad, std::defer_lock); + { + std::scoped_lock lock (mutexDeviceLoad); + device->siggen_upload(wavBuf.data(), wavBuf.size()); + device->siggen_start(); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } - lockDevice.lock(); - device->siggen_upload(wavBuf.data(), wavBuf.size()); wavBuf.resize(wavBuf.size() / 2); - device->siggen_start(); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - lockDevice.unlock(); - std::vector wavIntBuf (wavBuf.size()); while (device->is_siggening()) { const auto next = std::chrono::high_resolution_clock::now() + delay; wavOutput.next(wavIntBuf.data(), wavIntBuf.size()); - auto src = wavIntBuf.cbegin(); - std::generate(wavBuf.begin(), wavBuf.end(), - [&src] { return static_cast(*src++ / 16 + 2048); }); + std::transform(wavIntBuf.cbegin(), wavIntBuf.cend(), + wavBuf.begin(), + [](auto i) { return static_cast(i / 16 + 2048); }); - lockDevice.lock(); - while (!device->siggen_upload(wavBuf.data(), wavBuf.size())) - std::this_thread::sleep_for(uploadDelay); - lockDevice.unlock(); + { + std::scoped_lock lock (mutexDeviceLoad); + while (!device->siggen_upload(wavBuf.data(), wavBuf.size())) + std::this_thread::sleep_for(uploadDelay); + } std::this_thread::sleep_until(next); } @@ -179,10 +181,9 @@ static void statusTask(std::shared_ptr device) return; while (device->connected()) { - std::unique_lock lockDevice (mutexDeviceLoad, std::defer_lock); - lockDevice.lock(); + mutexDeviceLoad.lock(); const auto [status, error] = device->get_status(); - lockDevice.unlock(); + mutexDeviceLoad.unlock(); if (error != stmdsp::Error::None) { switch (error) { @@ -214,7 +215,7 @@ void deviceLoadAudioFile(const std::string& file) void deviceLoadLogFile(const std::string& file) { logSamplesFile = std::ofstream(file); - if (logSamplesFile.good()) + if (logSamplesFile.is_open()) log("Log file ready."); else log("Error: Could not open log file."); @@ -261,7 +262,7 @@ bool deviceConnect() if (!m_device) { stmdsp::scanner scanner; - if (auto devices = scanner.scan(); !devices.empty()) { + if (const auto devices = scanner.scan(); !devices.empty()) { try { m_device.reset(new stmdsp::device(devices.front())); } catch (...) { @@ -307,7 +308,7 @@ void deviceStart(bool measureCodeTime, bool logResults, bool drawSamples) std::this_thread::sleep_for(std::chrono::microseconds(150)); m_device->continuous_stop(); } - if (logSamplesFile.good()) { + if (logSamplesFile.is_open()) { logSamplesFile.close(); log("Log file saved and closed."); } @@ -329,12 +330,9 @@ void deviceAlgorithmUpload() { if (!m_device) { log("No device connected."); - return; } else if (m_device->is_running()) { - return; - } - - if (std::ifstream algo (tempFileName + ".o"); algo.good()) { + log("Cannot upload algorithm while running."); + } else if (std::ifstream algo (tempFileName + ".o"); algo.is_open()) { std::ostringstream sstr; sstr << algo.rdbuf(); auto str = sstr.str(); @@ -350,7 +348,9 @@ void deviceAlgorithmUnload() { if (!m_device) { log("No device connected."); - } else if (!m_device->is_running()) { + } else if (m_device->is_running()) { + log("Cannot unload algorithm while running."); + } else { m_device->unload_filter(); log("Algorithm unloaded."); } @@ -361,85 +361,82 @@ void deviceGenLoadList(const std::string_view list) std::vector samples; auto it = list.cbegin(); - while (it != list.cend() && samples.size() < stmdsp::SAMPLES_MAX * 2) { - const auto end = list.find_first_not_of("0123456789", - std::distance(list.cbegin(), it)); - const auto itend = end != std::string_view::npos ? list.cbegin() + end - : list.cend(); + while (it != list.cend()) { + const auto itend = std::find_if(it, list.cend(), + [](char c) { return !isdigit(c); }); + unsigned long n; - const auto [ptr, ec] = std::from_chars(it, itend, n); - if (ec != std::errc()) + const auto ec = std::from_chars(it, itend, n).ec; + if (ec != std::errc()) { + log("Error: Bad data in sample list."); + break; + } else if (n > 4095) { + log("Error: Sample data value larger than max of 4095."); break; + } else { + samples.push_back(n & 4095); + if (samples.size() >= stmdsp::SAMPLES_MAX * 2) { + log("Error: Too many samples for signal generator."); + break; + } + } - samples.push_back(n & 4095); it = itend; } - if (samples.size() <= stmdsp::SAMPLES_MAX * 2) { + if (it == list.cend()) { // DAC buffer must be of even size if (samples.size() % 2 != 0) samples.push_back(samples.back()); - if (m_device) - m_device->siggen_upload(samples.data(), samples.size()); + m_device->siggen_upload(samples.data(), samples.size()); log("Generator ready."); - } else { - log("Error: Too many samples for signal generator."); } } -void deviceGenLoadFormula(std::string_view formula) +void deviceGenLoadFormula(const std::string& formula) { auto samples = deviceGenLoadFormulaEval(formula); if (!samples.empty()) { - if (m_device) - m_device->siggen_upload(samples.data(), samples.size()); - + m_device->siggen_upload(samples.data(), samples.size()); log("Generator ready."); } else { log("Error: Bad formula."); } } -void pullFromQueue( +std::size_t pullFromQueue( std::deque& queue, - std::vector& buffer, - decltype(buffer.begin())& bufferCursor, + CircularBuffer& circ, double timeframe) { - if (buffer.size() != drawSamplesBufferSize) { - buffer.resize(drawSamplesBufferSize); - bufferCursor = buffer.begin(); - } + if (circ.size() != drawSamplesBufferSize) + return drawSamplesBufferSize; 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(); + const auto desiredCount = drawSamplesBufferSize / (60. * timeframe) * 1.025; + auto count = std::min(queue.size(), static_cast(desiredCount)); + while (count--) { + circ.put(queue.front()); queue.pop_front(); - - if (++bufferCursor == buffer.end()) - bufferCursor = buffer.begin(); } + + return 0; } -void pullFromDrawQueue( - std::vector& buffer, - decltype(buffer.begin())& bufferCursor, +std::size_t pullFromDrawQueue( + CircularBuffer& circ, double timeframe) { - pullFromQueue(drawSamplesQueue, buffer, bufferCursor, timeframe); + return pullFromQueue(drawSamplesQueue, circ, timeframe); } -void pullFromInputDrawQueue( - std::vector& buffer, - decltype(buffer.begin())& bufferCursor, +std::size_t pullFromInputDrawQueue( + CircularBuffer& circ, double timeframe) { - pullFromQueue(drawSamplesInputQueue, buffer, bufferCursor, timeframe); + return pullFromQueue(drawSamplesInputQueue, circ, timeframe); } diff --git a/source/device_formula.cpp b/source/device_formula.cpp index 5c1ca16..c94eab7 100644 --- a/source/device_formula.cpp +++ b/source/device_formula.cpp @@ -5,7 +5,7 @@ #include #include -std::vector deviceGenLoadFormulaEval(const std::string_view formulaString) +std::vector deviceGenLoadFormulaEval(const std::string& formulaString) { double x = 0; @@ -16,13 +16,17 @@ std::vector deviceGenLoadFormulaEval(const std::string_view symbol_table.add_variable("x", x); symbol_table.add_constants(); expression.register_symbol_table(symbol_table); - parser.compile(std::string(formulaString), expression); + parser.compile(formulaString, expression); std::vector samples (stmdsp::SAMPLES_MAX); - std::generate(samples.begin(), samples.end(), - [&] { ++x; return static_cast(expression.value()); }); + auto genFun = [&x, &expression] { + stmdsp::dacsample_t s = expression.value(); + ++x; + return s; + }; + std::generate(samples.begin(), samples.end(), genFun); return samples; } diff --git a/source/file.cpp b/source/file.cpp index dfd9148..a5ef1d8 100644 --- a/source/file.cpp +++ b/source/file.cpp @@ -26,6 +26,8 @@ #include #include +#include + extern TextEditor editor; extern void log(const std::string& str); @@ -133,8 +135,8 @@ void fileRenderMenu() ImGui::Separator(); if (ImGui::MenuItem("Quit")) { - extern bool done; - done = true; + SDL_Event quitEvent (SDL_QUIT); + SDL_PushEvent(&quitEvent); } ImGui::EndMenu(); diff --git a/source/gui.cpp b/source/gui.cpp index 9720442..515d471 100644 --- a/source/gui.cpp +++ b/source/gui.cpp @@ -108,8 +108,10 @@ void guiRender(void (*func)()) SDL_GL_SwapWindow(window); } -void guiHandleEvents(bool& done) +bool guiHandleEvents() { + bool done = false; + for (SDL_Event event; SDL_PollEvent(&event);) { ImGui_ImplSDL2_ProcessEvent(&event); if (event.type == SDL_QUIT) @@ -117,6 +119,8 @@ void guiHandleEvents(bool& done) if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)) done = true; } + + return done; } void guiShutdown() diff --git a/source/gui_device.cpp b/source/gui_device.cpp index 5399c8b..a1d0555 100644 --- a/source/gui_device.cpp +++ b/source/gui_device.cpp @@ -1,3 +1,4 @@ +#include "circular.hpp" #include "imgui.h" #include "imgui_internal.h" #include "ImGuiFileDialog.h" @@ -5,6 +6,7 @@ #include "stmdsp.hpp" #include +#include #include #include #include @@ -15,7 +17,7 @@ extern std::shared_ptr m_device; void deviceAlgorithmUnload(); void deviceAlgorithmUpload(); bool deviceConnect(); -void deviceGenLoadFormula(std::string_view list); +void deviceGenLoadFormula(const std::string& list); void deviceGenLoadList(std::string_view list); bool deviceGenStartToggle(); void deviceLoadAudioFile(const std::string& file); @@ -24,13 +26,11 @@ 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, +std::size_t pullFromDrawQueue( + CircularBuffer& circ, double timeframe); -void pullFromInputDrawQueue( - std::vector& buffer, - decltype(buffer.begin())& bufferCursor, +std::size_t pullFromInputDrawQueue( + CircularBuffer& circ, double timeframe); static std::string sampleRatePreview = "?"; @@ -56,11 +56,8 @@ void deviceRenderMenu() }; 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, [&] { + addMenuItem(connectLabel, !m_device || !m_device->is_running(), [&] { if (deviceConnect()) { connectLabel = "Disconnect"; sampleRatePreview = @@ -68,9 +65,15 @@ void deviceRenderMenu() deviceUpdateDrawBufferSize(drawSamplesTimeframe); } else { connectLabel = "Connect"; + measureCodeTime = false; + logResults = false; + drawSamples = false; } }); + const bool isConnected = m_device ? true : false; + const bool isRunning = isConnected && m_device->is_running(); + ImGui::Separator(); static std::string startLabel ("Start"); @@ -153,12 +156,11 @@ void deviceRenderToolbar() void deviceRenderWidgets() { - static std::string siggenInput; + static std::string siggenInput (32768, '\0'); static int siggenOption = 0; if (popupRequestSiggen) { popupRequestSiggen = false; - siggenInput.clear(); ImGui::OpenPopup("siggen"); } else if (popupRequestBuffer) { popupRequestBuffer = false; @@ -166,15 +168,19 @@ void deviceRenderWidgets() } else if (popupRequestLog) { popupRequestLog = false; ImGuiFileDialog::Instance()->OpenModal( - "ChooseFileLogGen", "Choose File", ".csv", "."); + "ChooseFileLog", "Choose File", ".csv", "."); } if (ImGui::BeginPopup("siggen")) { - if (ImGui::RadioButton("List", &siggenOption, 0)) - siggenInput.clear(); + if (ImGui::RadioButton("List", &siggenOption, 0)) { + siggenInput.resize(32768); + siggenInput[0] = '\0'; + } ImGui::SameLine(); - if (ImGui::RadioButton("Formula", &siggenOption, 1)) - siggenInput.clear(); + if (ImGui::RadioButton("Formula", &siggenOption, 1)) { + siggenInput.resize(1024); + siggenInput[0] = '\0'; + } ImGui::SameLine(); if (ImGui::RadioButton("Audio File", &siggenOption, 2)) siggenInput.clear(); @@ -183,7 +189,7 @@ void deviceRenderWidgets() if (ImGui::Button("Choose File")) { // This dialog will override the siggen popup, closing it. ImGuiFileDialog::Instance()->OpenModal( - "ChooseFileLogGen", "Choose File", ".wav", "."); + "ChooseFileGen", "Choose File", ".wav", "."); } } else { ImGui::Text(siggenOption == 0 ? "Enter a list of numbers:" @@ -201,10 +207,10 @@ void deviceRenderWidgets() if (ImGui::Button("Save")) { switch (siggenOption) { case 0: - deviceGenLoadList(siggenInput); + deviceGenLoadList(siggenInput.substr(0, siggenInput.find('\0'))); break; case 1: - deviceGenLoadFormula(siggenInput); + deviceGenLoadFormula(siggenInput.substr(0, siggenInput.find('\0'))); break; case 2: break; @@ -238,18 +244,27 @@ void deviceRenderWidgets() ImGui::EndPopup(); } - if (ImGuiFileDialog::Instance()->Display("ChooseFileLogGen", + if (ImGuiFileDialog::Instance()->Display("ChooseFileLog", ImGuiWindowFlags_NoCollapse, ImVec2(460, 540))) { if (ImGuiFileDialog::Instance()->IsOk()) { - auto filePathName = ImGuiFileDialog::Instance()->GetFilePathName(); - auto ext = filePathName.substr(filePathName.size() - 4); + const auto filePathName = ImGuiFileDialog::Instance()->GetFilePathName(); + deviceLoadLogFile(filePathName); + } else { + logResults = false; + } + + ImGuiFileDialog::Instance()->Close(); + } - if (ext.compare(".wav") == 0) - deviceLoadAudioFile(filePathName); - else if (ext.compare(".csv") == 0) - deviceLoadLogFile(filePathName); + 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(); @@ -260,9 +275,9 @@ void deviceRenderDraw() { if (drawSamples) { static std::vector buffer; - static decltype(buffer.begin()) bufferCursor; static std::vector bufferInput; - static decltype(bufferInput.begin()) bufferInputCursor; + static auto bufferCirc = CircularBuffer(buffer); + static auto bufferInputCirc = CircularBuffer(bufferInput); static bool drawSamplesInput = false; static unsigned int yMinMax = 4095; @@ -295,16 +310,50 @@ void deviceRenderDraw() yMinMax = std::min(4095u, (yMinMax << 1) | 1); } - pullFromDrawQueue(buffer, bufferCursor, drawSamplesTimeframe); - if (drawSamplesInput) - pullFromInputDrawQueue(bufferInput, bufferInputCursor, drawSamplesTimeframe); + auto newSize = pullFromDrawQueue(bufferCirc, drawSamplesTimeframe); + if (newSize > 0) { + buffer.resize(newSize); + bufferCirc = CircularBuffer(buffer); + pullFromDrawQueue(bufferCirc, drawSamplesTimeframe); + } + + if (drawSamplesInput) { + auto newSize = pullFromInputDrawQueue(bufferInputCirc, drawSamplesTimeframe); + if (newSize > 0) { + bufferInput.resize(newSize); + bufferInputCirc = CircularBuffer(bufferInput); + pullFromInputDrawQueue(bufferInputCirc, 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)); + 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(buffer.size()) / size.x; const float dx = std::ceil(size.x / static_cast(buffer.size())); @@ -334,6 +383,18 @@ void deviceRenderDraw() } } + 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) + { + const std::size_t si = (mouse.x - p0.x) / size.x * buffer.size(); + const float s = buffer[si] / 4095.f * 6.6f - 3.3f; + char buf[12]; + snprintf(buf, 16, " %1.3fV", s); + drawList->AddLine({mouse.x, p0.y}, {mouse.x, p0.y + size.y}, IM_COL32(255, 255, 0, 255)); + drawList->AddText(ImGui::GetMousePos(), IM_COL32(210, 210, 0, 255), buf); + } + ImGui::End(); } } diff --git a/source/imgui/TextEditor.cpp b/source/imgui/TextEditor.cpp index 02966f0..b45a21e 100644 --- a/source/imgui/TextEditor.cpp +++ b/source/imgui/TextEditor.cpp @@ -50,7 +50,7 @@ TextEditor::TextEditor() , mStartTime(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) { SetPalette(GetDarkPalette()); - SetLanguageDefinition(LanguageDefinition::HLSL()); + SetLanguageDefinition(LanguageDefinition::CPlusPlus()); mLines.push_back(Line()); } @@ -2804,357 +2804,3 @@ const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::CPlusPlus( return langDef; } -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::HLSL() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) - { - static const char* const keywords[] = { - "AppendStructuredBuffer", "asm", "asm_fragment", "BlendState", "bool", "break", "Buffer", "ByteAddressBuffer", "case", "cbuffer", "centroid", "class", "column_major", "compile", "compile_fragment", - "CompileShader", "const", "continue", "ComputeShader", "ConsumeStructuredBuffer", "default", "DepthStencilState", "DepthStencilView", "discard", "do", "double", "DomainShader", "dword", "else", - "export", "extern", "false", "float", "for", "fxgroup", "GeometryShader", "groupshared", "half", "Hullshader", "if", "in", "inline", "inout", "InputPatch", "int", "interface", "line", "lineadj", - "linear", "LineStream", "matrix", "min16float", "min10float", "min16int", "min12int", "min16uint", "namespace", "nointerpolation", "noperspective", "NULL", "out", "OutputPatch", "packoffset", - "pass", "pixelfragment", "PixelShader", "point", "PointStream", "precise", "RasterizerState", "RenderTargetView", "return", "register", "row_major", "RWBuffer", "RWByteAddressBuffer", "RWStructuredBuffer", - "RWTexture1D", "RWTexture1DArray", "RWTexture2D", "RWTexture2DArray", "RWTexture3D", "sample", "sampler", "SamplerState", "SamplerComparisonState", "shared", "snorm", "stateblock", "stateblock_state", - "static", "string", "struct", "switch", "StructuredBuffer", "tbuffer", "technique", "technique10", "technique11", "texture", "Texture1D", "Texture1DArray", "Texture2D", "Texture2DArray", "Texture2DMS", - "Texture2DMSArray", "Texture3D", "TextureCube", "TextureCubeArray", "true", "typedef", "triangle", "triangleadj", "TriangleStream", "uint", "uniform", "unorm", "unsigned", "vector", "vertexfragment", - "VertexShader", "void", "volatile", "while", - "bool1","bool2","bool3","bool4","double1","double2","double3","double4", "float1", "float2", "float3", "float4", "int1", "int2", "int3", "int4", "in", "out", "inout", - "uint1", "uint2", "uint3", "uint4", "dword1", "dword2", "dword3", "dword4", "half1", "half2", "half3", "half4", - "float1x1","float2x1","float3x1","float4x1","float1x2","float2x2","float3x2","float4x2", - "float1x3","float2x3","float3x3","float4x3","float1x4","float2x4","float3x4","float4x4", - "half1x1","half2x1","half3x1","half4x1","half1x2","half2x2","half3x2","half4x2", - "half1x3","half2x3","half3x3","half4x3","half1x4","half2x4","half3x4","half4x4", - }; - for (auto& k : keywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "abort", "abs", "acos", "all", "AllMemoryBarrier", "AllMemoryBarrierWithGroupSync", "any", "asdouble", "asfloat", "asin", "asint", "asint", "asuint", - "asuint", "atan", "atan2", "ceil", "CheckAccessFullyMapped", "clamp", "clip", "cos", "cosh", "countbits", "cross", "D3DCOLORtoUBYTE4", "ddx", - "ddx_coarse", "ddx_fine", "ddy", "ddy_coarse", "ddy_fine", "degrees", "determinant", "DeviceMemoryBarrier", "DeviceMemoryBarrierWithGroupSync", - "distance", "dot", "dst", "errorf", "EvaluateAttributeAtCentroid", "EvaluateAttributeAtSample", "EvaluateAttributeSnapped", "exp", "exp2", - "f16tof32", "f32tof16", "faceforward", "firstbithigh", "firstbitlow", "floor", "fma", "fmod", "frac", "frexp", "fwidth", "GetRenderTargetSampleCount", - "GetRenderTargetSamplePosition", "GroupMemoryBarrier", "GroupMemoryBarrierWithGroupSync", "InterlockedAdd", "InterlockedAnd", "InterlockedCompareExchange", - "InterlockedCompareStore", "InterlockedExchange", "InterlockedMax", "InterlockedMin", "InterlockedOr", "InterlockedXor", "isfinite", "isinf", "isnan", - "ldexp", "length", "lerp", "lit", "log", "log10", "log2", "mad", "max", "min", "modf", "msad4", "mul", "noise", "normalize", "pow", "printf", - "Process2DQuadTessFactorsAvg", "Process2DQuadTessFactorsMax", "Process2DQuadTessFactorsMin", "ProcessIsolineTessFactors", "ProcessQuadTessFactorsAvg", - "ProcessQuadTessFactorsMax", "ProcessQuadTessFactorsMin", "ProcessTriTessFactorsAvg", "ProcessTriTessFactorsMax", "ProcessTriTessFactorsMin", - "radians", "rcp", "reflect", "refract", "reversebits", "round", "rsqrt", "saturate", "sign", "sin", "sincos", "sinh", "smoothstep", "sqrt", "step", - "tan", "tanh", "tex1D", "tex1D", "tex1Dbias", "tex1Dgrad", "tex1Dlod", "tex1Dproj", "tex2D", "tex2D", "tex2Dbias", "tex2Dgrad", "tex2Dlod", "tex2Dproj", - "tex3D", "tex3D", "tex3Dbias", "tex3Dgrad", "tex3Dlod", "tex3Dproj", "texCUBE", "texCUBE", "texCUBEbias", "texCUBEgrad", "texCUBElod", "texCUBEproj", "transpose", "trunc" - }; - for (auto& k : identifiers) - { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } - - langDef.mTokenRegexStrings.push_back(std::make_pair("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor)); - langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); - - langDef.mCommentStart = "/*"; - langDef.mCommentEnd = "*/"; - langDef.mSingleLineComment = "//"; - - langDef.mCaseSensitive = true; - langDef.mAutoIndentation = true; - - langDef.mName = "HLSL"; - - inited = true; - } - return langDef; -} - -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::GLSL() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) - { - static const char* const keywords[] = { - "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "inline", "int", "long", "register", "restrict", "return", "short", - "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while", "_Alignas", "_Alignof", "_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary", - "_Noreturn", "_Static_assert", "_Thread_local" - }; - for (auto& k : keywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph", - "ispunct", "isspace", "isupper", "kbhit", "log10", "log2", "log", "memcmp", "modf", "pow", "putchar", "putenv", "puts", "rand", "remove", "rename", "sinh", "sqrt", "srand", "strcat", "strcmp", "strerror", "time", "tolower", "toupper" - }; - for (auto& k : identifiers) - { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } - - langDef.mTokenRegexStrings.push_back(std::make_pair("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor)); - langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); - - langDef.mCommentStart = "/*"; - langDef.mCommentEnd = "*/"; - langDef.mSingleLineComment = "//"; - - langDef.mCaseSensitive = true; - langDef.mAutoIndentation = true; - - langDef.mName = "GLSL"; - - inited = true; - } - return langDef; -} - -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::C() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) - { - static const char* const keywords[] = { - "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "inline", "int", "long", "register", "restrict", "return", "short", - "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while", "_Alignas", "_Alignof", "_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary", - "_Noreturn", "_Static_assert", "_Thread_local" - }; - for (auto& k : keywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph", - "ispunct", "isspace", "isupper", "kbhit", "log10", "log2", "log", "memcmp", "modf", "pow", "putchar", "putenv", "puts", "rand", "remove", "rename", "sinh", "sqrt", "srand", "strcat", "strcmp", "strerror", "time", "tolower", "toupper" - }; - for (auto& k : identifiers) - { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } - - langDef.mTokenize = [](const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end, PaletteIndex & paletteIndex) -> bool - { - paletteIndex = PaletteIndex::Max; - - while (in_begin < in_end && isascii(*in_begin) && isblank(*in_begin)) - in_begin++; - - if (in_begin == in_end) - { - out_begin = in_end; - out_end = in_end; - paletteIndex = PaletteIndex::Default; - } - else if (TokenizeCStyleString(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::String; - else if (TokenizeCStyleCharacterLiteral(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::CharLiteral; - else if (TokenizeCStyleIdentifier(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::Identifier; - else if (TokenizeCStyleNumber(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::Number; - else if (TokenizeCStylePunctuation(in_begin, in_end, out_begin, out_end)) - paletteIndex = PaletteIndex::Punctuation; - - return paletteIndex != PaletteIndex::Max; - }; - - langDef.mCommentStart = "/*"; - langDef.mCommentEnd = "*/"; - langDef.mSingleLineComment = "//"; - - langDef.mCaseSensitive = true; - langDef.mAutoIndentation = true; - - langDef.mName = "C"; - - inited = true; - } - return langDef; -} - -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::SQL() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) - { - static const char* const keywords[] = { - "ADD", "EXCEPT", "PERCENT", "ALL", "EXEC", "PLAN", "ALTER", "EXECUTE", "PRECISION", "AND", "EXISTS", "PRIMARY", "ANY", "EXIT", "PRINT", "AS", "FETCH", "PROC", "ASC", "FILE", "PROCEDURE", - "AUTHORIZATION", "FILLFACTOR", "PUBLIC", "BACKUP", "FOR", "RAISERROR", "BEGIN", "FOREIGN", "READ", "BETWEEN", "FREETEXT", "READTEXT", "BREAK", "FREETEXTTABLE", "RECONFIGURE", - "BROWSE", "FROM", "REFERENCES", "BULK", "FULL", "REPLICATION", "BY", "FUNCTION", "RESTORE", "CASCADE", "GOTO", "RESTRICT", "CASE", "GRANT", "RETURN", "CHECK", "GROUP", "REVOKE", - "CHECKPOINT", "HAVING", "RIGHT", "CLOSE", "HOLDLOCK", "ROLLBACK", "CLUSTERED", "IDENTITY", "ROWCOUNT", "COALESCE", "IDENTITY_INSERT", "ROWGUIDCOL", "COLLATE", "IDENTITYCOL", "RULE", - "COLUMN", "IF", "SAVE", "COMMIT", "IN", "SCHEMA", "COMPUTE", "INDEX", "SELECT", "CONSTRAINT", "INNER", "SESSION_USER", "CONTAINS", "INSERT", "SET", "CONTAINSTABLE", "INTERSECT", "SETUSER", - "CONTINUE", "INTO", "SHUTDOWN", "CONVERT", "IS", "SOME", "CREATE", "JOIN", "STATISTICS", "CROSS", "KEY", "SYSTEM_USER", "CURRENT", "KILL", "TABLE", "CURRENT_DATE", "LEFT", "TEXTSIZE", - "CURRENT_TIME", "LIKE", "THEN", "CURRENT_TIMESTAMP", "LINENO", "TO", "CURRENT_USER", "LOAD", "TOP", "CURSOR", "NATIONAL", "TRAN", "DATABASE", "NOCHECK", "TRANSACTION", - "DBCC", "NONCLUSTERED", "TRIGGER", "DEALLOCATE", "NOT", "TRUNCATE", "DECLARE", "NULL", "TSEQUAL", "DEFAULT", "NULLIF", "UNION", "DELETE", "OF", "UNIQUE", "DENY", "OFF", "UPDATE", - "DESC", "OFFSETS", "UPDATETEXT", "DISK", "ON", "USE", "DISTINCT", "OPEN", "USER", "DISTRIBUTED", "OPENDATASOURCE", "VALUES", "DOUBLE", "OPENQUERY", "VARYING","DROP", "OPENROWSET", "VIEW", - "DUMMY", "OPENXML", "WAITFOR", "DUMP", "OPTION", "WHEN", "ELSE", "OR", "WHERE", "END", "ORDER", "WHILE", "ERRLVL", "OUTER", "WITH", "ESCAPE", "OVER", "WRITETEXT" - }; - - for (auto& k : keywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "ABS", "ACOS", "ADD_MONTHS", "ASCII", "ASCIISTR", "ASIN", "ATAN", "ATAN2", "AVG", "BFILENAME", "BIN_TO_NUM", "BITAND", "CARDINALITY", "CASE", "CAST", "CEIL", - "CHARTOROWID", "CHR", "COALESCE", "COMPOSE", "CONCAT", "CONVERT", "CORR", "COS", "COSH", "COUNT", "COVAR_POP", "COVAR_SAMP", "CUME_DIST", "CURRENT_DATE", - "CURRENT_TIMESTAMP", "DBTIMEZONE", "DECODE", "DECOMPOSE", "DENSE_RANK", "DUMP", "EMPTY_BLOB", "EMPTY_CLOB", "EXP", "EXTRACT", "FIRST_VALUE", "FLOOR", "FROM_TZ", "GREATEST", - "GROUP_ID", "HEXTORAW", "INITCAP", "INSTR", "INSTR2", "INSTR4", "INSTRB", "INSTRC", "LAG", "LAST_DAY", "LAST_VALUE", "LEAD", "LEAST", "LENGTH", "LENGTH2", "LENGTH4", - "LENGTHB", "LENGTHC", "LISTAGG", "LN", "LNNVL", "LOCALTIMESTAMP", "LOG", "LOWER", "LPAD", "LTRIM", "MAX", "MEDIAN", "MIN", "MOD", "MONTHS_BETWEEN", "NANVL", "NCHR", - "NEW_TIME", "NEXT_DAY", "NTH_VALUE", "NULLIF", "NUMTODSINTERVAL", "NUMTOYMINTERVAL", "NVL", "NVL2", "POWER", "RANK", "RAWTOHEX", "REGEXP_COUNT", "REGEXP_INSTR", - "REGEXP_REPLACE", "REGEXP_SUBSTR", "REMAINDER", "REPLACE", "ROUND", "ROWNUM", "RPAD", "RTRIM", "SESSIONTIMEZONE", "SIGN", "SIN", "SINH", - "SOUNDEX", "SQRT", "STDDEV", "SUBSTR", "SUM", "SYS_CONTEXT", "SYSDATE", "SYSTIMESTAMP", "TAN", "TANH", "TO_CHAR", "TO_CLOB", "TO_DATE", "TO_DSINTERVAL", "TO_LOB", - "TO_MULTI_BYTE", "TO_NCLOB", "TO_NUMBER", "TO_SINGLE_BYTE", "TO_TIMESTAMP", "TO_TIMESTAMP_TZ", "TO_YMINTERVAL", "TRANSLATE", "TRIM", "TRUNC", "TZ_OFFSET", "UID", "UPPER", - "USER", "USERENV", "VAR_POP", "VAR_SAMP", "VARIANCE", "VSIZE " - }; - for (auto& k : identifiers) - { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } - - langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("\\\'[^\\\']*\\\'", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); - - langDef.mCommentStart = "/*"; - langDef.mCommentEnd = "*/"; - langDef.mSingleLineComment = "//"; - - langDef.mCaseSensitive = false; - langDef.mAutoIndentation = false; - - langDef.mName = "SQL"; - - inited = true; - } - return langDef; -} - -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::AngelScript() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) - { - static const char* const keywords[] = { - "and", "abstract", "auto", "bool", "break", "case", "cast", "class", "const", "continue", "default", "do", "double", "else", "enum", "false", "final", "float", "for", - "from", "funcdef", "function", "get", "if", "import", "in", "inout", "int", "interface", "int8", "int16", "int32", "int64", "is", "mixin", "namespace", "not", - "null", "or", "out", "override", "private", "protected", "return", "set", "shared", "super", "switch", "this ", "true", "typedef", "uint", "uint8", "uint16", "uint32", - "uint64", "void", "while", "xor" - }; - - for (auto& k : keywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "cos", "sin", "tab", "acos", "asin", "atan", "atan2", "cosh", "sinh", "tanh", "log", "log10", "pow", "sqrt", "abs", "ceil", "floor", "fraction", "closeTo", "fpFromIEEE", "fpToIEEE", - "complex", "opEquals", "opAddAssign", "opSubAssign", "opMulAssign", "opDivAssign", "opAdd", "opSub", "opMul", "opDiv" - }; - for (auto& k : identifiers) - { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } - - langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("\\'\\\\?[^\\']\\'", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); - - langDef.mCommentStart = "/*"; - langDef.mCommentEnd = "*/"; - langDef.mSingleLineComment = "//"; - - langDef.mCaseSensitive = true; - langDef.mAutoIndentation = true; - - langDef.mName = "AngelScript"; - - inited = true; - } - return langDef; -} - -const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Lua() -{ - static bool inited = false; - static LanguageDefinition langDef; - if (!inited) - { - static const char* const keywords[] = { - "and", "break", "do", "", "else", "elseif", "end", "false", "for", "function", "if", "in", "", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while" - }; - - for (auto& k : keywords) - langDef.mKeywords.insert(k); - - static const char* const identifiers[] = { - "assert", "collectgarbage", "dofile", "error", "getmetatable", "ipairs", "loadfile", "load", "loadstring", "next", "pairs", "pcall", "print", "rawequal", "rawlen", "rawget", "rawset", - "select", "setmetatable", "tonumber", "tostring", "type", "xpcall", "_G", "_VERSION","arshift", "band", "bnot", "bor", "bxor", "btest", "extract", "lrotate", "lshift", "replace", - "rrotate", "rshift", "create", "resume", "running", "status", "wrap", "yield", "isyieldable", "debug","getuservalue", "gethook", "getinfo", "getlocal", "getregistry", "getmetatable", - "getupvalue", "upvaluejoin", "upvalueid", "setuservalue", "sethook", "setlocal", "setmetatable", "setupvalue", "traceback", "close", "flush", "input", "lines", "open", "output", "popen", - "read", "tmpfile", "type", "write", "close", "flush", "lines", "read", "seek", "setvbuf", "write", "__gc", "__tostring", "abs", "acos", "asin", "atan", "ceil", "cos", "deg", "exp", "tointeger", - "floor", "fmod", "ult", "log", "max", "min", "modf", "rad", "random", "randomseed", "sin", "sqrt", "string", "tan", "type", "atan2", "cosh", "sinh", "tanh", - "pow", "frexp", "ldexp", "log10", "pi", "huge", "maxinteger", "mininteger", "loadlib", "searchpath", "seeall", "preload", "cpath", "path", "searchers", "loaded", "module", "require", "clock", - "date", "difftime", "execute", "exit", "getenv", "remove", "rename", "setlocale", "time", "tmpname", "byte", "char", "dump", "find", "format", "gmatch", "gsub", "len", "lower", "match", "rep", - "reverse", "sub", "upper", "pack", "packsize", "unpack", "concat", "maxn", "insert", "pack", "unpack", "remove", "move", "sort", "offset", "codepoint", "char", "len", "codes", "charpattern", - "coroutine", "table", "io", "os", "string", "utf8", "bit32", "math", "debug", "package" - }; - for (auto& k : identifiers) - { - Identifier id; - id.mDeclaration = "Built-in function"; - langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } - - langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("\\\'[^\\\']*\\\'", PaletteIndex::String)); - langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); - langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); - - langDef.mCommentStart = "--[["; - langDef.mCommentEnd = "]]"; - langDef.mSingleLineComment = "--"; - - langDef.mCaseSensitive = true; - langDef.mAutoIndentation = false; - - langDef.mName = "Lua"; - - inited = true; - } - return langDef; -} diff --git a/source/imgui/TextEditor.h b/source/imgui/TextEditor.h index bd52e13..e0e86cd 100644 --- a/source/imgui/TextEditor.h +++ b/source/imgui/TextEditor.h @@ -174,12 +174,6 @@ public: } static const LanguageDefinition& CPlusPlus(); - static const LanguageDefinition& HLSL(); - static const LanguageDefinition& GLSL(); - static const LanguageDefinition& C(); - static const LanguageDefinition& SQL(); - static const LanguageDefinition& AngelScript(); - static const LanguageDefinition& Lua(); }; TextEditor(); diff --git a/source/imgui/imgui.h b/source/imgui/imgui.h index 182c46a..8d2b376 100644 --- a/source/imgui/imgui.h +++ b/source/imgui/imgui.h @@ -243,9 +243,9 @@ typedef unsigned long long ImU64; // 64-bit unsigned integer (post C++11) IM_MSVC_RUNTIME_CHECKS_OFF struct ImVec2 { - float x, y; - ImVec2() { x = y = 0.0f; } - ImVec2(float _x, float _y) { x = _x; y = _y; } + float x = 0.f, y = 0.f; + constexpr ImVec2() = default; + constexpr ImVec2(float _x, float _y) : x(_x), y(_y) {} float operator[] (size_t idx) const { IM_ASSERT(idx <= 1); return (&x)[idx]; } // We very rarely use this [] operator, the assert overhead is fine. float& operator[] (size_t idx) { IM_ASSERT(idx <= 1); return (&x)[idx]; } // We very rarely use this [] operator, the assert overhead is fine. #ifdef IM_VEC2_CLASS_EXTRA diff --git a/source/logview.cpp b/source/logview.cpp new file mode 100644 index 0000000..267cecb --- /dev/null +++ b/source/logview.cpp @@ -0,0 +1,82 @@ +#include "logview.h" + +LogView::LogView() +{ + Clear(); +} + +void LogView::Clear() +{ + Buf.clear(); + LineOffsets.clear(); + LineOffsets.push_back(0); + updated = false; +} + +void LogView::AddLog(const std::string& str) +{ + AddLog(str.c_str()); +} + +void LogView::AddLog(const char* fmt, ...) +{ + int old_size = Buf.size(); + va_list args; + va_start(args, fmt); + Buf.appendfv(fmt, args); + Buf.appendfv("\n", args); + va_end(args); + for (int new_size = Buf.size(); old_size < new_size; old_size++) + if (Buf[old_size] == '\n') + LineOffsets.push_back(old_size + 1); + updated = true; +} + +void LogView::Draw(const char* title, bool* p_open, ImGuiWindowFlags flags) +{ + if (!ImGui::Begin(title, p_open, flags)) + { + ImGui::End(); + return; + } + + ImGui::Text("Log "); + ImGui::SameLine(); + if (ImGui::Button("Clear")) + Clear(); + ImGui::SameLine(); + if (ImGui::Button("Copy")) + ImGui::LogToClipboard(); + ImGui::Separator(); + ImGui::BeginChild("scrolling", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar); + + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + const char* buf = Buf.begin(); + const char* buf_end = Buf.end(); + + ImGuiListClipper clipper; + clipper.Begin(LineOffsets.Size); + + while (clipper.Step()) + { + for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++) + { + const char* line_start = buf + LineOffsets[line_no]; + const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end; + ImGui::TextUnformatted(line_start, line_end); + } + } + clipper.End(); + + ImGui::PopStyleVar(); + + if (updated) { + ImGui::SetScrollHereY(); + updated = false; + } + + ImGui::EndChild(); + ImGui::End(); +} + diff --git a/source/logview.h b/source/logview.h index 4ab2c94..3c6acf1 100644 --- a/source/logview.h +++ b/source/logview.h @@ -3,87 +3,23 @@ #include +#include "imgui.h" + // Adapted from ExampleAppLog from imgui_demo.cpp class LogView { public: - LogView() - { - Clear(); - } - - void Clear() - { - Buf.clear(); - LineOffsets.clear(); - LineOffsets.push_back(0); - } - - void AddLog(const std::string& str) - { - AddLog(str.c_str()); - } - - void AddLog(const char* fmt, ...) IM_FMTARGS(2) - { - int old_size = Buf.size(); - va_list args; - va_start(args, fmt); - Buf.appendfv(fmt, args); - Buf.appendfv("\n", args); - va_end(args); - for (int new_size = Buf.size(); old_size < new_size; old_size++) - if (Buf[old_size] == '\n') - LineOffsets.push_back(old_size + 1); - } - - void Draw(const char* title, bool* p_open = NULL, ImGuiWindowFlags flags = 0) - { - if (!ImGui::Begin(title, p_open, flags)) - { - ImGui::End(); - return; - } - - ImGui::Text("Log "); - ImGui::SameLine(); - if (ImGui::Button("Clear")) - Clear(); - ImGui::SameLine(); - if (ImGui::Button("Copy")) - ImGui::LogToClipboard(); - ImGui::Separator(); - ImGui::BeginChild("scrolling", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar); - - - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); - const char* buf = Buf.begin(); - const char* buf_end = Buf.end(); - ImGuiListClipper clipper; - clipper.Begin(LineOffsets.Size); - while (clipper.Step()) - { - for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++) - { - const char* line_start = buf + LineOffsets[line_no]; - const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end; - ImGui::TextUnformatted(line_start, line_end); - } - } - clipper.End(); - - ImGui::PopStyleVar(); - - if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) - ImGui::SetScrollHereY(1.0f); + LogView(); - ImGui::EndChild(); - ImGui::End(); - } + void Clear(); + void AddLog(const std::string& str); + void AddLog(const char* fmt, ...) IM_FMTARGS(2); + void Draw(const char* title, bool* p_open = NULL, ImGuiWindowFlags flags = 0); private: ImGuiTextBuffer Buf; ImVector LineOffsets; // Index to lines offset. We maintain this with AddLog() calls. + bool updated; }; #endif // LOGVIEW_H diff --git a/source/main.cpp b/source/main.cpp index 20c8ea1..5eac338 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -25,27 +25,24 @@ extern ImFont *fontSans; extern ImFont *fontMono; -extern bool guiInitialize(); -extern void guiHandleEvents(bool& done); -extern void guiShutdown(); -extern void guiRender(void (*func)()); +bool guiInitialize(); +bool guiHandleEvents(); +void guiShutdown(); +void guiRender(void (*func)()); -extern void fileRenderMenu(); -extern void fileRenderDialog(); -extern void fileInit(); +void fileRenderMenu(); +void fileRenderDialog(); +void fileInit(); -extern void codeEditorInit(); -extern void codeRenderMenu(); -extern void codeRenderToolbar(); -extern void codeRenderWidgets(); +void codeEditorInit(); +void codeRenderMenu(); +void codeRenderToolbar(); +void codeRenderWidgets(); -extern void deviceRenderDraw(); -extern void deviceRenderMenu(); -extern void deviceRenderToolbar(); -extern void deviceRenderWidgets(); - -// Globals that live here -bool done = false; +void deviceRenderDraw(); +void deviceRenderMenu(); +void deviceRenderToolbar(); +void deviceRenderWidgets(); static LogView logView; @@ -54,6 +51,8 @@ void log(const std::string& str) logView.AddLog(str); } +static void renderWindow(); + int main(int, char **) { if (!guiInitialize()) @@ -62,62 +61,72 @@ int main(int, char **) codeEditorInit(); fileInit(); - while (!done) { - auto endTime = std::chrono::steady_clock::now() + - std::chrono::milliseconds(static_cast(std::floor(1000. / 60.))); - - guiHandleEvents(done); - - // Start the new window frame and render the menu bar. - ImGui_ImplOpenGL2_NewFrame(); - ImGui_ImplSDL2_NewFrame(); - ImGui::NewFrame(); + while (1) { + constexpr std::chrono::duration fpsDelay (1. / 60.); + const auto endTime = std::chrono::steady_clock::now() + fpsDelay; - if (ImGui::BeginMainMenuBar()) { - fileRenderMenu(); - deviceRenderMenu(); - codeRenderMenu(); - ImGui::EndMainMenuBar(); + const bool isDone = guiHandleEvents(); + if (!isDone) { + renderWindow(); + std::this_thread::sleep_until(endTime); + } else { + break; } + } - // Begin the main view which the controls will be drawn onto. - ImGui::SetNextWindowPos({0, 22}); - ImGui::SetNextWindowSize({WINDOW_WIDTH, WINDOW_HEIGHT - 22 - 200}); - ImGui::Begin("main", nullptr, - ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration | - ImGuiWindowFlags_NoBringToFrontOnFocus); - - // Render main controls (order is important). - { - ImGui::PushFont(fontSans); - codeRenderToolbar(); - deviceRenderToolbar(); - fileRenderDialog(); - deviceRenderWidgets(); - ImGui::PopFont(); - - ImGui::PushFont(fontMono); - codeRenderWidgets(); - ImGui::SetNextWindowPos({0, WINDOW_HEIGHT - 200}); - ImGui::SetNextWindowSize({WINDOW_WIDTH, 200}); - logView.Draw("log", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBringToFrontOnFocus); - ImGui::PopFont(); - } + guiShutdown(); + return 0; +} - ImGui::End(); +void renderWindow() +{ + // Start the new window frame and render the menu bar. + ImGui_ImplOpenGL2_NewFrame(); + ImGui_ImplSDL2_NewFrame(); + ImGui::NewFrame(); + + if (ImGui::BeginMainMenuBar()) { + fileRenderMenu(); + deviceRenderMenu(); + codeRenderMenu(); + ImGui::EndMainMenuBar(); + } - deviceRenderDraw(); + // Begin the main view which the controls will be drawn onto. + constexpr float LOGVIEW_HEIGHT = 200; + constexpr ImVec2 WINDOW_POS (0, 22); + constexpr ImVec2 WINDOW_SIZE (WINDOW_WIDTH, WINDOW_HEIGHT - 22 - LOGVIEW_HEIGHT); + ImGui::SetNextWindowPos(WINDOW_POS); + ImGui::SetNextWindowSize(WINDOW_SIZE); + ImGui::Begin("main", nullptr, + ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_NoBringToFrontOnFocus); + + // Render main controls (order is important). + { + ImGui::PushFont(fontSans); + codeRenderToolbar(); + deviceRenderToolbar(); + fileRenderDialog(); + deviceRenderWidgets(); + ImGui::PopFont(); + + ImGui::PushFont(fontMono); + codeRenderWidgets(); + ImGui::SetNextWindowPos({0, WINDOW_HEIGHT - LOGVIEW_HEIGHT}); + ImGui::SetNextWindowSize({WINDOW_WIDTH, LOGVIEW_HEIGHT}); + logView.Draw("log", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBringToFrontOnFocus); + ImGui::PopFont(); + } - // Draw everything to the screen. - ImGui::Render(); - guiRender([] { - ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData()); - }); + ImGui::End(); - std::this_thread::sleep_until(endTime); - } + deviceRenderDraw(); - guiShutdown(); - return 0; + // Draw everything to the screen. + ImGui::Render(); + guiRender([] { + ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData()); + }); } From 1b176cf6cd75c8031a140961655cdd3c16589a68 Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Sun, 9 Jan 2022 12:28:19 -0500 Subject: [PATCH 12/21] small changes; sig gen square(), triangle(), pulse() --- {templates => examples}/1_convolve_simple.cpp | 0 .../2_convolve_overlap_save.cpp | 0 {templates => examples}/3_fir.cpp | 0 {templates => examples}/4_fir_pro.cpp | 0 .../5_fir_differentiator.cpp | 0 {templates => examples}/6_iir_test.cpp | 0 {templates => examples}/7_iir_echo.cpp | 0 source/circular.hpp | 11 +++++ source/code.cpp | 26 +++++++++- source/device.cpp | 18 ++++--- source/device_formula.cpp | 44 ++++++++++++++--- source/file.cpp | 12 ++--- source/gui.cpp | 49 ++++++++++++------- source/gui_code.cpp | 4 +- source/gui_device.cpp | 33 ++++++++----- source/imgui/TextEditor.cpp | 2 +- source/logview.cpp | 2 +- source/main.cpp | 48 +++++++++--------- 18 files changed, 175 insertions(+), 74 deletions(-) rename {templates => examples}/1_convolve_simple.cpp (100%) rename {templates => examples}/2_convolve_overlap_save.cpp (100%) rename {templates => examples}/3_fir.cpp (100%) rename {templates => examples}/4_fir_pro.cpp (100%) rename {templates => examples}/5_fir_differentiator.cpp (100%) rename {templates => examples}/6_iir_test.cpp (100%) rename {templates => examples}/7_iir_echo.cpp (100%) diff --git a/templates/1_convolve_simple.cpp b/examples/1_convolve_simple.cpp similarity index 100% rename from templates/1_convolve_simple.cpp rename to examples/1_convolve_simple.cpp diff --git a/templates/2_convolve_overlap_save.cpp b/examples/2_convolve_overlap_save.cpp similarity index 100% rename from templates/2_convolve_overlap_save.cpp rename to examples/2_convolve_overlap_save.cpp diff --git a/templates/3_fir.cpp b/examples/3_fir.cpp similarity index 100% rename from templates/3_fir.cpp rename to examples/3_fir.cpp diff --git a/templates/4_fir_pro.cpp b/examples/4_fir_pro.cpp similarity index 100% rename from templates/4_fir_pro.cpp rename to examples/4_fir_pro.cpp diff --git a/templates/5_fir_differentiator.cpp b/examples/5_fir_differentiator.cpp similarity index 100% rename from templates/5_fir_differentiator.cpp rename to examples/5_fir_differentiator.cpp diff --git a/templates/6_iir_test.cpp b/examples/6_iir_test.cpp similarity index 100% rename from templates/6_iir_test.cpp rename to examples/6_iir_test.cpp diff --git a/templates/7_iir_echo.cpp b/examples/7_iir_echo.cpp similarity index 100% rename from templates/7_iir_echo.cpp rename to examples/7_iir_echo.cpp diff --git a/source/circular.hpp b/source/circular.hpp index 33b8ee0..6b82068 100644 --- a/source/circular.hpp +++ b/source/circular.hpp @@ -1,3 +1,14 @@ +/** + * @file circular.hpp + * @brief Small utility for filling a buffer in a circular manner. + * + * Copyright (C) 2021 Clyne Sullivan + * + * Distributed under the GNU GPL v3 or later. You should have received a copy of + * the GNU General Public License along with this program. + * If not, see . + */ + #ifndef CIRCULAR_HPP #define CIRCULAR_HPP diff --git a/source/code.cpp b/source/code.cpp index 7a9afaa..14f603c 100644 --- a/source/code.cpp +++ b/source/code.cpp @@ -1,3 +1,14 @@ +/** + * @file code.cpp + * @brief Functionality for compiling and disassembling source code. + * + * 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 "stmdsp.hpp" #include "stmdsp_code.hpp" @@ -9,10 +20,13 @@ #include extern std::shared_ptr m_device; -extern void log(const std::string& str); +void log(const std::string& str); -std::string tempFileName; // device.cpp +std::ifstream compileOpenBinaryFile(); +void compileEditorCode(const std::string& code); +void disassembleCode(); +static std::string tempFileName; static std::string newTempFileName(); static bool codeExecuteCommand( const std::string& command, @@ -22,6 +36,14 @@ static void stringReplaceAll( const std::string& what, const std::string& with); +std::ifstream compileOpenBinaryFile() +{ + if (!tempFileName.empty()) + return std::ifstream(tempFileName + ".o"); + else + return std::ifstream(); +} + void compileEditorCode(const std::string& code) { log("Compiling..."); diff --git a/source/device.cpp b/source/device.cpp index abcc88a..11e181e 100644 --- a/source/device.cpp +++ b/source/device.cpp @@ -30,9 +30,9 @@ #include #include -extern std::string tempFileName; extern void log(const std::string& str); extern std::vector deviceGenLoadFormulaEval(const std::string&); +extern std::ifstream compileOpenBinaryFile(); std::shared_ptr m_device; @@ -224,15 +224,21 @@ void deviceLoadLogFile(const std::string& file) bool deviceGenStartToggle() { if (m_device) { - bool running = m_device->is_siggening(); + const bool running = m_device->is_siggening(); + if (!running) { - if (wavOutput.valid()) + if (wavOutput.valid()) { std::thread(feedSigGenTask, m_device).detach(); - else + } else { + std::scoped_lock dlock (mutexDeviceLoad); m_device->siggen_start(); + } log("Generator started."); } else { - m_device->siggen_stop(); + { + std::scoped_lock dlock (mutexDeviceLoad); + m_device->siggen_stop(); + } log("Generator stopped."); } @@ -332,7 +338,7 @@ void deviceAlgorithmUpload() log("No device connected."); } else if (m_device->is_running()) { log("Cannot upload algorithm while running."); - } else if (std::ifstream algo (tempFileName + ".o"); algo.is_open()) { + } else if (auto algo = compileOpenBinaryFile(); algo.is_open()) { std::ostringstream sstr; sstr << algo.rdbuf(); auto str = sstr.str(); diff --git a/source/device_formula.cpp b/source/device_formula.cpp index c94eab7..a70f465 100644 --- a/source/device_formula.cpp +++ b/source/device_formula.cpp @@ -1,31 +1,63 @@ +/** + * @file device_formula.cpp + * @brief Function for filling generator buffer using a mathematical formula. + * This is kept in its own file as exprtk.hpp takes forever to compile. + * + * Copyright (C) 2021 Clyne Sullivan + * + * Distributed under the GNU GPL v3 or later. You should have received a copy of + * the GNU General Public License along with this program. + * If not, see . + */ + #include "stmdsp.hpp" #include "exprtk.hpp" #include +#include #include #include +static std::random_device randomDevice; + std::vector deviceGenLoadFormulaEval(const std::string& formulaString) { double x = 0; exprtk::symbol_table symbol_table; + exprtk::function_compositor compositor (symbol_table); exprtk::expression expression; exprtk::parser parser; - symbol_table.add_variable("x", x); symbol_table.add_constants(); + symbol_table.add_variable("x", x); + symbol_table.add_function("random", + [](double l, double h) -> double { + return std::uniform_real_distribution(l, h)(randomDevice); + }); + compositor.add(exprtk::function_compositor::function() + .name("square") + .var("X") + .expression("ceil(sin(pi*X))")); + compositor.add(exprtk::function_compositor::function() + .name("triangle") + .var("X") + .expression("ceil(sin(pi*X))*(X-floor(X))+ceil(-sin(pi*X))*(-X-floor(-X))")); + compositor.add(exprtk::function_compositor::function() + .name("pulse") + .var("L") + .var("X") + .expression("if(X<=L,1,0)")); expression.register_symbol_table(symbol_table); parser.compile(formulaString, expression); - std::vector samples (stmdsp::SAMPLES_MAX); - - auto genFun = [&x, &expression] { - stmdsp::dacsample_t s = expression.value(); + const auto genFun = [&x, &expression] { + const auto s = std::clamp(expression.value(), -1., 1.) * 2048. + 2048.; ++x; - return s; + return static_cast(std::min(s, 4095.)); }; + std::vector samples (stmdsp::SAMPLES_MAX); std::generate(samples.begin(), samples.end(), genFun); return samples; } diff --git a/source/file.cpp b/source/file.cpp index a5ef1d8..fe5dafb 100644 --- a/source/file.cpp +++ b/source/file.cpp @@ -40,7 +40,7 @@ enum class FileAction { static FileAction fileAction = FileAction::None; static std::string fileCurrentPath; -static std::vector fileTemplateList; +static std::vector fileExampleList; static void saveCurrentFile() { @@ -66,9 +66,9 @@ static void openNewFile() editor.SetText(stmdsp::file_content); } -static std::vector fileScanTemplates() +static std::vector fileScanExamples() { - const auto path = std::filesystem::current_path() / "templates"; + const auto path = std::filesystem::current_path() / "examples"; const std::filesystem::recursive_directory_iterator rdi (path); std::vector list; @@ -83,7 +83,7 @@ static std::vector fileScanTemplates() void fileInit() { - fileTemplateList = fileScanTemplates(); + fileExampleList = fileScanExamples(); openNewFile(); } @@ -102,8 +102,8 @@ void fileRenderMenu() "ChooseFileOpenSave", "Choose File", ".cpp", "."); } - if (ImGui::BeginMenu("Open Template")) { - for (const auto& file : fileTemplateList) { + if (ImGui::BeginMenu("Open Example")) { + for (const auto& file : fileExampleList) { if (ImGui::MenuItem(file.filename().c_str())) { fileCurrentPath = file.string(); openCurrentFile(); diff --git a/source/gui.cpp b/source/gui.cpp index 515d471..80120c4 100644 --- a/source/gui.cpp +++ b/source/gui.cpp @@ -19,15 +19,17 @@ #include #include -ImFont *fontSans = nullptr; -ImFont *fontMono = nullptr; -static ImGuiIO *io = nullptr; +bool guiInitialize(); +void guiRender(); +bool guiHandleEvents(); +void guiShutdown(); + static SDL_Window *window = nullptr; -static decltype(SDL_GL_CreateContext(nullptr)) gl_context; +static SDL_GLContext gl_context; bool guiInitialize() { - if (SDL_Init(/*SDL_INIT_VIDEO*/0) != 0) { + if (SDL_Init(0) != 0) { printf("Error: %s\n", SDL_GetError()); return false; } @@ -42,6 +44,12 @@ bool guiInitialize() SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_OPENGL /*| SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI*/); + + if (window == nullptr) { + puts("Error: Could not create the window!"); + return false; + } + gl_context = SDL_GL_CreateContext(window); SDL_GL_MakeCurrent(window, gl_context); SDL_GL_SetSwapInterval(1); // Enable vsync @@ -49,10 +57,7 @@ bool guiInitialize() // Setup Dear ImGui context IMGUI_CHECKVERSION(); ImGui::CreateContext(); - io = &ImGui::GetIO(); //io->ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - fontSans = io->Fonts->AddFontFromFileTTF("fonts/Roboto-Regular.ttf", 20); - fontMono = io->Fonts->AddFontFromFileTTF("fonts/RobotoMono-Regular.ttf", 20); ImGui_ImplSDL2_InitForOpenGL(window, gl_context); ImGui_ImplOpenGL2_Init(); @@ -99,13 +104,19 @@ bool guiInitialize() return true; } -void guiRender(void (*func)()) +void guiRender() { - glViewport(0, 0, (int)io->DisplaySize.x, (int)io->DisplaySize.y); - glClearColor(1, 1, 1, 1); - glClear(GL_COLOR_BUFFER_BIT); - func(); - SDL_GL_SwapWindow(window); + ImGui::Render(); + + const auto& displaySize = ImGui::GetIO().DisplaySize; + const int sizeX = static_cast(displaySize.x); + const int sizeY = static_cast(displaySize.y); + + glViewport(0, 0, sizeX, sizeY); + glClearColor(1, 1, 1, 1); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData()); + SDL_GL_SwapWindow(window); } bool guiHandleEvents() @@ -114,10 +125,14 @@ bool guiHandleEvents() for (SDL_Event event; SDL_PollEvent(&event);) { ImGui_ImplSDL2_ProcessEvent(&event); - if (event.type == SDL_QUIT) - done = true; - if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)) + if (event.type == SDL_QUIT) { done = true; + } else if (event.type == SDL_WINDOWEVENT) { + const auto& ew = event.window; + const auto wid = SDL_GetWindowID(window); + if (ew.event == SDL_WINDOWEVENT_CLOSE && ew.windowID == wid) + done = true; + } } return done; diff --git a/source/gui_code.cpp b/source/gui_code.cpp index 6917c72..19fa572 100644 --- a/source/gui_code.cpp +++ b/source/gui_code.cpp @@ -37,9 +37,9 @@ void codeEditorInit() void codeRenderMenu() { if (ImGui::BeginMenu("Code")) { - if (ImGui::MenuItem("Compile code")) + if (ImGui::MenuItem("Compile")) codeCompile(); - if (ImGui::MenuItem("Show disassembly")) + if (ImGui::MenuItem("Disassemble")) codeDisassemble(); ImGui::EndMenu(); diff --git a/source/gui_device.cpp b/source/gui_device.cpp index a1d0555..43c0a58 100644 --- a/source/gui_device.cpp +++ b/source/gui_device.cpp @@ -55,7 +55,7 @@ void deviceRenderMenu() } }; - if (ImGui::BeginMenu("Run")) { + if (ImGui::BeginMenu("Device")) { static std::string connectLabel ("Connect"); addMenuItem(connectLabel, !m_device || !m_device->is_running(), [&] { if (deviceConnect()) { @@ -199,11 +199,6 @@ void deviceRenderWidgets() ImGui::PopStyleColor(); } - if (ImGui::Button("Cancel")) { - siggenInput.clear(); - ImGui::CloseCurrentPopup(); - } - if (ImGui::Button("Save")) { switch (siggenOption) { case 0: @@ -219,6 +214,12 @@ void deviceRenderWidgets() ImGui::CloseCurrentPopup(); } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) { + siggenInput.clear(); + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); } @@ -387,12 +388,22 @@ void deviceRenderDraw() if (mouse.x > p0.x && mouse.x < p0.x + size.x && mouse.y > p0.y && mouse.y < p0.y + size.y) { - const std::size_t si = (mouse.x - p0.x) / size.x * buffer.size(); - const float s = buffer[si] / 4095.f * 6.6f - 3.3f; - char buf[12]; - snprintf(buf, 16, " %1.3fV", s); + char buf[16]; drawList->AddLine({mouse.x, p0.y}, {mouse.x, p0.y + size.y}, IM_COL32(255, 255, 0, 255)); - drawList->AddText(ImGui::GetMousePos(), IM_COL32(210, 210, 0, 255), buf); + + { + 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(); diff --git a/source/imgui/TextEditor.cpp b/source/imgui/TextEditor.cpp index b45a21e..85bff74 100644 --- a/source/imgui/TextEditor.cpp +++ b/source/imgui/TextEditor.cpp @@ -46,7 +46,7 @@ TextEditor::TextEditor() , mHandleKeyboardInputs(true) , mHandleMouseInputs(true) , mIgnoreImGuiChild(false) - , mShowWhitespaces(true) + , mShowWhitespaces(false) , mStartTime(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) { SetPalette(GetDarkPalette()); diff --git a/source/logview.cpp b/source/logview.cpp index 267cecb..5a771bf 100644 --- a/source/logview.cpp +++ b/source/logview.cpp @@ -41,7 +41,7 @@ void LogView::Draw(const char* title, bool* p_open, ImGuiWindowFlags flags) } ImGui::Text("Log "); - ImGui::SameLine(); + ImGui::SameLine(ImGui::GetWindowWidth() - 120); if (ImGui::Button("Clear")) Clear(); ImGui::SameLine(); diff --git a/source/main.cpp b/source/main.cpp index 5eac338..e8641bc 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -19,37 +19,31 @@ #include #include +#include #include #include -extern ImFont *fontSans; -extern ImFont *fontMono; - -bool guiInitialize(); -bool guiHandleEvents(); -void guiShutdown(); -void guiRender(void (*func)()); - -void fileRenderMenu(); -void fileRenderDialog(); -void fileInit(); - void codeEditorInit(); void codeRenderMenu(); void codeRenderToolbar(); void codeRenderWidgets(); - void deviceRenderDraw(); void deviceRenderMenu(); void deviceRenderToolbar(); void deviceRenderWidgets(); +void fileRenderMenu(); +void fileRenderDialog(); +void fileInit(); +bool guiInitialize(); +bool guiHandleEvents(); +void guiShutdown(); +void guiRender(); -static LogView logView; +void log(const std::string& str); -void log(const std::string& str) -{ - logView.AddLog(str); -} +static LogView logView; +static ImFont *fontSans = nullptr; +static ImFont *fontMono = nullptr; static void renderWindow(); @@ -58,6 +52,14 @@ int main(int, char **) if (!guiInitialize()) return -1; + auto& io = ImGui::GetIO(); + fontSans = io.Fonts->AddFontFromFileTTF("fonts/Roboto-Regular.ttf", 20); + fontMono = io.Fonts->AddFontFromFileTTF("fonts/RobotoMono-Regular.ttf", 20); + if (fontSans == nullptr || fontMono == nullptr) { + std::cout << "Failed to load fonts!" << std::endl; + return -1; + } + codeEditorInit(); fileInit(); @@ -78,6 +80,11 @@ int main(int, char **) return 0; } +void log(const std::string& str) +{ + logView.AddLog(str); +} + void renderWindow() { // Start the new window frame and render the menu bar. @@ -124,9 +131,6 @@ void renderWindow() deviceRenderDraw(); // Draw everything to the screen. - ImGui::Render(); - guiRender([] { - ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData()); - }); + guiRender(); } From 67ffe1f2ce9f3432cb524dced5e6fba96519b4fd Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Mon, 16 May 2022 17:03:15 -0400 Subject: [PATCH 13/21] building with mingw --- Makefile | 26 ++++++++++++++++---------- source/file.cpp | 2 +- source/main.cpp | 2 +- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index 81e580d..fdf87d6 100644 --- a/Makefile +++ b/Makefile @@ -1,26 +1,32 @@ +#linux: CXXFILES += source/serial/src/impl/unix.cc source/serial/src/impl/list_ports/list_ports_unix.cc +#linux: LDFLAGS = -lSDL2 -lGL -lpthread + +CROSS = x86_64-w64-mingw32- +CXX = $(CROSS)g++ + CXXFILES := \ source/serial/src/serial.cc \ - source/serial/src/impl/unix.cc \ - source/serial/src/impl/list_ports/list_ports_linux.cc \ + source/serial/src/impl/win.cc \ + source/serial/src/impl/list_ports/list_ports_win.cc \ $(wildcard source/imgui/backends/*.cpp) \ $(wildcard source/imgui/*.cpp) \ $(wildcard source/stmdsp/*.cpp) \ $(wildcard source/*.cpp) OFILES := $(patsubst %.cc, %.o, $(patsubst %.cpp, %.o, $(CXXFILES))) -OUTPUT := stmdspgui +OUTPUT := stmdspgui.exe -#CXXFLAGS := -std=c++20 -O2 \ -# -Isource -Isource/imgui -Isource/stmdsp -Isource/serial/include -CXXFLAGS := -std=c++20 -ggdb -O0 -g3 \ +CXXFLAGS := -std=c++20 -O2 \ -Isource -Isource/imgui -Isource/stmdsp -Isource/serial/include \ - -Wall -Wextra -pedantic + -Wall -Wextra -pedantic \ + -DSTMDSP_WIN32 -Wa,-mbig-obj +LDFLAGS = -mwindows -lSDL2 -lopengl32 -lsetupapi -lole32 all: $(OUTPUT) $(OUTPUT): $(OFILES) @echo " LD " $(OUTPUT) - @g++ $(OFILES) -o $(OUTPUT) -lSDL2 -lGL -lpthread + @$(CXX) $(OFILES) -o $(OUTPUT) $(LDFLAGS) clean: @echo " CLEAN" @@ -28,9 +34,9 @@ clean: %.o: %.cpp @echo " CXX " $< - @g++ $(CXXFLAGS) -c $< -o $@ + @$(CXX) $(CXXFLAGS) -c $< -o $@ %.o: %.cc @echo " CXX " $< - @g++ $(CXXFLAGS) -c $< -o $@ + @$(CXX) $(CXXFLAGS) -c $< -o $@ diff --git a/source/file.cpp b/source/file.cpp index fe5dafb..b4332f4 100644 --- a/source/file.cpp +++ b/source/file.cpp @@ -104,7 +104,7 @@ void fileRenderMenu() if (ImGui::BeginMenu("Open Example")) { for (const auto& file : fileExampleList) { - if (ImGui::MenuItem(file.filename().c_str())) { + if (ImGui::MenuItem(file.filename().string().c_str())) { fileCurrentPath = file.string(); openCurrentFile(); diff --git a/source/main.cpp b/source/main.cpp index e8641bc..d6277b7 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -47,7 +47,7 @@ static ImFont *fontMono = nullptr; static void renderWindow(); -int main(int, char **) +int main(int argc, char *argv[]) { if (!guiInitialize()) return -1; From 29636cfebf58094d61b669d99c08a4f76dc98a8c Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Tue, 17 May 2022 11:42:14 -0800 Subject: [PATCH 14/21] fix serial timeout to prevent incomplete IO --- .gitignore | 1 + Makefile | 5 +++-- source/device.cpp | 28 ++++++++++++++++++++++++---- source/device_formula.cpp | 13 ++++++++++++- source/stmdsp/stmdsp.cpp | 4 +++- 5 files changed, 43 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index d7ed0eb..9c562a6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ stmdspgui stmdspgui.exe perf.data* *.o +*.dll .* diff --git a/Makefile b/Makefile index fdf87d6..1f4b1e1 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,9 @@ #linux: CXXFILES += source/serial/src/impl/unix.cc source/serial/src/impl/list_ports/list_ports_unix.cc #linux: LDFLAGS = -lSDL2 -lGL -lpthread -CROSS = x86_64-w64-mingw32- -CXX = $(CROSS)g++ +#CROSS = x86_64-w64-mingw32- +#CXX = $(CROSS)g++ +CXX = g++ CXXFILES := \ source/serial/src/serial.cc \ diff --git a/source/device.cpp b/source/device.cpp index 11e181e..edd950c 100644 --- a/source/device.cpp +++ b/source/device.cpp @@ -96,8 +96,10 @@ static void drawSamplesTask(std::shared_ptr device) if (!device) return; - const auto bufferTime = getBufferPeriod(device); + // This is the amount of time to wait between device reads. + const auto bufferTime = getBufferPeriod(device, 1); + // Adds the given chunk of samples to the given queue. const auto addToQueue = [](auto& queue, const auto& chunk) { std::scoped_lock lock (mutexDrawSamples); std::copy(chunk.cbegin(), chunk.cend(), std::back_inserter(queue)); @@ -114,13 +116,14 @@ static void drawSamplesTask(std::shared_ptr device) lockDevice.unlock(); addToQueue(drawSamplesQueue, chunk); + if (logSamplesFile.is_open()) { for (const auto& s : chunk) logSamplesFile << s << '\n'; } } else { - // Device must be busy, cooldown. - std::this_thread::sleep_for(std::chrono::milliseconds(500)); + // Device must be busy, back off for a bit. + std::this_thread::sleep_for(std::chrono::milliseconds(50)); } if (drawSamplesInput) { @@ -417,12 +420,25 @@ std::size_t pullFromQueue( CircularBuffer& circ, double timeframe) { + // We know how big the circular buffer should be to hold enough samples to + // fill the current draw samples view. + // If the given buffer does not match this size, notify the caller. + // TODO this could be done better... drawSamplesBufferSize should be a GUI- + // only thing. if (circ.size() != drawSamplesBufferSize) return drawSamplesBufferSize; std::scoped_lock lock (mutexDrawSamples); - const auto desiredCount = drawSamplesBufferSize / (60. * timeframe) * 1.025; + // The render code will draw all of the new samples we add to the buffer. + // So, we must provide a certain amount of samples at a time to make the + // render appear smooth. + // The 1.025 factor keeps us on top of the stream; don't want to fall + // behind. + const double FPS = ImGui::GetIO().Framerate; + const auto desiredCount = m_device->get_sample_rate() / FPS; + + // Transfer from the queue to the render buffer. auto count = std::min(queue.size(), static_cast(desiredCount)); while (count--) { circ.put(queue.front()); @@ -432,6 +448,10 @@ std::size_t pullFromQueue( return 0; } +/** + * Pulls a render frame's worth of samples from the draw samples queue, adding + * the samples to the given buffer. + */ std::size_t pullFromDrawQueue( CircularBuffer& circ, double timeframe) diff --git a/source/device_formula.cpp b/source/device_formula.cpp index a70f465..9a3372f 100644 --- a/source/device_formula.cpp +++ b/source/device_formula.cpp @@ -10,9 +10,20 @@ * If not, see . */ -#include "stmdsp.hpp" +#define exprtk_disable_comments +#define exprtk_disable_break_continue +#define exprtk_disable_sc_andor +#define exprtk_disable_return_statement +#define exprtk_disable_enhanced_features +//#define exprtk_disable_string_capabilities +#define exprtk_disable_superscalar_unroll +#define exprtk_disable_rtl_io_file +#define exprtk_disable_rtl_vecops +//#define exprtk_disable_caseinsensitivity #include "exprtk.hpp" +#include "stmdsp.hpp" + #include #include #include diff --git a/source/stmdsp/stmdsp.cpp b/source/stmdsp/stmdsp.cpp index 2252364..c835257 100644 --- a/source/stmdsp/stmdsp.cpp +++ b/source/stmdsp/stmdsp.cpp @@ -45,7 +45,9 @@ namespace stmdsp device::device(const std::string& file) { // This could throw! - m_serial.reset(new serial::Serial(file, 8'000'000, serial::Timeout::simpleTimeout(50))); + // Note: Windows needs a not-simple, positive timeout like this to + // ensure that reads block. + m_serial.reset(new serial::Serial(file, 921'600 /*8'000'000*/, serial::Timeout(1000, 1000, 1, 1000, 1))); // Test the ID command. m_serial->flush(); From 660d967ec0ac79ea2a43946be4c056ef2d21ffc4 Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Sun, 22 May 2022 13:29:45 -0400 Subject: [PATCH 15/21] bug fixes; dynamic time measure; sync sample drawing --- source/circular.hpp | 7 ++++- source/code.cpp | 19 ++++++------- source/device.cpp | 61 +++++++++++++++++++++++++--------------- source/gui_device.cpp | 40 +++++++++++++++----------- source/stmdsp/stmdsp.cpp | 40 +++++++++++++------------- source/stmdsp/stmdsp.hpp | 21 ++++++++------ 6 files changed, 112 insertions(+), 76 deletions(-) diff --git a/source/circular.hpp b/source/circular.hpp index 6b82068..4f49322 100644 --- a/source/circular.hpp +++ b/source/circular.hpp @@ -21,7 +21,7 @@ public: CircularBuffer(Container& container) : m_begin(std::begin(container)), m_end(std::end(container)), - m_current(std::begin(container)) {} + m_current(m_begin) {} void put(const T& value) noexcept { *m_current = value; @@ -33,6 +33,11 @@ public: return std::distance(m_begin, m_end); } + void reset(const T& fill) noexcept { + std::fill(m_begin, m_end, fill); + m_current = m_begin; + } + private: Container::iterator m_begin; Container::iterator m_end; diff --git a/source/code.cpp b/source/code.cpp index 14f603c..8e3bd6c 100644 --- a/source/code.cpp +++ b/source/code.cpp @@ -134,18 +134,17 @@ std::string newTempFileName() 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); + + 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; } diff --git a/source/device.cpp b/source/device.cpp index 11e181e..60b1bc9 100644 --- a/source/device.cpp +++ b/source/device.cpp @@ -33,6 +33,7 @@ extern void log(const std::string& str); extern std::vector deviceGenLoadFormulaEval(const std::string&); extern std::ifstream compileOpenBinaryFile(); +extern void deviceRenderDisconnect(); std::shared_ptr m_device; @@ -45,9 +46,15 @@ static std::deque drawSamplesInputQueue; static bool drawSamplesInput = false; static unsigned int drawSamplesBufferSize = 1; +bool deviceConnect(); + void deviceSetInputDrawing(bool enabled) { drawSamplesInput = enabled; + if (enabled) { + drawSamplesQueue.clear(); + drawSamplesInputQueue.clear(); + } } static void measureCodeTask(std::shared_ptr device) @@ -55,7 +62,7 @@ static void measureCodeTask(std::shared_ptr device) std::this_thread::sleep_for(std::chrono::seconds(1)); if (device) { - const auto cycles = device->continuous_start_get_measurement(); + const auto cycles = device->measurement_read(); log(std::string("Execution time: ") + std::to_string(cycles) + " cycles."); } } @@ -109,11 +116,21 @@ static void drawSamplesTask(std::shared_ptr device) const auto next = std::chrono::high_resolution_clock::now() + bufferTime; if (lockDevice.try_lock_until(next)) { - const auto chunk = tryReceiveChunk(device, + std::vector chunk, chunk2; + + chunk = tryReceiveChunk(device, std::mem_fn(&stmdsp::device::continuous_read)); + if (drawSamplesInput) { + chunk2 = tryReceiveChunk(device, + std::mem_fn(&stmdsp::device::continuous_read_input)); + } + lockDevice.unlock(); addToQueue(drawSamplesQueue, chunk); + if (drawSamplesInput) + addToQueue(drawSamplesInputQueue, chunk2); + if (logSamplesFile.is_open()) { for (const auto& s : chunk) logSamplesFile << s << '\n'; @@ -123,16 +140,6 @@ static void drawSamplesTask(std::shared_ptr device) std::this_thread::sleep_for(std::chrono::milliseconds(500)); } - if (drawSamplesInput) { - if (lockDevice.try_lock_for(std::chrono::milliseconds(1))) { - const auto chunk2 = tryReceiveChunk(device, - std::mem_fn(&stmdsp::device::continuous_read_input)); - lockDevice.unlock(); - - addToQueue(drawSamplesInputQueue, chunk2); - } - } - std::this_thread::sleep_until(next); } } @@ -193,6 +200,12 @@ static void statusTask(std::shared_ptr device) case stmdsp::Error::ConversionAborted: log("Error: Algorithm unloaded, a fault occurred!"); break; + case stmdsp::Error::GUIDisconnect: + // Do GUI events for disconnect if device was lost. + deviceConnect(); + deviceRenderDisconnect(); + return; + break; default: log("Error: Device had an issue..."); break; @@ -301,7 +314,7 @@ bool deviceConnect() return false; } -void deviceStart(bool measureCodeTime, bool logResults, bool drawSamples) +void deviceStart(bool logResults, bool drawSamples) { if (!m_device) { log("No device connected."); @@ -320,18 +333,22 @@ void deviceStart(bool measureCodeTime, bool logResults, bool drawSamples) } log("Ready."); } else { - if (measureCodeTime) { - m_device->continuous_start_measure(); - std::thread(measureCodeTask, m_device).detach(); - } else { - m_device->continuous_start(); - if (drawSamples || logResults || wavOutput.valid()) - std::thread(drawSamplesTask, m_device).detach(); - } + m_device->continuous_start(); + if (drawSamples || logResults || wavOutput.valid()) + std::thread(drawSamplesTask, m_device).detach(); + log("Running."); } } +void deviceStartMeasurement() +{ + if (m_device && m_device->is_running()) { + m_device->measurement_start(); + std::thread(measureCodeTask, m_device).detach(); + } +} + void deviceAlgorithmUpload() { if (!m_device) { @@ -387,7 +404,7 @@ void deviceGenLoadList(const std::string_view list) } } - it = itend; + it = std::find_if(itend, list.cend(), isdigit); } if (it == list.cend()) { diff --git a/source/gui_device.cpp b/source/gui_device.cpp index 43c0a58..f846414 100644 --- a/source/gui_device.cpp +++ b/source/gui_device.cpp @@ -24,7 +24,8 @@ 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 deviceStart(bool logResults, bool drawSamples); +void deviceStartMeasurement(); void deviceUpdateDrawBufferSize(double timeframe); std::size_t pullFromDrawQueue( CircularBuffer& circ, @@ -47,6 +48,15 @@ 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) { @@ -56,7 +66,6 @@ void deviceRenderMenu() }; if (ImGui::BeginMenu("Device")) { - static std::string connectLabel ("Connect"); addMenuItem(connectLabel, !m_device || !m_device->is_running(), [&] { if (deviceConnect()) { connectLabel = "Disconnect"; @@ -64,10 +73,7 @@ void deviceRenderMenu() getSampleRatePreview(m_device->get_sample_rate()); deviceUpdateDrawBufferSize(drawSamplesTimeframe); } else { - connectLabel = "Connect"; - measureCodeTime = false; - logResults = false; - drawSamples = false; + deviceRenderDisconnect(); } }); @@ -79,7 +85,7 @@ void deviceRenderMenu() static std::string startLabel ("Start"); addMenuItem(startLabel, isConnected, [&] { startLabel = isRunning ? "Start" : "Stop"; - deviceStart(measureCodeTime, logResults, drawSamples); + deviceStart(logResults, drawSamples); if (logResults && isRunning) logResults = false; }); @@ -87,28 +93,25 @@ void deviceRenderMenu() deviceAlgorithmUpload); addMenuItem("Unload algorithm", isConnected && !isRunning, deviceAlgorithmUnload); + addMenuItem("Measure Code Time", isRunning, deviceStartMeasurement); ImGui::Separator(); if (!isConnected || isRunning) - ImGui::PushDisabled(); + ImGui::PushDisabled(); // Hey, pushing disabled! - ImGui::Checkbox("Measure Code Time", &measureCodeTime); 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(); - - addMenuItem("Set buffer size...", isConnected && !isRunning, - [] { popupRequestBuffer = true; }); - ImGui::Separator(); addMenuItem("Load signal generator", - isConnected && !m_device->is_siggening(), + isConnected && !m_device->is_siggening() && !m_device->is_running(), [] { popupRequestSiggen = true; }); static std::string startSiggenLabel ("Start signal generator"); @@ -193,7 +196,7 @@ void deviceRenderWidgets() } } else { ImGui::Text(siggenOption == 0 ? "Enter a list of numbers:" - : "Enter a formula. f(x) = "); + : "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(); @@ -286,8 +289,13 @@ void deviceRenderDraw() ImGui::Begin("draw", &drawSamples); ImGui::Text("Draw input "); ImGui::SameLine(); - if (ImGui::Checkbox("", &drawSamplesInput)) + 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(); diff --git a/source/stmdsp/stmdsp.cpp b/source/stmdsp/stmdsp.cpp index 2252364..d7e977b 100644 --- a/source/stmdsp/stmdsp.cpp +++ b/source/stmdsp/stmdsp.cpp @@ -90,8 +90,7 @@ namespace stmdsp m_serial->write(cmd.data(), cmd.size()); success = true; } catch (...) { - m_serial.release(); - log("Lost connection!"); + handle_disconnect(); } } @@ -108,8 +107,7 @@ namespace stmdsp m_serial->read(dest, dest_size); success = true; } catch (...) { - m_serial.release(); - log("Lost connection!"); + handle_disconnect(); } } @@ -158,12 +156,11 @@ namespace stmdsp m_is_running = true; } - void device::continuous_start_measure() { - if (try_command({'M'})) - m_is_running = true; + void device::measurement_start() { + try_command({'M'}); } - uint32_t device::continuous_start_get_measurement() { + uint32_t device::measurement_read() { uint32_t count = 0; try_read({'m'}, reinterpret_cast(&count), sizeof(uint32_t)); return count / 2; @@ -193,8 +190,7 @@ namespace stmdsp } } catch (...) { - m_serial.release(); - log("Lost connection!"); + handle_disconnect(); } } @@ -225,8 +221,7 @@ namespace stmdsp } } catch (...) { - m_serial.release(); - log("Lost connection!"); + handle_disconnect(); } } @@ -251,8 +246,7 @@ namespace stmdsp m_serial->write(request, 3); m_serial->write((uint8_t *)buffer, size * sizeof(dacsample_t)); } catch (...) { - m_serial.release(); - log("Lost connection!"); + handle_disconnect(); } } else { try { @@ -262,8 +256,7 @@ namespace stmdsp else m_serial->write((uint8_t *)buffer, size * sizeof(dacsample_t)); } catch (...) { - m_serial.release(); - log("Lost connection!"); + handle_disconnect(); } } @@ -295,8 +288,7 @@ namespace stmdsp m_serial->write(request, 3); m_serial->write(buffer, size); } catch (...) { - m_serial.release(); - log("Lost connection!"); + handle_disconnect(); } } } @@ -318,9 +310,19 @@ namespace stmdsp bool running = ret.first == RunStatus::Running; if (m_is_running != running) m_is_running = running; + } else if (m_disconnect_error_flag) { + m_disconnect_error_flag = false; + return {RunStatus::Idle, Error::GUIDisconnect}; } return ret; } -} + + void device::handle_disconnect() + { + m_disconnect_error_flag = true; + m_serial.release(); + log("Lost connection!"); + } +} // namespace stmdsp diff --git a/source/stmdsp/stmdsp.hpp b/source/stmdsp/stmdsp.hpp index e0fca90..efed8a3 100644 --- a/source/stmdsp/stmdsp.hpp +++ b/source/stmdsp/stmdsp.hpp @@ -63,12 +63,15 @@ namespace stmdsp */ enum class Error : char { None = 0, - BadParam, /* An invalid parameter was passed for a command. */ - BadParamSize, /* An invaild param. size was given for a command. */ - BadUserCodeLoad, /* Device failed to load the given algorithm. */ - BadUserCodeSize, /* The given algorithm is too large for the device. */ - NotIdle, /* An idle-only command was received while not Idle. */ - ConversionAborted /* A conversion was aborted due to a fault. */ + BadParam, /* An invalid parameter was passed for a command. */ + BadParamSize, /* An invaild param. size was given for a command. */ + BadUserCodeLoad, /* Device failed to load the given algorithm. */ + BadUserCodeSize, /* The given algorithm is too large for the device. */ + NotIdle, /* An idle-only command was received while not Idle. */ + ConversionAborted, /* A conversion was aborted due to a fault. */ + NotRunning, /* A running-only command was received while not Running. */ + + GUIDisconnect = 100 /* The GUI lost connection with the device. */ }; /** @@ -123,8 +126,8 @@ namespace stmdsp void continuous_start(); void continuous_stop(); - void continuous_start_measure(); - uint32_t continuous_start_get_measurement(); + void measurement_start(); + uint32_t measurement_read(); std::vector continuous_read(); std::vector continuous_read_input(); @@ -149,11 +152,13 @@ namespace stmdsp unsigned int m_sample_rate = 0; bool m_is_siggening = false; bool m_is_running = false; + bool m_disconnect_error_flag = false; std::mutex m_lock; bool try_command(std::basic_string data); bool try_read(std::basic_string cmd, uint8_t *dest, unsigned int dest_size); + void handle_disconnect(); }; } From f211f9628854b417000192c59d6ab22b946119b1 Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Sun, 22 May 2022 13:37:49 -0400 Subject: [PATCH 16/21] make helper funcs inline; drop std::span for algo --- examples/1_convolve_simple.cpp | 9 +++-- examples/2_convolve_overlap_save.cpp | 8 ++-- examples/3_fir.cpp | 8 ++-- examples/4_fir_pro.cpp | 10 ++--- examples/5_fir_differentiator.cpp | 8 ++-- examples/6_iir_test.cpp | 18 +++++++-- examples/7_iir_echo.cpp | 16 ++++++-- source/stmdsp/stmdsp_code.hpp | 58 ++++++++++++++-------------- 8 files changed, 77 insertions(+), 58 deletions(-) diff --git a/examples/1_convolve_simple.cpp b/examples/1_convolve_simple.cpp index 8de05d3..95877f1 100644 --- a/examples/1_convolve_simple.cpp +++ b/examples/1_convolve_simple.cpp @@ -7,10 +7,10 @@ * transient response is not calculated. */ -Sample *process_data(Samples samples) +Sample* process_data(Samples samples) { - // Define our output buffer. SIZE is the largest size of the 'samples' buffer. - static Sample buffer[samples.size()]; + // Define our output buffer. + static Samples buffer; // Define our filter constexpr unsigned int filter_size = 3; @@ -19,7 +19,8 @@ Sample *process_data(Samples samples) }; // Begin convolving: - for (int n = 0; n < samples.size() - (filter_size - 1); n++) { + // SIZE is the size of the sample buffer. + for (int n = 0; n < SIZE - (filter_size - 1); n++) { buffer[n] = 0; for (int k = 0; k < filter_size; k++) buffer[n] += samples[n + k] * filter[k]; diff --git a/examples/2_convolve_overlap_save.cpp b/examples/2_convolve_overlap_save.cpp index 57c020a..5651f3e 100644 --- a/examples/2_convolve_overlap_save.cpp +++ b/examples/2_convolve_overlap_save.cpp @@ -11,9 +11,9 @@ * computation. */ -Sample *process_data(Samples samples) +Sample* process_data(Samples samples) { - static Sample buffer[samples.size()]; + static Samples buffer; constexpr unsigned int filter_size = 3; float filter[filter_size] = { @@ -23,7 +23,7 @@ Sample *process_data(Samples samples) // Keep a buffer of extra samples for overlap-save static Sample prev[filter_size]; - for (int n = 0; n < samples.size(); n++) { + for (int n = 0; n < SIZE; n++) { buffer[n] = 0; for (int k = 0; k < filter_size; k++) { @@ -40,7 +40,7 @@ Sample *process_data(Samples samples) // Save samples for the next convolution run for (int i = 0; i < filter_size; i++) - prev[i] = samples[samples.size() - filter_size + i]; + prev[i] = samples[SIZE - filter_size + i]; return buffer; } diff --git a/examples/3_fir.cpp b/examples/3_fir.cpp index 3a68500..b6d8751 100644 --- a/examples/3_fir.cpp +++ b/examples/3_fir.cpp @@ -7,9 +7,9 @@ * within the available execution time. Samples are also normalized so that they center around zero. */ -Sample *process_data(Samples samples) +Sample* process_data(Samples samples) { - static Sample buffer[samples.size()]; + static Samples buffer; // Define the filter: constexpr unsigned int filter_size = 3; @@ -21,7 +21,7 @@ Sample *process_data(Samples samples) // Do an overlap-save convolution static Sample prev[filter_size]; - for (int n = 0; n < samples.size(); n++) { + for (int n = 0; n < SIZE; n++) { // Using a float variable for accumulation allows for better code optimization float v = 0; @@ -40,7 +40,7 @@ Sample *process_data(Samples samples) // Save samples for next convolution for (int i = 0; i < filter_size; i++) - prev[i] = samples[samples.size() - filter_size + i]; + prev[i] = samples[SIZE - filter_size + i]; return buffer; } diff --git a/examples/4_fir_pro.cpp b/examples/4_fir_pro.cpp index b1a6832..1771cd5 100644 --- a/examples/4_fir_pro.cpp +++ b/examples/4_fir_pro.cpp @@ -10,7 +10,7 @@ typedef struct static void arm_fir_f32(const arm_fir_instance_f32 * S, float32_t * pSrc, float32_t * pDst, uint32_t blockSize); -Sample *process_data(Samples samples) +Sample* process_data(Samples samples) { // 1. Define our array sizes (Be sure to set Run > Set buffer size... to below value!) constexpr unsigned int buffer_size = 500; @@ -34,18 +34,18 @@ Sample *process_data(Samples samples) static float working[buffer_size + filter_size]; // 3. Scale 0-4095 interger sample values to +/- 1.0 floats - for (unsigned int i = 0; i < samples.size(); i++) + for (unsigned int i = 0; i < SIZE; i++) input[i] = (samples[i] - 2048) / 2048.f; // 4. Compute the FIR arm_fir_instance_f32 fir { filter_size, working, filter }; - arm_fir_f32(&fir, input, output, samples.size()); + arm_fir_f32(&fir, input, output, SIZE); // 5. Convert float results back to 0-4095 range for output - for (unsigned int i = 0; i < samples.size(); i++) + for (unsigned int i = 0; i < SIZE; i++) samples[i] = output[i] * 2048.f + 2048; - return samples.data(); + return samples; } // Below taken from the CMSIS DSP Library (find it on GitHub) diff --git a/examples/5_fir_differentiator.cpp b/examples/5_fir_differentiator.cpp index 72415c6..1500dee 100644 --- a/examples/5_fir_differentiator.cpp +++ b/examples/5_fir_differentiator.cpp @@ -7,23 +7,23 @@ * A scaling factor is applied so that the output's form is more clearly visible. */ -Sample *process_data(Samples samples) +Sample* process_data(Samples samples) { constexpr int scaling_factor = 4; - static Sample output[samples.size()]; + static Samples output; static Sample prev = 2048; // Compute the first output value using the saved sample. output[0] = 2048 + ((samples[0] - prev) * scaling_factor); - for (unsigned int i = 1; i < samples.size(); i++) { + for (unsigned int i = 1; i < SIZE; i++) { // Take the rate of change and scale it. // 2048 is added as the output should be centered in the voltage range. output[i] = 2048 + ((samples[i] - samples[i - 1]) * scaling_factor); } // Save the last sample for the next iteration. - prev = samples[samples.size() - 1]; + prev = samples[SIZE - 1]; return output; } diff --git a/examples/6_iir_test.cpp b/examples/6_iir_test.cpp index 116a680..e0b266d 100644 --- a/examples/6_iir_test.cpp +++ b/examples/6_iir_test.cpp @@ -1,13 +1,23 @@ -Sample *process_data(Samples samples) +/** + * 6_iir_test.cpp + * Written by Clyne Sullivan. + * + * Implements a simple infinite impulse response (IIR) filter using an alpha + * parameter. + * To build upon this example, try setting `alpha` with a parameter knob: + * alpha = param1() / 4095.0 + */ + +Sample* process_data(Samples samples) { constexpr float alpha = 0.7; static Sample prev = 2048; samples[0] = (1 - alpha) * samples[0] + alpha * prev; - for (unsigned int i = 1; i < samples.size(); i++) + for (unsigned int i = 1; i < SIZE; i++) samples[i] = (1 - alpha) * samples[i] + alpha * samples[i - 1]; - prev = samples[samples.size() - 1]; + prev = samples[SIZE - 1]; - return samples.data(); + return samples; } diff --git a/examples/7_iir_echo.cpp b/examples/7_iir_echo.cpp index 57e5605..75bf56e 100644 --- a/examples/7_iir_echo.cpp +++ b/examples/7_iir_echo.cpp @@ -1,9 +1,17 @@ -Sample *process_data(Samples samples) +/** + * 7_iir_echo.cpp + * Written by Clyne Sullivan. + * + * This filter produces an echo of the given input. There are two parameters: + * alpha controls the feedback gain, and D controls the echo/delay length. + */ + +Sample* process_data(Samples samples) { constexpr float alpha = 0.75; constexpr unsigned int D = 100; - static Sample output[samples.size()]; + static Samples output; static Sample prev[D]; // prev[0] = output[0 - D] // Do calculations with previous output @@ -11,12 +19,12 @@ Sample *process_data(Samples samples) output[i] = samples[i] + alpha * (prev[i] - 2048); // Do calculations with current samples - for (unsigned int i = D; i < samples.size(); i++) + for (unsigned int i = D; i < SIZE; i++) output[i] = samples[i] + alpha * (output[i - D] - 2048); // Save outputs for next computation for (unsigned int i = 0; i < D; i++) - prev[i] = output[samples.size() - (D - i)]; + prev[i] = output[SIZE - (D - i)]; return output; } diff --git a/source/stmdsp/stmdsp_code.hpp b/source/stmdsp/stmdsp_code.hpp index 6850459..7ba0ed2 100644 --- a/source/stmdsp/stmdsp_code.hpp +++ b/source/stmdsp/stmdsp_code.hpp @@ -118,67 +118,67 @@ return s; )cpp"; static std::string file_header_l4 = R"cpp( #include -#include using Sample = uint16_t; -using Samples = std::span; +using Samples = Sample[$0]; +constexpr unsigned int SIZE = $0; Sample *process_data(Samples samples); extern "C" void process_data_entry() { Sample *samples; asm("mov %0, r0" : "=r" (samples)); - process_data(Samples(samples, $0)); + process_data(samples); } -static float PI = 3.14159265358979L; +static inline float PI = 3.14159265358979L; __attribute__((naked)) -auto sin(float x) { -asm("vmov.f32 r1, s0;" +static inline auto sin(float x) { + asm("vmov.f32 r1, s0;" "eor r0, r0;" "svc 1;" "vmov.f32 s0, r1;" "bx lr"); -return 0; + return 0; } __attribute__((naked)) -auto cos(float x) { -asm("vmov.f32 r1, s0;" +static inline auto cos(float x) { + asm("vmov.f32 r1, s0;" "mov r0, #1;" "svc 1;" "vmov.f32 s0, r1;" "bx lr"); -return 0; + return 0; } __attribute__((naked)) -auto tan(float x) { -asm("vmov.f32 r1, s0;" +static inline auto tan(float x) { + asm("vmov.f32 r1, s0;" "mov r0, #2;" "svc 1;" "vmov.f32 s0, r1;" "bx lr"); -return 0; + return 0; } __attribute__((naked)) -auto sqrt(float) { -asm("vsqrt.f32 s0, s0; bx lr"); -return 0; +static inline auto sqrt(float) { + asm("vsqrt.f32 s0, s0; bx lr"); + return 0; } -auto readpot1() { -Sample s; -asm("push {r4-r11}; eor r0, r0; svc 3; mov %0, r0; pop {r4-r11}" : "=r"(s)); -return s; +static inline auto param1() { + Sample s; + asm("eor r0, r0; svc 3; mov %0, r0" : "=r" (s) :: "r0"); + return s; } -auto readpot2() { -Sample s; -asm("push {r4-r11}; mov r0, #1; svc 3; mov %0, r0; pop {r4-r11}" : "=r"(s)); -return s; +static inline auto param2() { + Sample s; + asm("mov r0, #1; svc 3; mov %0, r0" : "=r" (s) :: "r0"); + return s; } -//void puts(const char *s) { -// 's' will already be in r0. -//asm("push {r4-r6}; svc 4; pop {r4-r6}"); +//static inline void puts(const char *s) { +// // 's' will already be in r0. +// asm("push {r4-r6}; svc 4; pop {r4-r6}"); //} // End stmdspgui header code @@ -187,9 +187,9 @@ return s; static std::string file_content = -R"cpp(Sample *process_data(Samples samples) +R"cpp(Sample* process_data(Samples samples) { - return samples.data(); + return samples; } )cpp"; From 074c596605a9b64ad99f39d6edc37d31bbfb6536 Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Sun, 22 May 2022 15:40:12 -0400 Subject: [PATCH 17/21] window and panel resizing --- source/config.h | 8 ------ source/gui.cpp | 8 +++--- source/gui_code.cpp | 6 ++--- source/main.cpp | 59 ++++++++++++++++++++++++++------------------- 4 files changed, 40 insertions(+), 41 deletions(-) delete mode 100644 source/config.h diff --git a/source/config.h b/source/config.h deleted file mode 100644 index 879a0ea..0000000 --- a/source/config.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef STMDSP_CONFIG_H -#define STMDSP_CONFIG_H - -constexpr unsigned int WINDOW_WIDTH = 640; -constexpr unsigned int WINDOW_HEIGHT = 720; - -#endif - diff --git a/source/gui.cpp b/source/gui.cpp index 80120c4..0e67446 100644 --- a/source/gui.cpp +++ b/source/gui.cpp @@ -14,8 +14,6 @@ #include "backends/imgui_impl_sdl.h" #include "backends/imgui_impl_opengl2.h" -#include "config.h" - #include #include @@ -42,14 +40,16 @@ bool guiInitialize() SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); window = SDL_CreateWindow("stmdsp gui", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, - WINDOW_WIDTH, WINDOW_HEIGHT, - SDL_WINDOW_OPENGL /*| SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI*/); + 550, 700, + SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE /*| SDL_WINDOW_ALLOW_HIGHDPI*/); if (window == nullptr) { puts("Error: Could not create the window!"); return false; } + SDL_SetWindowMinimumSize(window, 320, 320); + gl_context = SDL_GL_CreateContext(window); SDL_GL_MakeCurrent(window, gl_context); SDL_GL_SetSwapInterval(1); // Enable vsync diff --git a/source/gui_code.cpp b/source/gui_code.cpp index 19fa572..50fd0c8 100644 --- a/source/gui_code.cpp +++ b/source/gui_code.cpp @@ -14,8 +14,6 @@ #include "backends/imgui_impl_opengl2.h" #include "TextEditor.h" -#include "config.h" - #include extern void compileEditorCode(const std::string& code); @@ -52,9 +50,9 @@ void codeRenderToolbar() codeCompile(); } -void codeRenderWidgets() +void codeRenderWidgets(const ImVec2& size) { - editor.Render("code", {WINDOW_WIDTH - 15, 450}, true); + editor.Render("code", size, true); } static void codeCompile() diff --git a/source/main.cpp b/source/main.cpp index e8641bc..f913cf1 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -13,7 +13,6 @@ #include "backends/imgui_impl_sdl.h" #include "backends/imgui_impl_opengl2.h" -#include "config.h" #include "logview.h" #include "stmdsp.hpp" @@ -26,7 +25,7 @@ void codeEditorInit(); void codeRenderMenu(); void codeRenderToolbar(); -void codeRenderWidgets(); +void codeRenderWidgets(const ImVec2& size); void deviceRenderDraw(); void deviceRenderMenu(); void deviceRenderToolbar(); @@ -45,6 +44,7 @@ static LogView logView; static ImFont *fontSans = nullptr; static ImFont *fontMono = nullptr; +template static void renderWindow(); int main(int, char **) @@ -63,6 +63,8 @@ int main(int, char **) codeEditorInit(); fileInit(); + renderWindow(); + while (1) { constexpr std::chrono::duration fpsDelay (1. / 60.); const auto endTime = std::chrono::steady_clock::now() + fpsDelay; @@ -85,6 +87,7 @@ void log(const std::string& str) logView.AddLog(str); } +template void renderWindow() { // Start the new window frame and render the menu bar. @@ -99,35 +102,41 @@ void renderWindow() ImGui::EndMainMenuBar(); } - // Begin the main view which the controls will be drawn onto. - constexpr float LOGVIEW_HEIGHT = 200; - constexpr ImVec2 WINDOW_POS (0, 22); - constexpr ImVec2 WINDOW_SIZE (WINDOW_WIDTH, WINDOW_HEIGHT - 22 - LOGVIEW_HEIGHT); - ImGui::SetNextWindowPos(WINDOW_POS); - ImGui::SetNextWindowSize(WINDOW_SIZE); + if constexpr (first) { + ImGui::SetNextWindowSize({550, 440}); + } + + constexpr int MainTopMargin = 22; + const auto& displaySize = ImGui::GetIO().DisplaySize; + + ImGui::SetNextWindowPos({0, MainTopMargin}); + ImGui::SetNextWindowSizeConstraints({displaySize.x, 150}, {displaySize.x, displaySize.y - 150}); ImGui::Begin("main", nullptr, - ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoBringToFrontOnFocus); - // Render main controls (order is important). - { - ImGui::PushFont(fontSans); - codeRenderToolbar(); - deviceRenderToolbar(); - fileRenderDialog(); - deviceRenderWidgets(); - ImGui::PopFont(); - - ImGui::PushFont(fontMono); - codeRenderWidgets(); - ImGui::SetNextWindowPos({0, WINDOW_HEIGHT - LOGVIEW_HEIGHT}); - ImGui::SetNextWindowSize({WINDOW_WIDTH, LOGVIEW_HEIGHT}); - logView.Draw("log", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBringToFrontOnFocus); - ImGui::PopFont(); - } + const float mainWindowHeight = ImGui::GetWindowHeight(); + + ImGui::PushFont(fontSans); + codeRenderToolbar(); + deviceRenderToolbar(); + fileRenderDialog(); + deviceRenderWidgets(); + ImGui::PopFont(); + + ImGui::PushFont(fontMono); + codeRenderWidgets({displaySize.x - 16, mainWindowHeight - MainTopMargin - 24}); + ImGui::PopFont(); ImGui::End(); + // The log window is kept separate from "main" to support panel resizing. + ImGui::PushFont(fontMono); + ImGui::SetNextWindowPos({0, mainWindowHeight + MainTopMargin}); + ImGui::SetNextWindowSize({displaySize.x, displaySize.y - mainWindowHeight - MainTopMargin}); + logView.Draw("log", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBringToFrontOnFocus); + ImGui::PopFont(); + deviceRenderDraw(); // Draw everything to the screen. From 77ff957e5eb03fb459ea1d56f7bd3b3c3f164699 Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Sun, 22 May 2022 20:15:17 -0800 Subject: [PATCH 18/21] add formula disable flag --- Makefile | 2 +- source/device_formula.cpp | 25 ++++++++++++++++++------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 1f4b1e1..6b85111 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ OUTPUT := stmdspgui.exe CXXFLAGS := -std=c++20 -O2 \ -Isource -Isource/imgui -Isource/stmdsp -Isource/serial/include \ -Wall -Wextra -pedantic \ - -DSTMDSP_WIN32 -Wa,-mbig-obj + -DSTMDSP_WIN32 -Wa,-mbig-obj -DSTMDSP_DISABLE_FORMULAS LDFLAGS = -mwindows -lSDL2 -lopengl32 -lsetupapi -lole32 all: $(OUTPUT) diff --git a/source/device_formula.cpp b/source/device_formula.cpp index 9a3372f..e21d374 100644 --- a/source/device_formula.cpp +++ b/source/device_formula.cpp @@ -10,6 +10,15 @@ * If not, see . */ +#include "stmdsp.hpp" + +#include +#include +#include +#include + +#ifndef STMDSP_DISABLE_FORMULAS + #define exprtk_disable_comments #define exprtk_disable_break_continue #define exprtk_disable_sc_andor @@ -22,13 +31,6 @@ //#define exprtk_disable_caseinsensitivity #include "exprtk.hpp" -#include "stmdsp.hpp" - -#include -#include -#include -#include - static std::random_device randomDevice; std::vector deviceGenLoadFormulaEval(const std::string& formulaString) @@ -73,3 +75,12 @@ std::vector deviceGenLoadFormulaEval(const std::string& for return samples; } +#else // no formula support + +std::vector deviceGenLoadFormulaEval(const std::string&) +{ + return {}; +} + +#endif // STMDSP_DISABLE_FORMULAS + From f3277c3878c4b2e41acd1f35acce6b06a1ca5ac5 Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Sun, 22 May 2022 16:49:19 -0400 Subject: [PATCH 19/21] makefile typo --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index dc62d10..ee9b224 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ LDFLAGS = -mwindows -lSDL2 -lopengl32 -lsetupapi -lole32 OUTPUT := stmdspgui.exe else CXXFILES += source/serial/src/impl/unix.cc \ - source/serial/src/impl/list_ports/list_ports_unix.cc + source/serial/src/impl/list_ports/list_ports_linux.cc LDFLAGS = -lSDL2 -lGL -lpthread OUTPUT := stmdspgui endif From 5846bda565dc100fc73550d937ec910bd29532f3 Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Sun, 22 May 2022 17:59:14 -0400 Subject: [PATCH 20/21] add help menu --- source/main.cpp | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/source/main.cpp b/source/main.cpp index e0d893e..b5514e4 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -47,7 +47,7 @@ static ImFont *fontMono = nullptr; template static void renderWindow(); -int main(int argc, char *argv[]) +int main(int, char **) { if (!guiInitialize()) return -1; @@ -90,6 +90,8 @@ void log(const std::string& str) template void renderWindow() { + static bool showHelp = false; + // Start the new window frame and render the menu bar. ImGui_ImplOpenGL2_NewFrame(); ImGui_ImplSDL2_NewFrame(); @@ -99,6 +101,25 @@ void renderWindow() fileRenderMenu(); deviceRenderMenu(); codeRenderMenu(); + + if (ImGui::BeginMenu("Help")) { + if (ImGui::MenuItem("Open wiki...")) { +#ifdef STMDSP_WIN32 + system("start " +#else + system("xdg-open " +#endif + "https://code.bitgloo.com/clyne/stmdspgui/wiki"); + } + + ImGui::Separator(); + if (ImGui::MenuItem("About")) { + showHelp = true; + } + + ImGui::EndMenu(); + } + ImGui::EndMainMenuBar(); } @@ -139,6 +160,18 @@ void renderWindow() deviceRenderDraw(); + if (showHelp) { + ImGui::Begin("help"); + + ImGui::Text("stmdspgui\nCompiled on " __DATE__ ".\n\nWritten by Clyne Sullivan.\n"); + + if (ImGui::Button("Close")) { + showHelp = false; + } + + ImGui::End(); + } + // Draw everything to the screen. guiRender(); } From dff847ff4455e7b8c5123167a7d01afe7c45f585 Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Tue, 24 May 2022 21:00:16 -1200 Subject: [PATCH 21/21] built-in openocd support --- .gitignore | 1 + openocd.cfg | 2 + source/gui_help.cpp | 113 ++++++++++++++++++++++++++++++++++++++++++++ source/main.cpp | 36 ++------------ 4 files changed, 120 insertions(+), 32 deletions(-) create mode 100644 openocd.cfg create mode 100644 source/gui_help.cpp diff --git a/.gitignore b/.gitignore index 9c562a6..b23a6a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ build/ +openocd/ imgui.ini stmdspgui stmdspgui.exe diff --git a/openocd.cfg b/openocd.cfg new file mode 100644 index 0000000..33f2c52 --- /dev/null +++ b/openocd.cfg @@ -0,0 +1,2 @@ +source [find interface/stlink.cfg] +source [find target/stm32l4x.cfg] \ No newline at end of file diff --git a/source/gui_help.cpp b/source/gui_help.cpp new file mode 100644 index 0000000..a17d9fa --- /dev/null +++ b/source/gui_help.cpp @@ -0,0 +1,113 @@ +#include "imgui.h" +#include "ImGuiFileDialog.h" + +#include +#include +#include +#include + +void log(const std::string& str); + +static bool showDownloadFirmware = false; +static bool showHelp = false; +static std::string firmwareFile; + +static void helpDownloadThread(); + +void helpRenderMenu() +{ + if (ImGui::BeginMenu("Help")) { + if (ImGui::MenuItem("Open wiki...")) { +#ifdef STMDSP_WIN32 + system("start " +#else + system("xdg-open " +#endif + "https://code.bitgloo.com/clyne/stmdspgui/wiki"); + } + + if (ImGui::MenuItem("Download firmware...")) { + showDownloadFirmware = true; + } + + ImGui::Separator(); + if (ImGui::MenuItem("About")) { + showHelp = true; + } + + ImGui::EndMenu(); + } +} + +void helpRenderDialog() +{ + if (showDownloadFirmware) { + showDownloadFirmware = false; + ImGuiFileDialog::Instance()->OpenModal( + "ChooseFileFW", "Choose Firmware File", ".hex", "."); + } + + if (ImGuiFileDialog::Instance()->Display("ChooseFileFW", + ImGuiWindowFlags_NoCollapse, + ImVec2(460, 540))) + { + if (ImGuiFileDialog::Instance()->IsOk()) { + firmwareFile = ImGuiFileDialog::Instance()->GetFilePathName(); +#ifdef STMDSP_WIN32 + size_t i = 0; + while ((i = firmwareFile.find('\\', i)) != std::string::npos) { + firmwareFile.replace(i, 1, "\\\\"); + i += 2; + } +#endif + + std::thread(helpDownloadThread).detach(); + } + + ImGuiFileDialog::Instance()->Close(); + } + + if (showHelp) { + ImGui::Begin("help"); + + ImGui::Text("stmdspgui\nCompiled on " __DATE__ ".\n\nWritten by Clyne Sullivan.\n"); + + if (ImGui::Button("Close")) { + showHelp = false; + } + + ImGui::End(); + } + + if (!firmwareFile.empty()) { + ImGui::Begin("Downloading"); + + ImGui::Text("Downloading firmware to device..."); + + ImGui::End(); + } +} + +void helpDownloadThread() +{ + std::string command ( +#ifdef STMDSP_WIN32 + "openocd\\bin\\openocd.exe" +#else + "openocd" +#endif + " -f openocd.cfg -c \"program $0 reset exit\""); + + command.replace(command.find("$0"), 2, firmwareFile); + + std::cout << "Run: " << command << std::endl; + + if (system(command.c_str()) == 0) { + log("Programming finished."); + } else { + log("Error while programming device!"); + } + + firmwareFile.clear(); +} + diff --git a/source/main.cpp b/source/main.cpp index b5514e4..aa9f7c4 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -37,6 +37,8 @@ bool guiInitialize(); bool guiHandleEvents(); void guiShutdown(); void guiRender(); +void helpRenderMenu(); +void helpRenderDialog(); void log(const std::string& str); @@ -90,8 +92,6 @@ void log(const std::string& str) template void renderWindow() { - static bool showHelp = false; - // Start the new window frame and render the menu bar. ImGui_ImplOpenGL2_NewFrame(); ImGui_ImplSDL2_NewFrame(); @@ -101,24 +101,7 @@ void renderWindow() fileRenderMenu(); deviceRenderMenu(); codeRenderMenu(); - - if (ImGui::BeginMenu("Help")) { - if (ImGui::MenuItem("Open wiki...")) { -#ifdef STMDSP_WIN32 - system("start " -#else - system("xdg-open " -#endif - "https://code.bitgloo.com/clyne/stmdspgui/wiki"); - } - - ImGui::Separator(); - if (ImGui::MenuItem("About")) { - showHelp = true; - } - - ImGui::EndMenu(); - } + helpRenderMenu(); ImGui::EndMainMenuBar(); } @@ -142,6 +125,7 @@ void renderWindow() codeRenderToolbar(); deviceRenderToolbar(); fileRenderDialog(); + helpRenderDialog(); deviceRenderWidgets(); ImGui::PopFont(); @@ -160,18 +144,6 @@ void renderWindow() deviceRenderDraw(); - if (showHelp) { - ImGui::Begin("help"); - - ImGui::Text("stmdspgui\nCompiled on " __DATE__ ".\n\nWritten by Clyne Sullivan.\n"); - - if (ImGui::Button("Close")) { - showHelp = false; - } - - ImGui::End(); - } - // Draw everything to the screen. guiRender(); }