]> sigrok.org Git - pulseview.git/blame - pv/data/mathsignal.cpp
Better segment handling in math signal and lock avoidance
[pulseview.git] / pv / data / mathsignal.cpp
CommitLineData
b0773a8a
SA
1/*
2 * This file is part of the PulseView project.
3 *
4 * Copyright (C) 2020 Soeren Apel <soeren@apelpie.net>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include <limits>
21
22#include <QDebug>
23
24#include "mathsignal.hpp"
25
516b0c41 26#include <extdef.h>
b0773a8a
SA
27#include <pv/globalsettings.hpp>
28#include <pv/session.hpp>
29#include <pv/data/analogsegment.hpp>
30#include <pv/data/signalbase.hpp>
31
4640a84e 32using std::dynamic_pointer_cast;
b0773a8a 33using std::make_shared;
4640a84e
SA
34using std::min;
35using std::unique_lock;
b0773a8a
SA
36
37namespace pv {
38namespace data {
39
40const int64_t MathSignal::ChunkLength = 256 * 1024;
41
42
3f1f6295
SA
43template<typename T>
44struct sig_sample : public exprtk::igeneric_function<T>
45{
46 typedef typename exprtk::igeneric_function<T>::parameter_list_t parameter_list_t;
47 typedef typename exprtk::igeneric_function<T>::generic_type generic_type;
48 typedef typename generic_type::scalar_view scalar_t;
49 typedef typename generic_type::string_view string_t;
50
51 sig_sample(MathSignal& owner) :
52 exprtk::igeneric_function<T>("ST"), // Require channel name and sample number
53 owner_(owner),
54 sig_data(nullptr)
55 {
56 }
57
58 T operator()(parameter_list_t parameters)
59 {
60 const string_t exprtk_sig_name = string_t(parameters[0]);
61 const scalar_t exprtk_sample_num = scalar_t(parameters[1]);
62
63 const std::string str_sig_name = to_str(exprtk_sig_name);
64 const double sample_num = exprtk_sample_num();
65
66 if (!sig_data)
67 sig_data = owner_.signal_from_name(str_sig_name);
68
69 assert(sig_data);
70 owner_.update_signal_sample(sig_data, current_segment, sample_num);
71
72 return T(sig_data->sample_value);
73 }
74
75 MathSignal& owner_;
76 uint32_t current_segment;
77 signal_data* sig_data;
78};
79
80
b0773a8a
SA
81MathSignal::MathSignal(pv::Session &session) :
82 SignalBase(nullptr, SignalBase::MathChannel),
4640a84e
SA
83 session_(session),
84 use_custom_sample_rate_(false),
85 use_custom_sample_count_(false),
86 expression_(""),
87 error_message_(""),
3f1f6295 88 exprtk_unknown_symbol_table_(nullptr),
4640a84e
SA
89 exprtk_symbol_table_(nullptr),
90 exprtk_expression_(nullptr),
3f1f6295
SA
91 exprtk_parser_(nullptr),
92 fnc_sig_sample_(nullptr)
b0773a8a 93{
516b0c41
SA
94 uint32_t sig_idx = session_.get_next_signal_index(MathChannel);
95 set_name(QString(tr("Math%1")).arg(sig_idx));
96 set_color(AnalogSignalColors[(sig_idx - 1) % countof(AnalogSignalColors)]);
4640a84e 97
5eb9d1c4 98 set_data(std::make_shared<data::Analog>());
b0773a8a 99
4640a84e
SA
100 connect(&session_, SIGNAL(capture_state_changed(int)),
101 this, SLOT(on_capture_state_changed(int)));
102 connect(&session_, SIGNAL(data_received()),
103 this, SLOT(on_data_received()));
b0773a8a
SA
104}
105
106MathSignal::~MathSignal()
107{
4640a84e 108 reset_generation();
3f1f6295
SA
109
110 if (fnc_sig_sample_)
111 delete fnc_sig_sample_;
b0773a8a
SA
112}
113
114void MathSignal::save_settings(QSettings &settings) const
115{
4640a84e
SA
116 settings.setValue("expression", expression_);
117
118 settings.setValue("custom_sample_rate", (qulonglong)custom_sample_rate_);
119 settings.setValue("custom_sample_count", (qulonglong)custom_sample_count_);
120 settings.setValue("use_custom_sample_rate", use_custom_sample_rate_);
121 settings.setValue("use_custom_sample_count", use_custom_sample_count_);
b0773a8a
SA
122}
123
124void MathSignal::restore_settings(QSettings &settings)
125{
4640a84e
SA
126 if (settings.contains("expression"))
127 expression_ = settings.value("expression").toString();
128
129 if (settings.contains("custom_sample_rate"))
130 custom_sample_rate_ = settings.value("custom_sample_rate").toULongLong();
131
132 if (settings.contains("custom_sample_count"))
133 custom_sample_count_ = settings.value("custom_sample_count").toULongLong();
134
135 if (settings.contains("use_custom_sample_rate"))
136 use_custom_sample_rate_ = settings.value("use_custom_sample_rate").toBool();
137
138 if (settings.contains("use_custom_sample_count"))
139 use_custom_sample_count_ = settings.value("use_custom_sample_count").toBool();
140}
141
142QString MathSignal::error_message() const
143{
144 return error_message_;
145}
146
147QString MathSignal::get_expression() const
148{
149 return expression_;
150}
151
152void MathSignal::set_expression(QString expression)
153{
154 expression_ = expression;
155
156 begin_generation();
157}
158
159void MathSignal::set_error_message(QString msg)
160{
161 error_message_ = msg;
162 // TODO Emulate noquote()
d203009f 163 qDebug().nospace() << name() << ": " << msg << "(Expression: '" << expression_ << "')";
4640a84e
SA
164}
165
166uint64_t MathSignal::get_working_sample_count(uint32_t segment_id) const
167{
168 // The working sample count is the highest sample number for
169 // which all used signals have data available, so go through all
170 // channels and use the lowest overall sample count of the segment
171
172 int64_t result = std::numeric_limits<int64_t>::max();
173
174 if (use_custom_sample_count_)
175 // A custom sample count implies that only one segment will be created
176 result = (segment_id == 0) ? custom_sample_count_ : 0;
177 else {
178 if (input_signals_.size() > 0) {
3f1f6295
SA
179 for (auto input_signal : input_signals_) {
180 const shared_ptr<SignalBase>& sb = input_signal.second.sb;
181
4640a84e 182 shared_ptr<Analog> a = sb->analog_data();
bee54d9e
SA
183 auto analog_segments = a->analog_segments();
184
185 if (analog_segments.size() == 0) {
186 result = 0;
187 continue;
188 }
189
190 const uint32_t highest_segment_id = (analog_segments.size() - 1);
191 if (segment_id > highest_segment_id)
4640a84e 192 continue;
bee54d9e
SA
193
194 const shared_ptr<AnalogSegment> segment = analog_segments.at(segment_id);
4640a84e
SA
195 result = min(result, (int64_t)segment->get_sample_count());
196 }
197 } else
198 result = session_.get_segment_sample_count(segment_id);
199 }
200
201 return result;
202}
203
bee54d9e
SA
204void MathSignal::update_completeness(uint32_t segment_id)
205{
206 bool output_complete = true;
207
208 if (input_signals_.size() > 0) {
209 for (auto input_signal : input_signals_) {
210 const shared_ptr<SignalBase>& sb = input_signal.second.sb;
211
212 shared_ptr<Analog> a = sb->analog_data();
213 auto analog_segments = a->analog_segments();
214
215 if (analog_segments.size() == 0) {
216 output_complete = false;
217 continue;
218 }
219
220 const uint32_t highest_segment_id = (analog_segments.size() - 1);
221 if (segment_id > highest_segment_id) {
222 output_complete = false;
223 continue;
224 }
225
226 const shared_ptr<AnalogSegment> segment = analog_segments.at(segment_id);
227 if (!segment->is_complete())
228 output_complete = false;
229 }
230 }
231
232 if (output_complete)
233 analog_data()->analog_segments().at(segment_id)->set_complete();
234}
235
4640a84e
SA
236void MathSignal::reset_generation()
237{
238 if (gen_thread_.joinable()) {
239 gen_interrupt_ = true;
240 gen_input_cond_.notify_one();
241 gen_thread_.join();
242 }
243
244 data_->clear();
3f1f6295 245 input_signals_.clear();
4640a84e 246
3f1f6295
SA
247 if (exprtk_parser_) {
248 delete exprtk_parser_;
249 exprtk_parser_ = nullptr;
4640a84e
SA
250 }
251
252 if (exprtk_expression_) {
253 delete exprtk_expression_;
254 exprtk_expression_ = nullptr;
255 }
256
3f1f6295
SA
257 if (exprtk_symbol_table_) {
258 delete exprtk_symbol_table_;
259 exprtk_symbol_table_ = nullptr;
260 }
261
262 if (exprtk_unknown_symbol_table_) {
263 delete exprtk_unknown_symbol_table_;
264 exprtk_unknown_symbol_table_ = nullptr;
265 }
266
267 if (fnc_sig_sample_) {
268 delete fnc_sig_sample_;
269 fnc_sig_sample_ = nullptr;
4640a84e
SA
270 }
271
272 if (!error_message_.isEmpty()) {
273 error_message_ = QString();
274 // TODO Emulate noquote()
275 qDebug().nospace() << name() << ": Error cleared";
276 }
277}
278
279void MathSignal::begin_generation()
280{
281 reset_generation();
282
283 if (expression_.isEmpty()) {
284 set_error_message(tr("No expression defined, nothing to do"));
285 return;
286 }
287
3f1f6295
SA
288 fnc_sig_sample_ = new sig_sample<double>(*this);
289
290 exprtk_unknown_symbol_table_ = new exprtk::symbol_table<double>();
291
4640a84e 292 exprtk_symbol_table_ = new exprtk::symbol_table<double>();
3f1f6295 293 exprtk_symbol_table_->add_function("sig_sample", *fnc_sig_sample_);
4640a84e
SA
294 exprtk_symbol_table_->add_variable("t", exprtk_current_time_);
295 exprtk_symbol_table_->add_variable("s", exprtk_current_sample_);
296 exprtk_symbol_table_->add_constants();
297
298 exprtk_expression_ = new exprtk::expression<double>();
3f1f6295 299 exprtk_expression_->register_symbol_table(*exprtk_unknown_symbol_table_);
4640a84e
SA
300 exprtk_expression_->register_symbol_table(*exprtk_symbol_table_);
301
302 exprtk_parser_ = new exprtk::parser<double>();
3f1f6295
SA
303 exprtk_parser_->enable_unknown_symbol_resolver();
304
d203009f 305 if (!exprtk_parser_->compile(expression_.toStdString(), *exprtk_expression_)) {
3f1f6295 306 set_error_message(tr("Error in expression"));
d203009f 307 } else {
3f1f6295
SA
308 // Resolve unknown scalars to signals and add them to the input signal list
309 vector<string> unknowns;
310 exprtk_unknown_symbol_table_->get_variable_list(unknowns);
311 for (string& unknown : unknowns) {
312 signal_data* sig_data = signal_from_name(unknown);
313 const shared_ptr<SignalBase> signal = (sig_data) ? (sig_data->sb) : nullptr;
314 if (!signal || (!signal->analog_data())) {
bee54d9e 315 set_error_message(QString(tr("%1 isn't a valid analog signal")).arg(
3f1f6295
SA
316 QString::fromStdString(unknown)));
317 } else
318 sig_data->ref = &(exprtk_unknown_symbol_table_->variable_ref(unknown));
319 }
320 }
321
322 if (error_message_.isEmpty()) {
d203009f
SA
323 gen_interrupt_ = false;
324 gen_thread_ = std::thread(&MathSignal::generation_proc, this);
325 }
4640a84e
SA
326}
327
328void MathSignal::generate_samples(uint32_t segment_id, const uint64_t start_sample,
329 const int64_t sample_count)
330{
331 shared_ptr<Analog> analog = dynamic_pointer_cast<Analog>(data_);
332 shared_ptr<AnalogSegment> segment = analog->analog_segments().at(segment_id);
333
3f1f6295
SA
334 // Keep the math functions segment IDs in sync
335 fnc_sig_sample_->current_segment = segment_id;
336
4640a84e
SA
337 const double sample_rate = data_->get_samplerate();
338
339 exprtk_current_sample_ = start_sample;
340
341 float *sample_data = new float[sample_count];
342
343 for (int64_t i = 0; i < sample_count; i++) {
4640a84e 344 exprtk_current_time_ = exprtk_current_sample_ / sample_rate;
3f1f6295
SA
345
346 for (auto& entry : input_signals_) {
347 signal_data* sig_data = &(entry.second);
348 update_signal_sample(sig_data, segment_id, exprtk_current_sample_);
349 }
350
4640a84e
SA
351 double value = exprtk_expression_->value();
352 sample_data[i] = value;
3f1f6295 353 exprtk_current_sample_ += 1;
4640a84e
SA
354 }
355
356 segment->append_interleaved_samples(sample_data, sample_count, 1);
357
358 delete[] sample_data;
359}
360
361void MathSignal::generation_proc()
362{
363 uint32_t segment_id = 0;
364
365 // Don't do anything until we have a valid sample rate
366 do {
367 if (use_custom_sample_rate_)
368 data_->set_samplerate(custom_sample_rate_);
369 else
370 data_->set_samplerate(session_.get_samplerate());
371
372 if (data_->get_samplerate() == 1) {
373 unique_lock<mutex> gen_input_lock(input_mutex_);
374 gen_input_cond_.wait(gen_input_lock);
375 }
376 } while ((!gen_interrupt_) && (data_->get_samplerate() == 1));
377
378 if (gen_interrupt_)
379 return;
380
5eb9d1c4 381 shared_ptr<Analog> analog = analog_data();
4640a84e
SA
382
383 // Create initial analog segment
384 shared_ptr<AnalogSegment> output_segment =
385 make_shared<AnalogSegment>(*analog.get(), segment_id, analog->get_samplerate());
386 analog->push_segment(output_segment);
387
388 // Create analog samples
389 do {
390 const uint64_t input_sample_count = get_working_sample_count(segment_id);
391 const uint64_t output_sample_count = output_segment->get_sample_count();
392
393 const uint64_t samples_to_process =
394 (input_sample_count > output_sample_count) ?
395 (input_sample_count - output_sample_count) : 0;
396
397 // Process the samples if necessary...
398 if (samples_to_process > 0) {
399 const uint64_t chunk_sample_count = ChunkLength;
400
401 uint64_t processed_samples = 0;
402 do {
403 const uint64_t start_sample = output_sample_count + processed_samples;
404 const uint64_t sample_count =
405 min(samples_to_process - processed_samples, chunk_sample_count);
406
407 generate_samples(segment_id, start_sample, sample_count);
408 processed_samples += sample_count;
409
410 // Notify consumers of this signal's data
411 // TODO Does this work when a conversion is active?
412 samples_added(segment_id, start_sample, start_sample + processed_samples);
413 } while (!gen_interrupt_ && (processed_samples < samples_to_process));
414 }
415
416 if (samples_to_process == 0) {
bee54d9e 417 update_completeness(segment_id);
5eb9d1c4 418
bee54d9e 419 if (segment_id < session_.get_highest_segment_id()) {
4640a84e
SA
420 // Process next segment
421 segment_id++;
422
423 output_segment =
424 make_shared<AnalogSegment>(*analog.get(), segment_id, analog->get_samplerate());
425 analog->push_segment(output_segment);
426 } else {
427 // All segments have been processed, wait for more input
428 unique_lock<mutex> gen_input_lock(input_mutex_);
429 gen_input_cond_.wait(gen_input_lock);
430 }
431 }
432
433 } while (!gen_interrupt_);
434}
435
3f1f6295
SA
436signal_data* MathSignal::signal_from_name(const std::string& name)
437{
438 // Look up signal in the map and if it doesn't exist yet, add it for future use
439
440 auto element = input_signals_.find(name);
441
442 if (element != input_signals_.end()) {
443 return &(element->second);
444 } else {
445 const vector< shared_ptr<SignalBase> > signalbases = session_.signalbases();
446 const QString sig_name = QString::fromStdString(name);
447
448 for (const shared_ptr<SignalBase>& sb : signalbases)
bee54d9e
SA
449 if (sb->name() == sig_name) {
450 if (!sb->analog_data())
451 continue;
452
453 connect(sb->analog_data().get(), SIGNAL(segment_completed()),
454 this, SLOT(on_data_received()));
455
3f1f6295 456 return &(input_signals_.insert({name, signal_data(sb)}).first->second);
bee54d9e 457 }
3f1f6295
SA
458 }
459
460 return nullptr;
461}
462
463void MathSignal::update_signal_sample(signal_data* sig_data, uint32_t segment_id, uint64_t sample_num)
464{
465 assert(sig_data);
466
467 // Update the value only if a different sample is requested
468 if (sig_data->sample_num == sample_num)
469 return;
470
471 assert(sig_data->sb);
472 const shared_ptr<pv::data::Analog> analog = sig_data->sb->analog_data();
473 assert(analog);
474
475 assert(segment_id < analog->analog_segments().size());
476
477 const shared_ptr<AnalogSegment> segment = analog->analog_segments().at(segment_id);
478
479 sig_data->sample_num = sample_num;
480 sig_data->sample_value = segment->get_sample(sample_num);
481
482 // We only have a reference if this signal is used as a scalar,
483 // if it's used by a function, it's null
484 if (sig_data->ref)
485 *(sig_data->ref) = sig_data->sample_value;
486}
487
4640a84e
SA
488void MathSignal::on_capture_state_changed(int state)
489{
490 if (state == Session::Running)
491 begin_generation();
5eb9d1c4
SA
492
493 if (state == Session::Stopped) {
bee54d9e
SA
494 // If we have input signals, we use those as the indicators
495 if (input_signals_.empty()) {
496 shared_ptr<Analog> analog = analog_data();
497 if (!analog->analog_segments().empty())
498 analog->analog_segments().back()->set_complete();
499 }
5eb9d1c4 500 }
4640a84e
SA
501}
502
503void MathSignal::on_data_received()
504{
505 gen_input_cond_.notify_one();
b0773a8a
SA
506}
507
508} // namespace data
509} // namespace pv