]>
Commit | Line | Data |
---|---|---|
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 | 31 | using std::dynamic_pointer_cast; |
b0773a8a | 32 | using std::make_shared; |
4640a84e SA |
33 | using std::min; |
34 | using std::unique_lock; | |
b0773a8a SA |
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), | |
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 | ||
66 | MathSignal::~MathSignal() | |
67 | { | |
4640a84e | 68 | reset_generation(); |
b0773a8a SA |
69 | } |
70 | ||
71 | void 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 | ||
81 | void 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 | ||
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() | |
d203009f | 120 | qDebug().nospace() << name() << ": " << msg << "(Expression: '" << expression_ << "')"; |
4640a84e SA |
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>(); | |
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 | ||
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(); | |
b0773a8a SA |
315 | } |
316 | ||
317 | } // namespace data | |
318 | } // namespace pv |