Files
sndlock/sndlock.cpp
2026-02-26 18:27:29 +08:00

781 lines
33 KiB
C++

#include <iostream>
#include <thread>
#include <cstdint>
#include <array>
#include <complex>
#include <limits>
#include <optional>
#include <chrono>
#include <GLFW/glfw3.h>
#include <sndio.h>
#include <poll.h>
#include <math.h>
#include <imgui/imgui.h>
#include <imgui/imgui_impl_glfw.h>
#include <imgui/imgui_impl_opengl3.h>
#include <implot/implot.h>
#include "dsp_lib.hpp"
#include "fifo.hpp"
#include "kirdy.hpp"
#define SND_BITS 24
#define SND_PCHAN 2
#define SND_RCHAN 2
#define SND_RATE 192000
#define SND_BUFLEN 4096
static std::atomic<bool> shutdown_threads;
static std::atomic<double> frequency[SND_PCHAN];
static std::atomic<double> amplitude[SND_PCHAN];
#define IN_WAVE_DEPTH 4096
static std::atomic<bool> clipped[SND_RCHAN];
static std::atomic<double> peak[SND_RCHAN];
static std::atomic<int> in_wave_trigger[SND_RCHAN];
static std::mutex in_wave_mutex[SND_RCHAN];
static float in_wave[SND_RCHAN][IN_WAVE_DEPTH];
static std::atomic<double> lpf_bandwidth;
static std::atomic<std::complex<double>> li_raw[SND_RCHAN][SND_PCHAN]; // fundamental only
static std::atomic<double> ramc_mag[SND_RCHAN][SND_PCHAN];
static std::atomic<double> ramc_phase[SND_RCHAN][SND_PCHAN];
static std::atomic<double> squelch[SND_RCHAN][SND_PCHAN];
static std::atomic<double> li_angle[SND_RCHAN][SND_PCHAN];
#define LI_HIST_DEPTH 1024
static std::atomic<bool> li_hist_hold[SND_PCHAN];
static std::mutex li_hist_mutex;
static float li_hist_mag[SND_RCHAN][SND_PCHAN][2][LI_HIST_DEPTH];
static float li_hist_phase[SND_RCHAN][SND_PCHAN][2][LI_HIST_DEPTH];
static float li_hist_real[SND_RCHAN][SND_PCHAN][2][LI_HIST_DEPTH];
static float li_hist_angle[SND_RCHAN][SND_PCHAN][LI_HIST_DEPTH];
enum class InWaveState { delay, wait_trigger, capturing };
static void dsp_thread()
{
struct sio_hdl *hdl;
struct sio_par par;
hdl = sio_open(SIO_DEVANY, SIO_PLAY|SIO_REC, 1);
if(hdl == nullptr) {
std::cerr << "failed to open sound device\n";
return;
}
sio_initpar(&par);
par.sig = 1;
par.bits = SND_BITS;
par.pchan = SND_PCHAN;
par.rchan = SND_RCHAN;
par.rate = SND_RATE;
par.le = SIO_LE_NATIVE;
par.xrun = SIO_ERROR;
if(!sio_setpar(hdl, &par)) {
std::cerr << "failed to set sound device parameters\n";
sio_close(hdl);
return;
}
if(!sio_getpar(hdl, &par)) {
std::cerr << "failed to get back sound device parameters\n";
sio_close(hdl);
return;
}
if(par.sig != 1
|| par.bits != SND_BITS
|| par.pchan != SND_PCHAN
|| par.rchan != SND_RCHAN
|| par.rate != SND_RATE
|| par.le != SIO_LE_NATIVE) {
std::cerr << "sound device parameter mismatch\n";
sio_close(hdl);
return;
}
if(!sio_start(hdl)) {
std::cerr << "failed to start sound device\n";
sio_close(hdl);
return;
}
DDS dds[SND_PCHAN];
int32_t buf_out[SND_BUFLEN*SND_PCHAN];
size_t buf_out_offset = sizeof(buf_out);
int32_t buf_in[SND_BUFLEN*SND_RCHAN];
size_t buf_in_offset = sizeof(buf_in);
FIFO<std::array<phase_t, SND_PCHAN>, 32> ftw_fifo;
int clipped_count[SND_RCHAN] = { };
double peak_running[SND_RCHAN] = { };
phase_t last_trig_phase[SND_RCHAN] = { };
InWaveState in_wave_state[SND_RCHAN] = { };
int in_wave_count[SND_RCHAN] = { };
float in_wave_buf[SND_RCHAN][IN_WAVE_DEPTH];
Lockin<2, 4> lockin[SND_RCHAN][SND_PCHAN];
for(int i=0;i<SND_RCHAN;i++)
for(int j=0;j<SND_PCHAN;j++)
lockin[i][j].set_scale(pow(0.5, SND_BITS-1));
int peak_count = 0;
int li_hist_count = 0;
nfds_t nfds = sio_nfds(hdl);
struct pollfd *pfd = (struct pollfd *)alloca(nfds*sizeof(struct pollfd));
while(!shutdown_threads) {
sio_pollfd(hdl, pfd, POLLOUT|POLLIN);
poll(pfd, nfds, INFTIM);
int revents = sio_revents(hdl, pfd);
for(int i=0;i<SND_RCHAN;i++)
for(int j=0;j<SND_PCHAN;j++)
lockin[i][j].set_bandwidth(lpf_bandwidth/SND_RATE);
if(revents & POLLOUT) {
if(buf_out_offset == sizeof(buf_out)) {
std::array<phase_t, SND_PCHAN> ftws;
for(int i=0;i<SND_PCHAN;i++) {
phase_t ftw = frequency_to_ftw(frequency[i]/SND_RATE);
dds[i].ftw = ftw;
ftws[i] = ftw;
double scale = amplitude[i]*(pow(2.0, SND_BITS-1) - 1.0);
for(int j=0;j<SND_BUFLEN;j++)
buf_out[SND_PCHAN*j+i] = scale*dds[i].get();
}
if(!ftw_fifo.push(ftws))
std::cerr << "FTW FIFO overflow\n";
buf_out_offset = 0;
}
size_t written = sio_write(hdl, (const char *)buf_out + buf_out_offset, sizeof(buf_out) - buf_out_offset);
buf_out_offset += written;
}
if(revents & POLLIN) {
size_t read = sio_read(hdl, buf_in, sizeof(buf_in));
buf_in_offset += read;
if(buf_in_offset >= sizeof(buf_in)) {
std::optional<std::array<phase_t, SND_PCHAN>> ftw = ftw_fifo.pull();
if(ftw.has_value())
for(int i=0;i<SND_PCHAN;i++)
for(int j=0;j<SND_RCHAN;j++)
lockin[j][i].ftw = ftw.value()[i];
else
std::cerr << "FTW FIFO underflow\n";
buf_in_offset -= sizeof(buf_in);
}
for(int i=0;i<read/(SND_RCHAN*sizeof(buf_in[0]));i++) {
peak_count++;
bool update_peak = peak_count == SND_RATE/10;
if(update_peak)
peak_count = 0;
li_hist_count++;
bool update_li_hist = li_hist_count == 600;
if(update_li_hist)
li_hist_count = 0;
for(int j=0;j<SND_RCHAN;j++) {
double sample = (double)buf_in[SND_RCHAN*i+j];
double sample_abs = std::abs(sample);
if(sample_abs > 0.99*pow(2.0, SND_BITS-1))
// display the clipped indicator for about one second
clipped_count[j] = SND_RATE;
if(clipped_count[j] > 0) {
clipped_count[j]--;
clipped[j] = true;
} else
clipped[j] = false;
if(sample_abs > peak_running[j])
peak_running[j] = sample_abs;
if(update_peak) {
peak[j] = peak_running[j]/pow(2.0, SND_BITS-1);
peak_running[j] = 0.0;
}
phase_t trig_phase = lockin[j][in_wave_trigger[j]].get_phase();
bool trigger = trig_phase > last_trig_phase[j];
last_trig_phase[j] = trig_phase;
switch(in_wave_state[j]) {
case InWaveState::delay:
in_wave_count[j]++;
if(in_wave_count[j] == SND_RATE/10)
in_wave_state[j] = InWaveState::wait_trigger;
break;
case InWaveState::wait_trigger:
if(trigger) {
in_wave_count[j] = 0;
in_wave_state[j] = InWaveState::capturing;
}
break;
case InWaveState::capturing:
in_wave_buf[j][in_wave_count[j]++] = sample/(pow(2.0, SND_BITS-1));
if(in_wave_count[j] == IN_WAVE_DEPTH) {
{
std::lock_guard<std::mutex> guard(in_wave_mutex[j]);
std::memcpy(in_wave[j], in_wave_buf[j], sizeof(in_wave[0]));
}
in_wave_count[j] = 0;
in_wave_state[j] = InWaveState::delay;
}
break;
}
for(int k=0;k<SND_PCHAN;k++) {
std::complex<double> lockin_raw[2];
lockin[j][k].update(sample, lockin_raw);
li_raw[j][k] = lockin_raw[0];
std::complex<double> lockin_out[2];
lockin_out[0] = lockin_raw[0]*std::polar(1.0, -(double)ramc_phase[j][k]) - (double)ramc_mag[j][k];
lockin_out[1] = lockin_raw[1]*std::polar(1.0, -2.0*(double)ramc_phase[j][k]);
double distance = std::sqrt(std::pow(std::real(lockin_out[0]), 2) + std::pow(std::real(lockin_out[1]), 2));
double angle;
if(distance > squelch[j][k])
angle = std::atan2(std::real(lockin_out[0]), std::real(lockin_out[1]));
else
angle = std::numeric_limits<double>::quiet_NaN();
li_angle[j][k] = angle;
if(!li_hist_hold[k] && update_li_hist) {
std::lock_guard<std::mutex> guard(li_hist_mutex);
for(int l=0;l<2;l++) {
std::memmove(&li_hist_mag[j][k][l][0], &li_hist_mag[j][k][l][1], (LI_HIST_DEPTH-1)*sizeof(float));
li_hist_mag[j][k][l][LI_HIST_DEPTH-1] = std::abs(lockin_out[l]);
// std::arg returns angles in the [-pi;pi] interval.
// this causes frequent jumps when the signal is a negative real.
// moving to [0;2*pi] interval avoids most jumps.
std::memmove(&li_hist_phase[j][k][l][0], &li_hist_phase[j][k][l][1], (LI_HIST_DEPTH-1)*sizeof(float));
double phase = std::arg(lockin_out[l]);
if(phase < 0.0)
phase += 2.0*M_PI;
li_hist_phase[j][k][l][LI_HIST_DEPTH-1] = phase;
std::memmove(&li_hist_real[j][k][l][0], &li_hist_real[j][k][l][1], (LI_HIST_DEPTH-1)*sizeof(float));
li_hist_real[j][k][l][LI_HIST_DEPTH-1] = std::real(lockin_out[l]);
}
std::memmove(&li_hist_angle[j][k][0], &li_hist_angle[j][k][1], (LI_HIST_DEPTH-1)*sizeof(float));
li_hist_angle[j][k][LI_HIST_DEPTH-1] = angle;
}
}
}
}
}
if(sio_eof(hdl)) {
std::cerr << "sound I/O error\n";
sio_close(hdl);
return;
}
}
sio_stop(hdl);
sio_close(hdl);
}
static const char *kirdies[SND_PCHAN][2] = {
{"192.168.1.128", "1550"},
{"192.168.1.126", "1550"},
};
static std::atomic<bool> servo_enable[SND_PCHAN];
static std::atomic<const char *> servo_state[SND_PCHAN];
static std::atomic<float> laser_temp[SND_PCHAN];
static std::atomic<float> init_current_cooling[SND_PCHAN];
static std::atomic<float> init_current_heating[SND_PCHAN];
static std::atomic<float> init_temp[SND_PCHAN];
static std::atomic<float> leadin_current[SND_PCHAN];
static std::atomic<float> leadin_thr[SND_PCHAN];
static std::atomic<float> loop_setpoint[SND_PCHAN];
static std::atomic<float> loop_bias[SND_PCHAN];
static std::atomic<float> loop_p[SND_PCHAN];
static void servo_thread(int channel)
{
Clocker clocker = Clocker(std::chrono::milliseconds(30));
Kirdy kirdy = Kirdy(kirdies[channel][0], kirdies[channel][1]);
float temp;
while(true) {
servo_state[channel] = "DISABLED";
while(!servo_enable[channel]) {
clocker.tick();
if(shutdown_threads)
return;
laser_temp[channel] = temp = kirdy.get_laser_temp();
}
servo_state[channel] = "INIT";
if(temp > init_temp[channel]) {
while(servo_enable[channel] && temp > init_temp[channel]) {
kirdy.set_tec_current(init_current_cooling[channel]);
clocker.tick();
laser_temp[channel] = temp = kirdy.get_laser_temp();
}
} else {
while(servo_enable[channel] && temp < init_temp[channel]) {
kirdy.set_tec_current(init_current_heating[channel]);
clocker.tick();
laser_temp[channel] = temp = kirdy.get_laser_temp();
}
}
if(!servo_enable[channel])
continue;
servo_state[channel] = "LEAD-IN-1";
float a = std::copysign(1.0f, (float)leadin_thr[channel]);
while(servo_enable[channel] && a*li_angle[channel][channel] < a*leadin_thr[channel]) {
kirdy.set_tec_current(leadin_current[channel]);
clocker.tick();
laser_temp[channel] = temp = kirdy.get_laser_temp();
}
servo_state[channel] = "LEAD-IN-2";
while(servo_enable[channel] && a*li_angle[channel][channel] > a*leadin_thr[channel]) {
kirdy.set_tec_current(leadin_current[channel]);
clocker.tick();
laser_temp[channel] = temp = kirdy.get_laser_temp();
}
servo_state[channel] = "LOCKING";
while(servo_enable[channel]) {
kirdy.set_tec_current(loop_bias[channel] + loop_p[channel]*(li_angle[channel][channel] - loop_setpoint[channel]));
clocker.tick();
laser_temp[channel] = temp = kirdy.get_laser_temp();
}
}
}
static void ChannelSeparator(int i, const char *type = "Channel")
{
char str[32];
snprintf(str, sizeof(str), "%s %d", type, i);
ImGui::SeparatorText(str);
}
int main(int argc, char* argv[])
{
bool noservo = false;
bool quiet = false;
for(int i=1;i<argc;i++) {
if(strcmp(argv[i], "noservo") == 0)
noservo = true;
else if(strcmp(argv[i], "quiet") == 0)
quiet = true;
else {
std::cerr << "unknown command line argument\n";
return 1;
}
}
if(!glfwInit()) {
std::cerr << "failed to initialize GLFW\n";
return 1;
}
std::atexit(glfwTerminate);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
static GLFWwindow *window = glfwCreateWindow(1300, 1370, "Soundlocker II", nullptr, nullptr);
if(window == nullptr) {
std::cerr << "failed to create GLFW window\n";
return 1;
}
static auto DestroyWindow = []() {
glfwDestroyWindow(window);
};
std::atexit(DestroyWindow);
glfwMakeContextCurrent(window);
glfwSwapInterval(1);
IMGUI_CHECKVERSION();
ImGui::CreateContext();
static auto ImGuiDestroyContext = []() {
ImGui::DestroyContext();
};
std::atexit(ImGuiDestroyContext);
ImPlot::CreateContext();
static auto ImPlotDestroyContext = []() {
ImPlot::DestroyContext();
};
std::atexit(ImPlotDestroyContext);
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
ImGui::StyleColorsDark();
ImGui_ImplGlfw_InitForOpenGL(window, true);
std::atexit(ImGui_ImplGlfw_Shutdown);
ImGui_ImplOpenGL3_Init("#version 130");
std::atexit(ImGui_ImplOpenGL3_Shutdown);
lpf_bandwidth = 8.5;
for(int i=0;i<SND_PCHAN;i++) {
frequency[i] = 441.0 + 202.0*i;
if(!quiet)
amplitude[i] = 1.0;
in_wave_trigger[i] = i;
for(int j=0;j<SND_RCHAN;j++)
squelch[j][i] = 0.005;
servo_state[i] = "DISABLED";
init_current_cooling[i] = 310.0f;
init_current_heating[i] = -30.0f;
init_temp[i] = 16.0f;
leadin_current[i] = 280.0f;
leadin_thr[i] = 0.01f;
loop_setpoint[i] = 0.01f;
loop_bias[i] = 280.0f;
loop_p[i] = 700.0f;
}
static std::thread dsp_thread_h = std::thread(dsp_thread);
static auto JoinDSP = []() {
dsp_thread_h.join();
};
std::atexit(JoinDSP);
if(!noservo) {
static std::thread servo_thread_h[SND_PCHAN];
for(int i=0;i<SND_PCHAN;i++)
servo_thread_h[i] = std::thread(servo_thread, i);
static auto JoinServo = []() {
for(int i=0;i<SND_PCHAN;i++)
servo_thread_h[i].join();
};
std::atexit(JoinServo);
}
shutdown_threads = false;
static auto SetShutdown = []() {
for(int i=0;i<SND_PCHAN;i++)
servo_enable[i] = false;
shutdown_threads = true;
};
std::atexit(SetShutdown);
int plot_sel[SND_PCHAN] = { };
int plot_sel_f[SND_PCHAN] = { };
while(!glfwWindowShouldClose(window)) {
glfwPollEvents();
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
ImGui::SetNextWindowPos(ImVec2(0.0f, 0.0f));
ImGui::SetNextWindowSize(io.DisplaySize);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::Begin("Soundlocker II", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoResize);
if(ImGui::CollapsingHeader("Modulation")) {
ImGui::PushID("modulation");
for(int i=0;i<SND_PCHAN;i++) {
ImGui::PushID(i);
ChannelSeparator(i);
float frequency_l = frequency[i];
ImGui::SliderFloat("frequency", &frequency_l, 50.0f, 8000.0f);
frequency[i] = frequency_l;
float amplitude_l = amplitude[i];
ImGui::SliderFloat("amplitude", &amplitude_l, 0.0f, 1.0f);
amplitude[i] = amplitude_l;
ImGui::PopID();
}
ImGui::PopID();
}
if(ImGui::CollapsingHeader("Input monitor")) {
ImGui::PushID("inputmon");
for(int i=0;i<SND_RCHAN;i++) {
ImGui::PushID(i);
ImGui::AlignTextToFramePadding();
ImGui::TextColored(ImPlot::GetColormapColor(i), "input ch%d", i);
ImGui::SameLine();
ImGui::Text("| level:");
ImGui::SameLine();
ImGui::ProgressBar((float)peak[i], ImVec2(30.0f*ImGui::GetFontSize(), 0.0f));
ImGui::SameLine();
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, clipped[i] ? 1.0f : 0.0f), "clipped!");
ImGui::SameLine();
ImGui::Text("| modulation trigger:");
ImGui::SameLine();
int in_wave_trigger_l = in_wave_trigger[i];
for(int j=0;j<SND_PCHAN;j++) {
char str[32];
snprintf(str, sizeof(str), "ch%d", j);
ImGui::RadioButton(str, &in_wave_trigger_l, j);
if(j < (SND_PCHAN-1)) ImGui::SameLine();
}
in_wave_trigger[i] = in_wave_trigger_l;
ImGui::PopID();
}
if(ImPlot::BeginPlot("inputmon##plot", ImVec2(-1, 0), ImPlotFlags_NoTitle)) {
ImPlot::SetupAxis(ImAxis_X1, "time", 0);
ImPlot::SetupAxis(ImAxis_Y1, "sample", 0);
ImPlot::SetupAxisLimits(ImAxis_X1, 0, IN_WAVE_DEPTH);
ImPlot::SetupAxisLimits(ImAxis_Y1, -1.0f, 1.0f);
for(int i=0;i<SND_RCHAN;i++) {
char str[32];
snprintf(str, sizeof(str), "ch%d", i);
std::lock_guard<std::mutex> guard(in_wave_mutex[i]);
ImPlot::PlotLine(str, in_wave[i], IN_WAVE_DEPTH);
}
ImPlot::EndPlot();
}
ImGui::PopID();
}
if(ImGui::CollapsingHeader("Demodulation", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::PushID("demodulation");
ImGui::SeparatorText("Common");
float lpf_bandwidth_l = lpf_bandwidth;
ImGui::SliderFloat("LPF BW", &lpf_bandwidth_l, 0.5f, 200.0f);
lpf_bandwidth = lpf_bandwidth_l;
for(int i=0;i<SND_PCHAN;i++) {
ImGui::PushID(i);
ChannelSeparator(i, "Fundamental frequency");
ImGui::AlignTextToFramePadding();
for(int j=0;j<SND_RCHAN;j++) {
ImGui::PushID(j);
ImGui::TextColored(ImPlot::GetColormapColor(j), "input ch%d", j);
ImGui::SameLine();
ImGui::Text("| RAM cancellation:");
ImGui::SameLine();
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x / 7.0f);
float ramc_mag_l = ramc_mag[j][i];
ImGui::InputFloat("magnitude##ramc", &ramc_mag_l, 0.001f, 0.01f, "%.4f");
ramc_mag[j][i] = ramc_mag_l;
ImGui::SameLine();
float ramc_phase_l = ramc_phase[j][i];
ImGui::InputFloat("phase##ramc", &ramc_phase_l, 0.1f, 0.5f, "%.4f");
ramc_phase[j][i] = ramc_phase_l;
ImGui::PopItemWidth();
ImGui::SameLine();
ImGui::Text("| squelch:");
ImGui::SameLine();
float squelch_l = squelch[j][i];
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
ImGui::SliderFloat("", &squelch_l, 0.0f, 0.02f);
ImGui::PopItemWidth();
squelch[j][i] = squelch_l;
ImGui::PopID();
}
ImGui::AlignTextToFramePadding();
ImGui::Text("RAM cancellation:");
ImGui::SameLine();
if((i < SND_RCHAN) && ImGui::Button("baseline")) {
std::complex<double> last_li_raw = li_raw[i][i];
for(int j=0;j<SND_RCHAN;j++) {
if(j == i)
ramc_mag[j][i] = std::abs(last_li_raw);
else
// No RAM to cancel when crossed (blocked by color filter).
ramc_mag[j][i] = 0.0;
// Both record channels are sampled at the same time,
// so phase compensation is the same for both.
ramc_phase[j][i] = std::arg(last_li_raw);
}
}
ImGui::SameLine();
if(ImGui::Button("reset")) {
for(int j=0;j<SND_RCHAN;j++) {
ramc_mag[j][i] = 0.0;
ramc_phase[j][i] = 0.0;
}
}
ImGui::SameLine();
ImGui::Text("| plot:");
ImGui::SameLine();
bool hold_l = li_hist_hold[i];
ImGui::Checkbox("hold", &hold_l);
li_hist_hold[i] = hold_l;
ImGui::SameLine();
ImGui::Text("|");
ImGui::SameLine();
ImGui::RadioButton("magnitude", &plot_sel[i], 0);
ImGui::SameLine();
ImGui::RadioButton("phase", &plot_sel[i], 1);
ImGui::SameLine();
ImGui::RadioButton("real", &plot_sel[i], 2);
ImGui::SameLine();
ImGui::RadioButton("real XY", &plot_sel[i], 3);
ImGui::SameLine();
ImGui::RadioButton("angle", &plot_sel[i], 4);
if((plot_sel[i] != 3) && (plot_sel[i] != 4)) {
ImGui::SameLine();
ImGui::Text("|");
ImGui::SameLine();
ImGui::RadioButton("1f", &plot_sel_f[i], 0);
ImGui::SameLine();
ImGui::RadioButton("2f", &plot_sel_f[i], 1);
}
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
{
std::lock_guard<std::mutex> guard(li_hist_mutex);
switch(plot_sel[i]) {
case 0:
if(ImPlot::BeginPlot("mag##plot", ImVec2(-1, 0), ImPlotFlags_NoTitle)) {
ImPlot::SetupAxis(ImAxis_X1, "time", ImPlotAxisFlags_Lock);
ImPlot::SetupAxisLimits(ImAxis_X1, 0, LI_HIST_DEPTH);
ImPlot::SetupAxisLimits(ImAxis_Y1, 0.0f, 0.02f);
for(int j=0;j<SND_RCHAN;j++) {
char str[32];
snprintf(str, sizeof(str), "ch%d", j);
ImPlot::PlotLine(str, li_hist_mag[j][i][plot_sel_f[i]], LI_HIST_DEPTH);
}
ImPlot::EndPlot();
}
break;
case 1:
if(ImPlot::BeginPlot("phase##plot", ImVec2(-1, 0), ImPlotFlags_NoTitle)) {
ImPlot::SetupAxis(ImAxis_X1, "time", ImPlotAxisFlags_Lock);
ImPlot::SetupAxisLimits(ImAxis_X1, 0, LI_HIST_DEPTH);
ImPlot::SetupAxisLimits(ImAxis_Y1, 0.0f, 2.0*M_PI);
for(int j=0;j<SND_RCHAN;j++) {
char str[32];
snprintf(str, sizeof(str), "ch%d", j);
ImPlot::PlotLine(str, li_hist_phase[j][i][plot_sel_f[i]], LI_HIST_DEPTH);
}
ImPlot::EndPlot();
}
break;
case 2:
if(ImPlot::BeginPlot("real##plot", ImVec2(-1, 0), ImPlotFlags_NoTitle)) {
ImPlot::SetupAxis(ImAxis_X1, "time", ImPlotAxisFlags_Lock);
ImPlot::SetupAxisLimits(ImAxis_X1, 0, LI_HIST_DEPTH);
ImPlot::SetupAxisLimits(ImAxis_Y1, -0.02f, 0.02f);
for(int j=0;j<SND_RCHAN;j++) {
char str[32];
snprintf(str, sizeof(str), "ch%d", j);
ImPlot::PlotLine(str, li_hist_real[j][i][plot_sel_f[i]], LI_HIST_DEPTH);
}
ImPlot::EndPlot();
}
break;
case 3:
if(ImPlot::BeginPlot("realxy##plot", ImVec2(-1, 0), ImPlotFlags_NoTitle)) {
ImPlot::SetupAxis(ImAxis_X1, "1f");
ImPlot::SetupAxis(ImAxis_Y1, "2f");
for(int j=0;j<SND_RCHAN;j++) {
char str[32];
snprintf(str, sizeof(str), "ch%d", j);
ImPlot::PlotScatter(str, li_hist_real[j][i][0], li_hist_real[j][i][1], LI_HIST_DEPTH);
ImPlot::PushPlotClipRect();
ImVec2 center = ImPlot::PlotToPixels(ImPlotPoint(0.0f, 0.0f));
ImVec2 boundaries = ImPlot::PlotToPixels(ImPlotPoint(squelch[j][i], squelch[j][i]));
ImVec2 radius = ImVec2(boundaries.x - center.x, boundaries.y - center.y);
ImDrawList *drawlist = ImPlot::GetPlotDrawList();
ImVec4 color = ImPlot::GetColormapColor(j);
ImVec4 color_a = color;
color_a.w = 0.3f;
ImVec4 color_d = color;
color_d.x = 0.8f*color_d.x; color_d.y = 0.8f*color_d.y; color_d.z = 0.8f*color_d.z;
drawlist->AddEllipseFilled(center, radius, ImGui::ColorConvertFloat4ToU32(color_a));
ImVec2 last = ImPlot::PlotToPixels(ImPlotPoint(li_hist_real[j][i][0][LI_HIST_DEPTH-1],
li_hist_real[j][i][1][LI_HIST_DEPTH-1]));
drawlist->AddLine(center, last, ImGui::ColorConvertFloat4ToU32(color_d));
ImPlot::PopPlotClipRect();
}
ImPlot::EndPlot();
}
break;
case 4:
if(ImPlot::BeginPlot("angle##plot", ImVec2(-1, 0), ImPlotFlags_NoTitle)) {
ImPlot::SetupAxis(ImAxis_X1, "time", ImPlotAxisFlags_Lock);
ImPlot::SetupAxisLimits(ImAxis_X1, 0, LI_HIST_DEPTH);
ImPlot::SetupAxisLimits(ImAxis_Y1, -M_PI, M_PI);
for(int j=0;j<SND_RCHAN;j++) {
char str[32];
snprintf(str, sizeof(str), "ch%d", j);
ImPlot::PlotLine(str, li_hist_angle[j][i], LI_HIST_DEPTH);
}
ImPlot::EndPlot();
}
break;
}
}
ImGui::PopItemWidth();
ImGui::PopID();
}
ImGui::PopID();
}
if(!noservo && ImGui::CollapsingHeader("Laser servo", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::PushID("servo");
for(int i=0;i<SND_PCHAN;i++) {
ImGui::PushID(i);
ChannelSeparator(i);
bool servo_enable_l = servo_enable[i];
ImGui::Checkbox("enable", &servo_enable_l);
servo_enable[i] = servo_enable_l;
ImGui::Text("servo state: %s", (const char *)servo_state[i]);
ImGui::Text("laser temperature: %.4f °C", (float)laser_temp[i]);
ImGui::AlignTextToFramePadding();
ImGui::Text("init: ");
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x / 6.0f);
ImGui::SameLine();
float init_current_cooling_l = init_current_cooling[i];
ImGui::InputFloat("cooling mA", &init_current_cooling_l, 1.0f, 500.0f, "%.3f");
init_current_cooling[i] = init_current_cooling_l;
ImGui::SameLine();
float init_current_heating_l = init_current_heating[i];
ImGui::InputFloat("heating mA", &init_current_heating_l, 1.0f, 500.0f, "%.3f");
init_current_heating[i] = init_current_heating_l;
ImGui::SameLine();
float init_temp_l = init_temp[i];
ImGui::InputFloat("temp °C ", &init_temp_l, 1.0f, 500.0f, "%.4f");
init_temp[i] = init_temp_l;
ImGui::PopItemWidth();
ImGui::AlignTextToFramePadding();
ImGui::Text("lead-in:");
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x / 6.0f);
ImGui::SameLine();
float leadin_current_l = leadin_current[i];
ImGui::InputFloat("current mA", &leadin_current_l, 1.0f, 500.0f, "%.3f");
leadin_current[i] = leadin_current_l;
ImGui::SameLine();
float leadin_thr_l = leadin_thr[i];
ImGui::InputFloat("threshold ", &leadin_thr_l, 0.0f, 0.001f, "%.6f");
leadin_thr[i] = leadin_thr_l;
ImGui::PopItemWidth();
ImGui::AlignTextToFramePadding();
ImGui::Text("loop: ");
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x / 6.0f);
ImGui::SameLine();
float loop_bias_l = loop_bias[i];
ImGui::InputFloat("bias mA ", &loop_bias_l, 1.0f, 500.0f, "%.3f");
loop_bias[i] = loop_bias_l;
ImGui::SameLine();
float loop_setpoint_l = loop_setpoint[i];
ImGui::InputFloat("setpoint ", &loop_setpoint_l, 0.0f, 0.001f, "%.6f");
loop_setpoint[i] = loop_setpoint_l;
ImGui::SameLine();
float loop_p_l = loop_p[i];
ImGui::InputFloat("P ", &loop_p_l, 0.0f, 50.0f, "%.2f");
loop_p[i] = loop_p_l;
ImGui::PopItemWidth();
ImGui::PopID();
}
ImGui::PopID();
}
ImGui::End();
ImGui::PopStyleVar(1);
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
int display_w, display_h;
glfwGetFramebufferSize(window, &display_w, &display_h);
glViewport(0, 0, display_w, display_h);
glfwSwapBuffers(window);
}
return 0;
}