]> sigrok.org Git - pulseview.git/blame - pv/data/mathsignal.cpp
MathSignal: Mark segments as complete
[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
26#include <pv/globalsettings.hpp>
27#include <pv/session.hpp>
28#include <pv/data/analogsegment.hpp>
29#include <pv/data/signalbase.hpp>
30
4640a84e 31using std::dynamic_pointer_cast;
b0773a8a 32using std::make_shared;
4640a84e
SA
33using std::min;
34using std::unique_lock;
b0773a8a
SA
35
36namespace pv {
37namespace data {
38
39const int64_t MathSignal::ChunkLength = 256 * 1024;
40
41
3f1f6295
SA
42template<typename T>
43struct 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
b0773a8a
SA
80MathSignal::MathSignal(pv::Session &session) :
81 SignalBase(nullptr, SignalBase::MathChannel),
4640a84e
SA
82 session_(session),
83 use_custom_sample_rate_(false),
84 use_custom_sample_count_(false),
85 expression_(""),
86 error_message_(""),
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{
4640a84e
SA
93 set_name(QString(tr("Math%1")).arg(session_.get_next_signal_index(MathChannel)));
94
5eb9d1c4 95 set_data(std::make_shared<data::Analog>());
b0773a8a 96
4640a84e
SA
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()));
b0773a8a 101
4640a84e 102 expression_ = "sin(2 * pi * t) + cos(t / 2 * pi)";
b0773a8a
SA
103}
104
105MathSignal::~MathSignal()
106{
4640a84e 107 reset_generation();
3f1f6295
SA
108
109 if (fnc_sig_sample_)
110 delete fnc_sig_sample_;
b0773a8a
SA
111}
112
113void MathSignal::save_settings(QSettings &settings) const
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{
4640a84e
SA
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
141QString MathSignal::error_message() const
142{
143 return error_message_;
144}
145
146QString MathSignal::get_expression() const
147{
148 return expression_;
149}
150
151void MathSignal::set_expression(QString expression)
152{
153 expression_ = expression;
154
155 begin_generation();
156}
157
158void MathSignal::set_error_message(QString msg)
159{
160 error_message_ = msg;
161 // TODO Emulate noquote()
d203009f 162 qDebug().nospace() << name() << ": " << msg << "(Expression: '" << expression_ << "')";
4640a84e
SA
163}
164
165uint64_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) {
3f1f6295
SA
178 for (auto input_signal : input_signals_) {
179 const shared_ptr<SignalBase>& sb = input_signal.second.sb;
180
4640a84e
SA
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
195void 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();
3f1f6295 204 input_signals_.clear();
4640a84e 205
3f1f6295
SA
206 if (exprtk_parser_) {
207 delete exprtk_parser_;
208 exprtk_parser_ = nullptr;
4640a84e
SA
209 }
210
211 if (exprtk_expression_) {
212 delete exprtk_expression_;
213 exprtk_expression_ = nullptr;
214 }
215
3f1f6295
SA
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;
4640a84e
SA
229 }
230
231 if (!error_message_.isEmpty()) {
232 error_message_ = QString();
233 // TODO Emulate noquote()
234 qDebug().nospace() << name() << ": Error cleared";
235 }
236}
237
238void 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
3f1f6295
SA
247 fnc_sig_sample_ = new sig_sample<double>(*this);
248
249 exprtk_unknown_symbol_table_ = new exprtk::symbol_table<double>();
250
4640a84e 251 exprtk_symbol_table_ = new exprtk::symbol_table<double>();
3f1f6295 252 exprtk_symbol_table_->add_function("sig_sample", *fnc_sig_sample_);
4640a84e
SA
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>();
3f1f6295 258 exprtk_expression_->register_symbol_table(*exprtk_unknown_symbol_table_);
4640a84e
SA
259 exprtk_expression_->register_symbol_table(*exprtk_symbol_table_);
260
261 exprtk_parser_ = new exprtk::parser<double>();
3f1f6295
SA
262 exprtk_parser_->enable_unknown_symbol_resolver();
263
d203009f 264 if (!exprtk_parser_->compile(expression_.toStdString(), *exprtk_expression_)) {
3f1f6295 265 set_error_message(tr("Error in expression"));
d203009f 266 } else {
3f1f6295
SA
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()) {
d203009f
SA
282 gen_interrupt_ = false;
283 gen_thread_ = std::thread(&MathSignal::generation_proc, this);
284 }
4640a84e
SA
285}
286
287void 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
3f1f6295
SA
293 // Keep the math functions segment IDs in sync
294 fnc_sig_sample_->current_segment = segment_id;
295
4640a84e
SA
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++) {
4640a84e 303 exprtk_current_time_ = exprtk_current_sample_ / sample_rate;
3f1f6295
SA
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
4640a84e
SA
310 double value = exprtk_expression_->value();
311 sample_data[i] = value;
3f1f6295 312 exprtk_current_sample_ += 1;
4640a84e
SA
313 }
314
315 segment->append_interleaved_samples(sample_data, sample_count, 1);
316
317 delete[] sample_data;
318}
319
320void 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
5eb9d1c4 340 shared_ptr<Analog> analog = analog_data();
4640a84e
SA
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()) {
5eb9d1c4
SA
377 analog->analog_segments().back()->set_complete();
378
4640a84e
SA
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
3f1f6295
SA
395signal_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
415void 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
4640a84e
SA
440void MathSignal::on_capture_state_changed(int state)
441{
442 if (state == Session::Running)
443 begin_generation();
5eb9d1c4
SA
444
445 if (state == Session::Stopped) {
446 shared_ptr<Analog> analog = analog_data();
447 analog->analog_segments().back()->set_complete();
448 }
4640a84e
SA
449}
450
451void MathSignal::on_data_received()
452{
453 gen_input_cond_.notify_one();
b0773a8a
SA
454}
455
456} // namespace data
457} // namespace pv