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 @@ + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Jakub Steiner + + + http://jimmac.musichall.cz + + + + + accessibility + assist + + + + + + + + + + + + + + + + + + + + + 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: