implement most of device features

master
Clyne 3 years ago
parent 1c5a7e71ce
commit 1cf4908a23

@ -19,7 +19,7 @@ all: $(OUTPUT)
$(OUTPUT): $(OFILES)
@echo " LD " $(OUTPUT)
@g++ $(OFILES) -o $(OUTPUT) -lSDL2 -lGL
@g++ $(OFILES) -o $(OUTPUT) -lSDL2 -lGL -lpthread
clean:
@echo " CLEAN"

@ -24,12 +24,12 @@
#include <iostream>
#include <string>
extern std::string tempFileName;
extern stmdsp::device *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();
@ -57,8 +57,6 @@ void codeRenderToolbar()
{
if (ImGui::Button("Compile"))
compileEditorCode();
ImGui::SameLine();
ImGui::Button("Upload");
}
void codeRenderWidgets()

@ -12,10 +12,20 @@
#include "stmdsp.hpp"
#include "imgui.h"
#include "ImGuiFileDialog.h"
#include "wav.hpp"
#include <charconv>
#include <fstream>
#include <mutex>
#include <thread>
extern std::string tempFileName;
extern stmdsp::device *m_device;
extern void log(const std::string& str);
extern std::vector<stmdsp::dacsample_t> deviceGenLoadFormulaEval(const std::string_view);
static const char *sampleRateList[6] = {
"8 kHz",
"16 kHz",
@ -25,43 +35,344 @@ static const char *sampleRateList[6] = {
"96 kHz"
};
static const char *sampleRatePreview = sampleRateList[0];
static const unsigned int sampleRateInts[6] = {
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::mutex mutexDrawSamples;
static std::vector<stmdsp::dacsample_t> drawSamplesBuf;
static std::vector<stmdsp::dacsample_t> drawSamplesBuf2;
static std::ofstream logSamplesFile;
static wav::clip wavOutput;
static void measureCodeTask(stmdsp::device *device)
{
if (device == nullptr)
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)
{
if (device == nullptr)
return;
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;
while (m_device->is_running()) {
{
std::scoped_lock lock (mutexDrawSamples);
drawSamplesBuf = m_device->continuous_read();
if (drawSamplesInput && popupRequestDraw)
drawSamplesBuf2 = m_device->continuous_read_input();
}
if (doLogger) {
for (const auto& s : drawSamplesBuf)
logSamplesFile << s << '\n';
}
std::this_thread::sleep_for(std::chrono::milliseconds(delay));
}
std::fill(drawSamplesBuf.begin(), drawSamplesBuf.end(), 2048);
std::fill(drawSamplesBuf2.begin(), drawSamplesBuf2.end(), 2048);
}
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;
auto wavBuf = new stmdsp::adcsample_t[bsize];
{
auto dst = wavBuf;
auto src = reinterpret_cast<uint16_t *>(wavOutput.next(bsize));
for (auto i = 0u; i < bsize; ++i)
*dst++ = *src++ / 16 + 2048;
m_device->siggen_upload(wavBuf, bsize);
}
m_device->siggen_start();
while (genRunning) {
auto dst = wavBuf;
auto src = reinterpret_cast<uint16_t *>(wavOutput.next(bsize));
for (auto i = 0u; i < bsize; ++i)
*dst++ = *src++ / 16 + 2048;
m_device->siggen_upload(wavBuf, bsize);
std::this_thread::sleep_for(std::chrono::milliseconds(delay));
}
delete[] wavBuf;
}
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()
{
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 != nullptr) {
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")) {
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();
}
}
}
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;
}
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;
}
}
}
ImGui::End();
}
}
void deviceRenderMenu()
{
if (ImGui::BeginMenu("Run")) {
bool isConnected = m_device != nullptr;
bool isRunning = isConnected && m_device->is_running();
static const char *connectLabel = "Connect";
if (ImGui::MenuItem(connectLabel)) {
deviceConnect();
connectLabel = m_device == nullptr ? "Connect" : "Disconnect";
connectLabel = isConnected ? "Disconnect" : "Connect";
}
ImGui::Separator();
static const char *startLabel = "Start";
if (ImGui::MenuItem(startLabel)) {
if (ImGui::MenuItem(startLabel, nullptr, false, isConnected)) {
deviceStart();
startLabel = m_device != nullptr && m_device->is_running() ? "Stop" : "Start";
startLabel = isRunning ? "Stop" : "Start";
}
/**
TODO: Run menu:
measure
draw
log
upload
unload
buffer size
load siggen
run siggen
TODO test siggen formula
TODO improve siggen audio streaming
TODO draw: smoothly chain captures
*/
ImGui::MenuItem("Upload algorithm");
ImGui::MenuItem("Unload algorithm");
ImGui::MenuItem("Measure Code Time");
ImGui::MenuItem("Draw samples");
ImGui::MenuItem("Log results...");
ImGui::MenuItem("Load signal generator");
ImGui::MenuItem("Start signal generator");
if (ImGui::MenuItem("Upload algorithm", nullptr, false, isConnected))
deviceAlgorithmUpload();
if (ImGui::MenuItem("Unload algorithm", nullptr, false, isConnected))
deviceAlgorithmUnload();
ImGui::Separator();
if (ImGui::Checkbox("Measure Code Time", &measureCodeTime)) {
if (!isConnected)
measureCodeTime = false;
}
if (ImGui::Checkbox("Draw samples", &drawSamples)) {
if (isConnected) {
if (drawSamples)
popupRequestDraw = true;
} else {
drawSamples = false;
}
}
if (ImGui::Checkbox("Log results...", &logResults)) {
if (isConnected) {
if (logResults)
popupRequestLog = true;
else if (logSamplesFile.is_open())
logSamplesFile.close();
} else {
logResults = false;
}
}
if (ImGui::MenuItem("Set buffer size...", nullptr, false, isConnected)) {
popupRequestBuffer = true;
}
ImGui::Separator();
if (ImGui::MenuItem("Load signal generator", nullptr, false, isConnected)) {
popupRequestSiggen = true;
}
static const char *startSiggenLabel = "Start signal generator";
if (ImGui::MenuItem(startSiggenLabel, nullptr, false, isConnected)) {
if (m_device != nullptr) {
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";
}
}
}
ImGui::EndMenu();
}
@ -70,6 +381,9 @@ run siggen
void deviceRenderToolbar()
{
ImGui::SameLine();
if (ImGui::Button("Upload"))
deviceAlgorithmUpload();
ImGui::SameLine();
ImGui::SetNextItemWidth(100);
if (ImGui::BeginCombo("", sampleRatePreview)) {
for (int i = 0; i < 6; ++i) {
@ -116,10 +430,103 @@ void deviceStart()
if (m_device->is_running()) {
m_device->continuous_stop();
if (logResults) {
logSamplesFile.close();
logResults = false;
log("Log file saved and closed.");
}
log("Ready.");
} else {
m_device->continuous_start();
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();
}
log("Running.");
}
}
void deviceAlgorithmUpload()
{
if (m_device == nullptr) {
log("No device connected.");
return;
}
if (m_device->is_running())
return;
if (std::ifstream algo (tempFileName + ".o"); algo.good()) {
std::ostringstream sstr;
sstr << algo.rdbuf();
auto str = sstr.str();
m_device->upload_filter(reinterpret_cast<unsigned char *>(&str[0]), str.size());
log("Algorithm uploaded.");
} else {
log("Algorithm must be compiled first.");
}
}
void deviceAlgorithmUnload()
{
if (m_device == nullptr) {
log("No device connected.");
return;
}
if (!m_device->is_running()) {
m_device->unload_filter();
log("Algorithm unloaded.");
}
}
void deviceGenLoadList(std::string_view listStr)
{
std::vector<stmdsp::dacsample_t> samples;
while (listStr.size() > 0 && samples.size() <= stmdsp::SAMPLES_MAX * 2) {
auto numberEnd = listStr.find_first_not_of("0123456789");
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);
if (ec != std::errc())
break;
samples.push_back(n & 4095);
if (end == listStr.end())
break;
listStr = listStr.substr(numberEnd + 1);
}
if (samples.size() <= stmdsp::SAMPLES_MAX * 2) {
// DAC buffer must be of even size
if ((samples.size() & 1) == 1)
samples.push_back(samples.back());
if (m_device != nullptr)
m_device->siggen_upload(&samples[0], samples.size());
log("Generator ready.");
} else {
log("Error: Too many samples for signal generator.");
}
}
void deviceGenLoadFormula(std::string_view formula)
{
auto samples = deviceGenLoadFormulaEval(formula);
if (samples.size() > 0) {
if (m_device != nullptr)
m_device->siggen_upload(&samples[0], samples.size());
log("Generator ready.");
} else {
log("Error: Bad formula.");
}
}

@ -0,0 +1,28 @@
#include "stmdsp.hpp"
#include "exprtk.hpp"
#include <algorithm>
#include <string_view>
#include <vector>
std::vector<stmdsp::dacsample_t> deviceGenLoadFormulaEval(const std::string_view formulaString)
{
double x = 0;
exprtk::symbol_table<double> symbol_table;
exprtk::expression<double> expression;
exprtk::parser<double> parser;
symbol_table.add_variable("x", x);
symbol_table.add_constants();
expression.register_symbol_table(symbol_table);
parser.compile(std::string(formulaString), expression);
std::vector<stmdsp::dacsample_t> samples (stmdsp::SAMPLES_MAX);
std::generate(samples.begin(), samples.end(),
[&] { ++x; return static_cast<stmdsp::dacsample_t>(expression.value()); });
return samples;
}

File diff suppressed because it is too large Load Diff

@ -75,8 +75,8 @@ void fileRenderMenu()
if (ImGui::MenuItem("Open")) {
fileAction = FileAction::Open;
ImGuiFileDialog::Instance()->OpenDialog(
"ChooseFileDlgKey", "Choose File", ".cpp", ".");
ImGuiFileDialog::Instance()->OpenModal(
"ChooseFileOpenSave", "Choose File", ".cpp", ".");
}
if (ImGui::BeginMenu("Open Template")) {
@ -99,15 +99,15 @@ void fileRenderMenu()
saveCurrentFile();
} else {
fileAction = FileAction::SaveAs;
ImGuiFileDialog::Instance()->OpenDialog(
"ChooseFileDlgKey", "Choose File", ".cpp", ".");
ImGuiFileDialog::Instance()->OpenModal(
"ChooseFileOpenSave", "Choose File", ".cpp", ".");
}
}
if (ImGui::MenuItem("Save As")) {
fileAction = FileAction::SaveAs;
ImGuiFileDialog::Instance()->OpenDialog(
"ChooseFileDlgKey", "Choose File", ".cpp", ".");
ImGuiFileDialog::Instance()->OpenModal(
"ChooseFileOpenSave", "Choose File", ".cpp", ".");
}
ImGui::Separator();
@ -122,7 +122,7 @@ void fileRenderMenu()
void fileRenderDialog()
{
if (ImGuiFileDialog::Instance()->Display("ChooseFileDlgKey")) {
if (ImGuiFileDialog::Instance()->Display("ChooseFileOpenSave")) {
if (ImGuiFileDialog::Instance()->IsOk()) {
std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName();

@ -45,6 +45,8 @@ public:
return;
}
ImGui::Text("Log ");
ImGui::SameLine();
if (ImGui::Button("Clear"))
Clear();
ImGui::SameLine();

@ -37,11 +37,12 @@ extern void codeRenderMenu();
extern void codeRenderToolbar();
extern void codeRenderWidgets();
extern void deviceRenderDraw();
extern void deviceRenderMenu();
extern void deviceRenderToolbar();
extern void deviceRenderWidgets();
// Globals that live here
std::string tempFileName;
bool done = false;
stmdsp::device *m_device = nullptr;
@ -79,25 +80,29 @@ int main(int, char **)
ImGui::SetNextWindowPos({0, 22});
ImGui::SetNextWindowSize({WINDOW_WIDTH, WINDOW_HEIGHT - 22 - 200});
ImGui::Begin("main", nullptr,
ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration);
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);
logView.Draw("log", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBringToFrontOnFocus);
ImGui::PopFont();
// Finish main view rendering.
ImGui::End();
deviceRenderDraw();
// Draw everything to the screen.
ImGui::Render();
guiRender([] {

@ -0,0 +1,96 @@
#ifndef WAV_HPP_
#define WAV_HPP_
#include <cstdint>
#include <cstring>
#include <fstream>
namespace wav
{
struct header {
char riff[4]; // "RIFF"
uint32_t filesize; // Total file size minus eight bytes
char wave[4]; // "WAVE"
bool valid() const {
return strncmp(riff, "RIFF", 4) == 0 && filesize > 8 && strncmp(wave, "WAVE", 4) == 0;
}
} __attribute__ ((packed));
struct format {
char fmt_[4]; // "fmt "
uint32_t size;
uint16_t type;
uint16_t channelcount;
uint32_t samplerate;
uint32_t byterate;
uint16_t unused;
uint16_t bps;
bool valid() const {
return strncmp(fmt_, "fmt ", 4) == 0;
}
} __attribute__ ((packed));
struct data {
char data[4]; // "data"
uint32_t size;
bool valid() const {
return strncmp(data, "data", 4) == 0;
}
} __attribute__ ((packed));
class clip {
public:
clip(const char *path) {
std::ifstream file (path);
if (!file.good())
return;
{
header h;
file.read(reinterpret_cast<char *>(&h), sizeof(header));
if (!h.valid())
return;
}
{
format f;
file.read(reinterpret_cast<char *>(&f), sizeof(format));
if (!f.valid() || f.type != 1) // ensure PCM
return;
}
{
wav::data d;
file.read(reinterpret_cast<char *>(&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);
}
}
clip() = default;
bool valid() const {
return m_data != nullptr && m_size > 0;
}
auto data() const {
return m_data;
}
auto next(unsigned int chunksize = 3000) {
if (m_pos == m_size) {
m_pos = 0;
}
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;
};
}
#endif // WAV_HPP_
Loading…
Cancel
Save