diff --git a/CMakeLists.txt b/CMakeLists.txt
index add9c02f..1cc445a7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -334,6 +334,7 @@ set(pulseview_HEADERS
pv/data/analogsegment.hpp
pv/data/logic.hpp
pv/data/logicsegment.hpp
+ pv/data/mathsignalbase.hpp
pv/data/mathsignal.hpp
pv/data/signalbase.hpp
pv/dialogs/connect.hpp
diff --git a/icons/add-math-logic-signal.svg b/icons/add-math-logic-signal.svg
new file mode 100644
index 00000000..db46b0f6
--- /dev/null
+++ b/icons/add-math-logic-signal.svg
@@ -0,0 +1,191 @@
+
+
diff --git a/pulseview.qrc b/pulseview.qrc
index 706fe8d0..2425ba77 100644
--- a/pulseview.qrc
+++ b/pulseview.qrc
@@ -1,6 +1,7 @@
icons/add-decoder.svg
+ icons/add-math-logic-signal.svg
icons/add-math-signal.svg
icons/application-exit.png
icons/channels.svg
diff --git a/pv/data/analog.cpp b/pv/data/analog.cpp
index da9736b5..e2e467d4 100644
--- a/pv/data/analog.cpp
+++ b/pv/data/analog.cpp
@@ -30,6 +30,22 @@ using std::vector;
namespace pv {
namespace data {
+const QColor Analog::SignalColors[8] =
+{
+ QColor(0xC4, 0xA0, 0x00), // Yellow HSV: 49 / 100 / 77
+ QColor(0x87, 0x20, 0x7A), // Magenta HSV: 308 / 70 / 53
+ QColor(0x20, 0x4A, 0x87), // Blue HSV: 216 / 76 / 53
+ QColor(0x4E, 0x9A, 0x06), // Green HSV: 91 / 96 / 60
+ QColor(0xBF, 0x6E, 0x00), // Orange HSV: 35 / 100 / 75
+ QColor(0x5E, 0x20, 0x80), // Purple HSV: 280 / 75 / 50
+ QColor(0x20, 0x80, 0x7A), // Turqoise HSV: 177 / 75 / 50
+ QColor(0x80, 0x20, 0x24) // Red HSV: 358 / 75 / 50
+};
+
+const char *Analog::InvalidSignal = "\"%1\" isn't a valid analog signal";
+
+//const SignalBase::ChannelType math_channel_type = SignalBase::AnalogMathChannel;
+
Analog::Analog() :
SignalData(),
samplerate_(1) // Default is 1 Hz to prevent division-by-zero errors
diff --git a/pv/data/analog.hpp b/pv/data/analog.hpp
index 560732f8..9786ca50 100644
--- a/pv/data/analog.hpp
+++ b/pv/data/analog.hpp
@@ -26,7 +26,9 @@
#include
#include
+#include
#include "pv/data/segment.hpp"
+#include "pv/data/signalbase.hpp"
using std::deque;
using std::shared_ptr;
@@ -41,6 +43,16 @@ class Analog : public SignalData
{
Q_OBJECT
+public:
+ /**
+ * Types and statics used with templates
+ */
+ typedef AnalogSegment segment_t;
+ typedef float sample_t;
+ static const QColor SignalColors[8];
+ static const char *InvalidSignal;
+ static const SignalBase::ChannelType math_channel_type = SignalBase::AnalogMathChannel;
+
public:
Analog();
@@ -48,6 +60,11 @@ class Analog : public SignalData
const deque< shared_ptr >& analog_segments() const;
+ inline const deque< shared_ptr >& typed_segments() const
+ {
+ return analog_segments();
+ }
+
vector< shared_ptr > segments() const;
uint32_t get_segment_count() const;
diff --git a/pv/data/analogsegment.cpp b/pv/data/analogsegment.cpp
index 57901309..038123ed 100644
--- a/pv/data/analogsegment.cpp
+++ b/pv/data/analogsegment.cpp
@@ -96,11 +96,13 @@ void AnalogSegment::append_interleaved_samples(const float *data,
prev_sample_count + 1, prev_sample_count + 1);
}
-float AnalogSegment::get_sample(int64_t sample_num) const
+float AnalogSegment::get_sample(int64_t sample_num, unsigned int index) const
{
assert(sample_num >= 0);
assert(sample_num <= (int64_t)sample_count_);
+ Q_UNUSED(index);
+
lock_guard lock(mutex_); // Because of free_unused_memory()
return *((const float*)get_raw_sample(sample_num));
diff --git a/pv/data/analogsegment.hpp b/pv/data/analogsegment.hpp
index 63317043..6c2636d9 100644
--- a/pv/data/analogsegment.hpp
+++ b/pv/data/analogsegment.hpp
@@ -81,7 +81,7 @@ class AnalogSegment : public Segment, public enable_shared_from_this
void append_interleaved_samples(const float *data,
size_t sample_count, size_t stride);
- float get_sample(int64_t sample_num) const;
+ float get_sample(int64_t sample_num, unsigned int index) const;
void get_samples(int64_t start_sample, int64_t end_sample, float* dest) const;
const pair get_min_max() const;
diff --git a/pv/data/logic.cpp b/pv/data/logic.cpp
index 4a13e568..ac91a200 100644
--- a/pv/data/logic.cpp
+++ b/pv/data/logic.cpp
@@ -30,6 +30,24 @@ using std::vector;
namespace pv {
namespace data {
+const QColor Logic::SignalColors[10] =
+{
+ QColor(0x16, 0x19, 0x1A), // Black
+ QColor(0x8F, 0x52, 0x02), // Brown
+ QColor(0xCC, 0x00, 0x00), // Red
+ QColor(0xF5, 0x79, 0x00), // Orange
+ QColor(0xED, 0xD4, 0x00), // Yellow
+ QColor(0x73, 0xD2, 0x16), // Green
+ QColor(0x34, 0x65, 0xA4), // Blue
+ QColor(0x75, 0x50, 0x7B), // Violet
+ QColor(0x88, 0x8A, 0x85), // Grey
+ QColor(0xEE, 0xEE, 0xEC), // White
+};
+
+const char *Logic::InvalidSignal = "\"%1\" isn't a valid logic signal";
+
+// const SignalBase::ChannelType math_channel_type = SignalBase::LogicMathChannel;
+
Logic::Logic(unsigned int num_channels) :
SignalData(),
samplerate_(1), // Default is 1 Hz to prevent division-by-zero errors
diff --git a/pv/data/logic.hpp b/pv/data/logic.hpp
index 07f8222c..3a460e67 100644
--- a/pv/data/logic.hpp
+++ b/pv/data/logic.hpp
@@ -22,10 +22,12 @@
#include "signaldata.hpp"
#include "segment.hpp"
+#include "signalbase.hpp"
#include
#include
+#include
using std::deque;
using std::shared_ptr;
@@ -41,7 +43,17 @@ class Logic : public SignalData
Q_OBJECT
public:
- Logic(unsigned int num_channels);
+ /**
+ * Types and statics used with templates
+ */
+ typedef LogicSegment segment_t;
+ typedef uint8_t sample_t;
+ static const QColor SignalColors[10];
+ static const char *InvalidSignal;
+ static const SignalBase::ChannelType math_channel_type = SignalBase::LogicMathChannel;
+
+public:
+ Logic(unsigned int num_channels = 1);
unsigned int num_channels() const;
@@ -50,6 +62,11 @@ class Logic : public SignalData
const deque< shared_ptr >& logic_segments() const;
deque< shared_ptr >& logic_segments();
+ inline const deque< shared_ptr >& typed_segments() const
+ {
+ return logic_segments();
+ }
+
vector< shared_ptr > segments() const;
uint32_t get_segment_count() const;
diff --git a/pv/data/logicsegment.cpp b/pv/data/logicsegment.cpp
index 739b2b9e..1fa4dedc 100644
--- a/pv/data/logicsegment.cpp
+++ b/pv/data/logicsegment.cpp
@@ -38,6 +38,7 @@ using std::max;
using std::min;
using std::shared_ptr;
using std::vector;
+using std::unique_ptr;
using sigrok::Logic;
@@ -49,6 +50,12 @@ const int LogicSegment::MipMapScaleFactor = 1 << MipMapScalePower;
const float LogicSegment::LogMipMapScaleFactor = logf(MipMapScaleFactor);
const uint64_t LogicSegment::MipMapDataUnit = 64 * 1024; // bytes
+LogicSegment::LogicSegment(pv::data::Logic& owner, uint32_t segment_id,
+ uint64_t samplerate) :
+ LogicSegment(owner, segment_id, 1, samplerate)
+{
+}
+
LogicSegment::LogicSegment(pv::data::Logic& owner, uint32_t segment_id,
unsigned int unit_size, uint64_t samplerate) :
Segment(segment_id, samplerate, unit_size),
@@ -388,6 +395,37 @@ void LogicSegment::append_subsignal_payload(unsigned int index, void *data,
}
}
+void LogicSegment::append_interleaved_samples(const uint8_t *data,
+ size_t sample_count, size_t stride)
+{
+ assert(unit_size_ == sizeof(uint8_t));
+
+ lock_guard lock(mutex_);
+
+ uint64_t prev_sample_count = sample_count_;
+
+ // Deinterleave the samples and add them
+ unique_ptr deint_data(new uint8_t[sample_count]);
+ uint8_t *deint_data_ptr = deint_data.get();
+ for (uint32_t i = 0; i < sample_count; i++) {
+ *deint_data_ptr = (uint8_t)(*data);
+ deint_data_ptr++;
+ data += stride;
+ }
+
+ append_samples(deint_data.get(), sample_count);
+
+ // Generate the first mip-map from the data
+ append_payload_to_mipmap();
+
+ if (sample_count > 1)
+ owner_.notify_samples_added(shared_ptr(shared_from_this()),
+ prev_sample_count + 1, prev_sample_count + 1 + sample_count);
+ else
+ owner_.notify_samples_added(shared_ptr(shared_from_this()),
+ prev_sample_count + 1, prev_sample_count + 1);
+}
+
void LogicSegment::get_samples(int64_t start_sample,
int64_t end_sample, uint8_t* dest) const
{
@@ -403,6 +441,19 @@ void LogicSegment::get_samples(int64_t start_sample,
get_raw_samples(start_sample, (end_sample - start_sample), dest);
}
+uint8_t LogicSegment::get_sample(int64_t sample_num, unsigned int index) const
+{
+ assert(sample_num >= 0);
+ assert(sample_num <= (int64_t)sample_count_);
+ assert(index >= 0);
+ assert(index < 64);
+
+ lock_guard lock(mutex_);
+
+ const uint8_t *sample = get_raw_sample(sample_num);
+ return (unpack_sample(sample) >> index) & 1;
+}
+
void LogicSegment::get_subsampled_edges(
vector &edges,
uint64_t start, uint64_t end,
diff --git a/pv/data/logicsegment.hpp b/pv/data/logicsegment.hpp
index 35e8eca9..b5087885 100644
--- a/pv/data/logicsegment.hpp
+++ b/pv/data/logicsegment.hpp
@@ -70,6 +70,8 @@ class LogicSegment : public Segment, public enable_shared_from_this
};
public:
+ LogicSegment(pv::data::Logic& owner, uint32_t segment_id,
+ uint64_t samplerate);
LogicSegment(pv::data::Logic& owner, uint32_t segment_id,
unsigned int unit_size, uint64_t samplerate);
@@ -99,8 +101,13 @@ class LogicSegment : public Segment, public enable_shared_from_this
void append_subsignal_payload(unsigned int index, void *data,
uint64_t data_size, vector& destination);
+ void append_interleaved_samples(const uint8_t *data,
+ size_t sample_count, size_t stride);
+
void get_samples(int64_t start_sample, int64_t end_sample, uint8_t* dest) const;
+ uint8_t get_sample(int64_t sample_num, unsigned int index) const;
+
/**
* Parses a logic data segment to generate a list of transitions
* in a time interval to a given level of detail.
diff --git a/pv/data/mathsignal.cpp b/pv/data/mathsignal.cpp
index adb27131..c218a6cb 100644
--- a/pv/data/mathsignal.cpp
+++ b/pv/data/mathsignal.cpp
@@ -17,577 +17,42 @@
* along with this program; if not, see .
*/
-#include
-
-#include
-
#include "mathsignal.hpp"
-#include
-#include
#include
-#include
-#include
-
-using std::dynamic_pointer_cast;
-using std::make_shared;
-using std::min;
-using std::unique_lock;
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
-struct fnc_sample : public exprtk::igeneric_function
-{
- typedef typename exprtk::igeneric_function::parameter_list_t parameter_list_t;
- typedef typename exprtk::igeneric_function::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("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_type_(MATH_ERR_NONE),
- exprtk_unknown_symbol_table_(nullptr),
- exprtk_symbol_table_(nullptr),
- exprtk_expression_(nullptr),
- exprtk_parser_(nullptr),
- fnc_sample_(nullptr)
-{
- 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)]);
-
- set_data(std::make_shared());
-
- connect(&session_, SIGNAL(capture_state_changed(int)),
- this, SLOT(on_capture_state_changed(int)));
-}
-
-MathSignal::~MathSignal()
-{
- reset_generation();
-}
-
-void MathSignal::save_settings(QSettings &settings) const
-{
- SignalBase::save_settings(settings);
-
- settings.setValue("expression", expression_);
-
- settings.setValue("custom_sample_rate", (qulonglong)custom_sample_rate_);
- settings.setValue("custom_sample_count", (qulonglong)custom_sample_count_);
- settings.setValue("use_custom_sample_rate", use_custom_sample_rate_);
- settings.setValue("use_custom_sample_count", use_custom_sample_count_);
-}
-
-void MathSignal::restore_settings(QSettings &settings)
-{
- SignalBase::restore_settings(settings);
-
- if (settings.contains("expression"))
- expression_ = settings.value("expression").toString();
-
- if (settings.contains("custom_sample_rate"))
- custom_sample_rate_ = settings.value("custom_sample_rate").toULongLong();
-
- if (settings.contains("custom_sample_count"))
- custom_sample_count_ = settings.value("custom_sample_count").toULongLong();
-
- if (settings.contains("use_custom_sample_rate"))
- use_custom_sample_rate_ = settings.value("use_custom_sample_rate").toBool();
-
- if (settings.contains("use_custom_sample_count"))
- use_custom_sample_count_ = settings.value("use_custom_sample_count").toBool();
-}
-
-QString MathSignal::get_expression() const
-{
- return expression_;
-}
-
-void MathSignal::set_expression(QString expression)
-{
- expression_ = expression;
-
- begin_generation();
-}
-
-void MathSignal::set_error(uint8_t type, QString msg)
-{
- error_type_ = type;
- error_message_ = msg;
- // TODO Emulate noquote()
- qDebug().nospace() << name() << ": " << msg << "(Expression: '" + expression_ + "')";
-
- error_message_changed(msg);
-}
-
-uint64_t MathSignal::get_working_sample_count(uint32_t segment_id) const
+void MathSignalAnalog::on_capture_state_changed(int state)
{
- // The working sample count is the highest sample number for
- // which all used signals have data available, so go through all
- // channels and use the lowest overall sample count of the segment
-
- int64_t result = std::numeric_limits::max();
-
- if (use_custom_sample_count_)
- // A custom sample count implies that only one segment will be created
- result = (segment_id == 0) ? custom_sample_count_ : 0;
- else {
- if (input_signals_.size() > 0) {
- for (auto input_signal : input_signals_) {
- const shared_ptr& sb = input_signal.second.sb;
-
- shared_ptr a = sb->analog_data();
- 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 segment = analog_segments.at(segment_id);
- result = min(result, (int64_t)segment->get_sample_count());
- }
- } else
- result = session_.get_segment_sample_count(segment_id);
- }
-
- 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& sb = input_signal.second.sb;
-
- shared_ptr 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 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 (state == Session::Running)
+ begin_generation();
- if (output_complete)
- analog_data()->analog_segments().at(segment_id)->set_complete();
+ // Make sure we don't miss any input samples, just in case
+ if (state == Session::Stopped)
+ gen_input_cond_.notify_one();
}
-void MathSignal::reset_generation()
+void MathSignalAnalog::on_data_received()
{
- if (gen_thread_.joinable()) {
- gen_interrupt_ = true;
- gen_input_cond_.notify_one();
- gen_thread_.join();
- }
-
- data_->clear();
- input_signals_.clear();
-
- if (exprtk_parser_) {
- delete exprtk_parser_;
- exprtk_parser_ = nullptr;
- }
-
- if (exprtk_expression_) {
- delete exprtk_expression_;
- exprtk_expression_ = 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_.clear();
- error_type_ = MATH_ERR_NONE;
- // TODO Emulate noquote()
- qDebug().nospace() << name() << ": Error cleared";
- }
-
- generation_chunk_size_ = ChunkLength;
+ gen_input_cond_.notify_one();
}
-void MathSignal::begin_generation()
+void MathSignalAnalog::on_enabled_changed()
{
- reset_generation();
-
- if (expression_.isEmpty()) {
- set_error(MATH_ERR_EMPTY_EXPR, tr("No expression defined, nothing to do"));
- return;
- }
-
- disconnect(&session_, SIGNAL(data_received()), this, SLOT(on_data_received()));
-
- for (const shared_ptr& sb : session_.signalbases()) {
- if (sb->analog_data())
- disconnect(sb->analog_data().get(), nullptr, this, SLOT(on_data_received()));
- disconnect(sb.get(), nullptr, this, SLOT(on_enabled_changed()));
- }
-
- fnc_sample_ = new fnc_sample(*this);
-
- exprtk_unknown_symbol_table_ = new exprtk::symbol_table();
-
- exprtk_symbol_table_ = new exprtk::symbol_table();
- 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();
- exprtk_expression_->register_symbol_table(*exprtk_unknown_symbol_table_);
- exprtk_expression_->register_symbol_table(*exprtk_symbol_table_);
-
- exprtk_parser_ = new exprtk::parser();
- 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 unknowns;
- exprtk_unknown_symbol_table_->get_variable_list(unknowns);
- for (string& unknown : unknowns) {
- signal_data* sig_data = signal_from_name(unknown);
- const shared_ptr 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));
- }
- }
-
QString disabled_signals;
- if (!all_input_signals_enabled(disabled_signals) && error_message_.isEmpty())
+ 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));
-
- 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);
- }
-}
-
-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 = dynamic_pointer_cast(data_);
- shared_ptr 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_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, count, 1);
-
- delete[] sample_data;
-
- return count;
-}
-
-void MathSignal::generation_proc()
-{
- // Don't do anything until we have a valid sample rate
- do {
- if (use_custom_sample_rate_)
- data_->set_samplerate(custom_sample_rate_);
- else
- data_->set_samplerate(session_.get_samplerate());
-
- if (data_->get_samplerate() == 1) {
- unique_lock gen_input_lock(input_mutex_);
- gen_input_cond_.wait(gen_input_lock);
- }
- } while ((!gen_interrupt_) && (data_->get_samplerate() == 1));
-
- if (gen_interrupt_)
- return;
-
- uint32_t segment_id = 0;
- shared_ptr analog = analog_data();
-
- // Create initial analog segment
- shared_ptr output_segment =
- make_shared(*analog.get(), segment_id, analog->get_samplerate());
- analog->push_segment(output_segment);
-
- // Create analog samples
- do {
- const uint64_t input_sample_count = get_working_sample_count(segment_id);
- const uint64_t output_sample_count = output_segment->get_sample_count();
-
- const uint64_t samples_to_process =
- (input_sample_count > output_sample_count) ?
- (input_sample_count - output_sample_count) : 0;
-
- // Process the samples if necessary...
- if (samples_to_process > 0) {
- uint64_t processed_samples = 0;
- do {
- const uint64_t start_sample = output_sample_count + processed_samples;
- uint64_t sample_count =
- min(samples_to_process - processed_samples, generation_chunk_size_);
-
- sample_count = generate_samples(segment_id, start_sample, sample_count);
- processed_samples += sample_count;
-
- // Notify consumers of this signal's data
- samples_added(segment_id, start_sample, start_sample + processed_samples);
- } while (!gen_interrupt_ && (processed_samples < samples_to_process));
- }
-
- 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(*analog.get(), segment_id, analog->get_samplerate());
- analog->push_segment(output_segment);
- }
-
- if (!gen_interrupt_ && (samples_to_process == 0)) {
- // Wait for more input
- unique_lock 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 > signalbases = session_.signalbases();
-
- for (const shared_ptr& 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 analog = sig_data->sb->analog_data();
- assert(analog);
-
- assert(segment_id < analog->analog_segments().size());
-
- const shared_ptr 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& sb = input_signal.second.sb;
-
- if (!sb->enabled()) {
- all_enabled = false;
- disabled_signals += disabled_signals.isEmpty() ?
- sb->name() : ", " + sb->name();
- }
+ else if (disabled_signals.isEmpty() && (error_type_ == MATH_ERR_ENABLE)) {
+ error_type_ = MATH_ERR_NONE;
+ error_message_.clear();
}
-
- return all_enabled;
}
-void MathSignal::on_capture_state_changed(int state)
+void MathSignalLogic::on_capture_state_changed(int state)
{
if (state == Session::Running)
begin_generation();
@@ -597,12 +62,12 @@ void MathSignal::on_capture_state_changed(int state)
gen_input_cond_.notify_one();
}
-void MathSignal::on_data_received()
+void MathSignalLogic::on_data_received()
{
gen_input_cond_.notify_one();
}
-void MathSignal::on_enabled_changed()
+void MathSignalLogic::on_enabled_changed()
{
QString disabled_signals;
if (!all_input_signals_enabled(disabled_signals) &&
diff --git a/pv/data/mathsignal.hpp b/pv/data/mathsignal.hpp
index 98adbe80..58a67acd 100644
--- a/pv/data/mathsignal.hpp
+++ b/pv/data/mathsignal.hpp
@@ -20,90 +20,23 @@
#ifndef PULSEVIEW_PV_DATA_MATHSIGNAL_HPP
#define PULSEVIEW_PV_DATA_MATHSIGNAL_HPP
-#define exprtk_disable_rtl_io_file /* Potential security issue, doubt anyone would use those anyway */
-#define exprtk_disable_rtl_vecops /* Vector ops are rather useless for math channels */
-#define exprtk_disable_caseinsensitivity /* So that we can have both 't' and 'T' */
-
-#include
-
#include
-#include
-#include
+#include
#include
-#include
-
-using std::atomic;
-using std::condition_variable;
-using std::mutex;
-using std::numeric_limits;
-using std::shared_ptr;
+#include
+#include
+#include
+#include
namespace pv {
-class Session;
-
namespace data {
-class SignalBase;
-
-template
-struct fnc_sample;
-
-struct signal_data {
- signal_data(const shared_ptr _sb) :
- sb(_sb), sample_num(numeric_limits::max()), sample_value(0), ref(nullptr)
- {}
-
- const shared_ptr sb;
- uint64_t sample_num;
- double sample_value;
- double* ref;
-};
-
-class MathSignal : public SignalBase
+class MathSignalAnalog : public MathSignalBase
{
Q_OBJECT
Q_PROPERTY(QString expression READ get_expression WRITE set_expression NOTIFY expression_changed)
-private:
- static const int64_t ChunkLength;
-
-public:
- MathSignal(pv::Session &session);
- virtual ~MathSignal();
-
- virtual void save_settings(QSettings &settings) const;
- virtual void restore_settings(QSettings &settings);
-
- QString get_expression() const;
- void set_expression(QString expression);
-
-private:
- void set_error(uint8_t type, QString msg);
-
- /**
- * Returns the number of samples that can be worked on,
- * i.e. the number of samples where samples are available
- * for all connected channels.
- * If the math signal uses no input channels, this is the
- * number of samples in the session.
- */
- uint64_t get_working_sample_count(uint32_t segment_id) const;
-
- void update_completeness(uint32_t segment_id, uint64_t output_sample_count);
-
- void reset_generation();
- virtual void begin_generation();
-
- uint64_t generate_samples(uint32_t segment_id, const uint64_t start_sample,
- const int64_t sample_count);
- void generation_proc();
-
- signal_data* signal_from_name(const std::string& name);
- void update_signal_sample(signal_data* sig_data, uint32_t segment_id, uint64_t sample_num);
-
- bool all_input_signals_enabled(QString &disabled_signals) const;
-
Q_SIGNALS:
void expression_changed(QString expression);
@@ -113,34 +46,32 @@ private Q_SLOTS:
void on_enabled_changed();
-private:
- pv::Session &session_;
-
- uint64_t custom_sample_rate_;
- uint64_t custom_sample_count_;
- bool use_custom_sample_rate_, use_custom_sample_count_;
- uint64_t generation_chunk_size_;
- map input_signals_;
-
- QString expression_;
-
- uint8_t error_type_;
+public:
+ MathSignalAnalog(pv::Session &session) :
+ MathSignalBase(session)
+ {
+ }
+};
- mutable mutex input_mutex_;
- mutable condition_variable gen_input_cond_;
+class MathSignalLogic : public MathSignalBase
+{
+ Q_OBJECT
+ Q_PROPERTY(QString expression READ get_expression WRITE set_expression NOTIFY expression_changed)
- std::thread gen_thread_;
- atomic gen_interrupt_;
+Q_SIGNALS:
+ void expression_changed(QString expression);
- exprtk::symbol_table *exprtk_unknown_symbol_table_, *exprtk_symbol_table_;
- exprtk::expression *exprtk_expression_;
- exprtk::parser *exprtk_parser_;
- double exprtk_current_time_, exprtk_current_sample_;
+private Q_SLOTS:
+ void on_capture_state_changed(int state);
+ void on_data_received();
- fnc_sample* fnc_sample_;
+ void on_enabled_changed();
- // Give sig_sample access to the private helper functions
- friend struct fnc_sample;
+public:
+ MathSignalLogic(pv::Session &session) :
+ MathSignalBase(session)
+ {
+ }
};
} // namespace data
diff --git a/pv/data/mathsignalbase.hpp b/pv/data/mathsignalbase.hpp
new file mode 100644
index 00000000..554a3c52
--- /dev/null
+++ b/pv/data/mathsignalbase.hpp
@@ -0,0 +1,708 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2020 Soeren Apel
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see .
+ */
+
+#ifndef PULSEVIEW_PV_DATA_MATHSIGNALBASE_HPP
+#define PULSEVIEW_PV_DATA_MATHSIGNALBASE_HPP
+
+#define exprtk_disable_rtl_io_file /* Potential security issue, doubt anyone would use those anyway */
+#define exprtk_disable_rtl_vecops /* Vector ops are rather useless for math channels */
+#define exprtk_disable_caseinsensitivity /* So that we can have both 't' and 'T' */
+
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+using std::atomic;
+using std::condition_variable;
+using std::mutex;
+using std::numeric_limits;
+using std::shared_ptr;
+
+using std::dynamic_pointer_cast;
+using std::make_shared;
+using std::min;
+using std::unique_lock;
+
+namespace pv {
+class Session;
+
+namespace data {
+
+class SignalBase;
+
+template
+struct fnc_sample;
+
+struct signal_data {
+ signal_data(const shared_ptr _sb) :
+ sb(_sb), sample_num(numeric_limits::max()), sample_value(0), ref(nullptr)
+ {}
+
+ const shared_ptr sb;
+ uint64_t sample_num;
+ double sample_value;
+ double* ref;
+};
+
+template
+class MathSignalBase : public SignalBase
+{
+protected:
+ static const int64_t ChunkLength = 256 * 1024;
+
+public:
+ MathSignalBase(pv::Session &session);
+ virtual ~MathSignalBase();
+
+ virtual void save_settings(QSettings &settings) const;
+ virtual void restore_settings(QSettings &settings);
+
+ QString get_expression() const;
+ void set_expression(QString expression);
+
+protected:
+ void set_error(uint8_t type, QString msg);
+
+ /**
+ * Returns the number of samples that can be worked on,
+ * i.e. the number of samples where samples are available
+ * for all connected channels.
+ * If the math signal uses no input channels, this is the
+ * number of samples in the session.
+ */
+ uint64_t get_working_sample_count(uint32_t segment_id) const;
+
+ void update_completeness(uint32_t segment_id, uint64_t output_sample_count);
+
+ void reset_generation();
+ virtual void begin_generation();
+
+ uint64_t generate_samples(uint32_t segment_id, const uint64_t start_sample,
+ const int64_t sample_count);
+ void generation_proc();
+
+ signal_data* signal_from_name(const std::string& name);
+ void update_signal_sample(signal_data* sig_data, uint32_t segment_id, uint64_t sample_num);
+
+ bool all_input_signals_enabled(QString &disabled_signals) const;
+
+protected:
+ pv::Session &session_;
+
+ uint64_t custom_sample_rate_;
+ uint64_t custom_sample_count_;
+ bool use_custom_sample_rate_, use_custom_sample_count_;
+ uint64_t generation_chunk_size_;
+ map input_signals_;
+
+ QString expression_;
+
+ uint8_t error_type_;
+
+ mutable mutex input_mutex_;
+ mutable condition_variable gen_input_cond_;
+
+ std::thread gen_thread_;
+ atomic gen_interrupt_;
+
+ exprtk::symbol_table *exprtk_unknown_symbol_table_, *exprtk_symbol_table_;
+ exprtk::expression *exprtk_expression_;
+ exprtk::parser *exprtk_parser_;
+ double exprtk_current_time_, exprtk_current_sample_;
+
+ fnc_sample* fnc_sample_;
+
+ // Give sig_sample access to the private helper functions
+ friend struct fnc_sample;
+};
+
+#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
+
+template
+struct fnc_sample : public exprtk::igeneric_function
+{
+ typedef typename exprtk::igeneric_function::parameter_list_t parameter_list_t;
+ typedef typename exprtk::igeneric_function::generic_type generic_type;
+ typedef typename generic_type::scalar_view scalar_t;
+ typedef typename generic_type::string_view string_t;
+
+ fnc_sample(MathSignalBase& owner) :
+ exprtk::igeneric_function("ST"), // Require channel name and sample number
+ owner_(owner),
+ sig_data(nullptr)
+ {
+ }
+
+ U 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 auto 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 U(sig_data->sample_value);
+ }
+
+ MathSignalBase& owner_;
+ uint32_t current_segment;
+ signal_data* sig_data;
+};
+
+template
+MathSignalBase::MathSignalBase(pv::Session &session) :
+ SignalBase(nullptr, T::math_channel_type),
+ session_(session),
+ use_custom_sample_rate_(false),
+ use_custom_sample_count_(false),
+ expression_(""),
+ error_type_(MATH_ERR_NONE),
+ exprtk_unknown_symbol_table_(nullptr),
+ exprtk_symbol_table_(nullptr),
+ exprtk_expression_(nullptr),
+ exprtk_parser_(nullptr),
+ fnc_sample_(nullptr)
+{
+ uint32_t sig_idx = session_.get_next_signal_index(T::math_channel_type);
+ set_name(QString(tr("Math%1")).arg(sig_idx));
+ set_color(T::SignalColors[(sig_idx - 1) % countof(T::SignalColors)]);
+
+ set_data(std::make_shared());
+
+ connect(&session_, SIGNAL(capture_state_changed(int)),
+ this, SLOT(on_capture_state_changed(int)));
+}
+
+template
+MathSignalBase::~MathSignalBase()
+{
+ reset_generation();
+}
+
+template
+void MathSignalBase::save_settings(QSettings &settings) const
+{
+ SignalBase::save_settings(settings);
+
+ settings.setValue("expression", expression_);
+
+ settings.setValue("custom_sample_rate", (qulonglong)custom_sample_rate_);
+ settings.setValue("custom_sample_count", (qulonglong)custom_sample_count_);
+ settings.setValue("use_custom_sample_rate", use_custom_sample_rate_);
+ settings.setValue("use_custom_sample_count", use_custom_sample_count_);
+}
+
+template
+void MathSignalBase::restore_settings(QSettings &settings)
+{
+ SignalBase::restore_settings(settings);
+
+ if (settings.contains("expression"))
+ expression_ = settings.value("expression").toString();
+
+ if (settings.contains("custom_sample_rate"))
+ custom_sample_rate_ = settings.value("custom_sample_rate").toULongLong();
+
+ if (settings.contains("custom_sample_count"))
+ custom_sample_count_ = settings.value("custom_sample_count").toULongLong();
+
+ if (settings.contains("use_custom_sample_rate"))
+ use_custom_sample_rate_ = settings.value("use_custom_sample_rate").toBool();
+
+ if (settings.contains("use_custom_sample_count"))
+ use_custom_sample_count_ = settings.value("use_custom_sample_count").toBool();
+}
+
+template
+QString MathSignalBase::get_expression() const
+{
+ return expression_;
+}
+
+template
+void MathSignalBase::set_expression(QString expression)
+{
+ expression_ = expression;
+
+ begin_generation();
+}
+
+template
+void MathSignalBase::set_error(uint8_t type, QString msg)
+{
+ error_type_ = type;
+ error_message_ = msg;
+ // TODO Emulate noquote()
+ qDebug().nospace() << name() << ": " << msg << "(Expression: '" + expression_ + "')";
+
+ error_message_changed(msg);
+}
+
+template
+uint64_t MathSignalBase::get_working_sample_count(uint32_t segment_id) const
+{
+ // The working sample count is the highest sample number for
+ // which all used signals have data available, so go through all
+ // channels and use the lowest overall sample count of the segment
+
+ int64_t result = std::numeric_limits::max();
+
+ if (use_custom_sample_count_)
+ // A custom sample count implies that only one segment will be created
+ result = (segment_id == 0) ? custom_sample_count_ : 0;
+ else {
+ if (input_signals_.size() > 0) {
+ for (auto input_signal : input_signals_) {
+ const shared_ptr& sb = input_signal.second.sb;
+
+ shared_ptr a = sb->typed_data();
+ auto typed_segments = a->typed_segments();
+
+ if (typed_segments.size() == 0) {
+ result = 0;
+ continue;
+ }
+
+ const uint32_t highest_segment_id = (typed_segments.size() - 1);
+ if (segment_id > highest_segment_id)
+ continue;
+
+ const auto segment = typed_segments.at(segment_id);
+ result = min(result, (int64_t)segment->get_sample_count());
+ }
+ } else
+ result = session_.get_segment_sample_count(segment_id);
+ }
+
+ return result;
+}
+
+template
+void MathSignalBase::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& sb = input_signal.second.sb;
+
+ auto a = sb->typed_data();
+ auto typed_segments = a->typed_segments();
+
+ if (typed_segments.size() == 0) {
+ output_complete = false;
+ continue;
+ }
+
+ const uint32_t highest_segment_id = (typed_segments.size() - 1);
+ if (segment_id > highest_segment_id) {
+ output_complete = false;
+ continue;
+ }
+
+ const auto segment = typed_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)
+ typed_data()->typed_segments().at(segment_id)->set_complete();
+}
+
+template
+void MathSignalBase::reset_generation()
+{
+ if (gen_thread_.joinable()) {
+ gen_interrupt_ = true;
+ gen_input_cond_.notify_one();
+ gen_thread_.join();
+ }
+
+ data_->clear();
+ input_signals_.clear();
+
+ if (exprtk_parser_) {
+ delete exprtk_parser_;
+ exprtk_parser_ = nullptr;
+ }
+
+ if (exprtk_expression_) {
+ delete exprtk_expression_;
+ exprtk_expression_ = 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_.clear();
+ error_type_ = MATH_ERR_NONE;
+ // TODO Emulate noquote()
+ qDebug().nospace() << name() << ": Error cleared";
+ }
+
+ generation_chunk_size_ = ChunkLength;
+}
+
+template
+void MathSignalBase::begin_generation()
+{
+ reset_generation();
+
+ if (expression_.isEmpty()) {
+ set_error(MATH_ERR_EMPTY_EXPR, tr("No expression defined, nothing to do"));
+ return;
+ }
+
+ disconnect(&session_, SIGNAL(data_received()), this, SLOT(on_data_received()));
+
+ for (const shared_ptr& sb : session_.signalbases()) {
+ if (sb->typed_data())
+ disconnect(sb->typed_data().get(), nullptr, this, SLOT(on_data_received()));
+ disconnect(sb.get(), nullptr, this, SLOT(on_enabled_changed()));
+ }
+
+ fnc_sample_ = new fnc_sample(*this);
+
+ exprtk_unknown_symbol_table_ = new exprtk::symbol_table();
+
+ exprtk_symbol_table_ = new exprtk::symbol_table();
+ 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();
+ exprtk_expression_->register_symbol_table(*exprtk_unknown_symbol_table_);
+ exprtk_expression_->register_symbol_table(*exprtk_symbol_table_);
+
+ exprtk_parser_ = new exprtk::parser();
+ 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 unknowns;
+ exprtk_unknown_symbol_table_->get_variable_list(unknowns);
+ for (string& unknown : unknowns) {
+ signal_data* sig_data = signal_from_name(unknown);
+ const shared_ptr signal = (sig_data) ? (sig_data->sb) : nullptr;
+ if (!signal || (!signal->typed_data())) {
+ set_error(MATH_ERR_INVALID_SIGNAL, QString(tr(T::InvalidSignal)) \
+ .arg(QString::fromStdString(unknown)));
+ } else
+ sig_data->ref = &(exprtk_unknown_symbol_table_->variable_ref(unknown));
+ }
+ }
+
+ 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(&MathSignalBase::generation_proc, this);
+ }
+}
+
+template
+uint64_t MathSignalBase::generate_samples(uint32_t segment_id, const uint64_t start_sample,
+ const int64_t sample_count)
+{
+ uint64_t count = 0;
+
+ auto local_data = dynamic_pointer_cast(data_);
+ auto segment = local_data->typed_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;
+
+ auto *sample_data = new typename T::sample_t[sample_count];
+
+ for (int64_t i = 0; i < sample_count; i++) {
+ 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, count, 1);
+
+ delete[] sample_data;
+
+ return count;
+}
+
+template
+void MathSignalBase::generation_proc()
+{
+ // Don't do anything until we have a valid sample rate
+ do {
+ if (use_custom_sample_rate_)
+ data_->set_samplerate(custom_sample_rate_);
+ else
+ data_->set_samplerate(session_.get_samplerate());
+
+ if (data_->get_samplerate() == 1) {
+ unique_lock gen_input_lock(input_mutex_);
+ gen_input_cond_.wait(gen_input_lock);
+ }
+ } while ((!gen_interrupt_) && (data_->get_samplerate() == 1));
+
+ if (gen_interrupt_)
+ return;
+
+ uint32_t segment_id = 0;
+ auto local_typed_data = typed_data();
+
+ // Create initial data segment
+ auto output_segment =
+ make_shared(*local_typed_data.get(), segment_id, local_typed_data->get_samplerate());
+ local_typed_data->push_segment(output_segment);
+
+ // Create data samples
+ do {
+ const uint64_t input_sample_count = get_working_sample_count(segment_id);
+ const uint64_t output_sample_count = output_segment->get_sample_count();
+
+ const uint64_t samples_to_process =
+ (input_sample_count > output_sample_count) ?
+ (input_sample_count - output_sample_count) : 0;
+
+ // Process the samples if necessary...
+ if (samples_to_process > 0) {
+ uint64_t processed_samples = 0;
+ do {
+ const uint64_t start_sample = output_sample_count + processed_samples;
+ uint64_t sample_count =
+ min(samples_to_process - processed_samples, generation_chunk_size_);
+
+ sample_count = generate_samples(segment_id, start_sample, sample_count);
+ processed_samples += sample_count;
+
+ // Notify consumers of this signal's data
+ samples_added(segment_id, start_sample, start_sample + processed_samples);
+ } while (!gen_interrupt_ && (processed_samples < samples_to_process));
+ }
+
+ 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(*local_typed_data.get(), segment_id, local_typed_data->get_samplerate());
+ local_typed_data->push_segment(output_segment);
+ }
+
+ if (!gen_interrupt_ && (samples_to_process == 0)) {
+ // Wait for more input
+ unique_lock gen_input_lock(input_mutex_);
+ gen_input_cond_.wait(gen_input_lock);
+ }
+ } while (!gen_interrupt_);
+}
+
+template
+signal_data* MathSignalBase::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 > signalbases = session_.signalbases();
+
+ for (const shared_ptr& sb : signalbases)
+ if (sb->name() == sig_name) {
+ if (!sb->typed_data())
+ continue;
+
+ connect(sb->typed_data().get(), SIGNAL(samples_added(SharedPtrToSegment, uint64_t, uint64_t)),
+ this, SLOT(on_data_received()));
+ connect(sb->typed_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(T::InvalidSignal)) \
+ .arg(QString::fromStdString(name)));
+
+ return nullptr;
+}
+
+template
+void MathSignalBase::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 auto local_data = sig_data->sb->typed_data();
+ assert(local_data);
+
+ assert(segment_id < local_data->typed_segments().size());
+
+ const auto segment = local_data->typed_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, sig_data->sb->index());
+ 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;
+}
+
+template
+bool MathSignalBase::all_input_signals_enabled(QString &disabled_signals) const
+{
+ bool all_enabled = true;
+
+ disabled_signals.clear();
+
+ for (auto input_signal : input_signals_) {
+ const shared_ptr& sb = input_signal.second.sb;
+
+ if (!sb->enabled()) {
+ all_enabled = false;
+ disabled_signals += disabled_signals.isEmpty() ?
+ sb->name() : ", " + sb->name();
+ }
+ }
+
+ return all_enabled;
+}
+
+} // namespace data
+} // namespace pv
+
+#endif // PULSEVIEW_PV_DATA_MATHSIGNALBASE_HPP
diff --git a/pv/data/signalbase.cpp b/pv/data/signalbase.cpp
index 5f95ec33..6dc67a8c 100644
--- a/pv/data/signalbase.cpp
+++ b/pv/data/signalbase.cpp
@@ -42,33 +42,6 @@ using std::unique_lock;
namespace pv {
namespace data {
-const QColor SignalBase::AnalogSignalColors[8] =
-{
- QColor(0xC4, 0xA0, 0x00), // Yellow HSV: 49 / 100 / 77
- QColor(0x87, 0x20, 0x7A), // Magenta HSV: 308 / 70 / 53
- QColor(0x20, 0x4A, 0x87), // Blue HSV: 216 / 76 / 53
- QColor(0x4E, 0x9A, 0x06), // Green HSV: 91 / 96 / 60
- QColor(0xBF, 0x6E, 0x00), // Orange HSV: 35 / 100 / 75
- QColor(0x5E, 0x20, 0x80), // Purple HSV: 280 / 75 / 50
- QColor(0x20, 0x80, 0x7A), // Turqoise HSV: 177 / 75 / 50
- QColor(0x80, 0x20, 0x24) // Red HSV: 358 / 75 / 50
-};
-
-const QColor SignalBase::LogicSignalColors[10] =
-{
- QColor(0x16, 0x19, 0x1A), // Black
- QColor(0x8F, 0x52, 0x02), // Brown
- QColor(0xCC, 0x00, 0x00), // Red
- QColor(0xF5, 0x79, 0x00), // Orange
- QColor(0xED, 0xD4, 0x00), // Yellow
- QColor(0x73, 0xD2, 0x16), // Green
- QColor(0x34, 0x65, 0xA4), // Blue
- QColor(0x75, 0x50, 0x7B), // Violet
- QColor(0x88, 0x8A, 0x85), // Grey
- QColor(0xEE, 0xEE, 0xEC), // White
-};
-
-
const int SignalBase::ColorBGAlpha = 8 * 256 / 100;
const uint64_t SignalBase::ConversionBlockSize = 4096;
const uint32_t SignalBase::ConversionDelay = 1000; // 1 second
@@ -140,9 +113,9 @@ SignalBase::SignalBase(shared_ptr channel, ChannelType channel_
// Only logic and analog SR channels can have their colors auto-set
// because for them, we have an index that can be used
if (channel_type == LogicChannel)
- set_color(LogicSignalColors[index() % countof(LogicSignalColors)]);
+ set_color(Logic::SignalColors[index() % countof(Logic::SignalColors)]);
else if (channel_type == AnalogChannel)
- set_color(AnalogSignalColors[index() % countof(AnalogSignalColors)]);
+ set_color(Analog::SignalColors[index() % countof(Analog::SignalColors)]);
}
SignalBase::~SignalBase()
@@ -330,6 +303,14 @@ shared_ptr SignalBase::logic_data() const
return result;
}
+/**
+ * logic specialization
+ */
+template <> shared_ptr SignalBase::typed_data() const
+{
+ return logic_data();
+}
+
shared_ptr SignalBase::data() const
{
return data_;
@@ -795,7 +776,7 @@ void SignalBase::conversion_thread_proc()
// Create the initial logic data segment if needed
if (logic_data->logic_segments().size() == 0) {
shared_ptr new_segment =
- make_shared(*logic_data.get(), 0, 1, asegment->samplerate());
+ make_shared(*logic_data.get(), 0, asegment->samplerate());
logic_data->push_segment(new_segment);
}
diff --git a/pv/data/signalbase.hpp b/pv/data/signalbase.hpp
index 19f8143d..86b019e1 100644
--- a/pv/data/signalbase.hpp
+++ b/pv/data/signalbase.hpp
@@ -91,10 +91,11 @@ class SignalBase : public QObject, public enable_shared_from_this
public:
enum ChannelType {
- AnalogChannel = 1, ///< Analog data
- LogicChannel, ///< Logic data
- DecodeChannel, ///< Protocol Decoder channel using libsigrokdecode
- MathChannel ///< Virtual channel generated by math operations
+ AnalogChannel = 1, ///< Analog data
+ LogicChannel, ///< Logic data
+ DecodeChannel, ///< Protocol Decoder channel using libsigrokdecode
+ AnalogMathChannel, ///< Virtual channel generated by analog math operations
+ LogicMathChannel ///< Virtual channel generated by logic math operations
};
enum ConversionType {
@@ -113,9 +114,6 @@ class SignalBase : public QObject, public enable_shared_from_this
DynamicPreset = 0 ///< Conversion uses calculated values
};
- static const QColor AnalogSignalColors[8];
- static const QColor LogicSignalColors[10];
-
private:
static const int ColorBGAlpha;
static const uint64_t ConversionBlockSize;
@@ -256,6 +254,11 @@ class SignalBase : public QObject, public enable_shared_from_this
*/
shared_ptr data() const;
+ /**
+ * Get the data as a typed object
+ */
+ template shared_ptr typed_data() const;
+
/**
* Determines whether a given segment is complete (i.e. end-of-frame has
* been seen). It only considers the original data, not the converted data.
@@ -426,6 +429,17 @@ private Q_SLOTS:
QString error_message_;
};
+/**
+ * Get the data as a typed object
+ */
+template shared_ptr SignalBase::typed_data() const
+{
+ if (!data_)
+ return nullptr;
+
+ return std::dynamic_pointer_cast(data_);
+}
+
} // namespace data
} // namespace pv
diff --git a/pv/session.cpp b/pv/session.cpp
index f992d9f1..be3540a6 100644
--- a/pv/session.cpp
+++ b/pv/session.cpp
@@ -390,8 +390,10 @@ void Session::restore_setup(QSettings &settings)
SignalBase::ChannelType type = (SignalBase::ChannelType)settings.value("type").toInt();
shared_ptr signal;
- if (type == SignalBase::MathChannel)
- signal = make_shared(*this);
+ if (type == SignalBase::AnalogMathChannel)
+ signal = make_shared(*this);
+ else if (type == SignalBase::LogicMathChannel)
+ signal = make_shared(*this);
else
qWarning() << tr("Can't restore generated signal of unknown type %1 (%2)") \
.arg((int)type) \
@@ -868,7 +870,8 @@ void Session::register_view(shared_ptr view)
switch (signalbase->type()) {
case data::SignalBase::AnalogChannel:
case data::SignalBase::LogicChannel:
- case data::SignalBase::MathChannel:
+ case data::SignalBase::AnalogMathChannel:
+ case data::SignalBase::LogicMathChannel:
view->add_signalbase(signalbase);
break;
case data::SignalBase::DecodeChannel:
diff --git a/pv/toolbars/mainbar.cpp b/pv/toolbars/mainbar.cpp
index 39c290df..01dc6066 100644
--- a/pv/toolbars/mainbar.cpp
+++ b/pv/toolbars/mainbar.cpp
@@ -114,7 +114,8 @@ MainBar::MainBar(Session &session, QWidget *parent, pv::views::trace::View *view
#ifdef ENABLE_DECODE
add_decoder_button_(new QToolButton()),
#endif
- add_math_signal_button_(new QToolButton())
+ add_math_signal_button_(new QToolButton()),
+ add_math_logic_signal_button_(new QToolButton())
{
setObjectName(QString::fromUtf8("MainBar"));
@@ -252,6 +253,14 @@ MainBar::MainBar(Session &session, QWidget *parent, pv::views::trace::View *view
connect(add_math_signal_button_, SIGNAL(clicked()),
this, SLOT(on_add_math_signal_clicked()));
+ // Setup the math logic signal button
+ add_math_logic_signal_button_->setIcon(QIcon(":/icons/add-math-logic-signal.svg"));
+ add_math_logic_signal_button_->setPopupMode(QToolButton::InstantPopup);
+ add_math_logic_signal_button_->setToolTip(tr("Add math logic signal"));
+ add_math_logic_signal_button_->setShortcut(QKeySequence(Qt::Key_M));
+
+ connect(add_math_logic_signal_button_, SIGNAL(clicked()),
+ this, SLOT(on_add_math_logic_signal_clicked()));
connect(&sample_count_, SIGNAL(value_changed()),
this, SLOT(on_sample_count_changed()));
@@ -908,7 +917,13 @@ void MainBar::on_add_decoder_clicked()
void MainBar::on_add_math_signal_clicked()
{
- shared_ptr signal = make_shared(session_);
+ shared_ptr signal = make_shared(session_);
+ session_.add_generated_signal(signal);
+}
+
+void MainBar::on_add_math_logic_signal_clicked()
+{
+ shared_ptr signal = make_shared(session_);
session_.add_generated_signal(signal);
}
@@ -932,6 +947,7 @@ void MainBar::add_toolbar_widgets()
addWidget(add_decoder_button_);
#endif
addWidget(add_math_signal_button_);
+ addWidget(add_math_logic_signal_button_);
}
bool MainBar::eventFilter(QObject *watched, QEvent *event)
diff --git a/pv/toolbars/mainbar.hpp b/pv/toolbars/mainbar.hpp
index e4aa39b6..08f4ebe0 100644
--- a/pv/toolbars/mainbar.hpp
+++ b/pv/toolbars/mainbar.hpp
@@ -146,6 +146,7 @@ private Q_SLOTS:
void on_add_decoder_clicked();
void on_add_math_signal_clicked();
+ void on_add_math_logic_signal_clicked();
protected:
void add_toolbar_widgets();
@@ -188,6 +189,7 @@ private Q_SLOTS:
#endif
QToolButton *add_math_signal_button_;
+ QToolButton *add_math_logic_signal_button_;
};
} // namespace toolbars
diff --git a/pv/views/trace/mathsignal.cpp b/pv/views/trace/mathsignal.cpp
index 2d242b99..9e4f1ae4 100644
--- a/pv/views/trace/mathsignal.cpp
+++ b/pv/views/trace/mathsignal.cpp
@@ -78,11 +78,12 @@ const vector< pair > MathEditDialog::Examples = {
MathEditDialog::MathEditDialog(pv::Session &session,
- shared_ptr math_signal, QWidget *parent) :
+ function math_signal_expr_setter, QString old_expression,
+ QWidget *parent) :
QDialog(parent),
session_(session),
- math_signal_(math_signal),
- old_expression_(math_signal->get_expression()),
+ math_signal_expr_setter_(math_signal_expr_setter),
+ old_expression_(old_expression),
expr_edit_(new QPlainTextEdit())
{
setWindowTitle(tr("Math Expression Editor"));
@@ -272,22 +273,22 @@ void MathEditDialog::set_expr(const QString &expr)
void MathEditDialog::accept()
{
- math_signal_->set_expression(expr_edit_->document()->toPlainText());
+ math_signal_expr_setter_(expr_edit_->document()->toPlainText());
QDialog::accept();
}
void MathEditDialog::reject()
{
- math_signal_->set_expression(old_expression_);
+ math_signal_expr_setter_(old_expression_);
QDialog::reject();
}
-MathSignal::MathSignal(
+MathSignalAnalog::MathSignalAnalog(
pv::Session &session,
shared_ptr base) :
AnalogSignal(session, base),
- math_signal_(dynamic_pointer_cast(base))
+ math_signal_(dynamic_pointer_cast(base))
{
delayed_expr_updater_.setSingleShot(true);
delayed_expr_updater_.setInterval(MATHSIGNAL_INPUT_TIMEOUT);
@@ -295,7 +296,7 @@ MathSignal::MathSignal(
this, [this]() { math_signal_->set_expression(expression_edit_->text()); });
}
-void MathSignal::populate_popup_form(QWidget *parent, QFormLayout *form)
+void MathSignalAnalog::populate_popup_form(QWidget *parent, QFormLayout *form)
{
AnalogSignal::populate_popup_form(parent, form);
@@ -331,7 +332,7 @@ void MathSignal::populate_popup_form(QWidget *parent, QFormLayout *form)
form->addRow(tr("Sample rate"), sample_rate_cb_);
}
-void MathSignal::on_expression_changed(const QString &text)
+void MathSignalAnalog::on_expression_changed(const QString &text)
{
(void)text;
@@ -339,14 +340,62 @@ void MathSignal::on_expression_changed(const QString &text)
delayed_expr_updater_.start();
}
-void MathSignal::on_sample_count_changed(const QString &text)
+void MathSignalAnalog::on_sample_count_changed(const QString &text)
{
(void)text;
}
-void MathSignal::on_edit_clicked()
+void MathSignalAnalog::on_edit_clicked()
{
- MathEditDialog dlg(session_, math_signal_);
+ MathEditDialog dlg(session_, [this](QString expr) { math_signal_->set_expression(expr); },
+ math_signal_->get_expression());
+ dlg.set_expr(expression_edit_->text());
+
+ dlg.exec();
+}
+
+MathSignalLogic::MathSignalLogic(
+ pv::Session &session,
+ shared_ptr base) :
+ LogicSignal(session, base),
+ math_signal_(dynamic_pointer_cast(base))
+{
+ delayed_expr_updater_.setSingleShot(true);
+ delayed_expr_updater_.setInterval(MATHSIGNAL_INPUT_TIMEOUT);
+ connect(&delayed_expr_updater_, &QTimer::timeout,
+ this, [this]() { math_signal_->set_expression(expression_edit_->text()); });
+}
+
+void MathSignalLogic::populate_popup_form(QWidget *parent, QFormLayout *form)
+{
+ LogicSignal::populate_popup_form(parent, form);
+
+ expression_edit_ = new QLineEdit();
+ expression_edit_->setText(math_signal_->get_expression());
+
+ const QIcon edit_icon(QIcon::fromTheme("edit", QIcon(":/icons/math.svg")));
+ QAction *edit_action =
+ expression_edit_->addAction(edit_icon, QLineEdit::TrailingPosition);
+
+ connect(expression_edit_, SIGNAL(textEdited(QString)),
+ this, SLOT(on_expression_changed(QString)));
+ connect(edit_action, SIGNAL(triggered(bool)),
+ this, SLOT(on_edit_clicked()));
+ form->addRow(tr("Expression"), expression_edit_);
+}
+
+void MathSignalLogic::on_expression_changed(const QString &text)
+{
+ (void)text;
+
+ // Restart update timer
+ delayed_expr_updater_.start();
+}
+
+void MathSignalLogic::on_edit_clicked()
+{
+ MathEditDialog dlg(session_, [this](QString expr) { math_signal_->set_expression(expr); },
+ math_signal_->get_expression());
dlg.set_expr(expression_edit_->text());
dlg.exec();
diff --git a/pv/views/trace/mathsignal.hpp b/pv/views/trace/mathsignal.hpp
index 6af708e5..c6d45849 100644
--- a/pv/views/trace/mathsignal.hpp
+++ b/pv/views/trace/mathsignal.hpp
@@ -22,6 +22,7 @@
#include
#include
+#include
#include
#include
@@ -36,6 +37,7 @@ using std::pair;
using std::shared_ptr;
using std::string;
using std::vector;
+using std::function;
namespace pv {
namespace views {
@@ -49,8 +51,8 @@ class MathEditDialog : public QDialog
static const vector< pair > Examples;
public:
- MathEditDialog(pv::Session &session, shared_ptr math_signal,
- QWidget *parent = nullptr);
+ MathEditDialog(pv::Session &session, function math_signal_expr_setter,
+ QString old_expression, QWidget *parent = nullptr);
void set_expr(const QString &expr);
@@ -60,24 +62,25 @@ private Q_SLOTS:
private:
pv::Session &session_;
- shared_ptr math_signal_;
+ shared_ptr math_signal_;
+ function math_signal_expr_setter_;
QString old_expression_;
QPlainTextEdit *expr_edit_;
};
-class MathSignal : public AnalogSignal
+class MathSignalAnalog : public AnalogSignal
{
Q_OBJECT
public:
- MathSignal(pv::Session &session, shared_ptr base);
+ MathSignalAnalog(pv::Session &session, shared_ptr base);
protected:
void populate_popup_form(QWidget *parent, QFormLayout *form);
- shared_ptr math_signal_;
+ shared_ptr math_signal_;
private Q_SLOTS:
void on_expression_changed(const QString &text);
@@ -92,6 +95,29 @@ private Q_SLOTS:
QTimer delayed_expr_updater_, delayed_count_updater_, delayed_rate_updater_;
};
+class MathSignalLogic : public LogicSignal
+{
+ Q_OBJECT
+
+public:
+ MathSignalLogic(pv::Session &session, shared_ptr base);
+
+protected:
+ void populate_popup_form(QWidget *parent, QFormLayout *form);
+
+ shared_ptr math_signal_;
+
+private Q_SLOTS:
+ void on_expression_changed(const QString &text);
+
+ void on_edit_clicked();
+
+private:
+ QLineEdit *expression_edit_;
+ QComboBox *sample_count_cb_, *sample_rate_cb_;
+ QTimer delayed_expr_updater_, delayed_count_updater_, delayed_rate_updater_;
+};
+
} // namespace trace
} // namespace views
} // namespace pv
diff --git a/pv/views/trace/view.cpp b/pv/views/trace/view.cpp
index f1de3b20..61d45d04 100644
--- a/pv/views/trace/view.cpp
+++ b/pv/views/trace/view.cpp
@@ -366,8 +366,12 @@ void View::add_signalbase(const shared_ptr signalbase)
signal = shared_ptr(new AnalogSignal(session_, signalbase));
break;
- case SignalBase::MathChannel:
- signal = shared_ptr(new MathSignal(session_, signalbase));
+ case SignalBase::AnalogMathChannel:
+ signal = shared_ptr(new MathSignalAnalog(session_, signalbase));
+ break;
+
+ case SignalBase::LogicMathChannel:
+ signal = shared_ptr(new MathSignalLogic(session_, signalbase));
break;
default: