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