]> sigrok.org Git - pulseview.git/blame - pv/data/mathsignal.cpp
Restore math signals as well
[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
40const int64_t MathSignal::ChunkLength = 256 * 1024;
41
42
3f1f6295
SA
43template<typename T>
44struct 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
b0773a8a
SA
81MathSignal::MathSignal(pv::Session &session) :
82 SignalBase(nullptr, SignalBase::MathChannel),
4640a84e
SA
83 session_(session),
84 use_custom_sample_rate_(false),
85 use_custom_sample_count_(false),
86 expression_(""),
3f1f6295 87 exprtk_unknown_symbol_table_(nullptr),
4640a84e
SA
88 exprtk_symbol_table_(nullptr),
89 exprtk_expression_(nullptr),
3f1f6295
SA
90 exprtk_parser_(nullptr),
91 fnc_sig_sample_(nullptr)
b0773a8a 92{
516b0c41
SA
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)]);
4640a84e 96
5eb9d1c4 97 set_data(std::make_shared<data::Analog>());
b0773a8a 98
4640a84e
SA
99 connect(&session_, SIGNAL(capture_state_changed(int)),
100 this, SLOT(on_capture_state_changed(int)));
b0773a8a
SA
101}
102
103MathSignal::~MathSignal()
104{
4640a84e 105 reset_generation();
3f1f6295
SA
106
107 if (fnc_sig_sample_)
108 delete fnc_sig_sample_;
b0773a8a
SA
109}
110
111void MathSignal::save_settings(QSettings &settings) const
112{
79c0e045
SA
113 SignalBase::save_settings(settings);
114
4640a84e
SA
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_);
b0773a8a
SA
121}
122
123void MathSignal::restore_settings(QSettings &settings)
124{
79c0e045
SA
125 SignalBase::restore_settings(settings);
126
4640a84e
SA
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
4640a84e
SA
143QString MathSignal::get_expression() const
144{
145 return expression_;
146}
147
148void MathSignal::set_expression(QString expression)
149{
150 expression_ = expression;
151
152 begin_generation();
153}
154
155void MathSignal::set_error_message(QString msg)
156{
157 error_message_ = msg;
158 // TODO Emulate noquote()
d203009f 159 qDebug().nospace() << name() << ": " << msg << "(Expression: '" << expression_ << "')";
f6a93932
SA
160
161 error_message_changed(msg);
4640a84e
SA
162}
163
164uint64_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) {
3f1f6295
SA
177 for (auto input_signal : input_signals_) {
178 const shared_ptr<SignalBase>& sb = input_signal.second.sb;
179
4640a84e 180 shared_ptr<Analog> a = sb->analog_data();
bee54d9e
SA
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)
4640a84e 190 continue;
bee54d9e
SA
191
192 const shared_ptr<AnalogSegment> segment = analog_segments.at(segment_id);
4640a84e
SA
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
144e72c9 202void MathSignal::update_completeness(uint32_t segment_id, uint64_t output_sample_count)
bee54d9e
SA
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);
144e72c9
SA
225 if (!segment->is_complete()) {
226 output_complete = false;
227 continue;
228 }
229
230 if (output_sample_count < segment->get_sample_count())
bee54d9e
SA
231 output_complete = false;
232 }
144e72c9
SA
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;
bee54d9e
SA
238 }
239
240 if (output_complete)
241 analog_data()->analog_segments().at(segment_id)->set_complete();
242}
243
4640a84e
SA
244void 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();
3f1f6295 253 input_signals_.clear();
4640a84e 254
3f1f6295
SA
255 if (exprtk_parser_) {
256 delete exprtk_parser_;
257 exprtk_parser_ = nullptr;
4640a84e
SA
258 }
259
260 if (exprtk_expression_) {
261 delete exprtk_expression_;
262 exprtk_expression_ = nullptr;
263 }
264
3f1f6295
SA
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;
4640a84e
SA
278 }
279
280 if (!error_message_.isEmpty()) {
281 error_message_ = QString();
282 // TODO Emulate noquote()
283 qDebug().nospace() << name() << ": Error cleared";
284 }
285}
286
287void 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
144e72c9
SA
296 disconnect(this, SLOT(on_data_received()));
297
3f1f6295
SA
298 fnc_sig_sample_ = new sig_sample<double>(*this);
299
300 exprtk_unknown_symbol_table_ = new exprtk::symbol_table<double>();
301
4640a84e 302 exprtk_symbol_table_ = new exprtk::symbol_table<double>();
3f1f6295 303 exprtk_symbol_table_->add_function("sig_sample", *fnc_sig_sample_);
4640a84e
SA
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>();
3f1f6295 309 exprtk_expression_->register_symbol_table(*exprtk_unknown_symbol_table_);
4640a84e
SA
310 exprtk_expression_->register_symbol_table(*exprtk_symbol_table_);
311
312 exprtk_parser_ = new exprtk::parser<double>();
3f1f6295
SA
313 exprtk_parser_->enable_unknown_symbol_resolver();
314
d203009f 315 if (!exprtk_parser_->compile(expression_.toStdString(), *exprtk_expression_)) {
f6a93932
SA
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);
d203009f 335 } else {
3f1f6295
SA
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())) {
bee54d9e 343 set_error_message(QString(tr("%1 isn't a valid analog signal")).arg(
3f1f6295
SA
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()) {
144e72c9
SA
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
d203009f
SA
356 gen_interrupt_ = false;
357 gen_thread_ = std::thread(&MathSignal::generation_proc, this);
358 }
4640a84e
SA
359}
360
361void 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
3f1f6295
SA
367 // Keep the math functions segment IDs in sync
368 fnc_sig_sample_->current_segment = segment_id;
369
4640a84e
SA
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++) {
4640a84e 377 exprtk_current_time_ = exprtk_current_sample_ / sample_rate;
3f1f6295
SA
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
4640a84e
SA
384 double value = exprtk_expression_->value();
385 sample_data[i] = value;
3f1f6295 386 exprtk_current_sample_ += 1;
4640a84e
SA
387 }
388
389 segment->append_interleaved_samples(sample_data, sample_count, 1);
390
391 delete[] sample_data;
392}
393
394void MathSignal::generation_proc()
395{
4640a84e
SA
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
144e72c9 412 uint32_t segment_id = 0;
5eb9d1c4 413 shared_ptr<Analog> analog = analog_data();
4640a84e
SA
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
4640a84e
SA
443 samples_added(segment_id, start_sample, start_sample + processed_samples);
444 } while (!gen_interrupt_ && (processed_samples < samples_to_process));
445 }
446
144e72c9 447 update_completeness(segment_id, output_sample_count);
5eb9d1c4 448
144e72c9 449 if (output_segment->is_complete() && (segment_id < session_.get_highest_segment_id())) {
4640a84e
SA
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);
4640a84e
SA
456 }
457
144e72c9
SA
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 }
4640a84e
SA
463 } while (!gen_interrupt_);
464}
465
3f1f6295
SA
466signal_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)
bee54d9e
SA
479 if (sb->name() == sig_name) {
480 if (!sb->analog_data())
481 continue;
482
144e72c9
SA
483 connect(sb->analog_data().get(), SIGNAL(samples_added(SharedPtrToSegment, uint64_t, uint64_t)),
484 this, SLOT(on_data_received()));
bee54d9e
SA
485 connect(sb->analog_data().get(), SIGNAL(segment_completed()),
486 this, SLOT(on_data_received()));
487
3f1f6295 488 return &(input_signals_.insert({name, signal_data(sb)}).first->second);
bee54d9e 489 }
3f1f6295
SA
490 }
491
492 return nullptr;
493}
494
495void 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
144e72c9 514 // We only have a reference if this signal is used as a scalar;
3f1f6295
SA
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
4640a84e
SA
520void MathSignal::on_capture_state_changed(int state)
521{
522 if (state == Session::Running)
523 begin_generation();
5eb9d1c4 524
144e72c9
SA
525 // Make sure we don't miss any input samples, just in case
526 if (state == Session::Stopped)
527 gen_input_cond_.notify_one();
4640a84e
SA
528}
529
530void MathSignal::on_data_received()
531{
532 gen_input_cond_.notify_one();
b0773a8a
SA
533}
534
535} // namespace data
536} // namespace pv