]> sigrok.org Git - pulseview.git/blob - pv/data/mathsignal.cpp
MathSignal: Mark segments as complete
[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 <pv/globalsettings.hpp>
27 #include <pv/session.hpp>
28 #include <pv/data/analogsegment.hpp>
29 #include <pv/data/signalbase.hpp>
30
31 using std::dynamic_pointer_cast;
32 using std::make_shared;
33 using std::min;
34 using std::unique_lock;
35
36 namespace pv {
37 namespace data {
38
39 const int64_t MathSignal::ChunkLength = 256 * 1024;
40
41
42 template<typename T>
43 struct sig_sample : public exprtk::igeneric_function<T>
44 {
45         typedef typename exprtk::igeneric_function<T>::parameter_list_t parameter_list_t;
46         typedef typename exprtk::igeneric_function<T>::generic_type generic_type;
47         typedef typename generic_type::scalar_view scalar_t;
48         typedef typename generic_type::string_view string_t;
49
50         sig_sample(MathSignal& owner) :
51                 exprtk::igeneric_function<T>("ST"),  // Require channel name and sample number
52                 owner_(owner),
53                 sig_data(nullptr)
54         {
55         }
56
57         T operator()(parameter_list_t parameters)
58         {
59                 const string_t exprtk_sig_name = string_t(parameters[0]);
60                 const scalar_t exprtk_sample_num = scalar_t(parameters[1]);
61
62                 const std::string str_sig_name = to_str(exprtk_sig_name);
63                 const double sample_num = exprtk_sample_num();
64
65                 if (!sig_data)
66                         sig_data = owner_.signal_from_name(str_sig_name);
67
68                 assert(sig_data);
69                 owner_.update_signal_sample(sig_data, current_segment, sample_num);
70
71                 return T(sig_data->sample_value);
72         }
73
74         MathSignal& owner_;
75         uint32_t current_segment;
76         signal_data* sig_data;
77 };
78
79
80 MathSignal::MathSignal(pv::Session &session) :
81         SignalBase(nullptr, SignalBase::MathChannel),
82         session_(session),
83         use_custom_sample_rate_(false),
84         use_custom_sample_count_(false),
85         expression_(""),
86         error_message_(""),
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         set_name(QString(tr("Math%1")).arg(session_.get_next_signal_index(MathChannel)));
94
95         set_data(std::make_shared<data::Analog>());
96
97         connect(&session_, SIGNAL(capture_state_changed(int)),
98                 this, SLOT(on_capture_state_changed(int)));
99         connect(&session_, SIGNAL(data_received()),
100                 this, SLOT(on_data_received()));
101
102         expression_ = "sin(2 * pi * t) + cos(t / 2 * pi)";
103 }
104
105 MathSignal::~MathSignal()
106 {
107         reset_generation();
108
109         if (fnc_sig_sample_)
110                 delete fnc_sig_sample_;
111 }
112
113 void MathSignal::save_settings(QSettings &settings) const
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         if (settings.contains("expression"))
126                 expression_ = settings.value("expression").toString();
127
128         if (settings.contains("custom_sample_rate"))
129                 custom_sample_rate_ = settings.value("custom_sample_rate").toULongLong();
130
131         if (settings.contains("custom_sample_count"))
132                 custom_sample_count_ = settings.value("custom_sample_count").toULongLong();
133
134         if (settings.contains("use_custom_sample_rate"))
135                 use_custom_sample_rate_ = settings.value("use_custom_sample_rate").toBool();
136
137         if (settings.contains("use_custom_sample_count"))
138                 use_custom_sample_count_ = settings.value("use_custom_sample_count").toBool();
139 }
140
141 QString MathSignal::error_message() const
142 {
143         return error_message_;
144 }
145
146 QString MathSignal::get_expression() const
147 {
148         return expression_;
149 }
150
151 void MathSignal::set_expression(QString expression)
152 {
153         expression_ = expression;
154
155         begin_generation();
156 }
157
158 void MathSignal::set_error_message(QString msg)
159 {
160         error_message_ = msg;
161         // TODO Emulate noquote()
162         qDebug().nospace() << name() << ": " << msg << "(Expression: '" << expression_ << "')";
163 }
164
165 uint64_t MathSignal::get_working_sample_count(uint32_t segment_id) const
166 {
167         // The working sample count is the highest sample number for
168         // which all used signals have data available, so go through all
169         // channels and use the lowest overall sample count of the segment
170
171         int64_t result = std::numeric_limits<int64_t>::max();
172
173         if (use_custom_sample_count_)
174                 // A custom sample count implies that only one segment will be created
175                 result = (segment_id == 0) ? custom_sample_count_ : 0;
176         else {
177                 if (input_signals_.size() > 0) {
178                         for (auto input_signal : input_signals_) {
179                                 const shared_ptr<SignalBase>& sb = input_signal.second.sb;
180
181                                 shared_ptr<Analog> a = sb->analog_data();
182                                 const uint32_t last_segment = (a->analog_segments().size() - 1);
183                                 if (segment_id > last_segment)
184                                         continue;
185                                 const shared_ptr<AnalogSegment> segment = a->analog_segments()[segment_id];
186                                 result = min(result, (int64_t)segment->get_sample_count());
187                         }
188                 } else
189                         result = session_.get_segment_sample_count(segment_id);
190         }
191
192         return result;
193 }
194
195 void MathSignal::reset_generation()
196 {
197         if (gen_thread_.joinable()) {
198                 gen_interrupt_ = true;
199                 gen_input_cond_.notify_one();
200                 gen_thread_.join();
201         }
202
203         data_->clear();
204         input_signals_.clear();
205
206         if (exprtk_parser_) {
207                 delete exprtk_parser_;
208                 exprtk_parser_ = nullptr;
209         }
210
211         if (exprtk_expression_) {
212                 delete exprtk_expression_;
213                 exprtk_expression_ = nullptr;
214         }
215
216         if (exprtk_symbol_table_) {
217                 delete exprtk_symbol_table_;
218                 exprtk_symbol_table_ = nullptr;
219         }
220
221         if (exprtk_unknown_symbol_table_) {
222                 delete exprtk_unknown_symbol_table_;
223                 exprtk_unknown_symbol_table_ = nullptr;
224         }
225
226         if (fnc_sig_sample_) {
227                 delete fnc_sig_sample_;
228                 fnc_sig_sample_ = nullptr;
229         }
230
231         if (!error_message_.isEmpty()) {
232                 error_message_ = QString();
233                 // TODO Emulate noquote()
234                 qDebug().nospace() << name() << ": Error cleared";
235         }
236 }
237
238 void MathSignal::begin_generation()
239 {
240         reset_generation();
241
242         if (expression_.isEmpty()) {
243                 set_error_message(tr("No expression defined, nothing to do"));
244                 return;
245         }
246
247         fnc_sig_sample_ = new sig_sample<double>(*this);
248
249         exprtk_unknown_symbol_table_ = new exprtk::symbol_table<double>();
250
251         exprtk_symbol_table_ = new exprtk::symbol_table<double>();
252         exprtk_symbol_table_->add_function("sig_sample", *fnc_sig_sample_);
253         exprtk_symbol_table_->add_variable("t", exprtk_current_time_);
254         exprtk_symbol_table_->add_variable("s", exprtk_current_sample_);
255         exprtk_symbol_table_->add_constants();
256
257         exprtk_expression_ = new exprtk::expression<double>();
258         exprtk_expression_->register_symbol_table(*exprtk_unknown_symbol_table_);
259         exprtk_expression_->register_symbol_table(*exprtk_symbol_table_);
260
261         exprtk_parser_ = new exprtk::parser<double>();
262         exprtk_parser_->enable_unknown_symbol_resolver();
263
264         if (!exprtk_parser_->compile(expression_.toStdString(), *exprtk_expression_)) {
265                 set_error_message(tr("Error in expression"));
266         } else {
267                 // Resolve unknown scalars to signals and add them to the input signal list
268                 vector<string> unknowns;
269                 exprtk_unknown_symbol_table_->get_variable_list(unknowns);
270                 for (string& unknown : unknowns) {
271                         signal_data* sig_data = signal_from_name(unknown);
272                         const shared_ptr<SignalBase> signal = (sig_data) ? (sig_data->sb) : nullptr;
273                         if (!signal || (!signal->analog_data())) {
274                                 set_error_message(QString(tr("%1 isn't a valid signal")).arg(
275                                         QString::fromStdString(unknown)));
276                         } else
277                                 sig_data->ref = &(exprtk_unknown_symbol_table_->variable_ref(unknown));
278                 }
279         }
280
281         if (error_message_.isEmpty()) {
282                 gen_interrupt_ = false;
283                 gen_thread_ = std::thread(&MathSignal::generation_proc, this);
284         }
285 }
286
287 void MathSignal::generate_samples(uint32_t segment_id, const uint64_t start_sample,
288         const int64_t sample_count)
289 {
290         shared_ptr<Analog> analog = dynamic_pointer_cast<Analog>(data_);
291         shared_ptr<AnalogSegment> segment = analog->analog_segments().at(segment_id);
292
293         // Keep the math functions segment IDs in sync
294         fnc_sig_sample_->current_segment = segment_id;
295
296         const double sample_rate = data_->get_samplerate();
297
298         exprtk_current_sample_ = start_sample;
299
300         float *sample_data = new float[sample_count];
301
302         for (int64_t i = 0; i < sample_count; i++) {
303                 exprtk_current_time_ = exprtk_current_sample_ / sample_rate;
304
305                 for (auto& entry : input_signals_) {
306                         signal_data* sig_data  = &(entry.second);
307                         update_signal_sample(sig_data, segment_id, exprtk_current_sample_);
308                 }
309
310                 double value = exprtk_expression_->value();
311                 sample_data[i] = value;
312                 exprtk_current_sample_ += 1;
313         }
314
315         segment->append_interleaved_samples(sample_data, sample_count, 1);
316
317         delete[] sample_data;
318 }
319
320 void MathSignal::generation_proc()
321 {
322         uint32_t segment_id = 0;
323
324         // Don't do anything until we have a valid sample rate
325         do {
326                 if (use_custom_sample_rate_)
327                         data_->set_samplerate(custom_sample_rate_);
328                 else
329                         data_->set_samplerate(session_.get_samplerate());
330
331                 if (data_->get_samplerate() == 1) {
332                         unique_lock<mutex> gen_input_lock(input_mutex_);
333                         gen_input_cond_.wait(gen_input_lock);
334                 }
335         } while ((!gen_interrupt_) && (data_->get_samplerate() == 1));
336
337         if (gen_interrupt_)
338                 return;
339
340         shared_ptr<Analog> analog = analog_data();
341
342         // Create initial analog segment
343         shared_ptr<AnalogSegment> output_segment =
344                 make_shared<AnalogSegment>(*analog.get(), segment_id, analog->get_samplerate());
345         analog->push_segment(output_segment);
346
347         // Create analog samples
348         do {
349                 const uint64_t input_sample_count = get_working_sample_count(segment_id);
350                 const uint64_t output_sample_count = output_segment->get_sample_count();
351
352                 const uint64_t samples_to_process =
353                         (input_sample_count > output_sample_count) ?
354                         (input_sample_count - output_sample_count) : 0;
355
356                 // Process the samples if necessary...
357                 if (samples_to_process > 0) {
358                         const uint64_t chunk_sample_count = ChunkLength;
359
360                         uint64_t processed_samples = 0;
361                         do {
362                                 const uint64_t start_sample = output_sample_count + processed_samples;
363                                 const uint64_t sample_count =
364                                         min(samples_to_process - processed_samples,     chunk_sample_count);
365
366                                 generate_samples(segment_id, start_sample, sample_count);
367                                 processed_samples += sample_count;
368
369                                 // Notify consumers of this signal's data
370                                 // TODO Does this work when a conversion is active?
371                                 samples_added(segment_id, start_sample, start_sample + processed_samples);
372                         } while (!gen_interrupt_ && (processed_samples < samples_to_process));
373                 }
374
375                 if (samples_to_process == 0) {
376                         if (segment_id < session_.get_highest_segment_id()) {
377                                 analog->analog_segments().back()->set_complete();
378
379                                 // Process next segment
380                                 segment_id++;
381
382                                 output_segment =
383                                         make_shared<AnalogSegment>(*analog.get(), segment_id, analog->get_samplerate());
384                                 analog->push_segment(output_segment);
385                         } else {
386                                 // All segments have been processed, wait for more input
387                                 unique_lock<mutex> gen_input_lock(input_mutex_);
388                                 gen_input_cond_.wait(gen_input_lock);
389                         }
390                 }
391
392         } while (!gen_interrupt_);
393 }
394
395 signal_data* MathSignal::signal_from_name(const std::string& name)
396 {
397         // Look up signal in the map and if it doesn't exist yet, add it for future use
398
399         auto element = input_signals_.find(name);
400
401         if (element != input_signals_.end()) {
402                 return &(element->second);
403         } else {
404                 const vector< shared_ptr<SignalBase> > signalbases = session_.signalbases();
405                 const QString sig_name = QString::fromStdString(name);
406
407                 for (const shared_ptr<SignalBase>& sb : signalbases)
408                         if (sb->name() == sig_name)
409                                 return &(input_signals_.insert({name, signal_data(sb)}).first->second);
410         }
411
412         return nullptr;
413 }
414
415 void MathSignal::update_signal_sample(signal_data* sig_data, uint32_t segment_id, uint64_t sample_num)
416 {
417         assert(sig_data);
418
419         // Update the value only if a different sample is requested
420         if (sig_data->sample_num == sample_num)
421                 return;
422
423         assert(sig_data->sb);
424         const shared_ptr<pv::data::Analog> analog = sig_data->sb->analog_data();
425         assert(analog);
426
427         assert(segment_id < analog->analog_segments().size());
428
429         const shared_ptr<AnalogSegment> segment = analog->analog_segments().at(segment_id);
430
431         sig_data->sample_num = sample_num;
432         sig_data->sample_value = segment->get_sample(sample_num);
433
434         // We only have a reference if this signal is used as a scalar,
435         // if it's used by a function, it's null
436         if (sig_data->ref)
437                 *(sig_data->ref) = sig_data->sample_value;
438 }
439
440 void MathSignal::on_capture_state_changed(int state)
441 {
442         if (state == Session::Running)
443                 begin_generation();
444
445         if (state == Session::Stopped) {
446                 shared_ptr<Analog> analog = analog_data();
447                 analog->analog_segments().back()->set_complete();
448         }
449 }
450
451 void MathSignal::on_data_received()
452 {
453         gen_input_cond_.notify_one();
454 }
455
456 } // namespace data
457 } // namespace pv