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