Trace View: Allow context menu to show basic options in empty area
[pulseview.git] / pv / data / mathsignal.cpp
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
26 #include <extdef.h>
27 #include <pv/globalsettings.hpp>
28 #include <pv/session.hpp>
29 #include <pv/data/analogsegment.hpp>
30 #include <pv/data/signalbase.hpp>
31
32 using std::dynamic_pointer_cast;
33 using std::make_shared;
34 using std::min;
35 using std::unique_lock;
36
37 namespace pv {
38 namespace data {
39
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
46 const int64_t MathSignal::ChunkLength = 256 * 1024;
47
48
49 template<typename T>
50 struct fnc_sample : public exprtk::igeneric_function<T>
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
57         fnc_sample(MathSignal& owner) :
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);
70                 const double sample_num = exprtk_sample_num();
71
72                 if (sample_num < 0)
73                         return 0;
74
75                 if (!sig_data)
76                         sig_data = owner_.signal_from_name(str_sig_name);
77
78                 if (!sig_data)
79                         // There doesn't actually exist a signal with that name
80                         return 0;
81
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
93 MathSignal::MathSignal(pv::Session &session) :
94         SignalBase(nullptr, SignalBase::MathChannel),
95         session_(session),
96         use_custom_sample_rate_(false),
97         use_custom_sample_count_(false),
98         expression_(""),
99         error_type_(MATH_ERR_NONE),
100         exprtk_unknown_symbol_table_(nullptr),
101         exprtk_symbol_table_(nullptr),
102         exprtk_expression_(nullptr),
103         exprtk_parser_(nullptr),
104         fnc_sample_(nullptr)
105 {
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)]);
109
110         set_data(std::make_shared<data::Analog>());
111
112         connect(&session_, SIGNAL(capture_state_changed(int)),
113                 this, SLOT(on_capture_state_changed(int)));
114 }
115
116 MathSignal::~MathSignal()
117 {
118         reset_generation();
119 }
120
121 void MathSignal::save_settings(QSettings &settings) const
122 {
123         SignalBase::save_settings(settings);
124
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_);
131 }
132
133 void MathSignal::restore_settings(QSettings &settings)
134 {
135         SignalBase::restore_settings(settings);
136
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
153 QString MathSignal::get_expression() const
154 {
155         return expression_;
156 }
157
158 void MathSignal::set_expression(QString expression)
159 {
160         expression_ = expression;
161
162         begin_generation();
163 }
164
165 void MathSignal::set_error(uint8_t type, QString msg)
166 {
167         error_type_ = type;
168         error_message_ = msg;
169         // TODO Emulate noquote()
170         qDebug().nospace() << name() << ": " << msg << "(Expression: '" + expression_ + "')";
171
172         error_message_changed(msg);
173 }
174
175 uint64_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) {
188                         for (auto input_signal : input_signals_) {
189                                 const shared_ptr<SignalBase>& sb = input_signal.second.sb;
190
191                                 shared_ptr<Analog> a = sb->analog_data();
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)
201                                         continue;
202
203                                 const shared_ptr<AnalogSegment> segment = analog_segments.at(segment_id);
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
213 void MathSignal::update_completeness(uint32_t segment_id, uint64_t output_sample_count)
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);
236                         if (!segment->is_complete()) {
237                                 output_complete = false;
238                                 continue;
239                         }
240
241                         if (output_sample_count < segment->get_sample_count())
242                                 output_complete = false;
243                 }
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;
249         }
250
251         if (output_complete)
252                 analog_data()->analog_segments().at(segment_id)->set_complete();
253 }
254
255 void 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();
264         input_signals_.clear();
265
266         if (exprtk_parser_) {
267                 delete exprtk_parser_;
268                 exprtk_parser_ = nullptr;
269         }
270
271         if (exprtk_expression_) {
272                 delete exprtk_expression_;
273                 exprtk_expression_ = nullptr;
274         }
275
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
286         if (fnc_sample_) {
287                 delete fnc_sample_;
288                 fnc_sample_ = nullptr;
289         }
290
291         if (!error_message_.isEmpty()) {
292                 error_message_.clear();
293                 error_type_ = MATH_ERR_NONE;
294                 // TODO Emulate noquote()
295                 qDebug().nospace() << name() << ": Error cleared";
296         }
297
298         generation_chunk_size_ = ChunkLength;
299 }
300
301 void MathSignal::begin_generation()
302 {
303         reset_generation();
304
305         if (expression_.isEmpty()) {
306                 set_error(MATH_ERR_EMPTY_EXPR, tr("No expression defined, nothing to do"));
307                 return;
308         }
309
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         }
317
318         fnc_sample_ = new fnc_sample<double>(*this);
319
320         exprtk_unknown_symbol_table_ = new exprtk::symbol_table<double>();
321
322         exprtk_symbol_table_ = new exprtk::symbol_table<double>();
323         exprtk_symbol_table_->add_constant("T", 1 / session_.get_samplerate());
324         exprtk_symbol_table_->add_function("sample", *fnc_sample_);
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>();
330         exprtk_expression_->register_symbol_table(*exprtk_unknown_symbol_table_);
331         exprtk_expression_->register_symbol_table(*exprtk_symbol_table_);
332
333         exprtk_parser_ = new exprtk::parser<double>();
334         exprtk_parser_->enable_unknown_symbol_resolver();
335
336         if (!exprtk_parser_->compile(expression_.toStdString(), *exprtk_expression_)) {
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                 }
355                 set_error(MATH_ERR_EXPRESSION, error_details);
356         } else {
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())) {
364                                 set_error(MATH_ERR_INVALID_SIGNAL, QString(tr("\"%1\" isn't a valid analog signal")) \
365                                         .arg(QString::fromStdString(unknown)));
366                         } else
367                                 sig_data->ref = &(exprtk_unknown_symbol_table_->variable_ref(unknown));
368                 }
369         }
370
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
376         if (error_message_.isEmpty()) {
377                 // Connect to the session data notification if we have no input signals
378                 if (input_signals_.empty())
379                         connect(&session_, SIGNAL(data_received()),     this, SLOT(on_data_received()));
380
381                 gen_interrupt_ = false;
382                 gen_thread_ = std::thread(&MathSignal::generation_proc, this);
383         }
384 }
385
386 uint64_t MathSignal::generate_samples(uint32_t segment_id, const uint64_t start_sample,
387         const int64_t sample_count)
388 {
389         uint64_t count = 0;
390
391         shared_ptr<Analog> analog = dynamic_pointer_cast<Analog>(data_);
392         shared_ptr<AnalogSegment> segment = analog->analog_segments().at(segment_id);
393
394         // Keep the math functions segment IDs in sync
395         fnc_sample_->current_segment = segment_id;
396
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++) {
404                 exprtk_current_time_ = exprtk_current_sample_ / sample_rate;
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
411                 double value = exprtk_expression_->value();
412                 sample_data[i] = value;
413                 exprtk_current_sample_ += 1;
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;
421         }
422
423         segment->append_interleaved_samples(sample_data, count, 1);
424
425         delete[] sample_data;
426
427         return count;
428 }
429
430 void MathSignal::generation_proc()
431 {
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
448         uint32_t segment_id = 0;
449         shared_ptr<Analog> analog = analog_data();
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) {
467                         uint64_t processed_samples = 0;
468                         do {
469                                 const uint64_t start_sample = output_sample_count + processed_samples;
470                                 uint64_t sample_count =
471                                         min(samples_to_process - processed_samples,     generation_chunk_size_);
472
473                                 sample_count = generate_samples(segment_id, start_sample, sample_count);
474                                 processed_samples += sample_count;
475
476                                 // Notify consumers of this signal's data
477                                 samples_added(segment_id, start_sample, start_sample + processed_samples);
478                         } while (!gen_interrupt_ && (processed_samples < samples_to_process));
479                 }
480
481                 update_completeness(segment_id, output_sample_count);
482
483                 if (output_segment->is_complete() && (segment_id < session_.get_highest_segment_id())) {
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);
490                 }
491
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                 }
497         } while (!gen_interrupt_);
498 }
499
500 signal_data* MathSignal::signal_from_name(const std::string& name)
501 {
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
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();
516
517                 for (const shared_ptr<SignalBase>& sb : signalbases)
518                         if (sb->name() == sig_name) {
519                                 if (!sb->analog_data())
520                                         continue;
521
522                                 connect(sb->analog_data().get(), SIGNAL(samples_added(SharedPtrToSegment, uint64_t, uint64_t)),
523                                         this, SLOT(on_data_received()));
524                                 connect(sb->analog_data().get(), SIGNAL(segment_completed()),
525                                         this, SLOT(on_data_received()));
526
527                                 connect(sb.get(), SIGNAL(enabled_changed(bool)),
528                                         this, SLOT(on_enabled_changed()));
529
530                                 return &(input_signals_.insert({name, signal_data(sb)}).first->second);
531                         }
532         }
533
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
539         return nullptr;
540 }
541
542 void 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;
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;
564
565         // We only have a reference if this signal is used as a scalar;
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
571 bool 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
590 void MathSignal::on_capture_state_changed(int state)
591 {
592         if (state == Session::Running)
593                 begin_generation();
594
595         // Make sure we don't miss any input samples, just in case
596         if (state == Session::Stopped)
597                 gen_input_cond_.notify_one();
598 }
599
600 void MathSignal::on_data_received()
601 {
602         gen_input_cond_.notify_one();
603 }
604
605 void 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
618 } // namespace data
619 } // namespace pv