]> sigrok.org Git - pulseview.git/blame - pv/data/mathsignal.cpp
MathSignal: Rename sig_sample() to sample()
[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
66b6f41b
SA
40#define MATH_ERR_NONE 0
41#define MATH_ERR_EMPTY_EXPR 1
42#define MATH_ERR_EXPRESSION 2
43#define MATH_ERR_INVALID_SIGNAL 3
44#define MATH_ERR_ENABLE 4
45
b0773a8a
SA
46const int64_t MathSignal::ChunkLength = 256 * 1024;
47
48
3f1f6295 49template<typename T>
36e62b17 50struct fnc_sample : public exprtk::igeneric_function<T>
3f1f6295
SA
51{
52 typedef typename exprtk::igeneric_function<T>::parameter_list_t parameter_list_t;
53 typedef typename exprtk::igeneric_function<T>::generic_type generic_type;
54 typedef typename generic_type::scalar_view scalar_t;
55 typedef typename generic_type::string_view string_t;
56
36e62b17 57 fnc_sample(MathSignal& owner) :
3f1f6295
SA
58 exprtk::igeneric_function<T>("ST"), // Require channel name and sample number
59 owner_(owner),
60 sig_data(nullptr)
61 {
62 }
63
64 T operator()(parameter_list_t parameters)
65 {
66 const string_t exprtk_sig_name = string_t(parameters[0]);
67 const scalar_t exprtk_sample_num = scalar_t(parameters[1]);
68
69 const std::string str_sig_name = to_str(exprtk_sig_name);
36e62b17 70 const double sample_num = std::max(exprtk_sample_num(), (double)0);
3f1f6295
SA
71
72 if (!sig_data)
73 sig_data = owner_.signal_from_name(str_sig_name);
74
75 assert(sig_data);
76 owner_.update_signal_sample(sig_data, current_segment, sample_num);
77
78 return T(sig_data->sample_value);
79 }
80
81 MathSignal& owner_;
82 uint32_t current_segment;
83 signal_data* sig_data;
84};
85
86
b0773a8a
SA
87MathSignal::MathSignal(pv::Session &session) :
88 SignalBase(nullptr, SignalBase::MathChannel),
4640a84e
SA
89 session_(session),
90 use_custom_sample_rate_(false),
91 use_custom_sample_count_(false),
92 expression_(""),
66b6f41b 93 error_type_(MATH_ERR_NONE),
3f1f6295 94 exprtk_unknown_symbol_table_(nullptr),
4640a84e
SA
95 exprtk_symbol_table_(nullptr),
96 exprtk_expression_(nullptr),
3f1f6295 97 exprtk_parser_(nullptr),
36e62b17 98 fnc_sample_(nullptr)
b0773a8a 99{
516b0c41
SA
100 uint32_t sig_idx = session_.get_next_signal_index(MathChannel);
101 set_name(QString(tr("Math%1")).arg(sig_idx));
102 set_color(AnalogSignalColors[(sig_idx - 1) % countof(AnalogSignalColors)]);
4640a84e 103
5eb9d1c4 104 set_data(std::make_shared<data::Analog>());
b0773a8a 105
4640a84e
SA
106 connect(&session_, SIGNAL(capture_state_changed(int)),
107 this, SLOT(on_capture_state_changed(int)));
b0773a8a
SA
108}
109
110MathSignal::~MathSignal()
111{
4640a84e 112 reset_generation();
b0773a8a
SA
113}
114
115void MathSignal::save_settings(QSettings &settings) const
116{
79c0e045
SA
117 SignalBase::save_settings(settings);
118
4640a84e
SA
119 settings.setValue("expression", expression_);
120
121 settings.setValue("custom_sample_rate", (qulonglong)custom_sample_rate_);
122 settings.setValue("custom_sample_count", (qulonglong)custom_sample_count_);
123 settings.setValue("use_custom_sample_rate", use_custom_sample_rate_);
124 settings.setValue("use_custom_sample_count", use_custom_sample_count_);
b0773a8a
SA
125}
126
127void MathSignal::restore_settings(QSettings &settings)
128{
79c0e045
SA
129 SignalBase::restore_settings(settings);
130
4640a84e
SA
131 if (settings.contains("expression"))
132 expression_ = settings.value("expression").toString();
133
134 if (settings.contains("custom_sample_rate"))
135 custom_sample_rate_ = settings.value("custom_sample_rate").toULongLong();
136
137 if (settings.contains("custom_sample_count"))
138 custom_sample_count_ = settings.value("custom_sample_count").toULongLong();
139
140 if (settings.contains("use_custom_sample_rate"))
141 use_custom_sample_rate_ = settings.value("use_custom_sample_rate").toBool();
142
143 if (settings.contains("use_custom_sample_count"))
144 use_custom_sample_count_ = settings.value("use_custom_sample_count").toBool();
145}
146
4640a84e
SA
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
66b6f41b 159void MathSignal::set_error(uint8_t type, QString msg)
4640a84e 160{
66b6f41b 161 error_type_ = type;
4640a84e
SA
162 error_message_ = msg;
163 // TODO Emulate noquote()
66b6f41b 164 qDebug().nospace() << name() << ": " << msg << "(Expression: '" + expression_ + "')";
f6a93932
SA
165
166 error_message_changed(msg);
4640a84e
SA
167}
168
169uint64_t MathSignal::get_working_sample_count(uint32_t segment_id) const
170{
171 // The working sample count is the highest sample number for
172 // which all used signals have data available, so go through all
173 // channels and use the lowest overall sample count of the segment
174
175 int64_t result = std::numeric_limits<int64_t>::max();
176
177 if (use_custom_sample_count_)
178 // A custom sample count implies that only one segment will be created
179 result = (segment_id == 0) ? custom_sample_count_ : 0;
180 else {
181 if (input_signals_.size() > 0) {
3f1f6295
SA
182 for (auto input_signal : input_signals_) {
183 const shared_ptr<SignalBase>& sb = input_signal.second.sb;
184
4640a84e 185 shared_ptr<Analog> a = sb->analog_data();
bee54d9e
SA
186 auto analog_segments = a->analog_segments();
187
188 if (analog_segments.size() == 0) {
189 result = 0;
190 continue;
191 }
192
193 const uint32_t highest_segment_id = (analog_segments.size() - 1);
194 if (segment_id > highest_segment_id)
4640a84e 195 continue;
bee54d9e
SA
196
197 const shared_ptr<AnalogSegment> segment = analog_segments.at(segment_id);
4640a84e
SA
198 result = min(result, (int64_t)segment->get_sample_count());
199 }
200 } else
201 result = session_.get_segment_sample_count(segment_id);
202 }
203
204 return result;
205}
206
144e72c9 207void MathSignal::update_completeness(uint32_t segment_id, uint64_t output_sample_count)
bee54d9e
SA
208{
209 bool output_complete = true;
210
211 if (input_signals_.size() > 0) {
212 for (auto input_signal : input_signals_) {
213 const shared_ptr<SignalBase>& sb = input_signal.second.sb;
214
215 shared_ptr<Analog> a = sb->analog_data();
216 auto analog_segments = a->analog_segments();
217
218 if (analog_segments.size() == 0) {
219 output_complete = false;
220 continue;
221 }
222
223 const uint32_t highest_segment_id = (analog_segments.size() - 1);
224 if (segment_id > highest_segment_id) {
225 output_complete = false;
226 continue;
227 }
228
229 const shared_ptr<AnalogSegment> segment = analog_segments.at(segment_id);
144e72c9
SA
230 if (!segment->is_complete()) {
231 output_complete = false;
232 continue;
233 }
234
235 if (output_sample_count < segment->get_sample_count())
bee54d9e
SA
236 output_complete = false;
237 }
144e72c9
SA
238 } else {
239 // We're done when we generated as many samples as the stopped session is long
240 if ((session_.get_capture_state() != Session::Stopped) ||
241 (output_sample_count < session_.get_segment_sample_count(segment_id)))
242 output_complete = false;
bee54d9e
SA
243 }
244
245 if (output_complete)
246 analog_data()->analog_segments().at(segment_id)->set_complete();
247}
248
4640a84e
SA
249void MathSignal::reset_generation()
250{
251 if (gen_thread_.joinable()) {
252 gen_interrupt_ = true;
253 gen_input_cond_.notify_one();
254 gen_thread_.join();
255 }
256
257 data_->clear();
3f1f6295 258 input_signals_.clear();
4640a84e 259
3f1f6295
SA
260 if (exprtk_parser_) {
261 delete exprtk_parser_;
262 exprtk_parser_ = nullptr;
4640a84e
SA
263 }
264
265 if (exprtk_expression_) {
266 delete exprtk_expression_;
267 exprtk_expression_ = nullptr;
268 }
269
3f1f6295
SA
270 if (exprtk_symbol_table_) {
271 delete exprtk_symbol_table_;
272 exprtk_symbol_table_ = nullptr;
273 }
274
275 if (exprtk_unknown_symbol_table_) {
276 delete exprtk_unknown_symbol_table_;
277 exprtk_unknown_symbol_table_ = nullptr;
278 }
279
36e62b17
SA
280 if (fnc_sample_) {
281 delete fnc_sample_;
282 fnc_sample_ = nullptr;
4640a84e
SA
283 }
284
285 if (!error_message_.isEmpty()) {
66b6f41b
SA
286 error_message_.clear();
287 error_type_ = MATH_ERR_NONE;
4640a84e
SA
288 // TODO Emulate noquote()
289 qDebug().nospace() << name() << ": Error cleared";
290 }
291}
292
293void MathSignal::begin_generation()
294{
295 reset_generation();
296
297 if (expression_.isEmpty()) {
66b6f41b 298 set_error(MATH_ERR_EMPTY_EXPR, tr("No expression defined, nothing to do"));
4640a84e
SA
299 return;
300 }
301
144e72c9 302 disconnect(this, SLOT(on_data_received()));
66b6f41b 303 disconnect(this, SLOT(on_enabled_changed()));
144e72c9 304
36e62b17 305 fnc_sample_ = new fnc_sample<double>(*this);
3f1f6295
SA
306
307 exprtk_unknown_symbol_table_ = new exprtk::symbol_table<double>();
308
4640a84e 309 exprtk_symbol_table_ = new exprtk::symbol_table<double>();
36e62b17 310 exprtk_symbol_table_->add_function("sample", *fnc_sample_);
4640a84e
SA
311 exprtk_symbol_table_->add_variable("t", exprtk_current_time_);
312 exprtk_symbol_table_->add_variable("s", exprtk_current_sample_);
313 exprtk_symbol_table_->add_constants();
314
315 exprtk_expression_ = new exprtk::expression<double>();
3f1f6295 316 exprtk_expression_->register_symbol_table(*exprtk_unknown_symbol_table_);
4640a84e
SA
317 exprtk_expression_->register_symbol_table(*exprtk_symbol_table_);
318
319 exprtk_parser_ = new exprtk::parser<double>();
3f1f6295
SA
320 exprtk_parser_->enable_unknown_symbol_resolver();
321
d203009f 322 if (!exprtk_parser_->compile(expression_.toStdString(), *exprtk_expression_)) {
f6a93932
SA
323 QString error_details;
324 size_t error_count = exprtk_parser_->error_count();
325
326 for (size_t i = 0; i < error_count; i++) {
327 typedef exprtk::parser_error::type error_t;
328 error_t error = exprtk_parser_->get_error(i);
329 exprtk::parser_error::update_error(error, expression_.toStdString());
330
331 QString error_detail = tr("%1 at line %2, column %3: %4");
332 if ((error_count > 1) && (i < (error_count - 1)))
333 error_detail += "\n";
334
335 error_details += error_detail \
336 .arg(exprtk::parser_error::to_str(error.mode).c_str()) \
337 .arg(error.line_no) \
338 .arg(error.column_no) \
339 .arg(error.diagnostic.c_str());
340 }
66b6f41b 341 set_error(MATH_ERR_EXPRESSION, error_details);
d203009f 342 } else {
3f1f6295
SA
343 // Resolve unknown scalars to signals and add them to the input signal list
344 vector<string> unknowns;
345 exprtk_unknown_symbol_table_->get_variable_list(unknowns);
346 for (string& unknown : unknowns) {
347 signal_data* sig_data = signal_from_name(unknown);
348 const shared_ptr<SignalBase> signal = (sig_data) ? (sig_data->sb) : nullptr;
349 if (!signal || (!signal->analog_data())) {
66b6f41b
SA
350 set_error(MATH_ERR_INVALID_SIGNAL, QString(tr("%1 isn't a valid analog signal")) \
351 .arg(QString::fromStdString(unknown)));
3f1f6295
SA
352 } else
353 sig_data->ref = &(exprtk_unknown_symbol_table_->variable_ref(unknown));
354 }
355 }
356
66b6f41b
SA
357 QString disabled_signals;
358 if (!all_input_signals_enabled(disabled_signals) && error_message_.isEmpty())
359 set_error(MATH_ERR_ENABLE,
360 tr("No data will be generated as %1 must be enabled").arg(disabled_signals));
361
3f1f6295 362 if (error_message_.isEmpty()) {
144e72c9
SA
363 // Connect to the session data notification if we have no input signals
364 if (input_signals_.empty())
365 connect(&session_, SIGNAL(data_received()),
366 this, SLOT(on_data_received()));
367
d203009f
SA
368 gen_interrupt_ = false;
369 gen_thread_ = std::thread(&MathSignal::generation_proc, this);
370 }
4640a84e
SA
371}
372
373void MathSignal::generate_samples(uint32_t segment_id, const uint64_t start_sample,
374 const int64_t sample_count)
375{
376 shared_ptr<Analog> analog = dynamic_pointer_cast<Analog>(data_);
377 shared_ptr<AnalogSegment> segment = analog->analog_segments().at(segment_id);
378
3f1f6295 379 // Keep the math functions segment IDs in sync
36e62b17 380 fnc_sample_->current_segment = segment_id;
3f1f6295 381
4640a84e
SA
382 const double sample_rate = data_->get_samplerate();
383
384 exprtk_current_sample_ = start_sample;
385
386 float *sample_data = new float[sample_count];
387
388 for (int64_t i = 0; i < sample_count; i++) {
4640a84e 389 exprtk_current_time_ = exprtk_current_sample_ / sample_rate;
3f1f6295
SA
390
391 for (auto& entry : input_signals_) {
392 signal_data* sig_data = &(entry.second);
393 update_signal_sample(sig_data, segment_id, exprtk_current_sample_);
394 }
395
4640a84e
SA
396 double value = exprtk_expression_->value();
397 sample_data[i] = value;
3f1f6295 398 exprtk_current_sample_ += 1;
4640a84e
SA
399 }
400
401 segment->append_interleaved_samples(sample_data, sample_count, 1);
402
403 delete[] sample_data;
404}
405
406void MathSignal::generation_proc()
407{
4640a84e
SA
408 // Don't do anything until we have a valid sample rate
409 do {
410 if (use_custom_sample_rate_)
411 data_->set_samplerate(custom_sample_rate_);
412 else
413 data_->set_samplerate(session_.get_samplerate());
414
415 if (data_->get_samplerate() == 1) {
416 unique_lock<mutex> gen_input_lock(input_mutex_);
417 gen_input_cond_.wait(gen_input_lock);
418 }
419 } while ((!gen_interrupt_) && (data_->get_samplerate() == 1));
420
421 if (gen_interrupt_)
422 return;
423
144e72c9 424 uint32_t segment_id = 0;
5eb9d1c4 425 shared_ptr<Analog> analog = analog_data();
4640a84e
SA
426
427 // Create initial analog segment
428 shared_ptr<AnalogSegment> output_segment =
429 make_shared<AnalogSegment>(*analog.get(), segment_id, analog->get_samplerate());
430 analog->push_segment(output_segment);
431
432 // Create analog samples
433 do {
434 const uint64_t input_sample_count = get_working_sample_count(segment_id);
435 const uint64_t output_sample_count = output_segment->get_sample_count();
436
437 const uint64_t samples_to_process =
438 (input_sample_count > output_sample_count) ?
439 (input_sample_count - output_sample_count) : 0;
440
441 // Process the samples if necessary...
442 if (samples_to_process > 0) {
443 const uint64_t chunk_sample_count = ChunkLength;
444
445 uint64_t processed_samples = 0;
446 do {
447 const uint64_t start_sample = output_sample_count + processed_samples;
448 const uint64_t sample_count =
449 min(samples_to_process - processed_samples, chunk_sample_count);
450
451 generate_samples(segment_id, start_sample, sample_count);
452 processed_samples += sample_count;
453
454 // Notify consumers of this signal's data
4640a84e
SA
455 samples_added(segment_id, start_sample, start_sample + processed_samples);
456 } while (!gen_interrupt_ && (processed_samples < samples_to_process));
457 }
458
144e72c9 459 update_completeness(segment_id, output_sample_count);
5eb9d1c4 460
144e72c9 461 if (output_segment->is_complete() && (segment_id < session_.get_highest_segment_id())) {
4640a84e
SA
462 // Process next segment
463 segment_id++;
464
465 output_segment =
466 make_shared<AnalogSegment>(*analog.get(), segment_id, analog->get_samplerate());
467 analog->push_segment(output_segment);
4640a84e
SA
468 }
469
144e72c9
SA
470 if (!gen_interrupt_ && (samples_to_process == 0)) {
471 // Wait for more input
472 unique_lock<mutex> gen_input_lock(input_mutex_);
473 gen_input_cond_.wait(gen_input_lock);
474 }
4640a84e
SA
475 } while (!gen_interrupt_);
476}
477
3f1f6295
SA
478signal_data* MathSignal::signal_from_name(const std::string& name)
479{
480 // Look up signal in the map and if it doesn't exist yet, add it for future use
481
482 auto element = input_signals_.find(name);
483
484 if (element != input_signals_.end()) {
485 return &(element->second);
486 } else {
487 const vector< shared_ptr<SignalBase> > signalbases = session_.signalbases();
488 const QString sig_name = QString::fromStdString(name);
489
490 for (const shared_ptr<SignalBase>& sb : signalbases)
bee54d9e
SA
491 if (sb->name() == sig_name) {
492 if (!sb->analog_data())
493 continue;
494
144e72c9
SA
495 connect(sb->analog_data().get(), SIGNAL(samples_added(SharedPtrToSegment, uint64_t, uint64_t)),
496 this, SLOT(on_data_received()));
bee54d9e
SA
497 connect(sb->analog_data().get(), SIGNAL(segment_completed()),
498 this, SLOT(on_data_received()));
499
66b6f41b
SA
500 connect(sb.get(), SIGNAL(enabled_changed(bool)),
501 this, SLOT(on_enabled_changed()));
502
3f1f6295 503 return &(input_signals_.insert({name, signal_data(sb)}).first->second);
bee54d9e 504 }
3f1f6295
SA
505 }
506
507 return nullptr;
508}
509
510void MathSignal::update_signal_sample(signal_data* sig_data, uint32_t segment_id, uint64_t sample_num)
511{
512 assert(sig_data);
513
514 // Update the value only if a different sample is requested
515 if (sig_data->sample_num == sample_num)
516 return;
517
518 assert(sig_data->sb);
519 const shared_ptr<pv::data::Analog> analog = sig_data->sb->analog_data();
520 assert(analog);
521
522 assert(segment_id < analog->analog_segments().size());
523
524 const shared_ptr<AnalogSegment> segment = analog->analog_segments().at(segment_id);
525
526 sig_data->sample_num = sample_num;
527 sig_data->sample_value = segment->get_sample(sample_num);
528
144e72c9 529 // We only have a reference if this signal is used as a scalar;
3f1f6295
SA
530 // if it's used by a function, it's null
531 if (sig_data->ref)
532 *(sig_data->ref) = sig_data->sample_value;
533}
534
66b6f41b
SA
535bool MathSignal::all_input_signals_enabled(QString &disabled_signals) const
536{
537 bool all_enabled = true;
538
539 disabled_signals.clear();
540
541 for (auto input_signal : input_signals_) {
542 const shared_ptr<SignalBase>& sb = input_signal.second.sb;
543
544 if (!sb->enabled()) {
545 all_enabled = false;
546 disabled_signals += disabled_signals.isEmpty() ?
547 sb->name() : ", " + sb->name();
548 }
549 }
550
551 return all_enabled;
552}
553
4640a84e
SA
554void MathSignal::on_capture_state_changed(int state)
555{
556 if (state == Session::Running)
557 begin_generation();
5eb9d1c4 558
144e72c9
SA
559 // Make sure we don't miss any input samples, just in case
560 if (state == Session::Stopped)
561 gen_input_cond_.notify_one();
4640a84e
SA
562}
563
564void MathSignal::on_data_received()
565{
566 gen_input_cond_.notify_one();
b0773a8a
SA
567}
568
66b6f41b
SA
569void MathSignal::on_enabled_changed()
570{
571 QString disabled_signals;
572 if (!all_input_signals_enabled(disabled_signals) &&
573 ((error_type_ == MATH_ERR_NONE) || (error_type_ == MATH_ERR_ENABLE)))
574 set_error(MATH_ERR_ENABLE,
575 tr("No data will be generated as %1 must be enabled").arg(disabled_signals));
576 else if (disabled_signals.isEmpty() && (error_type_ == MATH_ERR_ENABLE)) {
577 error_type_ = MATH_ERR_NONE;
578 error_message_.clear();
579 }
580}
581
b0773a8a
SA
582} // namespace data
583} // namespace pv