#include "mathsignal.hpp"
+#include <extdef.h>
#include <pv/globalsettings.hpp>
#include <pv/session.hpp>
#include <pv/data/analogsegment.hpp>
namespace pv {
namespace data {
+#define MATH_ERR_NONE 0
+#define MATH_ERR_EMPTY_EXPR 1
+#define MATH_ERR_EXPRESSION 2
+#define MATH_ERR_INVALID_SIGNAL 3
+#define MATH_ERR_ENABLE 4
+
const int64_t MathSignal::ChunkLength = 256 * 1024;
+template<typename T>
+struct fnc_sample : public exprtk::igeneric_function<T>
+{
+ typedef typename exprtk::igeneric_function<T>::parameter_list_t parameter_list_t;
+ typedef typename exprtk::igeneric_function<T>::generic_type generic_type;
+ typedef typename generic_type::scalar_view scalar_t;
+ typedef typename generic_type::string_view string_t;
+
+ fnc_sample(MathSignal& owner) :
+ exprtk::igeneric_function<T>("ST"), // Require channel name and sample number
+ owner_(owner),
+ sig_data(nullptr)
+ {
+ }
+
+ T operator()(parameter_list_t parameters)
+ {
+ const string_t exprtk_sig_name = string_t(parameters[0]);
+ const scalar_t exprtk_sample_num = scalar_t(parameters[1]);
+
+ const std::string str_sig_name = to_str(exprtk_sig_name);
+ const double sample_num = exprtk_sample_num();
+
+ if (sample_num < 0)
+ return 0;
+
+ if (!sig_data)
+ sig_data = owner_.signal_from_name(str_sig_name);
+
+ if (!sig_data)
+ // There doesn't actually exist a signal with that name
+ return 0;
+
+ owner_.update_signal_sample(sig_data, current_segment, sample_num);
+
+ return T(sig_data->sample_value);
+ }
+
+ MathSignal& owner_;
+ uint32_t current_segment;
+ signal_data* sig_data;
+};
+
+
MathSignal::MathSignal(pv::Session &session) :
SignalBase(nullptr, SignalBase::MathChannel),
session_(session),
use_custom_sample_rate_(false),
use_custom_sample_count_(false),
expression_(""),
- error_message_(""),
+ error_type_(MATH_ERR_NONE),
+ exprtk_unknown_symbol_table_(nullptr),
exprtk_symbol_table_(nullptr),
exprtk_expression_(nullptr),
- exprtk_parser_(nullptr)
+ exprtk_parser_(nullptr),
+ fnc_sample_(nullptr)
{
- set_name(QString(tr("Math%1")).arg(session_.get_next_signal_index(MathChannel)));
+ uint32_t sig_idx = session_.get_next_signal_index(MathChannel);
+ set_name(QString(tr("Math%1")).arg(sig_idx));
+ set_color(AnalogSignalColors[(sig_idx - 1) % countof(AnalogSignalColors)]);
- shared_ptr<Analog> data(new data::Analog());
- set_data(data);
+ set_data(std::make_shared<data::Analog>());
connect(&session_, SIGNAL(capture_state_changed(int)),
this, SLOT(on_capture_state_changed(int)));
- connect(&session_, SIGNAL(data_received()),
- this, SLOT(on_data_received()));
-
- expression_ = "sin(2 * pi * t) + cos(t / 2 * pi)";
}
MathSignal::~MathSignal()
void MathSignal::save_settings(QSettings &settings) const
{
+ SignalBase::save_settings(settings);
+
settings.setValue("expression", expression_);
settings.setValue("custom_sample_rate", (qulonglong)custom_sample_rate_);
void MathSignal::restore_settings(QSettings &settings)
{
+ SignalBase::restore_settings(settings);
+
if (settings.contains("expression"))
expression_ = settings.value("expression").toString();
use_custom_sample_count_ = settings.value("use_custom_sample_count").toBool();
}
-QString MathSignal::error_message() const
-{
- return error_message_;
-}
-
QString MathSignal::get_expression() const
{
return expression_;
begin_generation();
}
-void MathSignal::set_error_message(QString msg)
+void MathSignal::set_error(uint8_t type, QString msg)
{
+ error_type_ = type;
error_message_ = msg;
// TODO Emulate noquote()
- qDebug().nospace() << name() << ": " << msg;
+ qDebug().nospace() << name() << ": " << msg << "(Expression: '" + expression_ + "')";
+
+ error_message_changed(msg);
}
uint64_t MathSignal::get_working_sample_count(uint32_t segment_id) const
result = (segment_id == 0) ? custom_sample_count_ : 0;
else {
if (input_signals_.size() > 0) {
- for (const shared_ptr<SignalBase> &sb : input_signals_) {
+ for (auto input_signal : input_signals_) {
+ const shared_ptr<SignalBase>& sb = input_signal.second.sb;
+
shared_ptr<Analog> a = sb->analog_data();
- const uint32_t last_segment = (a->analog_segments().size() - 1);
- if (segment_id > last_segment)
+ auto analog_segments = a->analog_segments();
+
+ if (analog_segments.size() == 0) {
+ result = 0;
+ continue;
+ }
+
+ const uint32_t highest_segment_id = (analog_segments.size() - 1);
+ if (segment_id > highest_segment_id)
continue;
- const shared_ptr<AnalogSegment> segment = a->analog_segments()[segment_id];
+
+ const shared_ptr<AnalogSegment> segment = analog_segments.at(segment_id);
result = min(result, (int64_t)segment->get_sample_count());
}
} else
return result;
}
+void MathSignal::update_completeness(uint32_t segment_id, uint64_t output_sample_count)
+{
+ bool output_complete = true;
+
+ if (input_signals_.size() > 0) {
+ for (auto input_signal : input_signals_) {
+ const shared_ptr<SignalBase>& sb = input_signal.second.sb;
+
+ shared_ptr<Analog> a = sb->analog_data();
+ auto analog_segments = a->analog_segments();
+
+ if (analog_segments.size() == 0) {
+ output_complete = false;
+ continue;
+ }
+
+ const uint32_t highest_segment_id = (analog_segments.size() - 1);
+ if (segment_id > highest_segment_id) {
+ output_complete = false;
+ continue;
+ }
+
+ const shared_ptr<AnalogSegment> segment = analog_segments.at(segment_id);
+ if (!segment->is_complete()) {
+ output_complete = false;
+ continue;
+ }
+
+ if (output_sample_count < segment->get_sample_count())
+ output_complete = false;
+ }
+ } else {
+ // We're done when we generated as many samples as the stopped session is long
+ if ((session_.get_capture_state() != Session::Stopped) ||
+ (output_sample_count < session_.get_segment_sample_count(segment_id)))
+ output_complete = false;
+ }
+
+ if (output_complete)
+ analog_data()->analog_segments().at(segment_id)->set_complete();
+}
+
void MathSignal::reset_generation()
{
if (gen_thread_.joinable()) {
}
data_->clear();
+ input_signals_.clear();
- if (exprtk_symbol_table_) {
- delete exprtk_symbol_table_;
- exprtk_symbol_table_ = nullptr;
+ if (exprtk_parser_) {
+ delete exprtk_parser_;
+ exprtk_parser_ = nullptr;
}
if (exprtk_expression_) {
exprtk_expression_ = nullptr;
}
- if (exprtk_parser_) {
- delete exprtk_parser_;
- exprtk_parser_ = nullptr;
+ if (exprtk_symbol_table_) {
+ delete exprtk_symbol_table_;
+ exprtk_symbol_table_ = nullptr;
+ }
+
+ if (exprtk_unknown_symbol_table_) {
+ delete exprtk_unknown_symbol_table_;
+ exprtk_unknown_symbol_table_ = nullptr;
+ }
+
+ if (fnc_sample_) {
+ delete fnc_sample_;
+ fnc_sample_ = nullptr;
}
if (!error_message_.isEmpty()) {
- error_message_ = QString();
+ error_message_.clear();
+ error_type_ = MATH_ERR_NONE;
// TODO Emulate noquote()
qDebug().nospace() << name() << ": Error cleared";
}
+
+ generation_chunk_size_ = ChunkLength;
}
void MathSignal::begin_generation()
reset_generation();
if (expression_.isEmpty()) {
- set_error_message(tr("No expression defined, nothing to do"));
+ set_error(MATH_ERR_EMPTY_EXPR, tr("No expression defined, nothing to do"));
return;
}
+ disconnect(this, SLOT(on_data_received()));
+ disconnect(this, SLOT(on_enabled_changed()));
+
+ fnc_sample_ = new fnc_sample<double>(*this);
+
+ exprtk_unknown_symbol_table_ = new exprtk::symbol_table<double>();
+
exprtk_symbol_table_ = new exprtk::symbol_table<double>();
+ exprtk_symbol_table_->add_constant("T", 1 / session_.get_samplerate());
+ exprtk_symbol_table_->add_function("sample", *fnc_sample_);
exprtk_symbol_table_->add_variable("t", exprtk_current_time_);
exprtk_symbol_table_->add_variable("s", exprtk_current_sample_);
exprtk_symbol_table_->add_constants();
exprtk_expression_ = new exprtk::expression<double>();
+ exprtk_expression_->register_symbol_table(*exprtk_unknown_symbol_table_);
exprtk_expression_->register_symbol_table(*exprtk_symbol_table_);
exprtk_parser_ = new exprtk::parser<double>();
- exprtk_parser_->compile(expression_.toStdString(), *exprtk_expression_);
+ exprtk_parser_->enable_unknown_symbol_resolver();
+
+ if (!exprtk_parser_->compile(expression_.toStdString(), *exprtk_expression_)) {
+ QString error_details;
+ size_t error_count = exprtk_parser_->error_count();
+
+ for (size_t i = 0; i < error_count; i++) {
+ typedef exprtk::parser_error::type error_t;
+ error_t error = exprtk_parser_->get_error(i);
+ exprtk::parser_error::update_error(error, expression_.toStdString());
+
+ QString error_detail = tr("%1 at line %2, column %3: %4");
+ if ((error_count > 1) && (i < (error_count - 1)))
+ error_detail += "\n";
+
+ error_details += error_detail \
+ .arg(exprtk::parser_error::to_str(error.mode).c_str()) \
+ .arg(error.line_no) \
+ .arg(error.column_no) \
+ .arg(error.diagnostic.c_str());
+ }
+ set_error(MATH_ERR_EXPRESSION, error_details);
+ } else {
+ // Resolve unknown scalars to signals and add them to the input signal list
+ vector<string> unknowns;
+ exprtk_unknown_symbol_table_->get_variable_list(unknowns);
+ for (string& unknown : unknowns) {
+ signal_data* sig_data = signal_from_name(unknown);
+ const shared_ptr<SignalBase> signal = (sig_data) ? (sig_data->sb) : nullptr;
+ if (!signal || (!signal->analog_data())) {
+ set_error(MATH_ERR_INVALID_SIGNAL, QString(tr("\"%1\" isn't a valid analog signal")) \
+ .arg(QString::fromStdString(unknown)));
+ } else
+ sig_data->ref = &(exprtk_unknown_symbol_table_->variable_ref(unknown));
+ }
+ }
- gen_interrupt_ = false;
- gen_thread_ = std::thread(&MathSignal::generation_proc, this);
+ QString disabled_signals;
+ if (!all_input_signals_enabled(disabled_signals) && error_message_.isEmpty())
+ set_error(MATH_ERR_ENABLE,
+ tr("No data will be generated as %1 must be enabled").arg(disabled_signals));
+
+ if (error_message_.isEmpty()) {
+ // Connect to the session data notification if we have no input signals
+ if (input_signals_.empty())
+ connect(&session_, SIGNAL(data_received()),
+ this, SLOT(on_data_received()));
+
+ gen_interrupt_ = false;
+ gen_thread_ = std::thread(&MathSignal::generation_proc, this);
+ }
}
-void MathSignal::generate_samples(uint32_t segment_id, const uint64_t start_sample,
+uint64_t MathSignal::generate_samples(uint32_t segment_id, const uint64_t start_sample,
const int64_t sample_count)
{
+ uint64_t count = 0;
+
shared_ptr<Analog> analog = dynamic_pointer_cast<Analog>(data_);
shared_ptr<AnalogSegment> segment = analog->analog_segments().at(segment_id);
+ // Keep the math functions segment IDs in sync
+ fnc_sample_->current_segment = segment_id;
+
const double sample_rate = data_->get_samplerate();
exprtk_current_sample_ = start_sample;
float *sample_data = new float[sample_count];
for (int64_t i = 0; i < sample_count; i++) {
- exprtk_current_sample_ += 1;
exprtk_current_time_ = exprtk_current_sample_ / sample_rate;
+
+ for (auto& entry : input_signals_) {
+ signal_data* sig_data = &(entry.second);
+ update_signal_sample(sig_data, segment_id, exprtk_current_sample_);
+ }
+
double value = exprtk_expression_->value();
sample_data[i] = value;
+ exprtk_current_sample_ += 1;
+ count++;
+
+ // If during the evaluation of the expression it was found that this
+ // math signal itself is being accessed, the chunk size was reduced
+ // to 1, which means we must stop after this sample we just generated
+ if (generation_chunk_size_ == 1)
+ break;
}
- segment->append_interleaved_samples(sample_data, sample_count, 1);
+ segment->append_interleaved_samples(sample_data, count, 1);
delete[] sample_data;
+
+ return count;
}
void MathSignal::generation_proc()
{
- uint32_t segment_id = 0;
-
// Don't do anything until we have a valid sample rate
do {
if (use_custom_sample_rate_)
if (gen_interrupt_)
return;
- shared_ptr<Analog> analog = dynamic_pointer_cast<Analog>(data_);
+ uint32_t segment_id = 0;
+ shared_ptr<Analog> analog = analog_data();
// Create initial analog segment
shared_ptr<AnalogSegment> output_segment =
// Process the samples if necessary...
if (samples_to_process > 0) {
- const uint64_t chunk_sample_count = ChunkLength;
-
uint64_t processed_samples = 0;
do {
const uint64_t start_sample = output_sample_count + processed_samples;
- const uint64_t sample_count =
- min(samples_to_process - processed_samples, chunk_sample_count);
+ uint64_t sample_count =
+ min(samples_to_process - processed_samples, generation_chunk_size_);
- generate_samples(segment_id, start_sample, sample_count);
+ sample_count = generate_samples(segment_id, start_sample, sample_count);
processed_samples += sample_count;
// Notify consumers of this signal's data
- // TODO Does this work when a conversion is active?
samples_added(segment_id, start_sample, start_sample + processed_samples);
} while (!gen_interrupt_ && (processed_samples < samples_to_process));
}
- if (samples_to_process == 0) {
- if (segment_id < session_.get_highest_segment_id()) {
+ update_completeness(segment_id, output_sample_count);
+
+ if (output_segment->is_complete() && (segment_id < session_.get_highest_segment_id())) {
// Process next segment
segment_id++;
output_segment =
make_shared<AnalogSegment>(*analog.get(), segment_id, analog->get_samplerate());
analog->push_segment(output_segment);
- } else {
- // All segments have been processed, wait for more input
- unique_lock<mutex> gen_input_lock(input_mutex_);
- gen_input_cond_.wait(gen_input_lock);
- }
}
+ if (!gen_interrupt_ && (samples_to_process == 0)) {
+ // Wait for more input
+ unique_lock<mutex> gen_input_lock(input_mutex_);
+ gen_input_cond_.wait(gen_input_lock);
+ }
} while (!gen_interrupt_);
}
+signal_data* MathSignal::signal_from_name(const std::string& name)
+{
+ // If the expression contains the math signal itself, we must add every sample to
+ // the output segment immediately so that it can be accessed
+ const QString sig_name = QString::fromStdString(name);
+ if (sig_name == this->name())
+ generation_chunk_size_ = 1;
+
+ // Look up signal in the map and if it doesn't exist yet, add it for future use
+
+ auto element = input_signals_.find(name);
+
+ if (element != input_signals_.end()) {
+ return &(element->second);
+ } else {
+ const vector< shared_ptr<SignalBase> > signalbases = session_.signalbases();
+
+ for (const shared_ptr<SignalBase>& sb : signalbases)
+ if (sb->name() == sig_name) {
+ if (!sb->analog_data())
+ continue;
+
+ connect(sb->analog_data().get(), SIGNAL(samples_added(SharedPtrToSegment, uint64_t, uint64_t)),
+ this, SLOT(on_data_received()));
+ connect(sb->analog_data().get(), SIGNAL(segment_completed()),
+ this, SLOT(on_data_received()));
+
+ connect(sb.get(), SIGNAL(enabled_changed(bool)),
+ this, SLOT(on_enabled_changed()));
+
+ return &(input_signals_.insert({name, signal_data(sb)}).first->second);
+ }
+ }
+
+ // If we reach this point, no valid signal was found with the supplied name
+ if (error_type_ == MATH_ERR_NONE)
+ set_error(MATH_ERR_INVALID_SIGNAL, QString(tr("\"%1\" isn't a valid analog signal")) \
+ .arg(QString::fromStdString(name)));
+
+ return nullptr;
+}
+
+void MathSignal::update_signal_sample(signal_data* sig_data, uint32_t segment_id, uint64_t sample_num)
+{
+ assert(sig_data);
+
+ // Update the value only if a different sample is requested
+ if (sig_data->sample_num == sample_num)
+ return;
+
+ assert(sig_data->sb);
+ const shared_ptr<pv::data::Analog> analog = sig_data->sb->analog_data();
+ assert(analog);
+
+ assert(segment_id < analog->analog_segments().size());
+
+ const shared_ptr<AnalogSegment> segment = analog->analog_segments().at(segment_id);
+
+ sig_data->sample_num = sample_num;
+
+ if (sample_num < segment->get_sample_count())
+ sig_data->sample_value = segment->get_sample(sample_num);
+ else
+ sig_data->sample_value = 0;
+
+ // We only have a reference if this signal is used as a scalar;
+ // if it's used by a function, it's null
+ if (sig_data->ref)
+ *(sig_data->ref) = sig_data->sample_value;
+}
+
+bool MathSignal::all_input_signals_enabled(QString &disabled_signals) const
+{
+ bool all_enabled = true;
+
+ disabled_signals.clear();
+
+ for (auto input_signal : input_signals_) {
+ const shared_ptr<SignalBase>& sb = input_signal.second.sb;
+
+ if (!sb->enabled()) {
+ all_enabled = false;
+ disabled_signals += disabled_signals.isEmpty() ?
+ sb->name() : ", " + sb->name();
+ }
+ }
+
+ return all_enabled;
+}
+
void MathSignal::on_capture_state_changed(int state)
{
if (state == Session::Running)
begin_generation();
+
+ // Make sure we don't miss any input samples, just in case
+ if (state == Session::Stopped)
+ gen_input_cond_.notify_one();
}
void MathSignal::on_data_received()
gen_input_cond_.notify_one();
}
+void MathSignal::on_enabled_changed()
+{
+ QString disabled_signals;
+ if (!all_input_signals_enabled(disabled_signals) &&
+ ((error_type_ == MATH_ERR_NONE) || (error_type_ == MATH_ERR_ENABLE)))
+ set_error(MATH_ERR_ENABLE,
+ tr("No data will be generated as %1 must be enabled").arg(disabled_signals));
+ else if (disabled_signals.isEmpty() && (error_type_ == MATH_ERR_ENABLE)) {
+ error_type_ = MATH_ERR_NONE;
+ error_message_.clear();
+ }
+}
+
} // namespace data
} // namespace pv