2 * This file is part of the PulseView project.
4 * Copyright (C) 2020 Soeren Apel <soeren@apelpie.net>
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.
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.
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/>.
24 #include "mathsignal.hpp"
27 #include <pv/globalsettings.hpp>
28 #include <pv/session.hpp>
29 #include <pv/data/analogsegment.hpp>
30 #include <pv/data/signalbase.hpp>
32 using std::dynamic_pointer_cast;
33 using std::make_shared;
35 using std::unique_lock;
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
46 const int64_t MathSignal::ChunkLength = 256 * 1024;
50 struct sig_sample : public exprtk::igeneric_function<T>
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;
57 sig_sample(MathSignal& owner) :
58 exprtk::igeneric_function<T>("ST"), // Require channel name and sample number
64 T operator()(parameter_list_t parameters)
66 const string_t exprtk_sig_name = string_t(parameters[0]);
67 const scalar_t exprtk_sample_num = scalar_t(parameters[1]);
69 const std::string str_sig_name = to_str(exprtk_sig_name);
70 const double sample_num = exprtk_sample_num();
73 sig_data = owner_.signal_from_name(str_sig_name);
76 owner_.update_signal_sample(sig_data, current_segment, sample_num);
78 return T(sig_data->sample_value);
82 uint32_t current_segment;
83 signal_data* sig_data;
87 MathSignal::MathSignal(pv::Session &session) :
88 SignalBase(nullptr, SignalBase::MathChannel),
90 use_custom_sample_rate_(false),
91 use_custom_sample_count_(false),
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)
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)]);
104 set_data(std::make_shared<data::Analog>());
106 connect(&session_, SIGNAL(capture_state_changed(int)),
107 this, SLOT(on_capture_state_changed(int)));
110 MathSignal::~MathSignal()
115 delete fnc_sig_sample_;
118 void MathSignal::save_settings(QSettings &settings) const
120 SignalBase::save_settings(settings);
122 settings.setValue("expression", expression_);
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_);
130 void MathSignal::restore_settings(QSettings &settings)
132 SignalBase::restore_settings(settings);
134 if (settings.contains("expression"))
135 expression_ = settings.value("expression").toString();
137 if (settings.contains("custom_sample_rate"))
138 custom_sample_rate_ = settings.value("custom_sample_rate").toULongLong();
140 if (settings.contains("custom_sample_count"))
141 custom_sample_count_ = settings.value("custom_sample_count").toULongLong();
143 if (settings.contains("use_custom_sample_rate"))
144 use_custom_sample_rate_ = settings.value("use_custom_sample_rate").toBool();
146 if (settings.contains("use_custom_sample_count"))
147 use_custom_sample_count_ = settings.value("use_custom_sample_count").toBool();
150 QString MathSignal::get_expression() const
155 void MathSignal::set_expression(QString expression)
157 expression_ = expression;
162 void MathSignal::set_error(uint8_t type, QString msg)
165 error_message_ = msg;
166 // TODO Emulate noquote()
167 qDebug().nospace() << name() << ": " << msg << "(Expression: '" + expression_ + "')";
169 error_message_changed(msg);
172 uint64_t MathSignal::get_working_sample_count(uint32_t segment_id) const
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
178 int64_t result = std::numeric_limits<int64_t>::max();
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;
184 if (input_signals_.size() > 0) {
185 for (auto input_signal : input_signals_) {
186 const shared_ptr<SignalBase>& sb = input_signal.second.sb;
188 shared_ptr<Analog> a = sb->analog_data();
189 auto analog_segments = a->analog_segments();
191 if (analog_segments.size() == 0) {
196 const uint32_t highest_segment_id = (analog_segments.size() - 1);
197 if (segment_id > highest_segment_id)
200 const shared_ptr<AnalogSegment> segment = analog_segments.at(segment_id);
201 result = min(result, (int64_t)segment->get_sample_count());
204 result = session_.get_segment_sample_count(segment_id);
210 void MathSignal::update_completeness(uint32_t segment_id, uint64_t output_sample_count)
212 bool output_complete = true;
214 if (input_signals_.size() > 0) {
215 for (auto input_signal : input_signals_) {
216 const shared_ptr<SignalBase>& sb = input_signal.second.sb;
218 shared_ptr<Analog> a = sb->analog_data();
219 auto analog_segments = a->analog_segments();
221 if (analog_segments.size() == 0) {
222 output_complete = false;
226 const uint32_t highest_segment_id = (analog_segments.size() - 1);
227 if (segment_id > highest_segment_id) {
228 output_complete = false;
232 const shared_ptr<AnalogSegment> segment = analog_segments.at(segment_id);
233 if (!segment->is_complete()) {
234 output_complete = false;
238 if (output_sample_count < segment->get_sample_count())
239 output_complete = false;
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;
249 analog_data()->analog_segments().at(segment_id)->set_complete();
252 void MathSignal::reset_generation()
254 if (gen_thread_.joinable()) {
255 gen_interrupt_ = true;
256 gen_input_cond_.notify_one();
261 input_signals_.clear();
263 if (exprtk_parser_) {
264 delete exprtk_parser_;
265 exprtk_parser_ = nullptr;
268 if (exprtk_expression_) {
269 delete exprtk_expression_;
270 exprtk_expression_ = nullptr;
273 if (exprtk_symbol_table_) {
274 delete exprtk_symbol_table_;
275 exprtk_symbol_table_ = nullptr;
278 if (exprtk_unknown_symbol_table_) {
279 delete exprtk_unknown_symbol_table_;
280 exprtk_unknown_symbol_table_ = nullptr;
283 if (fnc_sig_sample_) {
284 delete fnc_sig_sample_;
285 fnc_sig_sample_ = nullptr;
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";
296 void MathSignal::begin_generation()
300 if (expression_.isEmpty()) {
301 set_error(MATH_ERR_EMPTY_EXPR, tr("No expression defined, nothing to do"));
305 disconnect(this, SLOT(on_data_received()));
306 disconnect(this, SLOT(on_enabled_changed()));
308 fnc_sig_sample_ = new sig_sample<double>(*this);
310 exprtk_unknown_symbol_table_ = new exprtk::symbol_table<double>();
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();
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_);
322 exprtk_parser_ = new exprtk::parser<double>();
323 exprtk_parser_->enable_unknown_symbol_resolver();
325 if (!exprtk_parser_->compile(expression_.toStdString(), *exprtk_expression_)) {
326 QString error_details;
327 size_t error_count = exprtk_parser_->error_count();
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());
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";
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());
344 set_error(MATH_ERR_EXPRESSION, error_details);
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)));
356 sig_data->ref = &(exprtk_unknown_symbol_table_->variable_ref(unknown));
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));
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()));
371 gen_interrupt_ = false;
372 gen_thread_ = std::thread(&MathSignal::generation_proc, this);
376 void MathSignal::generate_samples(uint32_t segment_id, const uint64_t start_sample,
377 const int64_t sample_count)
379 shared_ptr<Analog> analog = dynamic_pointer_cast<Analog>(data_);
380 shared_ptr<AnalogSegment> segment = analog->analog_segments().at(segment_id);
382 // Keep the math functions segment IDs in sync
383 fnc_sig_sample_->current_segment = segment_id;
385 const double sample_rate = data_->get_samplerate();
387 exprtk_current_sample_ = start_sample;
389 float *sample_data = new float[sample_count];
391 for (int64_t i = 0; i < sample_count; i++) {
392 exprtk_current_time_ = exprtk_current_sample_ / sample_rate;
394 for (auto& entry : input_signals_) {
395 signal_data* sig_data = &(entry.second);
396 update_signal_sample(sig_data, segment_id, exprtk_current_sample_);
399 double value = exprtk_expression_->value();
400 sample_data[i] = value;
401 exprtk_current_sample_ += 1;
404 segment->append_interleaved_samples(sample_data, sample_count, 1);
406 delete[] sample_data;
409 void MathSignal::generation_proc()
411 // Don't do anything until we have a valid sample rate
413 if (use_custom_sample_rate_)
414 data_->set_samplerate(custom_sample_rate_);
416 data_->set_samplerate(session_.get_samplerate());
418 if (data_->get_samplerate() == 1) {
419 unique_lock<mutex> gen_input_lock(input_mutex_);
420 gen_input_cond_.wait(gen_input_lock);
422 } while ((!gen_interrupt_) && (data_->get_samplerate() == 1));
427 uint32_t segment_id = 0;
428 shared_ptr<Analog> analog = analog_data();
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);
435 // Create analog samples
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();
440 const uint64_t samples_to_process =
441 (input_sample_count > output_sample_count) ?
442 (input_sample_count - output_sample_count) : 0;
444 // Process the samples if necessary...
445 if (samples_to_process > 0) {
446 const uint64_t chunk_sample_count = ChunkLength;
448 uint64_t processed_samples = 0;
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);
454 generate_samples(segment_id, start_sample, sample_count);
455 processed_samples += sample_count;
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));
462 update_completeness(segment_id, output_sample_count);
464 if (output_segment->is_complete() && (segment_id < session_.get_highest_segment_id())) {
465 // Process next segment
469 make_shared<AnalogSegment>(*analog.get(), segment_id, analog->get_samplerate());
470 analog->push_segment(output_segment);
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);
478 } while (!gen_interrupt_);
481 signal_data* MathSignal::signal_from_name(const std::string& name)
483 // Look up signal in the map and if it doesn't exist yet, add it for future use
485 auto element = input_signals_.find(name);
487 if (element != input_signals_.end()) {
488 return &(element->second);
490 const vector< shared_ptr<SignalBase> > signalbases = session_.signalbases();
491 const QString sig_name = QString::fromStdString(name);
493 for (const shared_ptr<SignalBase>& sb : signalbases)
494 if (sb->name() == sig_name) {
495 if (!sb->analog_data())
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()));
503 connect(sb.get(), SIGNAL(enabled_changed(bool)),
504 this, SLOT(on_enabled_changed()));
506 return &(input_signals_.insert({name, signal_data(sb)}).first->second);
513 void MathSignal::update_signal_sample(signal_data* sig_data, uint32_t segment_id, uint64_t sample_num)
517 // Update the value only if a different sample is requested
518 if (sig_data->sample_num == sample_num)
521 assert(sig_data->sb);
522 const shared_ptr<pv::data::Analog> analog = sig_data->sb->analog_data();
525 assert(segment_id < analog->analog_segments().size());
527 const shared_ptr<AnalogSegment> segment = analog->analog_segments().at(segment_id);
529 sig_data->sample_num = sample_num;
530 sig_data->sample_value = segment->get_sample(sample_num);
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
535 *(sig_data->ref) = sig_data->sample_value;
538 bool MathSignal::all_input_signals_enabled(QString &disabled_signals) const
540 bool all_enabled = true;
542 disabled_signals.clear();
544 for (auto input_signal : input_signals_) {
545 const shared_ptr<SignalBase>& sb = input_signal.second.sb;
547 if (!sb->enabled()) {
549 disabled_signals += disabled_signals.isEmpty() ?
550 sb->name() : ", " + sb->name();
557 void MathSignal::on_capture_state_changed(int state)
559 if (state == Session::Running)
562 // Make sure we don't miss any input samples, just in case
563 if (state == Session::Stopped)
564 gen_input_cond_.notify_one();
567 void MathSignal::on_data_received()
569 gen_input_cond_.notify_one();
572 void MathSignal::on_enabled_changed()
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();