satdump/src-interface/recorder/recorder.cpp
2026-01-13 20:29:08 +01:00

902 lines
36 KiB
C++

#include "recorder.h"
#include "common/utils.h"
#include "common/widgets/frequency_input.h"
#include "common/widgets/stepped_slider.h"
#include "core/config.h"
#include "core/plugin.h"
#include "core/style.h"
#include "handlers/handler.h"
#include "imgui/imgui.h"
#include "imgui/imgui_stdlib.h"
#include "logger.h"
#include <math.h>
#include "main_ui.h"
#include "imgui/imgui_internal.h"
#include "core/resources.h"
#include "pipeline/pipeline.h"
namespace satdump
{
RecorderApplication::RecorderApplication(nlohmann::json cli_set) : handlers::Handler(), pipeline_selector(true)
{
automated_live_output_dir = satdump_cfg.main_cfg["satdump_directories"]["live_processing_autogen"]["value"].get<bool>();
processing_modules_floating_windows = satdump_cfg.main_cfg["user_interface"]["recorder_floating_windows"]["value"].get<bool>();
remaining_disk_space_time = satdump_cfg.main_cfg["user_interface"]["remaining_disk_space_time"]["value"].get<int>();
load_rec_path_data();
dsp::registerAllSources();
sources = dsp::getAllAvailableSources();
for (dsp::SourceDescriptor src : sources)
{
logger->debug("Device " + src.name);
sdr_select_string += src.name + '\0';
}
// First, check to see if a source was passed via command line
if (satdump::satdump_cfg.main_cfg.contains("cli") && satdump::satdump_cfg.main_cfg["cli"].contains("source"))
{
auto &cli_settings = satdump::satdump_cfg.main_cfg["cli"];
std::string source = cli_settings["source"];
for (int i = 0; i < (int)sources.size(); i++)
{
if (sources[i].source_type == source)
{
if (cli_settings.contains("source_id") && cli_settings["source_id"].get<std::string>() != sources[i].unique_id)
continue;
try
{
source_ptr = dsp::getSourceFromDescriptor(sources[i]);
source_ptr->open();
sdr_select_id = i;
break;
}
catch (std::runtime_error &e)
{
logger->error(e.what());
}
}
}
}
// If not, or it failed to open, revert to last used SDR
auto dcfg = db->get_user_json("recorder_sdr_settings");
if (sdr_select_id == -1 && dcfg.contains("last_used_sdr"))
{
std::string last_used_sdr = dcfg["last_used_sdr"].get<std::string>();
for (int i = 0; i < (int)sources.size(); i++)
{
if (sources[i].name != last_used_sdr)
continue;
try
{
source_ptr = dsp::getSourceFromDescriptor(sources[i]);
source_ptr->open();
sdr_select_id = i;
break;
}
catch (std::runtime_error &e)
{
logger->error(e.what());
break;
}
}
}
// If no source has been opened yet, open the first possible one
for (int i = 0; sdr_select_id == -1 && i < (int)sources.size(); i++)
{
try
{
source_ptr = dsp::getSourceFromDescriptor(sources[i]);
source_ptr->open();
sdr_select_id = i;
}
catch (std::runtime_error &e)
{
logger->error(e.what());
}
}
// Load waterfall palettes
{
waterfall_palettes_str = "";
std::filesystem::recursive_directory_iterator paletteIterator(resources::getResourcePath("waterfall"));
std::error_code iteratorError;
while (paletteIterator != std::filesystem::recursive_directory_iterator())
{
if (!std::filesystem::is_directory(paletteIterator->path()))
{
auto map = colormaps::loadMap(paletteIterator->path().string());
waterfall_palettes.push_back(map);
waterfall_palettes_str += map.name + " [" + map.author + "]" + '\0';
}
paletteIterator.increment(iteratorError);
if (iteratorError)
logger->critical(iteratorError.message());
}
}
set_frequency(frequency_hz);
try_load_sdr_settings();
splitter = std::make_shared<dsp::SplitterBlock>(source_ptr->output_stream);
splitter->add_output("record");
splitter->add_output("live");
fft = std::make_shared<dsp::FFTPanBlock>(splitter->output_stream);
fft->set_fft_settings(fft_size, get_samplerate(), fft_rate);
fft->avg_num = 10;
fft->start();
file_sink = std::make_shared<dsp::FileSinkBlock>(splitter->get_output("record"));
file_sink->start();
fft_plot = std::make_shared<widgets::FFTPlot>(fft->output_stream->writeBuf, fft_size, -150, 150, 10);
fft_plot->frequency = frequency_hz;
waterfall_plot = std::make_shared<widgets::WaterfallPlot>(fft_sizes_lut[0], 500);
fft->on_fft = [this](float *v) { waterfall_plot->push_fft(v); };
// Load config
auto cfg = db->get_user_json("recorder_state");
if (!cfg.is_null())
deserialize_config(cfg);
file_sink->set_output_sample_type(baseband_format);
fft_plot->set_size(fft_size);
waterfall_plot->set_size(fft_size);
waterfall_plot->set_rate(fft_rate, waterfall_rate);
// Attempt to apply provided CLI settings
if (!cli_set.is_null())
{
auto &cli_settings = cli_set;
if (source_ptr)
{
if (cli_settings.contains("samplerate"))
source_ptr->set_samplerate(cli_settings["samplerate"].get<uint64_t>());
source_ptr->set_settings(cli_settings);
}
if (cli_settings.contains("start_recorder_device") && cli_settings["start_recorder_device"].get<bool>())
{
logger->warn("Recorder was asked to autostart!");
start();
}
if (cli_settings.contains("engage_autotrack") && cli_settings["engage_autotrack"].get<bool>())
{
tracking_started_cli = true;
try_init_tracking_widget();
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
eventBus->register_handler<RecorderSetFrequencyEvent>([this](const RecorderSetFrequencyEvent &evt) { set_frequency(evt.frequency); });
eventBus->register_handler<RecorderStartDeviceEvent>([this](const RecorderStartDeviceEvent &) { start(); });
eventBus->register_handler<RecorderStopDeviceEvent>([this](const RecorderStopDeviceEvent &) { stop(); });
eventBus->register_handler<RecorderSetDeviceSamplerateEvent>([this](const RecorderSetDeviceSamplerateEvent &evt) { source_ptr->set_samplerate(evt.samplerate); });
eventBus->register_handler<RecorderSetDeviceDecimationEvent>([this](const RecorderSetDeviceDecimationEvent &evt) { current_decimation = evt.decimation; });
eventBus->register_handler<RecorderSetDeviceLoOffsetEvent>([this](const RecorderSetDeviceLoOffsetEvent &evt) { xconverter_frequency = evt.offset; });
eventBus->register_handler<RecorderStartProcessingEvent>(
[this](const RecorderStartProcessingEvent &evt)
{
pipeline_selector.select_pipeline(evt.pipeline_id);
start_processing();
});
eventBus->register_handler<RecorderStopProcessingEvent>([this](const RecorderStopProcessingEvent &) { stop_processing(); });
eventBus->register_handler<RecorderSetFFTSettingsEvent>(
[this](const RecorderSetFFTSettingsEvent &evt)
{
if (evt.fft_min != -1)
fft_plot->scale_min = waterfall_plot->scale_min = evt.fft_min;
if (evt.fft_max != -1)
fft_plot->scale_max = waterfall_plot->scale_max = evt.fft_max;
if (evt.fft_avgn != -1)
fft->avg_num = evt.fft_avgn;
if (evt.fft_size != -1 || evt.fft_rate != -1 || evt.waterfall_rate != -1)
{
if (evt.fft_size != -1)
fft_size = evt.fft_size;
if (evt.fft_rate != -1)
fft_rate = evt.fft_rate;
if (evt.waterfall_rate != -1)
waterfall_rate = evt.waterfall_rate;
fft->set_fft_settings(fft_size, get_samplerate(), fft_rate);
waterfall_plot->set_rate(fft_rate, waterfall_rate);
waterfall_plot->set_size(fft_size);
fft_plot->set_size(fft_size);
for (int i = 0; i < (int)fft_sizes_lut.size(); i++)
if (fft_sizes_lut[i] == fft_size)
selected_fft_size = i;
}
});
}
RecorderApplication::~RecorderApplication()
{
save_settings();
retry_vfo:
for (auto &vfo : vfo_list)
{
del_vfo(vfo.id);
goto retry_vfo;
}
stop_processing();
if (is_started)
stop();
source_ptr->close();
splitter->input_stream = std::make_shared<dsp::stream<complex_t>>();
splitter->stop();
fft->stop();
file_sink->stop();
if (tracking_widget != nullptr)
delete tracking_widget;
if (constellation_debug != nullptr)
delete constellation_debug;
}
void RecorderApplication::drawMenu()
{
bool assume_started = is_started;
if (ImGui::CollapsingHeader("Device", tracking_started_cli ? ImGuiTreeNodeFlags_None : ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::Spacing();
if (assume_started)
style::beginDisabled();
if (ImGui::Combo("##Source", &sdr_select_id, sdr_select_string.c_str()))
{
// Try to open a device, if it doesn't work, we re-open a device we can
try
{
source_ptr.reset();
source_ptr = getSourceFromDescriptor(sources[sdr_select_id]);
source_ptr->open();
}
catch (std::runtime_error &e)
{
logger->error("Could not open device! %s", e.what());
for (int i = 0; i < (int)sources.size(); i++)
{
try
{
source_ptr.reset();
source_ptr = dsp::getSourceFromDescriptor(sources[i]);
source_ptr->open();
sdr_select_id = i;
break;
}
catch (std::runtime_error &e)
{
logger->error(e.what());
}
}
}
set_frequency(frequency_hz);
try_load_sdr_settings();
}
ImGui::SameLine();
if (ImGui::Button(u8" \uead2 "))
{
sources = dsp::getAllAvailableSources();
sdr_select_string.clear();
for (dsp::SourceDescriptor src : sources)
{
logger->debug("Device " + src.name);
sdr_select_string += src.name + '\0';
}
while (sdr_select_id >= (int)sources.size())
sdr_select_id--;
source_ptr = getSourceFromDescriptor(sources[sdr_select_id]);
source_ptr->open();
set_frequency(frequency_hz);
try_load_sdr_settings();
}
/*
if (ImGui::IsItemHovered())
{
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
ImGui::TextUnformatted("Refresh source list");
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
*/
ImGui::InputInt("Decimation##recorderdecimation", &current_decimation);
if (current_decimation < 1)
current_decimation = 1;
bool disableLO = satdump_cfg.main_cfg["user_interface"]["lock_xconverter_input"]["value"];
if (assume_started && !disableLO)
style::endDisabled();
bool pushed_color_xconv = xconverter_frequency != 0;
if (pushed_color_xconv)
ImGui::PushStyleColor(ImGuiCol_Text, style::theme.green.Value);
if (ImGui::InputDouble("MHz (LO offset)##downupconverter", &xconverter_frequency))
set_frequency(frequency_hz);
if (pushed_color_xconv)
ImGui::PopStyleColor();
if (assume_started && disableLO)
style::endDisabled();
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
if (widgets::FrequencyInput("Hz##mainfreq", &frequency_hz))
set_frequency(frequency_hz);
ImGui::Spacing();
source_ptr->drawControlUI();
if (!assume_started)
{
if (ImGui::Button("Start"))
start();
}
else
{
if (ImGui::Button("Stop"))
stop();
}
sdr_error.draw();
}
if (ImGui::CollapsingHeader("FFT", tracking_started_cli ? ImGuiTreeNodeFlags_None : ImGuiTreeNodeFlags_DefaultOpen))
{
if (ImGui::Combo("FFT Size", &selected_fft_size,
"131072\0"
"65536\0"
"32768\0"
"16384\0"
"8192\0"
"4096\0"
"2048\0"
"1024\0"))
{
fft_size = fft_sizes_lut[selected_fft_size];
fft->set_fft_settings(fft_size, get_samplerate(), fft_rate);
fft_plot->set_size(fft_size);
waterfall_plot->set_size(fft_size);
logger->info("Set FFT size to %d", fft_size);
}
int old_rate = fft_rate;
if (ImGui::InputInt("FFT Rate", &fft_rate))
{
if (fft_rate <= 0)
fft_rate = old_rate;
else
{
fft_size = fft_sizes_lut[selected_fft_size];
fft->set_fft_settings(fft_size, get_samplerate(), fft_rate);
waterfall_plot->set_rate(fft_rate, waterfall_rate);
logger->info("Set FFT rate to %d", fft_rate);
}
}
if (ImGui::IsItemDeactivatedAfterEdit() && fft_rate < waterfall_rate)
{
waterfall_rate = fft_rate;
waterfall_plot->set_rate(fft_rate, waterfall_rate);
logger->info("Set Waterfall rate to %d", waterfall_rate);
}
old_rate = waterfall_rate;
if (ImGui::InputInt("Waterfall Rate", &waterfall_rate))
{
if (waterfall_rate <= 0)
waterfall_rate = old_rate;
else
{
waterfall_plot->set_rate(fft_rate, waterfall_rate);
logger->info("Set Waterfall rate to %d", waterfall_rate);
}
}
if (ImGui::IsItemDeactivatedAfterEdit() && waterfall_rate > fft_rate)
{
fft_rate = waterfall_rate;
fft->set_fft_settings(fft_size, get_samplerate(), fft_rate);
waterfall_plot->set_rate(fft_rate, waterfall_rate);
logger->info("Set FFT rate to %d", fft_rate);
}
widgets::SteppedSliderFloat("FFT Max", &fft_plot->scale_max, -160, 150);
widgets::SteppedSliderFloat("FFT Min", &fft_plot->scale_min, -160, 150);
widgets::SteppedSliderFloat("Avg Num", &fft->avg_num, 1, 500, 1);
if (ImGui::Combo("Palette", &selected_waterfall_palette, waterfall_palettes_str.c_str()))
waterfall_plot->set_palette(waterfall_palettes[selected_waterfall_palette]);
ImGui::Checkbox("Show Waterfall", &show_waterfall);
ImGui::Checkbox("Frequency Scale", &fft_plot->enable_freq_scale);
}
if (fft_plot->scale_max < fft_plot->scale_min)
{
fft_plot->scale_min = waterfall_plot->scale_min;
fft_plot->scale_max = waterfall_plot->scale_max;
}
else if (fft_plot->scale_min > fft_plot->scale_max)
{
fft_plot->scale_min = waterfall_plot->scale_min;
fft_plot->scale_max = waterfall_plot->scale_max;
}
else
{
waterfall_plot->scale_min = fft_plot->scale_min;
waterfall_plot->scale_max = fft_plot->scale_max;
}
if (ImGui::CollapsingHeader("Processing"))
{
// Settings & Selection menu
bool assume_processing = is_processing;
if (assume_processing)
style::beginDisabled();
pipeline_selector.renderSelectionBox(ImGui::GetContentRegionAvail().x);
if (!automated_live_output_dir)
pipeline_selector.drawMainparamsLive();
pipeline_selector.renderParamTable();
if (assume_processing)
style::endDisabled();
// Preset Menu
if (pipeline_selector.selected_pipeline.preset.frequencies.size() > 0)
{
if (ImGui::BeginCombo("Freq###presetscombo", pipeline_selector.selected_pipeline.preset.frequencies[pipeline_preset_id].second == frequency_hz
? pipeline_selector.selected_pipeline.preset.frequencies[pipeline_preset_id].first.c_str()
: ""))
{
for (int n = 0; n < (int)pipeline_selector.selected_pipeline.preset.frequencies.size(); n++)
{
const bool is_selected = (pipeline_preset_id == n);
if (ImGui::Selectable(pipeline_selector.selected_pipeline.preset.frequencies[n].first.c_str(), is_selected))
{
pipeline_preset_id = n;
if (pipeline_selector.selected_pipeline.preset.frequencies[pipeline_preset_id].second != 0)
{
frequency_hz = pipeline_selector.selected_pipeline.preset.frequencies[pipeline_preset_id].second;
set_frequency(frequency_hz);
}
}
if (is_selected)
ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
}
if (!assume_started)
style::beginDisabled();
bool assume_stopping_processing = is_stopping_processing;
if (!assume_processing)
{
if (ImGui::Button("Start###startprocessing"))
start_processing();
}
else if (assume_stopping_processing)
{
style::beginDisabled();
ImGui::Button("Stopping...##stoppingprocessing");
style::endDisabled();
}
else
{
if (ImGui::Button("Stop##stopprocessing"))
ui_thread_pool.push([=](int) { stop_processing(); });
}
error.draw();
if (!assume_started)
style::endDisabled();
}
if (ImGui::CollapsingHeader("Recording"))
{
bool assume_recording = is_recording;
if (assume_recording)
style::beginDisabled();
if (baseband_format.draw_record_combo("Format##basebandrecordformat"))
file_sink->set_output_sample_type(baseband_format);
if (assume_recording)
style::endDisabled();
uint64_t file_written = file_sink->get_written();
uint64_t estimated_available = 0;
if (file_written <= disk_available)
estimated_available = disk_available - file_written;
if (file_written < 1e9)
ImGui::Text("Size : %.2f MB", file_written / 1e6);
else
ImGui::Text("Size : %.2f GB", file_written / 1e9);
ImGui::Text("Free Space: %.2f GB", estimated_available / pow(1024, 3));
int timeleft;
switch (baseband_format)
{
case dsp::CF_32:
case dsp::CS_32:
timeleft = estimated_available / (8 * get_samplerate());
break;
case dsp::CS_16:
case dsp::WAV_16:
timeleft = estimated_available / (4 * get_samplerate());
break;
case dsp::CS_8:
case dsp::CU_8:
timeleft = estimated_available / (2 * get_samplerate());
break;
default:
// Silence GCC warns
timeleft = 0;
break;
}
#ifdef BUILD_ZIQ
if (baseband_format != dsp::ZIQ)
#endif
{
#ifdef BUILD_ZIQ2
if (baseband_format != dsp::ZIQ2)
#endif
{
if (is_recording && remaining_disk_space_time > timeleft && !been_warned)
{
logger->warn("!!!!WARNING - LOW AMOUNT OF FREE DISK SPACE!!!!");
been_warned = true;
}
int day = timeleft / (24 * 3600);
timeleft = timeleft % (24 * 3600);
int hour = timeleft / 3600;
timeleft %= 3600;
int minutes = timeleft / 60;
timeleft %= 60;
int seconds = timeleft;
ImGui::Text("Time left: %02d:%02d:%02d:%02d", day, hour, minutes, seconds);
}
}
#ifdef BUILD_ZIQ
if (baseband_format == dsp::ZIQ)
{
if (file_sink->get_written_raw() < 1e9)
ImGui::Text("Size (raw) : %.2f MB", file_sink->get_written_raw() / 1e6);
else
ImGui::Text("Size (raw) : %.2f GB", file_sink->get_written_raw() / 1e9);
}
#endif
ImGui::Text("File : %s", recorder_filename.c_str());
ImGui::Spacing();
if (!assume_recording)
ImGui::TextColored(style::theme.red, "IDLE");
else
ImGui::TextColored(style::theme.green, "RECORDING");
ImGui::Spacing();
if (!assume_recording)
{
if (ImGui::Button("Start###startrecording"))
start_recording();
}
else
{
if (ImGui::Button("Stop##stoprecording"))
stop_recording();
}
}
show_tracking = ImGui::CollapsingHeader("Tracking", tracking_started_cli ? ImGuiTreeNodeFlags_DefaultOpen : ImGuiTreeNodeFlags_None);
if (show_tracking)
{
try_init_tracking_widget();
tracking_widget->render();
}
if (ImGui::CollapsingHeader("VFOs"))
{
vfos_mtx.lock();
std::string to_delete = "";
if (vfo_list.size() == 0)
{
const char *no_vfo_text = "No Active VFOs";
ImGui::SetCursorPosX(ImGui::GetContentRegionAvail().x / 2 - ImGui::CalcTextSize(no_vfo_text).x / 2);
ImGui::TextDisabled("%s", no_vfo_text);
}
else
{
for (auto &vfo : vfo_list)
{
ImGui::PushStyleColor(ImGuiCol_Text, style::theme.green.Value);
ImGui::SeparatorText(vfo.name.c_str());
ImGui::PopStyleColor();
ImGui::BulletText("Frequency: %s", format_notated(vfo.freq, "Hz").c_str());
if (vfo.selected_pipeline.id != "")
{
ImGui::BulletText("Pipeline: %s", vfo.selected_pipeline.id.c_str());
ImGui::BulletText("Directory: %s", vfo.output_dir.c_str());
}
else if (vfo.file_sink)
{
if (vfo.file_sink->get_written() < 1e9)
ImGui::BulletText("IQ Size: %.2f MB", vfo.file_sink->get_written() / 1e6);
else
ImGui::BulletText("IQ Size: %.2f GB", vfo.file_sink->get_written() / 1e9);
}
ImGui::Spacing();
if (ImGui::Button(std::string("Stop##" + vfo.id).c_str(), ImVec2(ImGui::GetContentRegionAvail().x, 0)))
to_delete = vfo.id;
}
}
vfos_mtx.unlock();
if (to_delete != "")
del_vfo(to_delete);
#if 0
if (ImGui::Button("Add Test"))
{
int idp = 0;
for (int n = 0; n < (int)pipeline::pipelines.size(); n++)
{
if ("meteor_m2-x_lrpt" == pipeline::pipelines[n].id)
idp = n;
}
add_vfo_live("meteor_test", "Meteor Test", 137.9e6, pipeline::pipelines[idp], {});
}
if (ImGui::Button("Del Test"))
{
del_vfo("meteor_test");
}
#endif
}
if (ImGui::CollapsingHeader("Debug"))
{
if (constellation_debug == nullptr)
constellation_debug = new widgets::ConstellationViewer();
if (is_started)
constellation_debug->pushComplex(source_ptr->output_stream->readBuf, 256);
constellation_debug->draw();
}
eventBus->fire_event<RecorderDrawPanelEvent>({});
}
void RecorderApplication::drawContents(ImVec2 win_size)
{
float right_width = win_size.x;
float wf_size = win_size.y;
float left_width = ImGui::GetCursorPosX();
float recorder_size_x = right_width;
float _live_height = 250;
float wf_size_offset = 0;
if (is_processing && !processing_modules_floating_windows)
wf_size_offset = _live_height * ui_scale;
if (vfo_list.size() > 0)
{
wf_size_offset = 270 * ui_scale;
_live_height = 270;
}
wf_size = win_size.y - wf_size_offset; // + 13 * ui_scale;
// float wf_size_offset = 0;
// if (is_processing && !processing_modules_floating_windows)
// wf_size_offset = 250 * ui_scale;
// if (vfo_list.size() > 0)
// wf_size_offset = 270 * ui_scale;
// ImVec2 recorder_size = ImGui::GetContentRegionAvail();
// float wf_size_offset = 0;
// if (is_processing && !processing_modules_floating_windows)
// wf_size_offset = 250 * ui_scale;
// if (vfo_list.size() > 0)
// wf_size_offset = 270 * ui_scale;
// float wf_size = recorder_size.y - wf_size_offset; // + 13 * ui_scale;
{
ImGui::BeginChild("RecorderFFT", {right_width, wf_size}, false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
{
float fft_height = wf_size * (show_waterfall ? waterfall_ratio : 1.0);
float wf_height = wf_size * (1 - waterfall_ratio) + 15 * ui_scale;
float wfft_widht = right_width - 9 * ui_scale;
bool t = true;
#ifdef __ANDROID__
int offset = 8;
#else
int offset = 30;
#endif
ImGui::SetNextWindowSizeConstraints(ImVec2((right_width + offset * ui_scale), 50), ImVec2((right_width + offset * ui_scale), wf_size));
ImGui::SetNextWindowSize(ImVec2((right_width + offset * ui_scale), show_waterfall ? waterfall_ratio * wf_size : wf_size));
ImGui::SetNextWindowPos(ImVec2(left_width, 25 * ui_scale));
if (ImGui::Begin("#fft", &t,
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse))
{
// ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 2 * ui_scale);
fft_plot->draw({float(wfft_widht), fft_height});
if (show_waterfall && ImGui::IsMouseDragging(ImGuiMouseButton_Left))
waterfall_ratio = ImGui::GetWindowHeight() / wf_size;
if (ImGui::IsWindowHovered())
{
ImVec2 mouse_pos = ImGui::GetMousePos();
float ratio = (mouse_pos.x - left_width - 16 * ui_scale) / (right_width - 9 * ui_scale) - 0.5;
ImGui::SetTooltip(
"%s",
((ratio >= 0 ? "" : "- ") + format_notated(abs(ratio) * get_samplerate(), "Hz\n") + format_notated(source_ptr->get_frequency() + ratio * get_samplerate(), "Hz")).c_str());
}
ImGui::EndChild();
}
if (show_waterfall)
{
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8 * ui_scale);
ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 15 * ui_scale);
waterfall_plot->draw({wfft_widht, wf_height}, is_started);
}
}
ImGui::EndChild();
}
if (vfo_list.size() > 0)
{
if (ImGui::BeginTabBar("VFO TabBar", ImGuiTabBarFlags_None))
{
if (is_processing)
{
if (ImGui::BeginTabItem("Live Processing"))
{
float y_pos = ImGui::GetCursorPosY(); //+ 35 * ui_scale;
float live_width = recorder_size_x + 16 * ui_scale;
float live_height = _live_height * ui_scale;
float winwidth = live_pipeline->modules.size() > 0 ? live_width / live_pipeline->modules.size() : live_width;
float currentPos = ImGui::GetCursorPosX();
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, ImGui::GetStyleColorVec4(ImGuiCol_TitleBg));
for (std::shared_ptr<pipeline::ProcessingModule> &module : live_pipeline->modules)
{
ImGui::SetNextWindowPos({currentPos, y_pos});
ImGui::SetNextWindowSize({(float)winwidth, (float)live_height});
module->drawUI(false);
currentPos += winwidth;
// if (ImGui::GetCurrentContext()->last_window != NULL)
// currentPos += ImGui::GetCurrentContext()->last_window->Size.x;
// logger->info(ImGui::GetCurrentContext()->last_window->Name);
}
ImGui::PopStyleColor();
ImGui::EndTabItem();
}
}
vfos_mtx.lock();
for (auto &vfo : vfo_list)
{
if (ImGui::BeginTabItem(vfo.name.c_str()))
{
if (vfo.selected_pipeline.id != "")
{
float y_pos = ImGui::GetCursorPosY(); //+ 35 * ui_scale;
float live_width = recorder_size_x + 16 * ui_scale;
float live_height = _live_height * ui_scale;
float winwidth = vfo.live_pipeline->modules.size() > 0 ? live_width / vfo.live_pipeline->modules.size() : live_width;
float currentPos = ImGui::GetCursorPosX();
;
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, ImGui::GetStyleColorVec4(ImGuiCol_TitleBg));
for (std::shared_ptr<pipeline::ProcessingModule> &module : vfo.live_pipeline->modules)
{
ImGui::SetNextWindowPos({currentPos, y_pos});
ImGui::SetNextWindowSize({(float)winwidth, (float)live_height});
module->drawUI(false);
currentPos += winwidth;
// if (ImGui::GetCurrentContext()->last_window != NULL)
// currentPos += ImGui::GetCurrentContext()->last_window->Size.x;
// logger->info(ImGui::GetCurrentContext()->last_window->Name);
}
ImGui::PopStyleColor();
}
ImGui::EndTabItem();
}
}
vfos_mtx.unlock();
ImGui::EndTabBar();
}
}
else if (is_processing)
{
if (processing_modules_floating_windows)
{
for (std::shared_ptr<pipeline::ProcessingModule> &module : live_pipeline->modules)
module->drawUI(true);
}
else
{
float y_pos = ImGui::GetCursorPosY(); //+ 35 * ui_scale;
float live_width = recorder_size_x + 16 * ui_scale;
float live_height = _live_height * ui_scale;
float winwidth = live_pipeline->modules.size() > 0 ? live_width / live_pipeline->modules.size() : live_width;
float currentPos = ImGui::GetCursorPosX();
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, ImGui::GetStyleColorVec4(ImGuiCol_TitleBg));
for (std::shared_ptr<pipeline::ProcessingModule> &module : live_pipeline->modules)
{
ImGui::SetNextWindowPos({currentPos, y_pos});
ImGui::SetNextWindowSize({(float)winwidth, (float)live_height});
module->drawUI(false);
currentPos += winwidth;
// if (ImGui::GetCurrentContext()->last_window != NULL)
// currentPos += ImGui::GetCurrentContext()->last_window->Size.x;
// logger->info(ImGui::GetCurrentContext()->last_window->Name);
}
ImGui::PopStyleColor();
}
}
// Keyboard shortcuts
{
// FFT
if (ImGui::IsKeyDown(ImGuiKey_ReservedForModShift) && ImGui::IsKeyDown(ImGuiKey_X))
{
if (ImGui::IsKeyDown(ImGuiKey_DownArrow))
fft_plot->scale_max -= 0.5;
else if (ImGui::IsKeyDown(ImGuiKey_UpArrow))
fft_plot->scale_max += 0.5;
else if (ImGui::IsKeyDown(ImGuiKey_LeftArrow))
fft_plot->scale_min -= 0.5;
else if (ImGui::IsKeyDown(ImGuiKey_RightArrow))
fft_plot->scale_min += 0.5;
#if 0
else if (ImGui::IsKeyDown(ImGuiKey_PageUp))
fft->avg_rate += 0.001;
else if (ImGui::IsKeyDown(ImGuiKey_PageDown))
fft->avg_rate -= 0.001;
if (fft->avg_rate > 0.99)
fft->avg_rate = 0.99;
else if (fft->avg_rate < 0.01)
fft->avg_rate = 0.01;
#endif
}
}
}
}; // namespace satdump