From f6318e328402a5e839d24e7d52f5bb13062bccdc Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Sat, 20 Nov 2021 16:55:26 -0500 Subject: [PATCH] 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; }; }