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 const int64_t MathSignal::ChunkLength = 256 * 1024;
44 struct sig_sample : public exprtk::igeneric_function<T>
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;
51 sig_sample(MathSignal& owner) :
52 exprtk::igeneric_function<T>("ST"), // Require channel name and sample number
58 T operator()(parameter_list_t parameters)
60 const string_t exprtk_sig_name = string_t(parameters[0]);
61 const scalar_t exprtk_sample_num = scalar_t(parameters[1]);
63 const std::string str_sig_name = to_str(exprtk_sig_name);
64 const double sample_num = exprtk_sample_num();
67 sig_data = owner_.signal_from_name(str_sig_name);
70 owner_.update_signal_sample(sig_data, current_segment, sample_num);
72 return T(sig_data->sample_value);
76 uint32_t current_segment;
77 signal_data* sig_data;
81 MathSignal::MathSignal(pv::Session &session) :
82 SignalBase(nullptr, SignalBase::MathChannel),
84 use_custom_sample_rate_(false),
85 use_custom_sample_count_(false),
88 exprtk_unknown_symbol_table_(nullptr),
89 exprtk_symbol_table_(nullptr),
90 exprtk_expression_(nullptr),
91 exprtk_parser_(nullptr),
92 fnc_sig_sample_(nullptr)
94 uint32_t sig_idx = session_.get_next_signal_index(MathChannel);
95 set_name(QString(tr("Math%1")).arg(sig_idx));
96 set_color(AnalogSignalColors[(sig_idx - 1) % countof(AnalogSignalColors)]);
98 set_data(std::make_shared<data::Analog>());
100 connect(&session_, SIGNAL(capture_state_changed(int)),
101 this, SLOT(on_capture_state_changed(int)));
102 connect(&session_, SIGNAL(data_received()),
103 this, SLOT(on_data_received()));
105 expression_ = "sin(2 * pi * t) + cos(t / 2 * pi)";
108 MathSignal::~MathSignal()
113 delete fnc_sig_sample_;
116 void MathSignal::save_settings(QSettings &settings) const
118 settings.setValue("expression", expression_);
120 settings.setValue("custom_sample_rate", (qulonglong)custom_sample_rate_);
121 settings.setValue("custom_sample_count", (qulonglong)custom_sample_count_);
122 settings.setValue("use_custom_sample_rate", use_custom_sample_rate_);
123 settings.setValue("use_custom_sample_count", use_custom_sample_count_);
126 void MathSignal::restore_settings(QSettings &settings)
128 if (settings.contains("expression"))
129 expression_ = settings.value("expression").toString();
131 if (settings.contains("custom_sample_rate"))
132 custom_sample_rate_ = settings.value("custom_sample_rate").toULongLong();
134 if (settings.contains("custom_sample_count"))
135 custom_sample_count_ = settings.value("custom_sample_count").toULongLong();
137 if (settings.contains("use_custom_sample_rate"))
138 use_custom_sample_rate_ = settings.value("use_custom_sample_rate").toBool();
140 if (settings.contains("use_custom_sample_count"))
141 use_custom_sample_count_ = settings.value("use_custom_sample_count").toBool();
144 QString MathSignal::error_message() const
146 return error_message_;
149 QString MathSignal::get_expression() const
154 void MathSignal::set_expression(QString expression)
156 expression_ = expression;
161 void MathSignal::set_error_message(QString msg)
163 error_message_ = msg;
164 // TODO Emulate noquote()
165 qDebug().nospace() << name() << ": " << msg << "(Expression: '" << expression_ << "')";
168 uint64_t MathSignal::get_working_sample_count(uint32_t segment_id) const
170 // The working sample count is the highest sample number for
171 // which all used signals have data available, so go through all
172 // channels and use the lowest overall sample count of the segment
174 int64_t result = std::numeric_limits<int64_t>::max();
176 if (use_custom_sample_count_)
177 // A custom sample count implies that only one segment will be created
178 result = (segment_id == 0) ? custom_sample_count_ : 0;
180 if (input_signals_.size() > 0) {
181 for (auto input_signal : input_signals_) {
182 const shared_ptr<SignalBase>& sb = input_signal.second.sb;
184 shared_ptr<Analog> a = sb->analog_data();
185 const uint32_t last_segment = (a->analog_segments().size() - 1);
186 if (segment_id > last_segment)
188 const shared_ptr<AnalogSegment> segment = a->analog_segments()[segment_id];
189 result = min(result, (int64_t)segment->get_sample_count());
192 result = session_.get_segment_sample_count(segment_id);
198 void MathSignal::reset_generation()
200 if (gen_thread_.joinable()) {
201 gen_interrupt_ = true;
202 gen_input_cond_.notify_one();
207 input_signals_.clear();
209 if (exprtk_parser_) {
210 delete exprtk_parser_;
211 exprtk_parser_ = nullptr;
214 if (exprtk_expression_) {
215 delete exprtk_expression_;
216 exprtk_expression_ = nullptr;
219 if (exprtk_symbol_table_) {
220 delete exprtk_symbol_table_;
221 exprtk_symbol_table_ = nullptr;
224 if (exprtk_unknown_symbol_table_) {
225 delete exprtk_unknown_symbol_table_;
226 exprtk_unknown_symbol_table_ = nullptr;
229 if (fnc_sig_sample_) {
230 delete fnc_sig_sample_;
231 fnc_sig_sample_ = nullptr;
234 if (!error_message_.isEmpty()) {
235 error_message_ = QString();
236 // TODO Emulate noquote()
237 qDebug().nospace() << name() << ": Error cleared";
241 void MathSignal::begin_generation()
245 if (expression_.isEmpty()) {
246 set_error_message(tr("No expression defined, nothing to do"));
250 fnc_sig_sample_ = new sig_sample<double>(*this);
252 exprtk_unknown_symbol_table_ = new exprtk::symbol_table<double>();
254 exprtk_symbol_table_ = new exprtk::symbol_table<double>();
255 exprtk_symbol_table_->add_function("sig_sample", *fnc_sig_sample_);
256 exprtk_symbol_table_->add_variable("t", exprtk_current_time_);
257 exprtk_symbol_table_->add_variable("s", exprtk_current_sample_);
258 exprtk_symbol_table_->add_constants();
260 exprtk_expression_ = new exprtk::expression<double>();
261 exprtk_expression_->register_symbol_table(*exprtk_unknown_symbol_table_);
262 exprtk_expression_->register_symbol_table(*exprtk_symbol_table_);
264 exprtk_parser_ = new exprtk::parser<double>();
265 exprtk_parser_->enable_unknown_symbol_resolver();
267 if (!exprtk_parser_->compile(expression_.toStdString(), *exprtk_expression_)) {
268 set_error_message(tr("Error in expression"));
270 // Resolve unknown scalars to signals and add them to the input signal list
271 vector<string> unknowns;
272 exprtk_unknown_symbol_table_->get_variable_list(unknowns);
273 for (string& unknown : unknowns) {
274 signal_data* sig_data = signal_from_name(unknown);
275 const shared_ptr<SignalBase> signal = (sig_data) ? (sig_data->sb) : nullptr;
276 if (!signal || (!signal->analog_data())) {
277 set_error_message(QString(tr("%1 isn't a valid signal")).arg(
278 QString::fromStdString(unknown)));
280 sig_data->ref = &(exprtk_unknown_symbol_table_->variable_ref(unknown));
284 if (error_message_.isEmpty()) {
285 gen_interrupt_ = false;
286 gen_thread_ = std::thread(&MathSignal::generation_proc, this);
290 void MathSignal::generate_samples(uint32_t segment_id, const uint64_t start_sample,
291 const int64_t sample_count)
293 shared_ptr<Analog> analog = dynamic_pointer_cast<Analog>(data_);
294 shared_ptr<AnalogSegment> segment = analog->analog_segments().at(segment_id);
296 // Keep the math functions segment IDs in sync
297 fnc_sig_sample_->current_segment = segment_id;
299 const double sample_rate = data_->get_samplerate();
301 exprtk_current_sample_ = start_sample;
303 float *sample_data = new float[sample_count];
305 for (int64_t i = 0; i < sample_count; i++) {
306 exprtk_current_time_ = exprtk_current_sample_ / sample_rate;
308 for (auto& entry : input_signals_) {
309 signal_data* sig_data = &(entry.second);
310 update_signal_sample(sig_data, segment_id, exprtk_current_sample_);
313 double value = exprtk_expression_->value();
314 sample_data[i] = value;
315 exprtk_current_sample_ += 1;
318 segment->append_interleaved_samples(sample_data, sample_count, 1);
320 delete[] sample_data;
323 void MathSignal::generation_proc()
325 uint32_t segment_id = 0;
327 // Don't do anything until we have a valid sample rate
329 if (use_custom_sample_rate_)
330 data_->set_samplerate(custom_sample_rate_);
332 data_->set_samplerate(session_.get_samplerate());
334 if (data_->get_samplerate() == 1) {
335 unique_lock<mutex> gen_input_lock(input_mutex_);
336 gen_input_cond_.wait(gen_input_lock);
338 } while ((!gen_interrupt_) && (data_->get_samplerate() == 1));
343 shared_ptr<Analog> analog = analog_data();
345 // Create initial analog segment
346 shared_ptr<AnalogSegment> output_segment =
347 make_shared<AnalogSegment>(*analog.get(), segment_id, analog->get_samplerate());
348 analog->push_segment(output_segment);
350 // Create analog samples
352 const uint64_t input_sample_count = get_working_sample_count(segment_id);
353 const uint64_t output_sample_count = output_segment->get_sample_count();
355 const uint64_t samples_to_process =
356 (input_sample_count > output_sample_count) ?
357 (input_sample_count - output_sample_count) : 0;
359 // Process the samples if necessary...
360 if (samples_to_process > 0) {
361 const uint64_t chunk_sample_count = ChunkLength;
363 uint64_t processed_samples = 0;
365 const uint64_t start_sample = output_sample_count + processed_samples;
366 const uint64_t sample_count =
367 min(samples_to_process - processed_samples, chunk_sample_count);
369 generate_samples(segment_id, start_sample, sample_count);
370 processed_samples += sample_count;
372 // Notify consumers of this signal's data
373 // TODO Does this work when a conversion is active?
374 samples_added(segment_id, start_sample, start_sample + processed_samples);
375 } while (!gen_interrupt_ && (processed_samples < samples_to_process));
378 if (samples_to_process == 0) {
379 if (segment_id < session_.get_highest_segment_id()) {
380 analog->analog_segments().back()->set_complete();
382 // Process next segment
386 make_shared<AnalogSegment>(*analog.get(), segment_id, analog->get_samplerate());
387 analog->push_segment(output_segment);
389 // All segments have been processed, wait for more input
390 unique_lock<mutex> gen_input_lock(input_mutex_);
391 gen_input_cond_.wait(gen_input_lock);
395 } while (!gen_interrupt_);
398 signal_data* MathSignal::signal_from_name(const std::string& name)
400 // Look up signal in the map and if it doesn't exist yet, add it for future use
402 auto element = input_signals_.find(name);
404 if (element != input_signals_.end()) {
405 return &(element->second);
407 const vector< shared_ptr<SignalBase> > signalbases = session_.signalbases();
408 const QString sig_name = QString::fromStdString(name);
410 for (const shared_ptr<SignalBase>& sb : signalbases)
411 if (sb->name() == sig_name)
412 return &(input_signals_.insert({name, signal_data(sb)}).first->second);
418 void MathSignal::update_signal_sample(signal_data* sig_data, uint32_t segment_id, uint64_t sample_num)
422 // Update the value only if a different sample is requested
423 if (sig_data->sample_num == sample_num)
426 assert(sig_data->sb);
427 const shared_ptr<pv::data::Analog> analog = sig_data->sb->analog_data();
430 assert(segment_id < analog->analog_segments().size());
432 const shared_ptr<AnalogSegment> segment = analog->analog_segments().at(segment_id);
434 sig_data->sample_num = sample_num;
435 sig_data->sample_value = segment->get_sample(sample_num);
437 // We only have a reference if this signal is used as a scalar,
438 // if it's used by a function, it's null
440 *(sig_data->ref) = sig_data->sample_value;
443 void MathSignal::on_capture_state_changed(int state)
445 if (state == Session::Running)
448 if (state == Session::Stopped) {
449 shared_ptr<Analog> analog = analog_data();
450 analog->analog_segments().back()->set_complete();
454 void MathSignal::on_data_received()
456 gen_input_cond_.notify_one();