From 5add000e21a4dde7964344ffb48727e5e30f78d4 Mon Sep 17 00:00:00 2001 From: David Graeff Date: Thu, 11 Jan 2018 22:52:39 +0100 Subject: [PATCH] Introduce new directory 'post' for post processing. Move all post processing funtionality there. --- openhantek/src/analyse/dataanalyzer.cpp | 338 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- openhantek/src/analyse/dataanalyzer.h | 50 -------------------------------------------------- openhantek/src/analyse/dataanalyzerresult.cpp | 36 ------------------------------------ openhantek/src/analyse/dataanalyzerresult.h | 42 ------------------------------------------ openhantek/src/analyse/enums.cpp | 8 -------- openhantek/src/analyse/enums.h | 43 ------------------------------------------- openhantek/src/hantekdso/softwaretrigger.cpp | 73 ------------------------------------------------------------------------- openhantek/src/hantekdso/softwaretrigger.h | 22 ---------------------- openhantek/src/post/enums.cpp | 8 ++++++++ openhantek/src/post/enums.h | 43 +++++++++++++++++++++++++++++++++++++++++++ openhantek/src/post/graphgenerator.cpp | 180 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ openhantek/src/post/graphgenerator.h | 42 ++++++++++++++++++++++++++++++++++++++++++ openhantek/src/post/mathchannelgenerator.cpp | 45 +++++++++++++++++++++++++++++++++++++++++++++ openhantek/src/post/mathchannelgenerator.h | 19 +++++++++++++++++++ openhantek/src/post/postprocessing.cpp | 29 +++++++++++++++++++++++++++++ openhantek/src/post/postprocessing.h | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ openhantek/src/post/ppresult.cpp | 33 +++++++++++++++++++++++++++++++++ openhantek/src/post/ppresult.h | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ openhantek/src/post/processor.h | 8 ++++++++ openhantek/src/post/softwaretrigger.cpp | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ openhantek/src/post/softwaretrigger.h | 22 ++++++++++++++++++++++ openhantek/src/post/spectrumgenerator.cpp | 226 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ openhantek/src/post/spectrumgenerator.h | 35 +++++++++++++++++++++++++++++++++++ 23 files changed, 866 insertions(+), 612 deletions(-) delete mode 100644 openhantek/src/analyse/dataanalyzer.cpp delete mode 100644 openhantek/src/analyse/dataanalyzer.h delete mode 100644 openhantek/src/analyse/dataanalyzerresult.cpp delete mode 100644 openhantek/src/analyse/dataanalyzerresult.h delete mode 100644 openhantek/src/analyse/enums.cpp delete mode 100644 openhantek/src/analyse/enums.h delete mode 100644 openhantek/src/hantekdso/softwaretrigger.cpp delete mode 100644 openhantek/src/hantekdso/softwaretrigger.h create mode 100644 openhantek/src/post/enums.cpp create mode 100644 openhantek/src/post/enums.h create mode 100644 openhantek/src/post/graphgenerator.cpp create mode 100644 openhantek/src/post/graphgenerator.h create mode 100644 openhantek/src/post/mathchannelgenerator.cpp create mode 100644 openhantek/src/post/mathchannelgenerator.h create mode 100644 openhantek/src/post/postprocessing.cpp create mode 100644 openhantek/src/post/postprocessing.h create mode 100644 openhantek/src/post/ppresult.cpp create mode 100644 openhantek/src/post/ppresult.h create mode 100644 openhantek/src/post/processor.h create mode 100644 openhantek/src/post/softwaretrigger.cpp create mode 100644 openhantek/src/post/softwaretrigger.h create mode 100644 openhantek/src/post/spectrumgenerator.cpp create mode 100644 openhantek/src/post/spectrumgenerator.h diff --git a/openhantek/src/analyse/dataanalyzer.cpp b/openhantek/src/analyse/dataanalyzer.cpp deleted file mode 100644 index 85a2f66..0000000 --- a/openhantek/src/analyse/dataanalyzer.cpp +++ /dev/null @@ -1,338 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ - -#define _USE_MATH_DEFINES -#include - -#include -#include -#include - -#include - -#include "dataanalyzer.h" - -#include "glscope.h" -#include "settings.h" -#include "utils/printutils.h" - -std::unique_ptr DataAnalyzer::convertData(const DSOsamples *data, const DsoSettingsScope *scope, unsigned physicalChannels) { - QReadLocker locker(&data->lock); - - unsigned int channelCount = (unsigned int)scope->voltage.size(); - - std::unique_ptr result = - std::unique_ptr(new DataAnalyzerResult(channelCount)); - - for (ChannelID channel = 0; channel < channelCount; ++channel) { - DataChannel *const channelData = result->modifyData(channel); - - bool gotDataForChannel = channel < physicalChannels && channel < (unsigned int)data->data.size() && - !data->data.at(channel).empty(); - bool isMathChannel = channel >= physicalChannels && - (scope->voltage[channel].used || scope->spectrum[channel].used) && - result->channelCount() >= 2 && !result->data(0)->voltage.sample.empty() && - !result->data(1)->voltage.sample.empty(); - - if (!gotDataForChannel && !isMathChannel) { - // Clear unused channels - channelData->voltage.sample.clear(); - result->modifyData(physicalChannels)->voltage.interval = 0; - continue; - } - - // Set sampling interval - const double interval = 1.0 / data->samplerate; - if (interval != channelData->voltage.interval) { - channelData->voltage.interval = interval; - if (data->append) // Clear roll buffer if the samplerate changed - channelData->voltage.sample.clear(); - } - - unsigned int size; - if (channel < physicalChannels) { - size = (unsigned) data->data.at(channel).size(); - if (data->append) size += channelData->voltage.sample.size(); - result->challengeMaxSamples(size); - } else - size = result->getMaxSamples(); - - // Physical channels - if (channel < physicalChannels) { - // Copy the buffer of the oscilloscope into the sample buffer - if (data->append) - channelData->voltage.sample.insert(channelData->voltage.sample.end(), data->data.at(channel).begin(), - data->data.at(channel).end()); - else - channelData->voltage.sample = data->data.at(channel); - } else { // Math channel - // Resize the sample vector - channelData->voltage.sample.resize(size); - // Set sampling interval - result->modifyData(physicalChannels)->voltage.interval = result->data(0)->voltage.interval; - - // Resize the sample vector - result->modifyData(physicalChannels) - ->voltage.sample.resize( - qMin(result->data(0)->voltage.sample.size(), result->data(1)->voltage.sample.size())); - - // Calculate values and write them into the sample buffer - std::vector::const_iterator ch1Iterator = result->data(0)->voltage.sample.begin(); - std::vector::const_iterator ch2Iterator = result->data(1)->voltage.sample.begin(); - std::vector &resultData = result->modifyData(physicalChannels)->voltage.sample; - for (std::vector::iterator resultIterator = resultData.begin(); resultIterator != resultData.end(); - ++resultIterator) { - switch (scope->voltage[physicalChannels].math) { - case Dso::MathMode::ADD_CH1_CH2: - *resultIterator = *ch1Iterator + *ch2Iterator; - break; - case Dso::MathMode::SUB_CH2_FROM_CH1: - *resultIterator = *ch1Iterator - *ch2Iterator; - break; - case Dso::MathMode::SUB_CH1_FROM_CH2: - *resultIterator = *ch2Iterator - *ch1Iterator; - break; - } - ++ch1Iterator; - ++ch2Iterator; - } - } - } - return result; -} - -/// \brief Analyzes the data from the dso. -DataAnalyzer::~DataAnalyzer() { - if (window) fftw_free(window); -} - -void DataAnalyzer::applySettings(DsoSettings *settings) { this->settings = settings; } - -void DataAnalyzer::setSourceData(const DSOsamples *data) { sourceData = data; } - -std::unique_ptr DataAnalyzer::getNextResult() { return std::move(lastResult); } - -void DataAnalyzer::samplesAvailable() { - if (sourceData == nullptr) return; - std::unique_ptr result = convertData(sourceData, &settings->scope,settings->deviceSpecification->channels); - spectrumAnalysis(result.get(), lastWindow, lastRecordLength, window, &settings->scope); - lastResult.swap(result); - emit analyzed(); -} - -void DataAnalyzer::spectrumAnalysis(DataAnalyzerResult *result, Dso::WindowFunction &lastWindow, - unsigned int lastRecordLength, double *&lastWindowBuffer, - const DsoSettingsScope *scope) { - // Calculate frequencies, peak-to-peak voltages and spectrums - for (ChannelID channel = 0; channel < result->channelCount(); ++channel) { - DataChannel *const channelData = result->modifyData(channel); - - if (channelData->voltage.sample.empty()) { - // Clear unused channels - channelData->spectrum.interval = 0; - channelData->spectrum.sample.clear(); - continue; - } - - // Calculate new window - size_t sampleCount = channelData->voltage.sample.size(); - if (!lastWindowBuffer || lastWindow != scope->spectrumWindow || lastRecordLength != sampleCount) { - if (lastWindowBuffer) fftw_free(lastWindowBuffer); - lastWindowBuffer = fftw_alloc_real(sampleCount); - lastRecordLength = (unsigned)sampleCount; - - unsigned int windowEnd = lastRecordLength - 1; - lastWindow = scope->spectrumWindow; - - switch (scope->spectrumWindow) { - case Dso::WindowFunction::HAMMING: - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) - *(lastWindowBuffer + windowPosition) = 0.54 - 0.46 * cos(2.0 * M_PI * windowPosition / windowEnd); - break; - case Dso::WindowFunction::HANN: - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) - *(lastWindowBuffer + windowPosition) = 0.5 * (1.0 - cos(2.0 * M_PI * windowPosition / windowEnd)); - break; - case Dso::WindowFunction::COSINE: - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) - *(lastWindowBuffer + windowPosition) = sin(M_PI * windowPosition / windowEnd); - break; - case Dso::WindowFunction::LANCZOS: - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) { - double sincParameter = (2.0 * windowPosition / windowEnd - 1.0) * M_PI; - if (sincParameter == 0) - *(lastWindowBuffer + windowPosition) = 1; - else - *(lastWindowBuffer + windowPosition) = sin(sincParameter) / sincParameter; - } - break; - case Dso::WindowFunction::BARTLETT: - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) - *(lastWindowBuffer + windowPosition) = - 2.0 / windowEnd * (windowEnd / 2 - std::abs((double)(windowPosition - windowEnd / 2.0))); - break; - case Dso::WindowFunction::TRIANGULAR: - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) - *(lastWindowBuffer + windowPosition) = - 2.0 / lastRecordLength * - (lastRecordLength / 2 - std::abs((double)(windowPosition - windowEnd / 2.0))); - break; - case Dso::WindowFunction::GAUSS: { - double sigma = 0.4; - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) - *(lastWindowBuffer + windowPosition) = - exp(-0.5 * pow(((windowPosition - windowEnd / 2) / (sigma * windowEnd / 2)), 2)); - } break; - case Dso::WindowFunction::BARTLETTHANN: - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) - *(lastWindowBuffer + windowPosition) = 0.62 - - 0.48 * std::abs((double)(windowPosition / windowEnd - 0.5)) - - 0.38 * cos(2.0 * M_PI * windowPosition / windowEnd); - break; - case Dso::WindowFunction::BLACKMAN: { - double alpha = 0.16; - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) - *(lastWindowBuffer + windowPosition) = (1 - alpha) / 2 - - 0.5 * cos(2.0 * M_PI * windowPosition / windowEnd) + - alpha / 2 * cos(4.0 * M_PI * windowPosition / windowEnd); - } break; - // case Dso::WindowFunction::WINDOW_KAISER: - // TODO WINDOW_KAISER - // double alpha = 3.0; - // for(unsigned int windowPosition = 0; windowPosition < - // lastRecordLength; ++windowPosition) - //*(window + windowPosition) = ; - // break; - case Dso::WindowFunction::NUTTALL: - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) - *(lastWindowBuffer + windowPosition) = 0.355768 - - 0.487396 * cos(2 * M_PI * windowPosition / windowEnd) + - 0.144232 * cos(4 * M_PI * windowPosition / windowEnd) - - 0.012604 * cos(6 * M_PI * windowPosition / windowEnd); - break; - case Dso::WindowFunction::BLACKMANHARRIS: - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) - *(lastWindowBuffer + windowPosition) = 0.35875 - - 0.48829 * cos(2 * M_PI * windowPosition / windowEnd) + - 0.14128 * cos(4 * M_PI * windowPosition / windowEnd) - - 0.01168 * cos(6 * M_PI * windowPosition / windowEnd); - break; - case Dso::WindowFunction::BLACKMANNUTTALL: - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) - *(lastWindowBuffer + windowPosition) = 0.3635819 - - 0.4891775 * cos(2 * M_PI * windowPosition / windowEnd) + - 0.1365995 * cos(4 * M_PI * windowPosition / windowEnd) - - 0.0106411 * cos(6 * M_PI * windowPosition / windowEnd); - break; - case Dso::WindowFunction::FLATTOP: - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) - *(lastWindowBuffer + windowPosition) = 1.0 - 1.93 * cos(2 * M_PI * windowPosition / windowEnd) + - 1.29 * cos(4 * M_PI * windowPosition / windowEnd) - - 0.388 * cos(6 * M_PI * windowPosition / windowEnd) + - 0.032 * cos(8 * M_PI * windowPosition / windowEnd); - break; - default: // Dso::WINDOW_RECTANGULAR - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) - *(lastWindowBuffer + windowPosition) = 1.0; - } - } - - // Set sampling interval - channelData->spectrum.interval = 1.0 / channelData->voltage.interval / sampleCount; - - // Number of real/complex samples - unsigned int dftLength = sampleCount / 2; - - // Reallocate memory for samples if the sample count has changed - channelData->spectrum.sample.resize(sampleCount); - - // Create sample buffer and apply window - std::unique_ptr windowedValues = std::unique_ptr(new double[sampleCount]); - - for (unsigned int position = 0; position < sampleCount; ++position) - windowedValues[position] = lastWindowBuffer[position] * channelData->voltage.sample[position]; - - { - // Do discrete real to half-complex transformation - /// \todo Check if record length is multiple of 2 - /// \todo Reuse plan and use FFTW_MEASURE to get fastest algorithm - fftw_plan fftPlan = fftw_plan_r2r_1d(sampleCount, windowedValues.get(), - &channelData->spectrum.sample.front(), FFTW_R2HC, FFTW_ESTIMATE); - fftw_execute(fftPlan); - fftw_destroy_plan(fftPlan); - } - - // Do an autocorrelation to get the frequency of the signal - std::unique_ptr conjugateComplex = std::move(windowedValues); - - // Real values - unsigned int position; - double correctionFactor = 1.0 / dftLength / dftLength; - conjugateComplex[0] = (channelData->spectrum.sample[0] * channelData->spectrum.sample[0]) * correctionFactor; - for (position = 1; position < dftLength; ++position) - conjugateComplex[position] = - (channelData->spectrum.sample[position] * channelData->spectrum.sample[position] + - channelData->spectrum.sample[sampleCount - position] * - channelData->spectrum.sample[sampleCount - position]) * - correctionFactor; - // Complex values, all zero for autocorrelation - conjugateComplex[dftLength] = - (channelData->spectrum.sample[dftLength] * channelData->spectrum.sample[dftLength]) * correctionFactor; - for (++position; position < sampleCount; ++position) conjugateComplex[position] = 0; - - // Do half-complex to real inverse transformation - std::unique_ptr correlation = std::unique_ptr(new double[sampleCount]); - fftw_plan fftPlan = - fftw_plan_r2r_1d(sampleCount, conjugateComplex.get(), correlation.get(), FFTW_HC2R, FFTW_ESTIMATE); - fftw_execute(fftPlan); - fftw_destroy_plan(fftPlan); - - // Calculate peak-to-peak voltage - double minimalVoltage, maximalVoltage; - minimalVoltage = maximalVoltage = channelData->voltage.sample[0]; - - for (unsigned int position = 1; position < sampleCount; ++position) { - if (channelData->voltage.sample[position] < minimalVoltage) - minimalVoltage = channelData->voltage.sample[position]; - else if (channelData->voltage.sample[position] > maximalVoltage) - maximalVoltage = channelData->voltage.sample[position]; - } - - channelData->amplitude = maximalVoltage - minimalVoltage; - - // Get the frequency from the correlation results - double minimumCorrelation = correlation[0]; - double peakCorrelation = 0; - unsigned int peakPosition = 0; - - for (unsigned int position = 1; position < sampleCount / 2; ++position) { - if (correlation[position] > peakCorrelation && correlation[position] > minimumCorrelation * 2) { - peakCorrelation = correlation[position]; - peakPosition = position; - } else if (correlation[position] < minimumCorrelation) - minimumCorrelation = correlation[position]; - } - correlation.reset(nullptr); - - // Calculate the frequency in Hz - if (peakPosition) - channelData->frequency = 1.0 / (channelData->voltage.interval * peakPosition); - else - channelData->frequency = 0; - - // Finally calculate the real spectrum if we want it - if (scope->spectrum[channel].used) { - // Convert values into dB (Relative to the reference level) - double offset = 60 - scope->spectrumReference - 20 * log10(dftLength); - double offsetLimit = scope->spectrumLimit - scope->spectrumReference; - for (std::vector::iterator spectrumIterator = channelData->spectrum.sample.begin(); - spectrumIterator != channelData->spectrum.sample.end(); ++spectrumIterator) { - double value = 20 * log10(fabs(*spectrumIterator)) + offset; - - // Check if this value has to be limited - if (offsetLimit > value) value = offsetLimit; - - *spectrumIterator = value; - } - } - } -} diff --git a/openhantek/src/analyse/dataanalyzer.h b/openhantek/src/analyse/dataanalyzer.h deleted file mode 100644 index 64e7fb3..0000000 --- a/openhantek/src/analyse/dataanalyzer.h +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ - -#pragma once - -#include - -#include -#include -#include - -#include "dataanalyzerresult.h" -#include "dsosamples.h" -#include "utils/printutils.h" -#include "enums.h" - -class DsoSettings; -struct DsoSettingsScope; - -/// \brief Analyzes the data from the dso. -/// Calculates the spectrum and various data about the signal and saves the -/// time-/frequencysteps between two values. -class DataAnalyzer : public QObject { - Q_OBJECT - - public: - ~DataAnalyzer(); - void applySettings(DsoSettings *settings); - void setSourceData(const DSOsamples *data); - std::unique_ptr getNextResult(); - /** - * Call this if the source data changed. - */ - void samplesAvailable(); - - private: - static std::unique_ptr convertData(const DSOsamples *data, const DsoSettingsScope *scope, unsigned physicalChannels); - static void spectrumAnalysis(DataAnalyzerResult *result, Dso::WindowFunction &lastWindow, - unsigned int lastRecordLength, double *&lastWindowBuffer, - const DsoSettingsScope *scope); - - private: - DsoSettings *settings; - unsigned int lastRecordLength = 0; ///< The record length of the previously analyzed data - Dso::WindowFunction lastWindow = (Dso::WindowFunction)-1; ///< The previously used dft window function - double *window = nullptr; - const DSOsamples *sourceData = nullptr; - std::unique_ptr lastResult; - signals: - void analyzed(); -}; diff --git a/openhantek/src/analyse/dataanalyzerresult.cpp b/openhantek/src/analyse/dataanalyzerresult.cpp deleted file mode 100644 index 2fa46a2..0000000 --- a/openhantek/src/analyse/dataanalyzerresult.cpp +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ - -#include "dataanalyzerresult.h" -#include -#include - -DataAnalyzerResult::DataAnalyzerResult(unsigned int channelCount) { analyzedData.resize(channelCount); } - -/// \brief Returns the analyzed data. -/// \param channel Channel, whose data should be returned. -/// \return Analyzed data as AnalyzedData struct. -const DataChannel *DataAnalyzerResult::data(ChannelID channel) const { - if (channel >= this->analyzedData.size()) return 0; - - return &this->analyzedData[(size_t)channel]; -} - -DataChannel *DataAnalyzerResult::modifyData(ChannelID channel) { - if (channel >= this->analyzedData.size()) - throw new std::runtime_error("If you modfiy the DataAnalyzerResult, you " - "need to set the channels first!"); - - return &this->analyzedData[(size_t)channel]; -} - -/// \brief Returns the sample count of the analyzed data. -/// \return The maximum sample count of the last analyzed data. -unsigned int DataAnalyzerResult::sampleCount() const { return this->maxSamples; } - -unsigned int DataAnalyzerResult::channelCount() const { return analyzedData.size(); } - -void DataAnalyzerResult::challengeMaxSamples(unsigned int newMaxSamples) { - if (newMaxSamples > this->maxSamples) this->maxSamples = newMaxSamples; -} - -unsigned int DataAnalyzerResult::getMaxSamples() const { return maxSamples; } diff --git a/openhantek/src/analyse/dataanalyzerresult.h b/openhantek/src/analyse/dataanalyzerresult.h deleted file mode 100644 index 2e06067..0000000 --- a/openhantek/src/analyse/dataanalyzerresult.h +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ - -#pragma once - -#include -#include "hantekprotocol/definitions.h" - -/// \brief Struct for a array of sample values. -struct SampleValues { - std::vector sample; ///< Vector holding the sampling data - double interval = 0.0; ///< The interval between two sample values -}; - -/// \brief Struct for the analyzed data. -struct DataChannel { - SampleValues voltage; ///< The time-domain voltage levels (V) - SampleValues spectrum; ///< The frequency-domain power levels (dB) - double amplitude = 0.0; ///< The amplitude of the signal - double frequency = 0.0; ///< The frequency of the signal -}; - -/// A result from the { @link DataAnalyzer } class. -class DataAnalyzerResult { - public: - DataAnalyzerResult(unsigned int channelCount); - const DataChannel *data(ChannelID channel) const; - DataChannel *modifyData(ChannelID channel); - unsigned int sampleCount() const; - unsigned int channelCount() const; - - /** - * Applies a new maximum samples value, if the given value is higher than the - * already stored one - * @param newMaxSamples Maximum samples value - */ - void challengeMaxSamples(unsigned int newMaxSamples); - unsigned int getMaxSamples() const; - - private: - std::vector analyzedData; ///< The analyzed data for each channel - unsigned int maxSamples = 0; ///< The maximum record length of the analyzed data -}; diff --git a/openhantek/src/analyse/enums.cpp b/openhantek/src/analyse/enums.cpp deleted file mode 100644 index fc49508..0000000 --- a/openhantek/src/analyse/enums.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "enums.h" - -namespace Dso { - -Enum MathModeEnum; -Enum WindowFunctionEnum; - -} diff --git a/openhantek/src/analyse/enums.h b/openhantek/src/analyse/enums.h deleted file mode 100644 index a9872f9..0000000 --- a/openhantek/src/analyse/enums.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include -#include "utils/enumclass.h" -namespace Dso { - -/// \enum MathMode -/// \brief The different math modes for the math-channel. -enum class MathMode { - ADD_CH1_CH2, - SUB_CH2_FROM_CH1, - SUB_CH1_FROM_CH2 -}; -extern Enum MathModeEnum; - -/// \enum WindowFunction -/// \brief The supported window functions. -/// These are needed for spectrum analysis and are applied to the sample values -/// before calculating the DFT. -enum class WindowFunction: int { - RECTANGULAR, ///< Rectangular window (aka Dirichlet) - HAMMING, ///< Hamming window - HANN, ///< Hann window - COSINE, ///< Cosine window (aka Sine) - LANCZOS, ///< Lanczos window (aka Sinc) - BARTLETT, ///< Bartlett window (Endpoints == 0) - TRIANGULAR, ///< Triangular window (Endpoints != 0) - GAUSS, ///< Gauss window (simga = 0.4) - BARTLETTHANN, ///< Bartlett-Hann window - BLACKMAN, ///< Blackman window (alpha = 0.16) - // KAISER, ///< Kaiser window (alpha = 3.0) - NUTTALL, ///< Nuttall window, cont. first deriv. - BLACKMANHARRIS, ///< Blackman-Harris window - BLACKMANNUTTALL, ///< Blackman-Nuttall window - FLATTOP ///< Flat top window -}; -extern Enum WindowFunctionEnum; - -} - -Q_DECLARE_METATYPE(Dso::MathMode) -Q_DECLARE_METATYPE(Dso::WindowFunction) - diff --git a/openhantek/src/hantekdso/softwaretrigger.cpp b/openhantek/src/hantekdso/softwaretrigger.cpp deleted file mode 100644 index 7e6873d..0000000 --- a/openhantek/src/hantekdso/softwaretrigger.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#include "softwaretrigger.h" -#include "analyse/dataanalyzerresult.h" -#include "scopesettings.h" -#include "viewconstants.h" -#include "utils/printutils.h" - -SoftwareTrigger::PrePostStartTriggerSamples SoftwareTrigger::compute(const DataAnalyzerResult *data, - const DsoSettingsScope *scope) -{ - unsigned int preTrigSamples = 0; - unsigned int postTrigSamples = 0; - unsigned int swTriggerStart = 0; - ChannelID channel = scope->trigger.source; - - // Trigger channel not in use - if (!scope->voltage[channel].used || !data->data(channel) || - data->data(channel)->voltage.sample.empty()) - return PrePostStartTriggerSamples(preTrigSamples, postTrigSamples, swTriggerStart); - - const std::vector& samples = data->data(channel)->voltage.sample; - double level = scope->voltage[channel].trigger; - size_t sampleCount = samples.size(); - double timeDisplay = scope->horizontal.timebase * DIVS_TIME; - double samplesDisplay = timeDisplay * scope->horizontal.samplerate; - - if (samplesDisplay >= sampleCount) { - // For sure not enough samples to adjust for jitter. - // Following options exist: - // 1: Decrease sample rate - // 2: Change trigger mode to auto - // 3: Ignore samples - // For now #3 is chosen - timestampDebug(QString("Too few samples to make a steady " - "picture. Decrease sample rate")); - return PrePostStartTriggerSamples(preTrigSamples, postTrigSamples, swTriggerStart); - } - preTrigSamples = (unsigned)(scope->trigger.position * samplesDisplay); - postTrigSamples = (unsigned)sampleCount - ((unsigned)samplesDisplay - preTrigSamples); - - double prev; - bool (*opcmp)(double,double,double); - bool (*smplcmp)(double,double); - if (scope->trigger.slope == Dso::Slope::Positive) { - prev = INT_MAX; - opcmp = [](double value, double level, double prev) { return value > level && prev <= level;}; - smplcmp = [](double sampleK, double value) { return sampleK >= value;}; - } else { - prev = INT_MIN; - opcmp = [](double value, double level, double prev) { return value < level && prev >= level;}; - smplcmp = [](double sampleK, double value) { return sampleK < value;}; - } - - for (unsigned int i = preTrigSamples; i < postTrigSamples; i++) { - double value = samples[i]; - if (opcmp(value, level, prev)) { - unsigned rising = 0; - for (unsigned int k = i + 1; k < i + scope->trigger.swTriggerSampleSet && k < sampleCount; k++) { - if (smplcmp(samples[k], value)) { rising++; } - } - if (rising > scope->trigger.swTriggerThreshold) { - swTriggerStart = i; - break; - } - } - prev = value; - } - if (swTriggerStart == 0) { - timestampDebug(QString("Trigger not asserted. Data ignored")); - preTrigSamples = 0; // preTrigSamples may never be greater than swTriggerStart - postTrigSamples = 0; - } - return PrePostStartTriggerSamples(preTrigSamples, postTrigSamples, swTriggerStart); -} diff --git a/openhantek/src/hantekdso/softwaretrigger.h b/openhantek/src/hantekdso/softwaretrigger.h deleted file mode 100644 index 64d1ae3..0000000 --- a/openhantek/src/hantekdso/softwaretrigger.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once -#include -struct DsoSettingsScope; -class DataAnalyzerResult; - - -/** - * Contains software trigger algorithms. At the moment this works on the analysed data of the - * DataAnalyser class. - * TODO Should work on the raw data within HantekDsoControl - */ -class SoftwareTrigger { - public: - typedef std::tuple PrePostStartTriggerSamples; - /** - * @brief Computes a software trigger point. - * @param data Analysed data from the - * @param scope Scope settings - * @return Returns a tuple of positions [preTrigger, postTrigger, startTrigger] - */ - static PrePostStartTriggerSamples compute(const DataAnalyzerResult *data, const DsoSettingsScope *scope); -}; diff --git a/openhantek/src/post/enums.cpp b/openhantek/src/post/enums.cpp new file mode 100644 index 0000000..fc49508 --- /dev/null +++ b/openhantek/src/post/enums.cpp @@ -0,0 +1,8 @@ +#include "enums.h" + +namespace Dso { + +Enum MathModeEnum; +Enum WindowFunctionEnum; + +} diff --git a/openhantek/src/post/enums.h b/openhantek/src/post/enums.h new file mode 100644 index 0000000..a9872f9 --- /dev/null +++ b/openhantek/src/post/enums.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include "utils/enumclass.h" +namespace Dso { + +/// \enum MathMode +/// \brief The different math modes for the math-channel. +enum class MathMode { + ADD_CH1_CH2, + SUB_CH2_FROM_CH1, + SUB_CH1_FROM_CH2 +}; +extern Enum MathModeEnum; + +/// \enum WindowFunction +/// \brief The supported window functions. +/// These are needed for spectrum analysis and are applied to the sample values +/// before calculating the DFT. +enum class WindowFunction: int { + RECTANGULAR, ///< Rectangular window (aka Dirichlet) + HAMMING, ///< Hamming window + HANN, ///< Hann window + COSINE, ///< Cosine window (aka Sine) + LANCZOS, ///< Lanczos window (aka Sinc) + BARTLETT, ///< Bartlett window (Endpoints == 0) + TRIANGULAR, ///< Triangular window (Endpoints != 0) + GAUSS, ///< Gauss window (simga = 0.4) + BARTLETTHANN, ///< Bartlett-Hann window + BLACKMAN, ///< Blackman window (alpha = 0.16) + // KAISER, ///< Kaiser window (alpha = 3.0) + NUTTALL, ///< Nuttall window, cont. first deriv. + BLACKMANHARRIS, ///< Blackman-Harris window + BLACKMANNUTTALL, ///< Blackman-Nuttall window + FLATTOP ///< Flat top window +}; +extern Enum WindowFunctionEnum; + +} + +Q_DECLARE_METATYPE(Dso::MathMode) +Q_DECLARE_METATYPE(Dso::WindowFunction) + diff --git a/openhantek/src/post/graphgenerator.cpp b/openhantek/src/post/graphgenerator.cpp new file mode 100644 index 0000000..3f6828b --- /dev/null +++ b/openhantek/src/post/graphgenerator.cpp @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include +#include +#include + +#include "post/graphgenerator.h" +#include "post/ppresult.h" +#include "post/softwaretrigger.h" +#include "hantekdso/controlspecification.h" +#include "scopesettings.h" +#include "utils/printutils.h" +#include "viewconstants.h" + +static const SampleValues &useSpecSamplesOf(ChannelID channel, const PPresult *result, + const DsoSettingsScope *scope) { + static SampleValues emptyDefault; + if (!scope->spectrum[channel].used || !result->data(channel)) return emptyDefault; + return result->data(channel)->spectrum; +} + +static const SampleValues &useVoltSamplesOf(ChannelID channel, const PPresult *result, + const DsoSettingsScope *scope) { + static SampleValues emptyDefault; + if (!scope->voltage[channel].used || !result->data(channel)) return emptyDefault; + return result->data(channel)->voltage; +} + +GraphGenerator::GraphGenerator(const DsoSettingsScope *scope, bool isSoftwareTriggerDevice) + : scope(scope), isSoftwareTriggerDevice(isSoftwareTriggerDevice) {} + +bool GraphGenerator::isReady() const { return ready; } + +void GraphGenerator::generateGraphsTYvoltage(PPresult *result) { + unsigned preTrigSamples = 0; + unsigned postTrigSamples = 0; + unsigned swTriggerStart = 0; + + // check trigger point for software trigger + if (isSoftwareTriggerDevice && scope->trigger.source < result->channelCount()) + std::tie(preTrigSamples, postTrigSamples, swTriggerStart) = SoftwareTrigger::compute(result, scope); + result->softwareTriggerTriggered = postTrigSamples > preTrigSamples; + + result->vaChannelVoltage.resize(scope->voltage.size()); + for (ChannelID channel = 0; channel < scope->voltage.size(); ++channel) { + ChannelGraph &target = result->vaChannelVoltage[channel]; + const SampleValues &samples = useVoltSamplesOf(channel, result, scope); + + // Check if this channel is used and available at the data analyzer + if (samples.sample.empty()) { + // Delete all vector arrays + target.clear(); + continue; + } + // Check if the sample count has changed + size_t sampleCount = samples.sample.size(); + if (sampleCount > 500000) { + qWarning() << "Sample count too high!"; + throw new std::runtime_error("Sample count too high!"); + } + sampleCount -= (swTriggerStart - preTrigSamples); + size_t neededSize = sampleCount * 2; + + // Set size directly to avoid reallocations + target.resize(neededSize); + + // What's the horizontal distance between sampling points? + float horizontalFactor = (float)(samples.interval / scope->horizontal.timebase); + + // Fill vector array + std::vector::const_iterator dataIterator = samples.sample.begin(); + const float gain = (float)scope->gain(channel); + const float offset = (float)scope->voltage[channel].offset; + const float invert = scope->voltage[channel].inverted ? -1.0f : 1.0f; + + std::advance(dataIterator, swTriggerStart - preTrigSamples); + + for (unsigned int position = 0; position < sampleCount; ++position) { + target.push_back(QVector3D(position * horizontalFactor - DIVS_TIME / 2, + (float)*(dataIterator++) / gain * invert + offset, 0.0)); + } + } +} + +void GraphGenerator::generateGraphsTYspectrum(PPresult *result) { + ready = true; + result->vaChannelSpectrum.resize(scope->spectrum.size()); + for (ChannelID channel = 0; channel < scope->voltage.size(); ++channel) { + ChannelGraph &target = result->vaChannelSpectrum[channel]; + const SampleValues &samples = useSpecSamplesOf(channel, result, scope); + + // Check if this channel is used and available at the data analyzer + if (samples.sample.empty()) { + // Delete all vector arrays + target.clear(); + continue; + } + // Check if the sample count has changed + size_t sampleCount = samples.sample.size(); + if (sampleCount > 500000) { + qWarning() << "Sample count too high!"; + throw new std::runtime_error("Sample count too high!"); + } + size_t neededSize = sampleCount * 2; + + // Set size directly to avoid reallocations + target.resize(neededSize); + + // What's the horizontal distance between sampling points? + float horizontalFactor = (float)(samples.interval / scope->horizontal.frequencybase); + + // Fill vector array + std::vector::const_iterator dataIterator = samples.sample.begin(); + const float magnitude = (float)scope->spectrum[channel].magnitude; + const float offset = (float)scope->spectrum[channel].offset; + + for (unsigned int position = 0; position < sampleCount; ++position) { + target.push_back(QVector3D(position * horizontalFactor - DIVS_TIME / 2, + (float)*(dataIterator++) / magnitude + offset, 0.0)); + } + } +} + +void GraphGenerator::process(PPresult *data) { + if (scope->horizontal.format == Dso::GraphFormat::TY) { + ready = true; + generateGraphsTYspectrum(data); + generateGraphsTYvoltage(data); + } else + generateGraphsXY(data, scope); +} + +void GraphGenerator::generateGraphsXY(PPresult *result, const DsoSettingsScope *scope) { + result->vaChannelVoltage.resize(scope->voltage.size()); + + // Delete all spectrum graphs + for (ChannelGraph &data : result->vaChannelSpectrum) data.clear(); + + // Generate voltage graphs for pairs of channels + for (ChannelID channel = 0; channel < scope->voltage.size(); channel += 2) { + // We need pairs of channels. + if (channel + 1 == scope->voltage.size()) { + result->vaChannelVoltage[channel].clear(); + continue; + } + + const ChannelID xChannel = channel; + const ChannelID yChannel = channel + 1; + + const SampleValues &xSamples = useVoltSamplesOf(xChannel, result, scope); + const SampleValues &ySamples = useVoltSamplesOf(yChannel, result, scope); + + // The channels need to be active + if (!xSamples.sample.size() || !ySamples.sample.size()) { + result->vaChannelVoltage[channel].clear(); + result->vaChannelVoltage[channel + 1].clear(); + continue; + } + + // Check if the sample count has changed + const size_t sampleCount = std::min(xSamples.sample.size(), ySamples.sample.size()); + ChannelGraph &drawLines = result->vaChannelVoltage[channel]; + drawLines.resize(sampleCount * 2); + + // Fill vector array + std::vector::const_iterator xIterator = xSamples.sample.begin(); + std::vector::const_iterator yIterator = ySamples.sample.begin(); + const double xGain = scope->gain(xChannel); + const double yGain = scope->gain(yChannel); + const double xOffset = scope->voltage[xChannel].offset; + const double yOffset = scope->voltage[yChannel].offset; + const double xInvert = scope->voltage[xChannel].inverted ? -1.0 : 1.0; + const double yInvert = scope->voltage[yChannel].inverted ? -1.0 : 1.0; + + for (unsigned int position = 0; position < sampleCount; ++position) { + drawLines.push_back(QVector3D((float)(*(xIterator++) / xGain * xInvert + xOffset), + (float)(*(yIterator++) / yGain * yInvert + yOffset), 0.0)); + } + } +} diff --git a/openhantek/src/post/graphgenerator.h b/openhantek/src/post/graphgenerator.h new file mode 100644 index 0000000..1642a89 --- /dev/null +++ b/openhantek/src/post/graphgenerator.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#pragma once + +#include + +#include +#include + +#include "hantekdso/enums.h" +#include "hantekprotocol/types.h" +#include "processor.h" + +struct DsoSettingsScope; +class PPresult; +namespace Dso { +struct ControlSpecification; +} + +/// \brief Generates ready to be used vertex arrays +class GraphGenerator : public QObject, public Processor { + Q_OBJECT + + public: + GraphGenerator(const DsoSettingsScope *scope, bool isSoftwareTriggerDevice); + void generateGraphsXY(PPresult *result, const DsoSettingsScope *scope); + + bool isReady() const; + + private: + void generateGraphsTYvoltage(PPresult *result); + void generateGraphsTYspectrum(PPresult *result); + + private: + bool ready = false; + const DsoSettingsScope *scope; + const bool isSoftwareTriggerDevice; + + // Processor interface + private: + virtual void process(PPresult *) override; +}; diff --git a/openhantek/src/post/mathchannelgenerator.cpp b/openhantek/src/post/mathchannelgenerator.cpp new file mode 100644 index 0000000..309d698 --- /dev/null +++ b/openhantek/src/post/mathchannelgenerator.cpp @@ -0,0 +1,45 @@ +#include "mathchannelgenerator.h" +#include "scopesettings.h" + +MathChannelGenerator::MathChannelGenerator(const DsoSettingsScope *scope, unsigned physicalChannels) + : physicalChannels(physicalChannels), scope(scope) {} + +MathChannelGenerator::~MathChannelGenerator() {} + +void MathChannelGenerator::process(PPresult *result) { + bool channelsHaveData = !result->data(0)->voltage.sample.empty() && !result->data(1)->voltage.sample.empty(); + if (!channelsHaveData) return; + + for (ChannelID channel = physicalChannels; channel < result->channelCount(); ++channel) { + DataChannel *const channelData = result->modifyData(channel); + + // Math channel enabled? + if (!scope->voltage[channel].used && !scope->spectrum[channel].used) continue; + + // Set sampling interval + channelData->voltage.interval = result->data(0)->voltage.interval; + + // Resize the sample vector + std::vector &resultData = channelData->voltage.sample; + resultData.resize(std::min(result->data(0)->voltage.sample.size(), result->data(1)->voltage.sample.size())); + + // Calculate values and write them into the sample buffer + std::vector::const_iterator ch1Iterator = result->data(0)->voltage.sample.begin(); + std::vector::const_iterator ch2Iterator = result->data(1)->voltage.sample.begin(); + for (std::vector::iterator it = resultData.begin(); it != resultData.end(); ++it) { + switch (scope->voltage[physicalChannels].math) { + case Dso::MathMode::ADD_CH1_CH2: + *it = *ch1Iterator + *ch2Iterator; + break; + case Dso::MathMode::SUB_CH2_FROM_CH1: + *it = *ch1Iterator - *ch2Iterator; + break; + case Dso::MathMode::SUB_CH1_FROM_CH2: + *it = *ch2Iterator - *ch1Iterator; + break; + } + ++ch1Iterator; + ++ch2Iterator; + } + } +} diff --git a/openhantek/src/post/mathchannelgenerator.h b/openhantek/src/post/mathchannelgenerator.h new file mode 100644 index 0000000..7195e15 --- /dev/null +++ b/openhantek/src/post/mathchannelgenerator.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#pragma once + +#include "processor.h" + +struct DsoSettingsScope; +class PPresult; + +class MathChannelGenerator : public Processor +{ +public: + MathChannelGenerator(const DsoSettingsScope *scope, unsigned physicalChannels); + virtual ~MathChannelGenerator(); + virtual void process(PPresult *) override; +private: + const unsigned physicalChannels; + const DsoSettingsScope *scope; +}; diff --git a/openhantek/src/post/postprocessing.cpp b/openhantek/src/post/postprocessing.cpp new file mode 100644 index 0000000..b78f7b3 --- /dev/null +++ b/openhantek/src/post/postprocessing.cpp @@ -0,0 +1,29 @@ +#include "postprocessing.h" + +PostProcessing::PostProcessing(unsigned channelCount) : channelCount(channelCount) { + qRegisterMetaType>(); +} + +void PostProcessing::registerProcessor(Processor *processor) { processors.push_back(processor); } + +void PostProcessing::convertData(const DSOsamples *source, PPresult *destination) { + QReadLocker locker(&source->lock); + + for (ChannelID channel = 0; channel < source->data.size(); ++channel) { + const std::vector &rawChannelData = source->data.at(channel); + + if (rawChannelData.empty()) { continue; } + + DataChannel *const channelData = destination->modifyData(channel); + channelData->voltage.interval = 1.0 / source->samplerate; + channelData->voltage.sample = rawChannelData; + } +} + +void PostProcessing::input(const DSOsamples *data) { + currentData.reset(new PPresult(channelCount)); + convertData(data, currentData.get()); + for (Processor *p : processors) p->process(currentData.get()); + std::shared_ptr res = std::move(currentData); + emit processingFinished(res); +} diff --git a/openhantek/src/post/postprocessing.h b/openhantek/src/post/postprocessing.h new file mode 100644 index 0000000..56e4f7e --- /dev/null +++ b/openhantek/src/post/postprocessing.h @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#pragma once + +#include "dsosamples.h" +#include "processor.h" + +#include +#include + +#include + +struct DsoSettingsScope; + +/** + * Manages all post processing processors. Register another processor with `registerProcessor(p)`. + * All processors, in the order of insertion, will process the input data, given by `input(data)`. + * The final result will be made available via the `processingFinished` signal. + */ +class PostProcessing : public QObject { + Q_OBJECT + public: + PostProcessing(unsigned channelCount); + /** + * Adds a new processor that is called when a new input arrived. The order of the processors is + * imporant. The first added processor will be called first. This class does not take ownership + * of the processors. + * @param processor + */ + void registerProcessor(Processor *processor); + + + private: + /// A new `PPresult` is created for each new input. We need to know the channel size. + const unsigned channelCount; + /// The list of processors. Processors are not memory managed by this class. + std::vector processors; + /// + std::unique_ptr currentData; + static void convertData(const DSOsamples *source, PPresult *destination); + public slots: + /** + * Start processing new data. The actual data may be processed in another thread if you have moved + * this class object into another thread. + * @param data + */ + void input(const DSOsamples *data); +signals: + void processingFinished(std::shared_ptr result); +}; + +Q_DECLARE_METATYPE(std::shared_ptr) diff --git a/openhantek/src/post/ppresult.cpp b/openhantek/src/post/ppresult.cpp new file mode 100644 index 0000000..ae323f9 --- /dev/null +++ b/openhantek/src/post/ppresult.cpp @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include "ppresult.h" +#include +#include + +PPresult::PPresult(unsigned int channelCount) { analyzedData.resize(channelCount); } + +const DataChannel *PPresult::data(ChannelID channel) const { + if (channel >= this->analyzedData.size()) return 0; + + return &this->analyzedData[(size_t)channel]; +} + +DataChannel *PPresult::modifyData(ChannelID channel) { return &this->analyzedData[(size_t)channel]; } + +unsigned int PPresult::sampleCount() const { return (unsigned)analyzedData[0].voltage.sample.size(); } + +unsigned int PPresult::channelCount() const { return (unsigned)analyzedData.size(); } + +double DataChannel::computeAmplitude() const { + double minimalVoltage, maximalVoltage; + minimalVoltage = maximalVoltage = voltage.sample[0]; + + for (unsigned int position = 1; position < voltage.sample.size(); ++position) { + if (voltage.sample[position] < minimalVoltage) + minimalVoltage = voltage.sample[position]; + else if (voltage.sample[position] > maximalVoltage) + maximalVoltage = voltage.sample[position]; + } + + return maximalVoltage - minimalVoltage; +} diff --git a/openhantek/src/post/ppresult.h b/openhantek/src/post/ppresult.h new file mode 100644 index 0000000..16b8022 --- /dev/null +++ b/openhantek/src/post/ppresult.h @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#pragma once + +#include +#include + +#include +#include "hantekprotocol/types.h" + +/// \brief Struct for a array of sample values. +struct SampleValues { + std::vector sample; ///< Vector holding the sampling data + double interval = 0.0; ///< The interval between two sample values +}; + +/// \brief Struct for the analyzed data. +struct DataChannel { + SampleValues voltage; ///< The time-domain voltage levels (V) + SampleValues spectrum; ///< The frequency-domain power levels (dB) + + double frequency = 0.0; ///< The frequency of the signal + // Calculate peak-to-peak voltage + double computeAmplitude() const; +}; + +typedef std::vector ChannelGraph; +typedef std::vector ChannelsGraphs; + +/// Post processing results +class PPresult { + public: + PPresult(unsigned int channelCount); + + /// \brief Returns the analyzed data. + /// \param channel Channel, whose data should be returned. + const DataChannel *data(ChannelID channel) const; + /// \brief Returns the analyzed data. The data structure can be modifed. + /// \param channel Channel, whose data should be returned. + DataChannel *modifyData(ChannelID channel); + /// \return The maximum sample count of the last analyzed data. This assumes there is at least one channel. + unsigned int sampleCount() const; + unsigned int channelCount() const; + + bool softwareTriggerTriggered = false; + + ChannelsGraphs vaChannelSpectrum; + ChannelsGraphs vaChannelVoltage; + private: + std::vector analyzedData; ///< The analyzed data for each channel +}; diff --git a/openhantek/src/post/processor.h b/openhantek/src/post/processor.h new file mode 100644 index 0000000..71da37b --- /dev/null +++ b/openhantek/src/post/processor.h @@ -0,0 +1,8 @@ +#pragma once + +#include "ppresult.h" + +class Processor { +public: + virtual void process(PPresult*) = 0; +}; diff --git a/openhantek/src/post/softwaretrigger.cpp b/openhantek/src/post/softwaretrigger.cpp new file mode 100644 index 0000000..19ecc96 --- /dev/null +++ b/openhantek/src/post/softwaretrigger.cpp @@ -0,0 +1,73 @@ +#include "post/softwaretrigger.h" +#include "post/ppresult.h" +#include "scopesettings.h" +#include "viewconstants.h" +#include "utils/printutils.h" + +SoftwareTrigger::PrePostStartTriggerSamples SoftwareTrigger::compute(const PPresult *data, + const DsoSettingsScope *scope) +{ + unsigned int preTrigSamples = 0; + unsigned int postTrigSamples = 0; + unsigned int swTriggerStart = 0; + ChannelID channel = scope->trigger.source; + + // Trigger channel not in use + if (!scope->voltage[channel].used || !data->data(channel) || + data->data(channel)->voltage.sample.empty()) + return PrePostStartTriggerSamples(preTrigSamples, postTrigSamples, swTriggerStart); + + const std::vector& samples = data->data(channel)->voltage.sample; + double level = scope->voltage[channel].trigger; + size_t sampleCount = samples.size(); + double timeDisplay = scope->horizontal.timebase * DIVS_TIME; + double samplesDisplay = timeDisplay * scope->horizontal.samplerate; + + if (samplesDisplay >= sampleCount) { + // For sure not enough samples to adjust for jitter. + // Following options exist: + // 1: Decrease sample rate + // 2: Change trigger mode to auto + // 3: Ignore samples + // For now #3 is chosen + timestampDebug(QString("Too few samples to make a steady " + "picture. Decrease sample rate")); + return PrePostStartTriggerSamples(preTrigSamples, postTrigSamples, swTriggerStart); + } + preTrigSamples = (unsigned)(scope->trigger.position * samplesDisplay); + postTrigSamples = (unsigned)sampleCount - ((unsigned)samplesDisplay - preTrigSamples); + + double prev; + bool (*opcmp)(double,double,double); + bool (*smplcmp)(double,double); + if (scope->trigger.slope == Dso::Slope::Positive) { + prev = INT_MAX; + opcmp = [](double value, double level, double prev) { return value > level && prev <= level;}; + smplcmp = [](double sampleK, double value) { return sampleK >= value;}; + } else { + prev = INT_MIN; + opcmp = [](double value, double level, double prev) { return value < level && prev >= level;}; + smplcmp = [](double sampleK, double value) { return sampleK < value;}; + } + + for (unsigned int i = preTrigSamples; i < postTrigSamples; i++) { + double value = samples[i]; + if (opcmp(value, level, prev)) { + unsigned rising = 0; + for (unsigned int k = i + 1; k < i + scope->trigger.swTriggerSampleSet && k < sampleCount; k++) { + if (smplcmp(samples[k], value)) { rising++; } + } + if (rising > scope->trigger.swTriggerThreshold) { + swTriggerStart = i; + break; + } + } + prev = value; + } + if (swTriggerStart == 0) { + timestampDebug(QString("Trigger not asserted. Data ignored")); + preTrigSamples = 0; // preTrigSamples may never be greater than swTriggerStart + postTrigSamples = 0; + } + return PrePostStartTriggerSamples(preTrigSamples, postTrigSamples, swTriggerStart); +} diff --git a/openhantek/src/post/softwaretrigger.h b/openhantek/src/post/softwaretrigger.h new file mode 100644 index 0000000..0ed730a --- /dev/null +++ b/openhantek/src/post/softwaretrigger.h @@ -0,0 +1,22 @@ +#pragma once +#include +struct DsoSettingsScope; +class PPresult; + + +/** + * Contains software trigger algorithms. At the moment this works on the analysed data of the + * DataAnalyser class. + * TODO Should work on the raw data within HantekDsoControl + */ +class SoftwareTrigger { + public: + typedef std::tuple PrePostStartTriggerSamples; + /** + * @brief Computes a software trigger point. + * @param data Analysed data from the + * @param scope Scope settings + * @return Returns a tuple of positions [preTrigger, postTrigger, startTrigger] + */ + static PrePostStartTriggerSamples compute(const PPresult *data, const DsoSettingsScope *scope); +}; diff --git a/openhantek/src/post/spectrumgenerator.cpp b/openhantek/src/post/spectrumgenerator.cpp new file mode 100644 index 0000000..4fb395c --- /dev/null +++ b/openhantek/src/post/spectrumgenerator.cpp @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#define _USE_MATH_DEFINES +#include + +#include +#include +#include + +#include + +#include "spectrumgenerator.h" + +#include "glscope.h" +#include "settings.h" +#include "utils/printutils.h" + +/// \brief Analyzes the data from the dso. +SpectrumGenerator::SpectrumGenerator(const DsoSettingsScope *scope) : scope(scope) {} + +SpectrumGenerator::~SpectrumGenerator() { + if (lastWindowBuffer) fftw_free(lastWindowBuffer); +} + +void SpectrumGenerator::process(PPresult *result) { + // Calculate frequencies and spectrums + for (ChannelID channel = 0; channel < result->channelCount(); ++channel) { + DataChannel *const channelData = result->modifyData(channel); + + if (channelData->voltage.sample.empty()) { + // Clear unused channels + channelData->spectrum.interval = 0; + channelData->spectrum.sample.clear(); + continue; + } + + // Calculate new window + size_t sampleCount = channelData->voltage.sample.size(); + if (!lastWindowBuffer || lastWindow != scope->spectrumWindow || lastRecordLength != sampleCount) { + if (lastWindowBuffer) fftw_free(lastWindowBuffer); + lastWindowBuffer = fftw_alloc_real(sampleCount); + lastRecordLength = (unsigned)sampleCount; + + unsigned int windowEnd = lastRecordLength - 1; + lastWindow = scope->spectrumWindow; + + switch (scope->spectrumWindow) { + case Dso::WindowFunction::HAMMING: + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) + *(lastWindowBuffer + windowPosition) = 0.54 - 0.46 * cos(2.0 * M_PI * windowPosition / windowEnd); + break; + case Dso::WindowFunction::HANN: + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) + *(lastWindowBuffer + windowPosition) = 0.5 * (1.0 - cos(2.0 * M_PI * windowPosition / windowEnd)); + break; + case Dso::WindowFunction::COSINE: + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) + *(lastWindowBuffer + windowPosition) = sin(M_PI * windowPosition / windowEnd); + break; + case Dso::WindowFunction::LANCZOS: + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) { + double sincParameter = (2.0 * windowPosition / windowEnd - 1.0) * M_PI; + if (sincParameter == 0) + *(lastWindowBuffer + windowPosition) = 1; + else + *(lastWindowBuffer + windowPosition) = sin(sincParameter) / sincParameter; + } + break; + case Dso::WindowFunction::BARTLETT: + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) + *(lastWindowBuffer + windowPosition) = + 2.0 / windowEnd * (windowEnd / 2 - std::abs((double)(windowPosition - windowEnd / 2.0))); + break; + case Dso::WindowFunction::TRIANGULAR: + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) + *(lastWindowBuffer + windowPosition) = + 2.0 / lastRecordLength * + (lastRecordLength / 2 - std::abs((double)(windowPosition - windowEnd / 2.0))); + break; + case Dso::WindowFunction::GAUSS: { + double sigma = 0.4; + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) + *(lastWindowBuffer + windowPosition) = + exp(-0.5 * pow(((windowPosition - windowEnd / 2) / (sigma * windowEnd / 2)), 2)); + } break; + case Dso::WindowFunction::BARTLETTHANN: + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) + *(lastWindowBuffer + windowPosition) = 0.62 - + 0.48 * std::abs((double)(windowPosition / windowEnd - 0.5)) - + 0.38 * cos(2.0 * M_PI * windowPosition / windowEnd); + break; + case Dso::WindowFunction::BLACKMAN: { + double alpha = 0.16; + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) + *(lastWindowBuffer + windowPosition) = (1 - alpha) / 2 - + 0.5 * cos(2.0 * M_PI * windowPosition / windowEnd) + + alpha / 2 * cos(4.0 * M_PI * windowPosition / windowEnd); + } break; + // case Dso::WindowFunction::WINDOW_KAISER: + // TODO WINDOW_KAISER + // double alpha = 3.0; + // for(unsigned int windowPosition = 0; windowPosition < + // lastRecordLength; ++windowPosition) + //*(window + windowPosition) = ; + // break; + case Dso::WindowFunction::NUTTALL: + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) + *(lastWindowBuffer + windowPosition) = 0.355768 - + 0.487396 * cos(2 * M_PI * windowPosition / windowEnd) + + 0.144232 * cos(4 * M_PI * windowPosition / windowEnd) - + 0.012604 * cos(6 * M_PI * windowPosition / windowEnd); + break; + case Dso::WindowFunction::BLACKMANHARRIS: + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) + *(lastWindowBuffer + windowPosition) = 0.35875 - + 0.48829 * cos(2 * M_PI * windowPosition / windowEnd) + + 0.14128 * cos(4 * M_PI * windowPosition / windowEnd) - + 0.01168 * cos(6 * M_PI * windowPosition / windowEnd); + break; + case Dso::WindowFunction::BLACKMANNUTTALL: + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) + *(lastWindowBuffer + windowPosition) = 0.3635819 - + 0.4891775 * cos(2 * M_PI * windowPosition / windowEnd) + + 0.1365995 * cos(4 * M_PI * windowPosition / windowEnd) - + 0.0106411 * cos(6 * M_PI * windowPosition / windowEnd); + break; + case Dso::WindowFunction::FLATTOP: + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) + *(lastWindowBuffer + windowPosition) = 1.0 - 1.93 * cos(2 * M_PI * windowPosition / windowEnd) + + 1.29 * cos(4 * M_PI * windowPosition / windowEnd) - + 0.388 * cos(6 * M_PI * windowPosition / windowEnd) + + 0.032 * cos(8 * M_PI * windowPosition / windowEnd); + break; + default: // Dso::WINDOW_RECTANGULAR + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) + *(lastWindowBuffer + windowPosition) = 1.0; + } + } + + // Set sampling interval + channelData->spectrum.interval = 1.0 / channelData->voltage.interval / sampleCount; + + // Number of real/complex samples + unsigned int dftLength = sampleCount / 2; + + // Reallocate memory for samples if the sample count has changed + channelData->spectrum.sample.resize(sampleCount); + + // Create sample buffer and apply window + std::unique_ptr windowedValues = std::unique_ptr(new double[sampleCount]); + + for (unsigned int position = 0; position < sampleCount; ++position) + windowedValues[position] = lastWindowBuffer[position] * channelData->voltage.sample[position]; + + { + // Do discrete real to half-complex transformation + /// \todo Check if record length is multiple of 2 + /// \todo Reuse plan and use FFTW_MEASURE to get fastest algorithm + fftw_plan fftPlan = fftw_plan_r2r_1d(sampleCount, windowedValues.get(), + &channelData->spectrum.sample.front(), FFTW_R2HC, FFTW_ESTIMATE); + fftw_execute(fftPlan); + fftw_destroy_plan(fftPlan); + } + + // Do an autocorrelation to get the frequency of the signal + std::unique_ptr conjugateComplex = std::move(windowedValues); + + // Real values + unsigned int position; + double correctionFactor = 1.0 / dftLength / dftLength; + conjugateComplex[0] = (channelData->spectrum.sample[0] * channelData->spectrum.sample[0]) * correctionFactor; + for (position = 1; position < dftLength; ++position) + conjugateComplex[position] = + (channelData->spectrum.sample[position] * channelData->spectrum.sample[position] + + channelData->spectrum.sample[sampleCount - position] * + channelData->spectrum.sample[sampleCount - position]) * + correctionFactor; + // Complex values, all zero for autocorrelation + conjugateComplex[dftLength] = + (channelData->spectrum.sample[dftLength] * channelData->spectrum.sample[dftLength]) * correctionFactor; + for (++position; position < sampleCount; ++position) conjugateComplex[position] = 0; + + // Do half-complex to real inverse transformation + std::unique_ptr correlation = std::unique_ptr(new double[sampleCount]); + fftw_plan fftPlan = + fftw_plan_r2r_1d(sampleCount, conjugateComplex.get(), correlation.get(), FFTW_HC2R, FFTW_ESTIMATE); + fftw_execute(fftPlan); + fftw_destroy_plan(fftPlan); + + // Get the frequency from the correlation results + double minimumCorrelation = correlation[0]; + double peakCorrelation = 0; + unsigned int peakPosition = 0; + + for (unsigned int position = 1; position < sampleCount / 2; ++position) { + if (correlation[position] > peakCorrelation && correlation[position] > minimumCorrelation * 2) { + peakCorrelation = correlation[position]; + peakPosition = position; + } else if (correlation[position] < minimumCorrelation) + minimumCorrelation = correlation[position]; + } + correlation.reset(nullptr); + + // Calculate the frequency in Hz + if (peakPosition) + channelData->frequency = 1.0 / (channelData->voltage.interval * peakPosition); + else + channelData->frequency = 0; + + // Finally calculate the real spectrum if we want it + if (scope->spectrum[channel].used) { + // Convert values into dB (Relative to the reference level) + double offset = 60 - scope->spectrumReference - 20 * log10(dftLength); + double offsetLimit = scope->spectrumLimit - scope->spectrumReference; + for (std::vector::iterator spectrumIterator = channelData->spectrum.sample.begin(); + spectrumIterator != channelData->spectrum.sample.end(); ++spectrumIterator) { + double value = 20 * log10(fabs(*spectrumIterator)) + offset; + + // Check if this value has to be limited + if (offsetLimit > value) value = offsetLimit; + + *spectrumIterator = value; + } + } + } +} diff --git a/openhantek/src/post/spectrumgenerator.h b/openhantek/src/post/spectrumgenerator.h new file mode 100644 index 0000000..7b7386b --- /dev/null +++ b/openhantek/src/post/spectrumgenerator.h @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#pragma once + +#include + +#include +#include +#include + +#include "ppresult.h" +#include "dsosamples.h" +#include "utils/printutils.h" +#include "enums.h" + +#include "processor.h" + +class DsoSettings; +struct DsoSettingsScope; + +/// \brief Analyzes the data from the dso. +/// Calculates the spectrum and various data about the signal and saves the +/// time-/frequencysteps between two values. +class SpectrumGenerator : public Processor { + public: + SpectrumGenerator(const DsoSettingsScope* scope); + virtual ~SpectrumGenerator(); + virtual void process(PPresult *data) override; + + private: + const DsoSettingsScope* scope; + unsigned int lastRecordLength = 0; ///< The record length of the previously analyzed data + Dso::WindowFunction lastWindow = (Dso::WindowFunction)-1; ///< The previously used dft window function + double *lastWindowBuffer = nullptr; +}; -- libgit2 0.21.4