]> sigrok.org Git - pulseview.git/blame - pv/data/mathsignal.cpp
MathSignal: Use error message
[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
42MathSignal::MathSignal(pv::Session &session) :
43 SignalBase(nullptr, SignalBase::MathChannel),
4640a84e
SA
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)
b0773a8a 52{
4640a84e
SA
53 set_name(QString(tr("Math%1")).arg(session_.get_next_signal_index(MathChannel)));
54
55 shared_ptr<Analog> data(new data::Analog());
b0773a8a
SA
56 set_data(data);
57
4640a84e
SA
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()));
b0773a8a 62
4640a84e 63 expression_ = "sin(2 * pi * t) + cos(t / 2 * pi)";
b0773a8a
SA
64}
65
66MathSignal::~MathSignal()
67{
4640a84e 68 reset_generation();
b0773a8a
SA
69}
70
71void MathSignal::save_settings(QSettings &settings) const
72{
4640a84e
SA
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_);
b0773a8a
SA
79}
80
81void MathSignal::restore_settings(QSettings &settings)
82{
4640a84e
SA
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
99QString MathSignal::error_message() const
100{
101 return error_message_;
102}
103
104QString MathSignal::get_expression() const
105{
106 return expression_;
107}
108
109void MathSignal::set_expression(QString expression)
110{
111 expression_ = expression;
112
113 begin_generation();
114}
115
116void MathSignal::set_error_message(QString msg)
117{
118 error_message_ = msg;
119 // TODO Emulate noquote()
d203009f 120 qDebug().nospace() << name() << ": " << msg << "(Expression: '" << expression_ << "')";
4640a84e
SA
121}
122
123uint64_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
151void 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
183void 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>();
d203009f
SA
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 }
4640a84e
SA
207}
208
209void 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
233void 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
306void MathSignal::on_capture_state_changed(int state)
307{
308 if (state == Session::Running)
309 begin_generation();
310}
311
312void MathSignal::on_data_received()
313{
314 gen_input_cond_.notify_one();
b0773a8a
SA
315}
316
317} // namespace data
318} // namespace pv