From 9f480396204e8ccdd89779860cb61d4e4aded8b4 Mon Sep 17 00:00:00 2001
From: Clyne Sullivan <clyne@bitgloo.com>
Date: Tue, 5 Oct 2021 13:56:33 -0400
Subject: buffered draw samples; increased read rate

---
 source/device.cpp | 220 +++++++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 170 insertions(+), 50 deletions(-)

(limited to 'source/device.cpp')

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 <charconv>
+#include <cmath>
+#include <cstdio>
+#include <deque>
 #include <fstream>
 #include <mutex>
 #include <thread>
@@ -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<stmdsp::dacsample_t> drawSamplesBuf;
-static std::vector<stmdsp::dacsample_t> drawSamplesBuf2;
+//static std::vector<stmdsp::dacsample_t> drawSamplesBuf;
+//static std::vector<stmdsp::dacsample_t> drawSamplesBuf2;
 static std::ofstream logSamplesFile;
 static wav::clip wavOutput;
 
+static std::deque<stmdsp::dacsample_t> 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<stmdsp::dacsample_t> 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<double> 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<double> 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<float>(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<stmdsp::dacsample_t> buffer;
+        static decltype(buffer.begin()) bufferCursor;
+        static long unsigned int drawChunkSize = 0;
+        static std::chrono::time_point<std::chrono::high_resolution_clock> 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<double> 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<float>(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;
-- 
cgit v1.2.3