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