]> sigrok.org Git - pulseview.git/blob - pv/data/mathsignal.cpp
MathSignal: Use error message
[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 MathSignal::MathSignal(pv::Session &session) :
43         SignalBase(nullptr, SignalBase::MathChannel),
44         session_(session),
45         use_custom_sample_rate_(false),
46         use_custom_sample_count_(false),
47         expression_(""),
48         error_message_(""),
49         exprtk_symbol_table_(nullptr),
50         exprtk_expression_(nullptr),
51         exprtk_parser_(nullptr)
52 {
53         set_name(QString(tr("Math%1")).arg(session_.get_next_signal_index(MathChannel)));
54
55         shared_ptr<Analog> data(new data::Analog());
56         set_data(data);
57
58         connect(&session_, SIGNAL(capture_state_changed(int)),
59                 this, SLOT(on_capture_state_changed(int)));
60         connect(&session_, SIGNAL(data_received()),
61                 this, SLOT(on_data_received()));
62
63         expression_ = "sin(2 * pi * t) + cos(t / 2 * pi)";
64 }
65
66 MathSignal::~MathSignal()
67 {
68         reset_generation();
69 }
70
71 void MathSignal::save_settings(QSettings &settings) const
72 {
73         settings.setValue("expression", expression_);
74
75         settings.setValue("custom_sample_rate", (qulonglong)custom_sample_rate_);
76         settings.setValue("custom_sample_count", (qulonglong)custom_sample_count_);
77         settings.setValue("use_custom_sample_rate", use_custom_sample_rate_);
78         settings.setValue("use_custom_sample_count", use_custom_sample_count_);
79 }
80
81 void MathSignal::restore_settings(QSettings &settings)
82 {
83         if (settings.contains("expression"))
84                 expression_ = settings.value("expression").toString();
85
86         if (settings.contains("custom_sample_rate"))
87                 custom_sample_rate_ = settings.value("custom_sample_rate").toULongLong();
88
89         if (settings.contains("custom_sample_count"))
90                 custom_sample_count_ = settings.value("custom_sample_count").toULongLong();
91
92         if (settings.contains("use_custom_sample_rate"))
93                 use_custom_sample_rate_ = settings.value("use_custom_sample_rate").toBool();
94
95         if (settings.contains("use_custom_sample_count"))
96                 use_custom_sample_count_ = settings.value("use_custom_sample_count").toBool();
97 }
98
99 QString MathSignal::error_message() const
100 {
101         return error_message_;
102 }
103
104 QString MathSignal::get_expression() const
105 {
106         return expression_;
107 }
108
109 void MathSignal::set_expression(QString expression)
110 {
111         expression_ = expression;
112
113         begin_generation();
114 }
115
116 void MathSignal::set_error_message(QString msg)
117 {
118         error_message_ = msg;
119         // TODO Emulate noquote()
120         qDebug().nospace() << name() << ": " << msg << "(Expression: '" << expression_ << "')";
121 }
122
123 uint64_t MathSignal::get_working_sample_count(uint32_t segment_id) const
124 {
125         // The working sample count is the highest sample number for
126         // which all used signals have data available, so go through all
127         // channels and use the lowest overall sample count of the segment
128
129         int64_t result = std::numeric_limits<int64_t>::max();
130
131         if (use_custom_sample_count_)
132                 // A custom sample count implies that only one segment will be created
133                 result = (segment_id == 0) ? custom_sample_count_ : 0;
134         else {
135                 if (input_signals_.size() > 0) {
136                         for (const shared_ptr<SignalBase> &sb : input_signals_) {
137                                 shared_ptr<Analog> a = sb->analog_data();
138                                 const uint32_t last_segment = (a->analog_segments().size() - 1);
139                                 if (segment_id > last_segment)
140                                         continue;
141                                 const shared_ptr<AnalogSegment> segment = a->analog_segments()[segment_id];
142                                 result = min(result, (int64_t)segment->get_sample_count());
143                         }
144                 } else
145                         result = session_.get_segment_sample_count(segment_id);
146         }
147
148         return result;
149 }
150
151 void MathSignal::reset_generation()
152 {
153         if (gen_thread_.joinable()) {
154                 gen_interrupt_ = true;
155                 gen_input_cond_.notify_one();
156                 gen_thread_.join();
157         }
158
159         data_->clear();
160
161         if (exprtk_symbol_table_) {
162                 delete exprtk_symbol_table_;
163                 exprtk_symbol_table_ = nullptr;
164         }
165
166         if (exprtk_expression_) {
167                 delete exprtk_expression_;
168                 exprtk_expression_ = nullptr;
169         }
170
171         if (exprtk_parser_) {
172                 delete exprtk_parser_;
173                 exprtk_parser_ = nullptr;
174         }
175
176         if (!error_message_.isEmpty()) {
177                 error_message_ = QString();
178                 // TODO Emulate noquote()
179                 qDebug().nospace() << name() << ": Error cleared";
180         }
181 }
182
183 void MathSignal::begin_generation()
184 {
185         reset_generation();
186
187         if (expression_.isEmpty()) {
188                 set_error_message(tr("No expression defined, nothing to do"));
189                 return;
190         }
191
192         exprtk_symbol_table_ = new exprtk::symbol_table<double>();
193         exprtk_symbol_table_->add_variable("t", exprtk_current_time_);
194         exprtk_symbol_table_->add_variable("s", exprtk_current_sample_);
195         exprtk_symbol_table_->add_constants();
196
197         exprtk_expression_ = new exprtk::expression<double>();
198         exprtk_expression_->register_symbol_table(*exprtk_symbol_table_);
199
200         exprtk_parser_ = new exprtk::parser<double>();
201         if (!exprtk_parser_->compile(expression_.toStdString(), *exprtk_expression_)) {
202                 error_message_ = set_error_message(tr("Error in expression"));
203         } else {
204                 gen_interrupt_ = false;
205                 gen_thread_ = std::thread(&MathSignal::generation_proc, this);
206         }
207 }
208
209 void MathSignal::generate_samples(uint32_t segment_id, const uint64_t start_sample,
210         const int64_t sample_count)
211 {
212         shared_ptr<Analog> analog = dynamic_pointer_cast<Analog>(data_);
213         shared_ptr<AnalogSegment> segment = analog->analog_segments().at(segment_id);
214
215         const double sample_rate = data_->get_samplerate();
216
217         exprtk_current_sample_ = start_sample;
218
219         float *sample_data = new float[sample_count];
220
221         for (int64_t i = 0; i < sample_count; i++) {
222                 exprtk_current_sample_ += 1;
223                 exprtk_current_time_ = exprtk_current_sample_ / sample_rate;
224                 double value = exprtk_expression_->value();
225                 sample_data[i] = value;
226         }
227
228         segment->append_interleaved_samples(sample_data, sample_count, 1);
229
230         delete[] sample_data;
231 }
232
233 void MathSignal::generation_proc()
234 {
235         uint32_t segment_id = 0;
236
237         // Don't do anything until we have a valid sample rate
238         do {
239                 if (use_custom_sample_rate_)
240                         data_->set_samplerate(custom_sample_rate_);
241                 else
242                         data_->set_samplerate(session_.get_samplerate());
243
244                 if (data_->get_samplerate() == 1) {
245                         unique_lock<mutex> gen_input_lock(input_mutex_);
246                         gen_input_cond_.wait(gen_input_lock);
247                 }
248         } while ((!gen_interrupt_) && (data_->get_samplerate() == 1));
249
250         if (gen_interrupt_)
251                 return;
252
253         shared_ptr<Analog> analog = dynamic_pointer_cast<Analog>(data_);
254
255         // Create initial analog segment
256         shared_ptr<AnalogSegment> output_segment =
257                 make_shared<AnalogSegment>(*analog.get(), segment_id, analog->get_samplerate());
258         analog->push_segment(output_segment);
259
260         // Create analog samples
261         do {
262                 const uint64_t input_sample_count = get_working_sample_count(segment_id);
263                 const uint64_t output_sample_count = output_segment->get_sample_count();
264
265                 const uint64_t samples_to_process =
266                         (input_sample_count > output_sample_count) ?
267                         (input_sample_count - output_sample_count) : 0;
268
269                 // Process the samples if necessary...
270                 if (samples_to_process > 0) {
271                         const uint64_t chunk_sample_count = ChunkLength;
272
273                         uint64_t processed_samples = 0;
274                         do {
275                                 const uint64_t start_sample = output_sample_count + processed_samples;
276                                 const uint64_t sample_count =
277                                         min(samples_to_process - processed_samples,     chunk_sample_count);
278
279                                 generate_samples(segment_id, start_sample, sample_count);
280                                 processed_samples += sample_count;
281
282                                 // Notify consumers of this signal's data
283                                 // TODO Does this work when a conversion is active?
284                                 samples_added(segment_id, start_sample, start_sample + processed_samples);
285                         } while (!gen_interrupt_ && (processed_samples < samples_to_process));
286                 }
287
288                 if (samples_to_process == 0) {
289                         if (segment_id < session_.get_highest_segment_id()) {
290                                 // Process next segment
291                                 segment_id++;
292
293                                 output_segment =
294                                         make_shared<AnalogSegment>(*analog.get(), segment_id, analog->get_samplerate());
295                                 analog->push_segment(output_segment);
296                         } else {
297                                 // All segments have been processed, wait for more input
298                                 unique_lock<mutex> gen_input_lock(input_mutex_);
299                                 gen_input_cond_.wait(gen_input_lock);
300                         }
301                 }
302
303         } while (!gen_interrupt_);
304 }
305
306 void MathSignal::on_capture_state_changed(int state)
307 {
308         if (state == Session::Running)
309                 begin_generation();
310 }
311
312 void MathSignal::on_data_received()
313 {
314         gen_input_cond_.notify_one();
315 }
316
317 } // namespace data
318 } // namespace pv