From: Soeren Apel Date: Sat, 15 Aug 2020 22:11:14 +0000 (+0200) Subject: MathSignal: Allow use of other signals X-Git-Url: https://sigrok.org/gitaction?a=commitdiff_plain;h=3f1f6295935c875373d33cd23b0c8663a6788103;p=pulseview.git MathSignal: Allow use of other signals --- diff --git a/pv/data/mathsignal.cpp b/pv/data/mathsignal.cpp index 97e312bc..20a41a4c 100644 --- a/pv/data/mathsignal.cpp +++ b/pv/data/mathsignal.cpp @@ -39,6 +39,44 @@ namespace data { const int64_t MathSignal::ChunkLength = 256 * 1024; +template +struct sig_sample : public exprtk::igeneric_function +{ + typedef typename exprtk::igeneric_function::parameter_list_t parameter_list_t; + typedef typename exprtk::igeneric_function::generic_type generic_type; + typedef typename generic_type::scalar_view scalar_t; + typedef typename generic_type::string_view string_t; + + sig_sample(MathSignal& owner) : + exprtk::igeneric_function("ST"), // Require channel name and sample number + owner_(owner), + sig_data(nullptr) + { + } + + T operator()(parameter_list_t parameters) + { + const string_t exprtk_sig_name = string_t(parameters[0]); + const scalar_t exprtk_sample_num = scalar_t(parameters[1]); + + const std::string str_sig_name = to_str(exprtk_sig_name); + const double sample_num = exprtk_sample_num(); + + if (!sig_data) + sig_data = owner_.signal_from_name(str_sig_name); + + assert(sig_data); + owner_.update_signal_sample(sig_data, current_segment, sample_num); + + return T(sig_data->sample_value); + } + + MathSignal& owner_; + uint32_t current_segment; + signal_data* sig_data; +}; + + MathSignal::MathSignal(pv::Session &session) : SignalBase(nullptr, SignalBase::MathChannel), session_(session), @@ -46,9 +84,11 @@ MathSignal::MathSignal(pv::Session &session) : use_custom_sample_count_(false), expression_(""), error_message_(""), + exprtk_unknown_symbol_table_(nullptr), exprtk_symbol_table_(nullptr), exprtk_expression_(nullptr), - exprtk_parser_(nullptr) + exprtk_parser_(nullptr), + fnc_sig_sample_(nullptr) { set_name(QString(tr("Math%1")).arg(session_.get_next_signal_index(MathChannel))); @@ -66,6 +106,9 @@ MathSignal::MathSignal(pv::Session &session) : MathSignal::~MathSignal() { reset_generation(); + + if (fnc_sig_sample_) + delete fnc_sig_sample_; } void MathSignal::save_settings(QSettings &settings) const @@ -133,7 +176,9 @@ uint64_t MathSignal::get_working_sample_count(uint32_t segment_id) const result = (segment_id == 0) ? custom_sample_count_ : 0; else { if (input_signals_.size() > 0) { - for (const shared_ptr &sb : input_signals_) { + for (auto input_signal : input_signals_) { + const shared_ptr& sb = input_signal.second.sb; + shared_ptr a = sb->analog_data(); const uint32_t last_segment = (a->analog_segments().size() - 1); if (segment_id > last_segment) @@ -157,10 +202,11 @@ void MathSignal::reset_generation() } data_->clear(); + input_signals_.clear(); - if (exprtk_symbol_table_) { - delete exprtk_symbol_table_; - exprtk_symbol_table_ = nullptr; + if (exprtk_parser_) { + delete exprtk_parser_; + exprtk_parser_ = nullptr; } if (exprtk_expression_) { @@ -168,9 +214,19 @@ void MathSignal::reset_generation() exprtk_expression_ = nullptr; } - if (exprtk_parser_) { - delete exprtk_parser_; - exprtk_parser_ = nullptr; + if (exprtk_symbol_table_) { + delete exprtk_symbol_table_; + exprtk_symbol_table_ = nullptr; + } + + if (exprtk_unknown_symbol_table_) { + delete exprtk_unknown_symbol_table_; + exprtk_unknown_symbol_table_ = nullptr; + } + + if (fnc_sig_sample_) { + delete fnc_sig_sample_; + fnc_sig_sample_ = nullptr; } if (!error_message_.isEmpty()) { @@ -189,18 +245,41 @@ void MathSignal::begin_generation() return; } + fnc_sig_sample_ = new sig_sample(*this); + + exprtk_unknown_symbol_table_ = new exprtk::symbol_table(); + exprtk_symbol_table_ = new exprtk::symbol_table(); + exprtk_symbol_table_->add_function("sig_sample", *fnc_sig_sample_); exprtk_symbol_table_->add_variable("t", exprtk_current_time_); exprtk_symbol_table_->add_variable("s", exprtk_current_sample_); exprtk_symbol_table_->add_constants(); exprtk_expression_ = new exprtk::expression(); + exprtk_expression_->register_symbol_table(*exprtk_unknown_symbol_table_); exprtk_expression_->register_symbol_table(*exprtk_symbol_table_); exprtk_parser_ = new exprtk::parser(); + exprtk_parser_->enable_unknown_symbol_resolver(); + if (!exprtk_parser_->compile(expression_.toStdString(), *exprtk_expression_)) { - error_message_ = set_error_message(tr("Error in expression")); + set_error_message(tr("Error in expression")); } else { + // Resolve unknown scalars to signals and add them to the input signal list + vector unknowns; + exprtk_unknown_symbol_table_->get_variable_list(unknowns); + for (string& unknown : unknowns) { + signal_data* sig_data = signal_from_name(unknown); + const shared_ptr signal = (sig_data) ? (sig_data->sb) : nullptr; + if (!signal || (!signal->analog_data())) { + set_error_message(QString(tr("%1 isn't a valid signal")).arg( + QString::fromStdString(unknown))); + } else + sig_data->ref = &(exprtk_unknown_symbol_table_->variable_ref(unknown)); + } + } + + if (error_message_.isEmpty()) { gen_interrupt_ = false; gen_thread_ = std::thread(&MathSignal::generation_proc, this); } @@ -212,6 +291,9 @@ void MathSignal::generate_samples(uint32_t segment_id, const uint64_t start_samp shared_ptr analog = dynamic_pointer_cast(data_); shared_ptr segment = analog->analog_segments().at(segment_id); + // Keep the math functions segment IDs in sync + fnc_sig_sample_->current_segment = segment_id; + const double sample_rate = data_->get_samplerate(); exprtk_current_sample_ = start_sample; @@ -219,10 +301,16 @@ void MathSignal::generate_samples(uint32_t segment_id, const uint64_t start_samp float *sample_data = new float[sample_count]; for (int64_t i = 0; i < sample_count; i++) { - exprtk_current_sample_ += 1; exprtk_current_time_ = exprtk_current_sample_ / sample_rate; + + for (auto& entry : input_signals_) { + signal_data* sig_data = &(entry.second); + update_signal_sample(sig_data, segment_id, exprtk_current_sample_); + } + double value = exprtk_expression_->value(); sample_data[i] = value; + exprtk_current_sample_ += 1; } segment->append_interleaved_samples(sample_data, sample_count, 1); @@ -303,6 +391,51 @@ void MathSignal::generation_proc() } while (!gen_interrupt_); } +signal_data* MathSignal::signal_from_name(const std::string& name) +{ + // Look up signal in the map and if it doesn't exist yet, add it for future use + + auto element = input_signals_.find(name); + + if (element != input_signals_.end()) { + return &(element->second); + } else { + const vector< shared_ptr > signalbases = session_.signalbases(); + const QString sig_name = QString::fromStdString(name); + + for (const shared_ptr& sb : signalbases) + if (sb->name() == sig_name) + return &(input_signals_.insert({name, signal_data(sb)}).first->second); + } + + return nullptr; +} + +void MathSignal::update_signal_sample(signal_data* sig_data, uint32_t segment_id, uint64_t sample_num) +{ + assert(sig_data); + + // Update the value only if a different sample is requested + if (sig_data->sample_num == sample_num) + return; + + assert(sig_data->sb); + const shared_ptr analog = sig_data->sb->analog_data(); + assert(analog); + + assert(segment_id < analog->analog_segments().size()); + + const shared_ptr segment = analog->analog_segments().at(segment_id); + + sig_data->sample_num = sample_num; + sig_data->sample_value = segment->get_sample(sample_num); + + // We only have a reference if this signal is used as a scalar, + // if it's used by a function, it's null + if (sig_data->ref) + *(sig_data->ref) = sig_data->sample_value; +} + void MathSignal::on_capture_state_changed(int state) { if (state == Session::Running) diff --git a/pv/data/mathsignal.hpp b/pv/data/mathsignal.hpp index f8e15d16..b9bd6ee4 100644 --- a/pv/data/mathsignal.hpp +++ b/pv/data/mathsignal.hpp @@ -20,6 +20,8 @@ #ifndef PULSEVIEW_PV_DATA_MATHSIGNAL_HPP #define PULSEVIEW_PV_DATA_MATHSIGNAL_HPP +#include + #include #include @@ -30,6 +32,7 @@ using std::atomic; using std::condition_variable; using std::mutex; +using std::numeric_limits; using std::shared_ptr; namespace pv { @@ -39,6 +42,20 @@ namespace data { class SignalBase; +template +struct sig_sample; + +struct signal_data { + signal_data(const shared_ptr _sb) : + sb(_sb), sample_num(numeric_limits::max()), sample_value(0), ref(nullptr) + {} + + const shared_ptr sb; + uint64_t sample_num; + double sample_value; + double* ref; +}; + class MathSignal : public SignalBase { Q_OBJECT @@ -79,6 +96,9 @@ private: const int64_t sample_count); void generation_proc(); + signal_data* signal_from_name(const std::string& name); + void update_signal_sample(signal_data* sig_data, uint32_t segment_id, uint64_t sample_num); + Q_SIGNALS: void samples_cleared(); @@ -99,11 +119,9 @@ private: uint64_t custom_sample_rate_; uint64_t custom_sample_count_; bool use_custom_sample_rate_, use_custom_sample_count_; - vector< shared_ptr> input_signals_; - - QString expression_; + map input_signals_; - QString error_message_; + QString expression_, error_message_; mutable mutex input_mutex_; mutable condition_variable gen_input_cond_; @@ -111,10 +129,15 @@ private: std::thread gen_thread_; atomic gen_interrupt_; - exprtk::symbol_table *exprtk_symbol_table_; + exprtk::symbol_table *exprtk_unknown_symbol_table_, *exprtk_symbol_table_; exprtk::expression *exprtk_expression_; exprtk::parser *exprtk_parser_; double exprtk_current_time_, exprtk_current_sample_; + + sig_sample* fnc_sig_sample_; + + // Give sig_sample access to the private helper functions + friend struct sig_sample; }; } // namespace data