EchoMap 2026-07-25 6d3977c
An experimental cross-platform digital signal processing application for sound-source localisation.
Loading...
Searching...
No Matches
SignalWaveformPanel.cpp
Go to the documentation of this file.
1
7
9
10#include <algorithm>
11
12#include "../async/Worker.hpp"
16#include "../objects/Project.hpp"
17#include "../objects/Signal.hpp"
18#include "../utility/Logger.hpp"
19
20namespace echomap
21{
22
24 Worker* parent_worker,
25 WorkerResultDespatcher& despatcher,
26 const Project* const initial_project
27) :
29 std::numeric_limits<double>::max(),
30 std::numeric_limits<double>::lowest(),
31 Signal::normalised_range.first,
32 Signal::normalised_range.second
33 },
34 panel_name(std::string("Signal Waveform Preview") + get_imgui_stable_name()),
35 parent_worker(parent_worker),
36 active_project(initial_project)
37{
38 connections.emplace_back(despatcher.downsample_finished_channel.nominate_consumer(
39 sigc::mem_fun(*this, &SignalWaveformPanel::handle_downsampled_result)
40 ));
41}
42
43SignalWaveformPanel::~SignalWaveformPanel() noexcept = default;
44
46
47const char* SignalWaveformPanel::get_imgui_name() const noexcept
48{
49 return panel_name.c_str();
50}
51
53{
54 if (ImGui::Begin(panel_name.c_str())) {
55 if (active_project == nullptr)
56 ImGui::Text("No project is loaded.");
57 else if (ImPlot::BeginAlignedPlots("##WaveformAlignedGroup")) {
58 ImPlot::PushStyleColor(ImPlotCol_FrameBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
59 bool drawn_any = false;
60
61 for (const auto& signal : active_project->share_signals()) {
62 drawn_any = true;
63
64 if (const auto* const downsampled = get_downsampled_signal(signal); downsampled == nullptr)
65 ImGui::Text("Loading downsampled variant of %s...", signal->get_imgui_name());
66 else if (ImPlot::BeginPlot(downsampled->get_imgui_name())) {
67
68 ImPlot::SetupAxes("Time (seconds)", "Amplitude");
69 ImPlot::SetupAxisLinks(ImAxis_X1, &bounding_box.X.Min, &bounding_box.X.Max);
70 ImPlot::SetupAxisLinks(ImAxis_Y1, &bounding_box.Y.Min, &bounding_box.Y.Max);
71
72 CallbackData callback_data = {.signal = downsampled};
73 ImPlot::PlotLineG(
74 "",
75 &SignalWaveformPanel::get_indexed_signal_point,
76 &callback_data,
77 static_cast<int>(downsampled->get_sample_count()),
78 plotting_spec_2d
79 );
80
81 ImPlot::EndPlot();
82 }
83 }
84
85 ImPlot::EndAlignedPlots();
86 ImPlot::PopStyleColor();
87
88 if (!drawn_any)
89 ImGui::Text("No signals are available.");
90 }
91 }
92
93 ImGui::End();
94}
95
97 const Project* const new_project
98)
99{
100 active_project = new_project;
101 downsample_cache.clear();
102 update_bounding_box();
103}
104
105const char* SignalWaveformPanel::get_imgui_stable_name() noexcept
106{
107 return "###SignalWaveformPanel";
108}
109
110void SignalWaveformPanel::handle_downsampled_result(
111 DownsampleResult&& result
112)
113{
114 auto ds_slot_it = downsample_cache.find(result.get_source_id());
115 auto signal = std::move(result).take_downsampled();
116
117 if (ds_slot_it == downsample_cache.end()) {
118 LOG_F_WARN("Received an unexpected result for the downsampled Signal {}.", signal->get_name());
119 const auto signal_name_view = signal->get_name();
120 auto [it, success] = downsample_cache.emplace(result.get_source_id(), std::move(signal));
121
122 if (!success) {
123 LOG_F_ERROR("Could not store the downsampled Signal {} in the cache.", signal_name_view);
124 return;
125 }
126
127 ds_slot_it = it;
128 } else
129 ds_slot_it->second = std::move(signal);
130
131 // Update the bounding box for the signal graphical representation.
132 update_bounding_box(*ds_slot_it->second);
133}
134
135ImPlotPoint SignalWaveformPanel::get_indexed_signal_point(
136 const int index,
137 void* const user_data
138) noexcept
139{
140 const auto* const signal = static_cast<CallbackData*>(user_data)->signal;
141 return {signal->get_time_at_index(index), signal->begin()[index]};
142}
143
144void SignalWaveformPanel::update_bounding_box(
145 const Signal& signal
146) noexcept
147{
148 if (signal.get_sample_count() > 0) {
149 bounding_box.X.Min = std::min<double>(signal.get_time_at_index(0), bounding_box.X.Min);
150 bounding_box.X.Max =
151 std::max<double>(signal.get_time_at_index(signal.get_sample_count() - 1), bounding_box.X.Max);
152 }
153}
154
155void SignalWaveformPanel::update_bounding_box() noexcept
156{
161}
162
165)
166{
167 assert(signal != nullptr);
168 const auto downsampled_it = downsample_cache.find(signal->get_id());
169
170 if (downsampled_it == downsample_cache.end()) {
171 /*
172 * If the source signal hasn't already been downsampled and cached, submit a job to do it ASAP.
173 *
174 * The result won't be picked up on this render cycle, so return nullptr to indicate the "Loading" state, but it
175 * should come through shortly. We emplace a nullptr in the slot to indicate that the work has been requested,
176 * but not yet completed.
177 */
178
179 downsample_cache.emplace(signal->get_id(), nullptr);
180 parent_worker->submit(std::make_unique<DownsampleTask>(std::move(signal)));
181 return nullptr;
182 }
183
184 return downsampled_it->second.get();
185}
186
187} // namespace echomap
DownsampleResult specification.
DownsampleTask specification.
EchoMap portable logger specification.
#define LOG_F_WARN(msg,...)
Logs a formatted warning-level message using echomap::Logger::log_f.
Definition Logger.hpp:115
#define LOG_F_ERROR(msg,...)
Logs a formatted error-level message using echomap::Logger::log_f.
Definition Logger.hpp:125
EchoMap signal waveform preview panel specification.
Audio signal class specification.
WorkerResultDespatcher specification.
Worker specification.
The SignalWaveformPanel provides a preview of downsampled time-series waveforms of each Signal in the...
const Signal * get_downsampled_signal(std::shared_ptr< Signal > signal)
Retrieves a downsampled Signal from the cache.
const char * get_imgui_name() const noexcept override
Retrieves the name of the IPanel for Dear ImGui API functions.
SignalWaveformPanel(Worker *parent_worker, WorkerResultDespatcher &despatcher, const Project *initial_project=nullptr)
Create a new SignalWaveformPanel to display downsampled Signal waveforms in the time domain.
void draw() noexcept override
Draw the panel to the active rendering context.
ImPlotRect bounding_box
The maximum bounding box of an LTTB-downsampled wave form plot.
std::unordered_map< id_type, std::unique_ptr< Signal > > downsample_cache
The duplicated Signal objects, downsampled for visualisation.
void change_active_project(const Project *new_project) override
Updates the active Project being described by the IPanel.
A single channel of discretely sampled audio data.
Definition Signal.hpp:40
static constexpr std::pair< Sample::AmplitudeT, Sample::AmplitudeT > normalised_range
The range within which the amplitude values are normalised.
Definition Signal.hpp:74
Manages channels for WorkerResult message routing.
ResultChannel< DownsampleResult > downsample_finished_channel
Channel to indicate completion of DownsampleTask.
A Worker provides an encapsulated thread-safe despatch model for submitting work and reviewing result...
Definition Worker.hpp:45
T lowest(T... args)
T make_unique(T... args)
T max(T... args)
T min(T... args)
The main EchoMap outermost namespace for all non-exported symbols.
STL namespace.
T signal(T... args)