Commit 5add000e21a4dde7964344ffb48727e5e30f78d4

Authored by David Graeff
Committed by David Gräff
1 parent 84371ad2

Introduce new directory 'post' for post processing. Move all post processing funtionality there.

openhantek/src/analyse/dataanalyzer.cpp deleted
1   -// SPDX-License-Identifier: GPL-2.0+
2   -
3   -#define _USE_MATH_DEFINES
4   -#include <cmath>
5   -
6   -#include <QColor>
7   -#include <QMutex>
8   -#include <QTimer>
9   -
10   -#include <fftw3.h>
11   -
12   -#include "dataanalyzer.h"
13   -
14   -#include "glscope.h"
15   -#include "settings.h"
16   -#include "utils/printutils.h"
17   -
18   -std::unique_ptr<DataAnalyzerResult> DataAnalyzer::convertData(const DSOsamples *data, const DsoSettingsScope *scope, unsigned physicalChannels) {
19   - QReadLocker locker(&data->lock);
20   -
21   - unsigned int channelCount = (unsigned int)scope->voltage.size();
22   -
23   - std::unique_ptr<DataAnalyzerResult> result =
24   - std::unique_ptr<DataAnalyzerResult>(new DataAnalyzerResult(channelCount));
25   -
26   - for (ChannelID channel = 0; channel < channelCount; ++channel) {
27   - DataChannel *const channelData = result->modifyData(channel);
28   -
29   - bool gotDataForChannel = channel < physicalChannels && channel < (unsigned int)data->data.size() &&
30   - !data->data.at(channel).empty();
31   - bool isMathChannel = channel >= physicalChannels &&
32   - (scope->voltage[channel].used || scope->spectrum[channel].used) &&
33   - result->channelCount() >= 2 && !result->data(0)->voltage.sample.empty() &&
34   - !result->data(1)->voltage.sample.empty();
35   -
36   - if (!gotDataForChannel && !isMathChannel) {
37   - // Clear unused channels
38   - channelData->voltage.sample.clear();
39   - result->modifyData(physicalChannels)->voltage.interval = 0;
40   - continue;
41   - }
42   -
43   - // Set sampling interval
44   - const double interval = 1.0 / data->samplerate;
45   - if (interval != channelData->voltage.interval) {
46   - channelData->voltage.interval = interval;
47   - if (data->append) // Clear roll buffer if the samplerate changed
48   - channelData->voltage.sample.clear();
49   - }
50   -
51   - unsigned int size;
52   - if (channel < physicalChannels) {
53   - size = (unsigned) data->data.at(channel).size();
54   - if (data->append) size += channelData->voltage.sample.size();
55   - result->challengeMaxSamples(size);
56   - } else
57   - size = result->getMaxSamples();
58   -
59   - // Physical channels
60   - if (channel < physicalChannels) {
61   - // Copy the buffer of the oscilloscope into the sample buffer
62   - if (data->append)
63   - channelData->voltage.sample.insert(channelData->voltage.sample.end(), data->data.at(channel).begin(),
64   - data->data.at(channel).end());
65   - else
66   - channelData->voltage.sample = data->data.at(channel);
67   - } else { // Math channel
68   - // Resize the sample vector
69   - channelData->voltage.sample.resize(size);
70   - // Set sampling interval
71   - result->modifyData(physicalChannels)->voltage.interval = result->data(0)->voltage.interval;
72   -
73   - // Resize the sample vector
74   - result->modifyData(physicalChannels)
75   - ->voltage.sample.resize(
76   - qMin(result->data(0)->voltage.sample.size(), result->data(1)->voltage.sample.size()));
77   -
78   - // Calculate values and write them into the sample buffer
79   - std::vector<double>::const_iterator ch1Iterator = result->data(0)->voltage.sample.begin();
80   - std::vector<double>::const_iterator ch2Iterator = result->data(1)->voltage.sample.begin();
81   - std::vector<double> &resultData = result->modifyData(physicalChannels)->voltage.sample;
82   - for (std::vector<double>::iterator resultIterator = resultData.begin(); resultIterator != resultData.end();
83   - ++resultIterator) {
84   - switch (scope->voltage[physicalChannels].math) {
85   - case Dso::MathMode::ADD_CH1_CH2:
86   - *resultIterator = *ch1Iterator + *ch2Iterator;
87   - break;
88   - case Dso::MathMode::SUB_CH2_FROM_CH1:
89   - *resultIterator = *ch1Iterator - *ch2Iterator;
90   - break;
91   - case Dso::MathMode::SUB_CH1_FROM_CH2:
92   - *resultIterator = *ch2Iterator - *ch1Iterator;
93   - break;
94   - }
95   - ++ch1Iterator;
96   - ++ch2Iterator;
97   - }
98   - }
99   - }
100   - return result;
101   -}
102   -
103   -/// \brief Analyzes the data from the dso.
104   -DataAnalyzer::~DataAnalyzer() {
105   - if (window) fftw_free(window);
106   -}
107   -
108   -void DataAnalyzer::applySettings(DsoSettings *settings) { this->settings = settings; }
109   -
110   -void DataAnalyzer::setSourceData(const DSOsamples *data) { sourceData = data; }
111   -
112   -std::unique_ptr<DataAnalyzerResult> DataAnalyzer::getNextResult() { return std::move(lastResult); }
113   -
114   -void DataAnalyzer::samplesAvailable() {
115   - if (sourceData == nullptr) return;
116   - std::unique_ptr<DataAnalyzerResult> result = convertData(sourceData, &settings->scope,settings->deviceSpecification->channels);
117   - spectrumAnalysis(result.get(), lastWindow, lastRecordLength, window, &settings->scope);
118   - lastResult.swap(result);
119   - emit analyzed();
120   -}
121   -
122   -void DataAnalyzer::spectrumAnalysis(DataAnalyzerResult *result, Dso::WindowFunction &lastWindow,
123   - unsigned int lastRecordLength, double *&lastWindowBuffer,
124   - const DsoSettingsScope *scope) {
125   - // Calculate frequencies, peak-to-peak voltages and spectrums
126   - for (ChannelID channel = 0; channel < result->channelCount(); ++channel) {
127   - DataChannel *const channelData = result->modifyData(channel);
128   -
129   - if (channelData->voltage.sample.empty()) {
130   - // Clear unused channels
131   - channelData->spectrum.interval = 0;
132   - channelData->spectrum.sample.clear();
133   - continue;
134   - }
135   -
136   - // Calculate new window
137   - size_t sampleCount = channelData->voltage.sample.size();
138   - if (!lastWindowBuffer || lastWindow != scope->spectrumWindow || lastRecordLength != sampleCount) {
139   - if (lastWindowBuffer) fftw_free(lastWindowBuffer);
140   - lastWindowBuffer = fftw_alloc_real(sampleCount);
141   - lastRecordLength = (unsigned)sampleCount;
142   -
143   - unsigned int windowEnd = lastRecordLength - 1;
144   - lastWindow = scope->spectrumWindow;
145   -
146   - switch (scope->spectrumWindow) {
147   - case Dso::WindowFunction::HAMMING:
148   - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition)
149   - *(lastWindowBuffer + windowPosition) = 0.54 - 0.46 * cos(2.0 * M_PI * windowPosition / windowEnd);
150   - break;
151   - case Dso::WindowFunction::HANN:
152   - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition)
153   - *(lastWindowBuffer + windowPosition) = 0.5 * (1.0 - cos(2.0 * M_PI * windowPosition / windowEnd));
154   - break;
155   - case Dso::WindowFunction::COSINE:
156   - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition)
157   - *(lastWindowBuffer + windowPosition) = sin(M_PI * windowPosition / windowEnd);
158   - break;
159   - case Dso::WindowFunction::LANCZOS:
160   - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) {
161   - double sincParameter = (2.0 * windowPosition / windowEnd - 1.0) * M_PI;
162   - if (sincParameter == 0)
163   - *(lastWindowBuffer + windowPosition) = 1;
164   - else
165   - *(lastWindowBuffer + windowPosition) = sin(sincParameter) / sincParameter;
166   - }
167   - break;
168   - case Dso::WindowFunction::BARTLETT:
169   - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition)
170   - *(lastWindowBuffer + windowPosition) =
171   - 2.0 / windowEnd * (windowEnd / 2 - std::abs((double)(windowPosition - windowEnd / 2.0)));
172   - break;
173   - case Dso::WindowFunction::TRIANGULAR:
174   - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition)
175   - *(lastWindowBuffer + windowPosition) =
176   - 2.0 / lastRecordLength *
177   - (lastRecordLength / 2 - std::abs((double)(windowPosition - windowEnd / 2.0)));
178   - break;
179   - case Dso::WindowFunction::GAUSS: {
180   - double sigma = 0.4;
181   - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition)
182   - *(lastWindowBuffer + windowPosition) =
183   - exp(-0.5 * pow(((windowPosition - windowEnd / 2) / (sigma * windowEnd / 2)), 2));
184   - } break;
185   - case Dso::WindowFunction::BARTLETTHANN:
186   - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition)
187   - *(lastWindowBuffer + windowPosition) = 0.62 -
188   - 0.48 * std::abs((double)(windowPosition / windowEnd - 0.5)) -
189   - 0.38 * cos(2.0 * M_PI * windowPosition / windowEnd);
190   - break;
191   - case Dso::WindowFunction::BLACKMAN: {
192   - double alpha = 0.16;
193   - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition)
194   - *(lastWindowBuffer + windowPosition) = (1 - alpha) / 2 -
195   - 0.5 * cos(2.0 * M_PI * windowPosition / windowEnd) +
196   - alpha / 2 * cos(4.0 * M_PI * windowPosition / windowEnd);
197   - } break;
198   - // case Dso::WindowFunction::WINDOW_KAISER:
199   - // TODO WINDOW_KAISER
200   - // double alpha = 3.0;
201   - // for(unsigned int windowPosition = 0; windowPosition <
202   - // lastRecordLength; ++windowPosition)
203   - //*(window + windowPosition) = ;
204   - // break;
205   - case Dso::WindowFunction::NUTTALL:
206   - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition)
207   - *(lastWindowBuffer + windowPosition) = 0.355768 -
208   - 0.487396 * cos(2 * M_PI * windowPosition / windowEnd) +
209   - 0.144232 * cos(4 * M_PI * windowPosition / windowEnd) -
210   - 0.012604 * cos(6 * M_PI * windowPosition / windowEnd);
211   - break;
212   - case Dso::WindowFunction::BLACKMANHARRIS:
213   - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition)
214   - *(lastWindowBuffer + windowPosition) = 0.35875 -
215   - 0.48829 * cos(2 * M_PI * windowPosition / windowEnd) +
216   - 0.14128 * cos(4 * M_PI * windowPosition / windowEnd) -
217   - 0.01168 * cos(6 * M_PI * windowPosition / windowEnd);
218   - break;
219   - case Dso::WindowFunction::BLACKMANNUTTALL:
220   - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition)
221   - *(lastWindowBuffer + windowPosition) = 0.3635819 -
222   - 0.4891775 * cos(2 * M_PI * windowPosition / windowEnd) +
223   - 0.1365995 * cos(4 * M_PI * windowPosition / windowEnd) -
224   - 0.0106411 * cos(6 * M_PI * windowPosition / windowEnd);
225   - break;
226   - case Dso::WindowFunction::FLATTOP:
227   - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition)
228   - *(lastWindowBuffer + windowPosition) = 1.0 - 1.93 * cos(2 * M_PI * windowPosition / windowEnd) +
229   - 1.29 * cos(4 * M_PI * windowPosition / windowEnd) -
230   - 0.388 * cos(6 * M_PI * windowPosition / windowEnd) +
231   - 0.032 * cos(8 * M_PI * windowPosition / windowEnd);
232   - break;
233   - default: // Dso::WINDOW_RECTANGULAR
234   - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition)
235   - *(lastWindowBuffer + windowPosition) = 1.0;
236   - }
237   - }
238   -
239   - // Set sampling interval
240   - channelData->spectrum.interval = 1.0 / channelData->voltage.interval / sampleCount;
241   -
242   - // Number of real/complex samples
243   - unsigned int dftLength = sampleCount / 2;
244   -
245   - // Reallocate memory for samples if the sample count has changed
246   - channelData->spectrum.sample.resize(sampleCount);
247   -
248   - // Create sample buffer and apply window
249   - std::unique_ptr<double[]> windowedValues = std::unique_ptr<double[]>(new double[sampleCount]);
250   -
251   - for (unsigned int position = 0; position < sampleCount; ++position)
252   - windowedValues[position] = lastWindowBuffer[position] * channelData->voltage.sample[position];
253   -
254   - {
255   - // Do discrete real to half-complex transformation
256   - /// \todo Check if record length is multiple of 2
257   - /// \todo Reuse plan and use FFTW_MEASURE to get fastest algorithm
258   - fftw_plan fftPlan = fftw_plan_r2r_1d(sampleCount, windowedValues.get(),
259   - &channelData->spectrum.sample.front(), FFTW_R2HC, FFTW_ESTIMATE);
260   - fftw_execute(fftPlan);
261   - fftw_destroy_plan(fftPlan);
262   - }
263   -
264   - // Do an autocorrelation to get the frequency of the signal
265   - std::unique_ptr<double[]> conjugateComplex = std::move(windowedValues);
266   -
267   - // Real values
268   - unsigned int position;
269   - double correctionFactor = 1.0 / dftLength / dftLength;
270   - conjugateComplex[0] = (channelData->spectrum.sample[0] * channelData->spectrum.sample[0]) * correctionFactor;
271   - for (position = 1; position < dftLength; ++position)
272   - conjugateComplex[position] =
273   - (channelData->spectrum.sample[position] * channelData->spectrum.sample[position] +
274   - channelData->spectrum.sample[sampleCount - position] *
275   - channelData->spectrum.sample[sampleCount - position]) *
276   - correctionFactor;
277   - // Complex values, all zero for autocorrelation
278   - conjugateComplex[dftLength] =
279   - (channelData->spectrum.sample[dftLength] * channelData->spectrum.sample[dftLength]) * correctionFactor;
280   - for (++position; position < sampleCount; ++position) conjugateComplex[position] = 0;
281   -
282   - // Do half-complex to real inverse transformation
283   - std::unique_ptr<double[]> correlation = std::unique_ptr<double[]>(new double[sampleCount]);
284   - fftw_plan fftPlan =
285   - fftw_plan_r2r_1d(sampleCount, conjugateComplex.get(), correlation.get(), FFTW_HC2R, FFTW_ESTIMATE);
286   - fftw_execute(fftPlan);
287   - fftw_destroy_plan(fftPlan);
288   -
289   - // Calculate peak-to-peak voltage
290   - double minimalVoltage, maximalVoltage;
291   - minimalVoltage = maximalVoltage = channelData->voltage.sample[0];
292   -
293   - for (unsigned int position = 1; position < sampleCount; ++position) {
294   - if (channelData->voltage.sample[position] < minimalVoltage)
295   - minimalVoltage = channelData->voltage.sample[position];
296   - else if (channelData->voltage.sample[position] > maximalVoltage)
297   - maximalVoltage = channelData->voltage.sample[position];
298   - }
299   -
300   - channelData->amplitude = maximalVoltage - minimalVoltage;
301   -
302   - // Get the frequency from the correlation results
303   - double minimumCorrelation = correlation[0];
304   - double peakCorrelation = 0;
305   - unsigned int peakPosition = 0;
306   -
307   - for (unsigned int position = 1; position < sampleCount / 2; ++position) {
308   - if (correlation[position] > peakCorrelation && correlation[position] > minimumCorrelation * 2) {
309   - peakCorrelation = correlation[position];
310   - peakPosition = position;
311   - } else if (correlation[position] < minimumCorrelation)
312   - minimumCorrelation = correlation[position];
313   - }
314   - correlation.reset(nullptr);
315   -
316   - // Calculate the frequency in Hz
317   - if (peakPosition)
318   - channelData->frequency = 1.0 / (channelData->voltage.interval * peakPosition);
319   - else
320   - channelData->frequency = 0;
321   -
322   - // Finally calculate the real spectrum if we want it
323   - if (scope->spectrum[channel].used) {
324   - // Convert values into dB (Relative to the reference level)
325   - double offset = 60 - scope->spectrumReference - 20 * log10(dftLength);
326   - double offsetLimit = scope->spectrumLimit - scope->spectrumReference;
327   - for (std::vector<double>::iterator spectrumIterator = channelData->spectrum.sample.begin();
328   - spectrumIterator != channelData->spectrum.sample.end(); ++spectrumIterator) {
329   - double value = 20 * log10(fabs(*spectrumIterator)) + offset;
330   -
331   - // Check if this value has to be limited
332   - if (offsetLimit > value) value = offsetLimit;
333   -
334   - *spectrumIterator = value;
335   - }
336   - }
337   - }
338   -}
openhantek/src/analyse/dataanalyzerresult.cpp deleted
1   -// SPDX-License-Identifier: GPL-2.0+
2   -
3   -#include "dataanalyzerresult.h"
4   -#include <QDebug>
5   -#include <stdexcept>
6   -
7   -DataAnalyzerResult::DataAnalyzerResult(unsigned int channelCount) { analyzedData.resize(channelCount); }
8   -
9   -/// \brief Returns the analyzed data.
10   -/// \param channel Channel, whose data should be returned.
11   -/// \return Analyzed data as AnalyzedData struct.
12   -const DataChannel *DataAnalyzerResult::data(ChannelID channel) const {
13   - if (channel >= this->analyzedData.size()) return 0;
14   -
15   - return &this->analyzedData[(size_t)channel];
16   -}
17   -
18   -DataChannel *DataAnalyzerResult::modifyData(ChannelID channel) {
19   - if (channel >= this->analyzedData.size())
20   - throw new std::runtime_error("If you modfiy the DataAnalyzerResult, you "
21   - "need to set the channels first!");
22   -
23   - return &this->analyzedData[(size_t)channel];
24   -}
25   -
26   -/// \brief Returns the sample count of the analyzed data.
27   -/// \return The maximum sample count of the last analyzed data.
28   -unsigned int DataAnalyzerResult::sampleCount() const { return this->maxSamples; }
29   -
30   -unsigned int DataAnalyzerResult::channelCount() const { return analyzedData.size(); }
31   -
32   -void DataAnalyzerResult::challengeMaxSamples(unsigned int newMaxSamples) {
33   - if (newMaxSamples > this->maxSamples) this->maxSamples = newMaxSamples;
34   -}
35   -
36   -unsigned int DataAnalyzerResult::getMaxSamples() const { return maxSamples; }
openhantek/src/analyse/enums.cpp renamed to openhantek/src/post/enums.cpp
openhantek/src/analyse/enums.h renamed to openhantek/src/post/enums.h
openhantek/src/post/graphgenerator.cpp 0 → 100644
  1 +// SPDX-License-Identifier: GPL-2.0+
  2 +
  3 +#include <QDebug>
  4 +#include <QMutex>
  5 +#include <exception>
  6 +
  7 +#include "post/graphgenerator.h"
  8 +#include "post/ppresult.h"
  9 +#include "post/softwaretrigger.h"
  10 +#include "hantekdso/controlspecification.h"
  11 +#include "scopesettings.h"
  12 +#include "utils/printutils.h"
  13 +#include "viewconstants.h"
  14 +
  15 +static const SampleValues &useSpecSamplesOf(ChannelID channel, const PPresult *result,
  16 + const DsoSettingsScope *scope) {
  17 + static SampleValues emptyDefault;
  18 + if (!scope->spectrum[channel].used || !result->data(channel)) return emptyDefault;
  19 + return result->data(channel)->spectrum;
  20 +}
  21 +
  22 +static const SampleValues &useVoltSamplesOf(ChannelID channel, const PPresult *result,
  23 + const DsoSettingsScope *scope) {
  24 + static SampleValues emptyDefault;
  25 + if (!scope->voltage[channel].used || !result->data(channel)) return emptyDefault;
  26 + return result->data(channel)->voltage;
  27 +}
  28 +
  29 +GraphGenerator::GraphGenerator(const DsoSettingsScope *scope, bool isSoftwareTriggerDevice)
  30 + : scope(scope), isSoftwareTriggerDevice(isSoftwareTriggerDevice) {}
  31 +
  32 +bool GraphGenerator::isReady() const { return ready; }
  33 +
  34 +void GraphGenerator::generateGraphsTYvoltage(PPresult *result) {
  35 + unsigned preTrigSamples = 0;
  36 + unsigned postTrigSamples = 0;
  37 + unsigned swTriggerStart = 0;
  38 +
  39 + // check trigger point for software trigger
  40 + if (isSoftwareTriggerDevice && scope->trigger.source < result->channelCount())
  41 + std::tie(preTrigSamples, postTrigSamples, swTriggerStart) = SoftwareTrigger::compute(result, scope);
  42 + result->softwareTriggerTriggered = postTrigSamples > preTrigSamples;
  43 +
  44 + result->vaChannelVoltage.resize(scope->voltage.size());
  45 + for (ChannelID channel = 0; channel < scope->voltage.size(); ++channel) {
  46 + ChannelGraph &target = result->vaChannelVoltage[channel];
  47 + const SampleValues &samples = useVoltSamplesOf(channel, result, scope);
  48 +
  49 + // Check if this channel is used and available at the data analyzer
  50 + if (samples.sample.empty()) {
  51 + // Delete all vector arrays
  52 + target.clear();
  53 + continue;
  54 + }
  55 + // Check if the sample count has changed
  56 + size_t sampleCount = samples.sample.size();
  57 + if (sampleCount > 500000) {
  58 + qWarning() << "Sample count too high!";
  59 + throw new std::runtime_error("Sample count too high!");
  60 + }
  61 + sampleCount -= (swTriggerStart - preTrigSamples);
  62 + size_t neededSize = sampleCount * 2;
  63 +
  64 + // Set size directly to avoid reallocations
  65 + target.resize(neededSize);
  66 +
  67 + // What's the horizontal distance between sampling points?
  68 + float horizontalFactor = (float)(samples.interval / scope->horizontal.timebase);
  69 +
  70 + // Fill vector array
  71 + std::vector<double>::const_iterator dataIterator = samples.sample.begin();
  72 + const float gain = (float)scope->gain(channel);
  73 + const float offset = (float)scope->voltage[channel].offset;
  74 + const float invert = scope->voltage[channel].inverted ? -1.0f : 1.0f;
  75 +
  76 + std::advance(dataIterator, swTriggerStart - preTrigSamples);
  77 +
  78 + for (unsigned int position = 0; position < sampleCount; ++position) {
  79 + target.push_back(QVector3D(position * horizontalFactor - DIVS_TIME / 2,
  80 + (float)*(dataIterator++) / gain * invert + offset, 0.0));
  81 + }
  82 + }
  83 +}
  84 +
  85 +void GraphGenerator::generateGraphsTYspectrum(PPresult *result) {
  86 + ready = true;
  87 + result->vaChannelSpectrum.resize(scope->spectrum.size());
  88 + for (ChannelID channel = 0; channel < scope->voltage.size(); ++channel) {
  89 + ChannelGraph &target = result->vaChannelSpectrum[channel];
  90 + const SampleValues &samples = useSpecSamplesOf(channel, result, scope);
  91 +
  92 + // Check if this channel is used and available at the data analyzer
  93 + if (samples.sample.empty()) {
  94 + // Delete all vector arrays
  95 + target.clear();
  96 + continue;
  97 + }
  98 + // Check if the sample count has changed
  99 + size_t sampleCount = samples.sample.size();
  100 + if (sampleCount > 500000) {
  101 + qWarning() << "Sample count too high!";
  102 + throw new std::runtime_error("Sample count too high!");
  103 + }
  104 + size_t neededSize = sampleCount * 2;
  105 +
  106 + // Set size directly to avoid reallocations
  107 + target.resize(neededSize);
  108 +
  109 + // What's the horizontal distance between sampling points?
  110 + float horizontalFactor = (float)(samples.interval / scope->horizontal.frequencybase);
  111 +
  112 + // Fill vector array
  113 + std::vector<double>::const_iterator dataIterator = samples.sample.begin();
  114 + const float magnitude = (float)scope->spectrum[channel].magnitude;
  115 + const float offset = (float)scope->spectrum[channel].offset;
  116 +
  117 + for (unsigned int position = 0; position < sampleCount; ++position) {
  118 + target.push_back(QVector3D(position * horizontalFactor - DIVS_TIME / 2,
  119 + (float)*(dataIterator++) / magnitude + offset, 0.0));
  120 + }
  121 + }
  122 +}
  123 +
  124 +void GraphGenerator::process(PPresult *data) {
  125 + if (scope->horizontal.format == Dso::GraphFormat::TY) {
  126 + ready = true;
  127 + generateGraphsTYspectrum(data);
  128 + generateGraphsTYvoltage(data);
  129 + } else
  130 + generateGraphsXY(data, scope);
  131 +}
  132 +
  133 +void GraphGenerator::generateGraphsXY(PPresult *result, const DsoSettingsScope *scope) {
  134 + result->vaChannelVoltage.resize(scope->voltage.size());
  135 +
  136 + // Delete all spectrum graphs
  137 + for (ChannelGraph &data : result->vaChannelSpectrum) data.clear();
  138 +
  139 + // Generate voltage graphs for pairs of channels
  140 + for (ChannelID channel = 0; channel < scope->voltage.size(); channel += 2) {
  141 + // We need pairs of channels.
  142 + if (channel + 1 == scope->voltage.size()) {
  143 + result->vaChannelVoltage[channel].clear();
  144 + continue;
  145 + }
  146 +
  147 + const ChannelID xChannel = channel;
  148 + const ChannelID yChannel = channel + 1;
  149 +
  150 + const SampleValues &xSamples = useVoltSamplesOf(xChannel, result, scope);
  151 + const SampleValues &ySamples = useVoltSamplesOf(yChannel, result, scope);
  152 +
  153 + // The channels need to be active
  154 + if (!xSamples.sample.size() || !ySamples.sample.size()) {
  155 + result->vaChannelVoltage[channel].clear();
  156 + result->vaChannelVoltage[channel + 1].clear();
  157 + continue;
  158 + }
  159 +
  160 + // Check if the sample count has changed
  161 + const size_t sampleCount = std::min(xSamples.sample.size(), ySamples.sample.size());
  162 + ChannelGraph &drawLines = result->vaChannelVoltage[channel];
  163 + drawLines.resize(sampleCount * 2);
  164 +
  165 + // Fill vector array
  166 + std::vector<double>::const_iterator xIterator = xSamples.sample.begin();
  167 + std::vector<double>::const_iterator yIterator = ySamples.sample.begin();
  168 + const double xGain = scope->gain(xChannel);
  169 + const double yGain = scope->gain(yChannel);
  170 + const double xOffset = scope->voltage[xChannel].offset;
  171 + const double yOffset = scope->voltage[yChannel].offset;
  172 + const double xInvert = scope->voltage[xChannel].inverted ? -1.0 : 1.0;
  173 + const double yInvert = scope->voltage[yChannel].inverted ? -1.0 : 1.0;
  174 +
  175 + for (unsigned int position = 0; position < sampleCount; ++position) {
  176 + drawLines.push_back(QVector3D((float)(*(xIterator++) / xGain * xInvert + xOffset),
  177 + (float)(*(yIterator++) / yGain * yInvert + yOffset), 0.0));
  178 + }
  179 + }
  180 +}
... ...
openhantek/src/post/graphgenerator.h 0 → 100644
  1 +// SPDX-License-Identifier: GPL-2.0+
  2 +
  3 +#pragma once
  4 +
  5 +#include <deque>
  6 +
  7 +#include <QObject>
  8 +#include <QVector3D>
  9 +
  10 +#include "hantekdso/enums.h"
  11 +#include "hantekprotocol/types.h"
  12 +#include "processor.h"
  13 +
  14 +struct DsoSettingsScope;
  15 +class PPresult;
  16 +namespace Dso {
  17 +struct ControlSpecification;
  18 +}
  19 +
  20 +/// \brief Generates ready to be used vertex arrays
  21 +class GraphGenerator : public QObject, public Processor {
  22 + Q_OBJECT
  23 +
  24 + public:
  25 + GraphGenerator(const DsoSettingsScope *scope, bool isSoftwareTriggerDevice);
  26 + void generateGraphsXY(PPresult *result, const DsoSettingsScope *scope);
  27 +
  28 + bool isReady() const;
  29 +
  30 + private:
  31 + void generateGraphsTYvoltage(PPresult *result);
  32 + void generateGraphsTYspectrum(PPresult *result);
  33 +
  34 + private:
  35 + bool ready = false;
  36 + const DsoSettingsScope *scope;
  37 + const bool isSoftwareTriggerDevice;
  38 +
  39 + // Processor interface
  40 + private:
  41 + virtual void process(PPresult *) override;
  42 +};
... ...
openhantek/src/post/mathchannelgenerator.cpp 0 → 100644
  1 +#include "mathchannelgenerator.h"
  2 +#include "scopesettings.h"
  3 +
  4 +MathChannelGenerator::MathChannelGenerator(const DsoSettingsScope *scope, unsigned physicalChannels)
  5 + : physicalChannels(physicalChannels), scope(scope) {}
  6 +
  7 +MathChannelGenerator::~MathChannelGenerator() {}
  8 +
  9 +void MathChannelGenerator::process(PPresult *result) {
  10 + bool channelsHaveData = !result->data(0)->voltage.sample.empty() && !result->data(1)->voltage.sample.empty();
  11 + if (!channelsHaveData) return;
  12 +
  13 + for (ChannelID channel = physicalChannels; channel < result->channelCount(); ++channel) {
  14 + DataChannel *const channelData = result->modifyData(channel);
  15 +
  16 + // Math channel enabled?
  17 + if (!scope->voltage[channel].used && !scope->spectrum[channel].used) continue;
  18 +
  19 + // Set sampling interval
  20 + channelData->voltage.interval = result->data(0)->voltage.interval;
  21 +
  22 + // Resize the sample vector
  23 + std::vector<double> &resultData = channelData->voltage.sample;
  24 + resultData.resize(std::min(result->data(0)->voltage.sample.size(), result->data(1)->voltage.sample.size()));
  25 +
  26 + // Calculate values and write them into the sample buffer
  27 + std::vector<double>::const_iterator ch1Iterator = result->data(0)->voltage.sample.begin();
  28 + std::vector<double>::const_iterator ch2Iterator = result->data(1)->voltage.sample.begin();
  29 + for (std::vector<double>::iterator it = resultData.begin(); it != resultData.end(); ++it) {
  30 + switch (scope->voltage[physicalChannels].math) {
  31 + case Dso::MathMode::ADD_CH1_CH2:
  32 + *it = *ch1Iterator + *ch2Iterator;
  33 + break;
  34 + case Dso::MathMode::SUB_CH2_FROM_CH1:
  35 + *it = *ch1Iterator - *ch2Iterator;
  36 + break;
  37 + case Dso::MathMode::SUB_CH1_FROM_CH2:
  38 + *it = *ch2Iterator - *ch1Iterator;
  39 + break;
  40 + }
  41 + ++ch1Iterator;
  42 + ++ch2Iterator;
  43 + }
  44 + }
  45 +}
... ...
openhantek/src/post/mathchannelgenerator.h 0 → 100644
  1 +// SPDX-License-Identifier: GPL-2.0+
  2 +
  3 +#pragma once
  4 +
  5 +#include "processor.h"
  6 +
  7 +struct DsoSettingsScope;
  8 +class PPresult;
  9 +
  10 +class MathChannelGenerator : public Processor
  11 +{
  12 +public:
  13 + MathChannelGenerator(const DsoSettingsScope *scope, unsigned physicalChannels);
  14 + virtual ~MathChannelGenerator();
  15 + virtual void process(PPresult *) override;
  16 +private:
  17 + const unsigned physicalChannels;
  18 + const DsoSettingsScope *scope;
  19 +};
... ...
openhantek/src/post/postprocessing.cpp 0 → 100644
  1 +#include "postprocessing.h"
  2 +
  3 +PostProcessing::PostProcessing(unsigned channelCount) : channelCount(channelCount) {
  4 + qRegisterMetaType<std::shared_ptr<PPresult>>();
  5 +}
  6 +
  7 +void PostProcessing::registerProcessor(Processor *processor) { processors.push_back(processor); }
  8 +
  9 +void PostProcessing::convertData(const DSOsamples *source, PPresult *destination) {
  10 + QReadLocker locker(&source->lock);
  11 +
  12 + for (ChannelID channel = 0; channel < source->data.size(); ++channel) {
  13 + const std::vector<double> &rawChannelData = source->data.at(channel);
  14 +
  15 + if (rawChannelData.empty()) { continue; }
  16 +
  17 + DataChannel *const channelData = destination->modifyData(channel);
  18 + channelData->voltage.interval = 1.0 / source->samplerate;
  19 + channelData->voltage.sample = rawChannelData;
  20 + }
  21 +}
  22 +
  23 +void PostProcessing::input(const DSOsamples *data) {
  24 + currentData.reset(new PPresult(channelCount));
  25 + convertData(data, currentData.get());
  26 + for (Processor *p : processors) p->process(currentData.get());
  27 + std::shared_ptr<PPresult> res = std::move(currentData);
  28 + emit processingFinished(res);
  29 +}
... ...
openhantek/src/post/postprocessing.h 0 → 100644
  1 +// SPDX-License-Identifier: GPL-2.0+
  2 +
  3 +#pragma once
  4 +
  5 +#include "dsosamples.h"
  6 +#include "processor.h"
  7 +
  8 +#include <memory>
  9 +#include <vector>
  10 +
  11 +#include <QObject>
  12 +
  13 +struct DsoSettingsScope;
  14 +
  15 +/**
  16 + * Manages all post processing processors. Register another processor with `registerProcessor(p)`.
  17 + * All processors, in the order of insertion, will process the input data, given by `input(data)`.
  18 + * The final result will be made available via the `processingFinished` signal.
  19 + */
  20 +class PostProcessing : public QObject {
  21 + Q_OBJECT
  22 + public:
  23 + PostProcessing(unsigned channelCount);
  24 + /**
  25 + * Adds a new processor that is called when a new input arrived. The order of the processors is
  26 + * imporant. The first added processor will be called first. This class does not take ownership
  27 + * of the processors.
  28 + * @param processor
  29 + */
  30 + void registerProcessor(Processor *processor);
  31 +
  32 +
  33 + private:
  34 + /// A new `PPresult` is created for each new input. We need to know the channel size.
  35 + const unsigned channelCount;
  36 + /// The list of processors. Processors are not memory managed by this class.
  37 + std::vector<Processor *> processors;
  38 + ///
  39 + std::unique_ptr<PPresult> currentData;
  40 + static void convertData(const DSOsamples *source, PPresult *destination);
  41 + public slots:
  42 + /**
  43 + * Start processing new data. The actual data may be processed in another thread if you have moved
  44 + * this class object into another thread.
  45 + * @param data
  46 + */
  47 + void input(const DSOsamples *data);
  48 +signals:
  49 + void processingFinished(std::shared_ptr<PPresult> result);
  50 +};
  51 +
  52 +Q_DECLARE_METATYPE(std::shared_ptr<PPresult>)
... ...
openhantek/src/post/ppresult.cpp 0 → 100644
  1 +// SPDX-License-Identifier: GPL-2.0+
  2 +
  3 +#include "ppresult.h"
  4 +#include <QDebug>
  5 +#include <stdexcept>
  6 +
  7 +PPresult::PPresult(unsigned int channelCount) { analyzedData.resize(channelCount); }
  8 +
  9 +const DataChannel *PPresult::data(ChannelID channel) const {
  10 + if (channel >= this->analyzedData.size()) return 0;
  11 +
  12 + return &this->analyzedData[(size_t)channel];
  13 +}
  14 +
  15 +DataChannel *PPresult::modifyData(ChannelID channel) { return &this->analyzedData[(size_t)channel]; }
  16 +
  17 +unsigned int PPresult::sampleCount() const { return (unsigned)analyzedData[0].voltage.sample.size(); }
  18 +
  19 +unsigned int PPresult::channelCount() const { return (unsigned)analyzedData.size(); }
  20 +
  21 +double DataChannel::computeAmplitude() const {
  22 + double minimalVoltage, maximalVoltage;
  23 + minimalVoltage = maximalVoltage = voltage.sample[0];
  24 +
  25 + for (unsigned int position = 1; position < voltage.sample.size(); ++position) {
  26 + if (voltage.sample[position] < minimalVoltage)
  27 + minimalVoltage = voltage.sample[position];
  28 + else if (voltage.sample[position] > maximalVoltage)
  29 + maximalVoltage = voltage.sample[position];
  30 + }
  31 +
  32 + return maximalVoltage - minimalVoltage;
  33 +}
... ...
openhantek/src/analyse/dataanalyzerresult.h renamed to openhantek/src/post/ppresult.h
... ... @@ -2,8 +2,11 @@
2 2  
3 3 #pragma once
4 4  
  5 +#include <QVector3D>
  6 +#include <QReadWriteLock>
  7 +
5 8 #include <vector>
6   -#include "hantekprotocol/definitions.h"
  9 +#include "hantekprotocol/types.h"
7 10  
8 11 /// \brief Struct for a array of sample values.
9 12 struct SampleValues {
... ... @@ -15,28 +18,34 @@ struct SampleValues {
15 18 struct DataChannel {
16 19 SampleValues voltage; ///< The time-domain voltage levels (V)
17 20 SampleValues spectrum; ///< The frequency-domain power levels (dB)
18   - double amplitude = 0.0; ///< The amplitude of the signal
  21 +
19 22 double frequency = 0.0; ///< The frequency of the signal
  23 + // Calculate peak-to-peak voltage
  24 + double computeAmplitude() const;
20 25 };
21 26  
22   -/// A result from the { @link DataAnalyzer } class.
23   -class DataAnalyzerResult {
  27 +typedef std::vector<QVector3D> ChannelGraph;
  28 +typedef std::vector<ChannelGraph> ChannelsGraphs;
  29 +
  30 +/// Post processing results
  31 +class PPresult {
24 32 public:
25   - DataAnalyzerResult(unsigned int channelCount);
  33 + PPresult(unsigned int channelCount);
  34 +
  35 + /// \brief Returns the analyzed data.
  36 + /// \param channel Channel, whose data should be returned.
26 37 const DataChannel *data(ChannelID channel) const;
  38 + /// \brief Returns the analyzed data. The data structure can be modifed.
  39 + /// \param channel Channel, whose data should be returned.
27 40 DataChannel *modifyData(ChannelID channel);
  41 + /// \return The maximum sample count of the last analyzed data. This assumes there is at least one channel.
28 42 unsigned int sampleCount() const;
29 43 unsigned int channelCount() const;
30 44  
31   - /**
32   - * Applies a new maximum samples value, if the given value is higher than the
33   - * already stored one
34   - * @param newMaxSamples Maximum samples value
35   - */
36   - void challengeMaxSamples(unsigned int newMaxSamples);
37   - unsigned int getMaxSamples() const;
  45 + bool softwareTriggerTriggered = false;
38 46  
  47 + ChannelsGraphs vaChannelSpectrum;
  48 + ChannelsGraphs vaChannelVoltage;
39 49 private:
40 50 std::vector<DataChannel> analyzedData; ///< The analyzed data for each channel
41   - unsigned int maxSamples = 0; ///< The maximum record length of the analyzed data
42 51 };
... ...
openhantek/src/post/processor.h 0 → 100644
  1 +#pragma once
  2 +
  3 +#include "ppresult.h"
  4 +
  5 +class Processor {
  6 +public:
  7 + virtual void process(PPresult*) = 0;
  8 +};
... ...
openhantek/src/hantekdso/softwaretrigger.cpp renamed to openhantek/src/post/softwaretrigger.cpp
1   -#include "softwaretrigger.h"
2   -#include "analyse/dataanalyzerresult.h"
  1 +#include "post/softwaretrigger.h"
  2 +#include "post/ppresult.h"
3 3 #include "scopesettings.h"
4 4 #include "viewconstants.h"
5 5 #include "utils/printutils.h"
6 6  
7   -SoftwareTrigger::PrePostStartTriggerSamples SoftwareTrigger::compute(const DataAnalyzerResult *data,
  7 +SoftwareTrigger::PrePostStartTriggerSamples SoftwareTrigger::compute(const PPresult *data,
8 8 const DsoSettingsScope *scope)
9 9 {
10 10 unsigned int preTrigSamples = 0;
... ...
openhantek/src/hantekdso/softwaretrigger.h renamed to openhantek/src/post/softwaretrigger.h
1 1 #pragma once
2 2 #include <tuple>
3 3 struct DsoSettingsScope;
4   -class DataAnalyzerResult;
  4 +class PPresult;
5 5  
6 6  
7 7 /**
... ... @@ -18,5 +18,5 @@ class SoftwareTrigger {
18 18 * @param scope Scope settings
19 19 * @return Returns a tuple of positions [preTrigger, postTrigger, startTrigger]
20 20 */
21   - static PrePostStartTriggerSamples compute(const DataAnalyzerResult *data, const DsoSettingsScope *scope);
  21 + static PrePostStartTriggerSamples compute(const PPresult *data, const DsoSettingsScope *scope);
22 22 };
... ...
openhantek/src/post/spectrumgenerator.cpp 0 → 100644
  1 +// SPDX-License-Identifier: GPL-2.0+
  2 +
  3 +#define _USE_MATH_DEFINES
  4 +#include <cmath>
  5 +
  6 +#include <QColor>
  7 +#include <QMutex>
  8 +#include <QTimer>
  9 +
  10 +#include <fftw3.h>
  11 +
  12 +#include "spectrumgenerator.h"
  13 +
  14 +#include "glscope.h"
  15 +#include "settings.h"
  16 +#include "utils/printutils.h"
  17 +
  18 +/// \brief Analyzes the data from the dso.
  19 +SpectrumGenerator::SpectrumGenerator(const DsoSettingsScope *scope) : scope(scope) {}
  20 +
  21 +SpectrumGenerator::~SpectrumGenerator() {
  22 + if (lastWindowBuffer) fftw_free(lastWindowBuffer);
  23 +}
  24 +
  25 +void SpectrumGenerator::process(PPresult *result) {
  26 + // Calculate frequencies and spectrums
  27 + for (ChannelID channel = 0; channel < result->channelCount(); ++channel) {
  28 + DataChannel *const channelData = result->modifyData(channel);
  29 +
  30 + if (channelData->voltage.sample.empty()) {
  31 + // Clear unused channels
  32 + channelData->spectrum.interval = 0;
  33 + channelData->spectrum.sample.clear();
  34 + continue;
  35 + }
  36 +
  37 + // Calculate new window
  38 + size_t sampleCount = channelData->voltage.sample.size();
  39 + if (!lastWindowBuffer || lastWindow != scope->spectrumWindow || lastRecordLength != sampleCount) {
  40 + if (lastWindowBuffer) fftw_free(lastWindowBuffer);
  41 + lastWindowBuffer = fftw_alloc_real(sampleCount);
  42 + lastRecordLength = (unsigned)sampleCount;
  43 +
  44 + unsigned int windowEnd = lastRecordLength - 1;
  45 + lastWindow = scope->spectrumWindow;
  46 +
  47 + switch (scope->spectrumWindow) {
  48 + case Dso::WindowFunction::HAMMING:
  49 + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition)
  50 + *(lastWindowBuffer + windowPosition) = 0.54 - 0.46 * cos(2.0 * M_PI * windowPosition / windowEnd);
  51 + break;
  52 + case Dso::WindowFunction::HANN:
  53 + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition)
  54 + *(lastWindowBuffer + windowPosition) = 0.5 * (1.0 - cos(2.0 * M_PI * windowPosition / windowEnd));
  55 + break;
  56 + case Dso::WindowFunction::COSINE:
  57 + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition)
  58 + *(lastWindowBuffer + windowPosition) = sin(M_PI * windowPosition / windowEnd);
  59 + break;
  60 + case Dso::WindowFunction::LANCZOS:
  61 + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) {
  62 + double sincParameter = (2.0 * windowPosition / windowEnd - 1.0) * M_PI;
  63 + if (sincParameter == 0)
  64 + *(lastWindowBuffer + windowPosition) = 1;
  65 + else
  66 + *(lastWindowBuffer + windowPosition) = sin(sincParameter) / sincParameter;
  67 + }
  68 + break;
  69 + case Dso::WindowFunction::BARTLETT:
  70 + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition)
  71 + *(lastWindowBuffer + windowPosition) =
  72 + 2.0 / windowEnd * (windowEnd / 2 - std::abs((double)(windowPosition - windowEnd / 2.0)));
  73 + break;
  74 + case Dso::WindowFunction::TRIANGULAR:
  75 + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition)
  76 + *(lastWindowBuffer + windowPosition) =
  77 + 2.0 / lastRecordLength *
  78 + (lastRecordLength / 2 - std::abs((double)(windowPosition - windowEnd / 2.0)));
  79 + break;
  80 + case Dso::WindowFunction::GAUSS: {
  81 + double sigma = 0.4;
  82 + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition)
  83 + *(lastWindowBuffer + windowPosition) =
  84 + exp(-0.5 * pow(((windowPosition - windowEnd / 2) / (sigma * windowEnd / 2)), 2));
  85 + } break;
  86 + case Dso::WindowFunction::BARTLETTHANN:
  87 + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition)
  88 + *(lastWindowBuffer + windowPosition) = 0.62 -
  89 + 0.48 * std::abs((double)(windowPosition / windowEnd - 0.5)) -
  90 + 0.38 * cos(2.0 * M_PI * windowPosition / windowEnd);
  91 + break;
  92 + case Dso::WindowFunction::BLACKMAN: {
  93 + double alpha = 0.16;
  94 + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition)
  95 + *(lastWindowBuffer + windowPosition) = (1 - alpha) / 2 -
  96 + 0.5 * cos(2.0 * M_PI * windowPosition / windowEnd) +
  97 + alpha / 2 * cos(4.0 * M_PI * windowPosition / windowEnd);
  98 + } break;
  99 + // case Dso::WindowFunction::WINDOW_KAISER:
  100 + // TODO WINDOW_KAISER
  101 + // double alpha = 3.0;
  102 + // for(unsigned int windowPosition = 0; windowPosition <
  103 + // lastRecordLength; ++windowPosition)
  104 + //*(window + windowPosition) = ;
  105 + // break;
  106 + case Dso::WindowFunction::NUTTALL:
  107 + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition)
  108 + *(lastWindowBuffer + windowPosition) = 0.355768 -
  109 + 0.487396 * cos(2 * M_PI * windowPosition / windowEnd) +
  110 + 0.144232 * cos(4 * M_PI * windowPosition / windowEnd) -
  111 + 0.012604 * cos(6 * M_PI * windowPosition / windowEnd);
  112 + break;
  113 + case Dso::WindowFunction::BLACKMANHARRIS:
  114 + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition)
  115 + *(lastWindowBuffer + windowPosition) = 0.35875 -
  116 + 0.48829 * cos(2 * M_PI * windowPosition / windowEnd) +
  117 + 0.14128 * cos(4 * M_PI * windowPosition / windowEnd) -
  118 + 0.01168 * cos(6 * M_PI * windowPosition / windowEnd);
  119 + break;
  120 + case Dso::WindowFunction::BLACKMANNUTTALL:
  121 + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition)
  122 + *(lastWindowBuffer + windowPosition) = 0.3635819 -
  123 + 0.4891775 * cos(2 * M_PI * windowPosition / windowEnd) +
  124 + 0.1365995 * cos(4 * M_PI * windowPosition / windowEnd) -
  125 + 0.0106411 * cos(6 * M_PI * windowPosition / windowEnd);
  126 + break;
  127 + case Dso::WindowFunction::FLATTOP:
  128 + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition)
  129 + *(lastWindowBuffer + windowPosition) = 1.0 - 1.93 * cos(2 * M_PI * windowPosition / windowEnd) +
  130 + 1.29 * cos(4 * M_PI * windowPosition / windowEnd) -
  131 + 0.388 * cos(6 * M_PI * windowPosition / windowEnd) +
  132 + 0.032 * cos(8 * M_PI * windowPosition / windowEnd);
  133 + break;
  134 + default: // Dso::WINDOW_RECTANGULAR
  135 + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition)
  136 + *(lastWindowBuffer + windowPosition) = 1.0;
  137 + }
  138 + }
  139 +
  140 + // Set sampling interval
  141 + channelData->spectrum.interval = 1.0 / channelData->voltage.interval / sampleCount;
  142 +
  143 + // Number of real/complex samples
  144 + unsigned int dftLength = sampleCount / 2;
  145 +
  146 + // Reallocate memory for samples if the sample count has changed
  147 + channelData->spectrum.sample.resize(sampleCount);
  148 +
  149 + // Create sample buffer and apply window
  150 + std::unique_ptr<double[]> windowedValues = std::unique_ptr<double[]>(new double[sampleCount]);
  151 +
  152 + for (unsigned int position = 0; position < sampleCount; ++position)
  153 + windowedValues[position] = lastWindowBuffer[position] * channelData->voltage.sample[position];
  154 +
  155 + {
  156 + // Do discrete real to half-complex transformation
  157 + /// \todo Check if record length is multiple of 2
  158 + /// \todo Reuse plan and use FFTW_MEASURE to get fastest algorithm
  159 + fftw_plan fftPlan = fftw_plan_r2r_1d(sampleCount, windowedValues.get(),
  160 + &channelData->spectrum.sample.front(), FFTW_R2HC, FFTW_ESTIMATE);
  161 + fftw_execute(fftPlan);
  162 + fftw_destroy_plan(fftPlan);
  163 + }
  164 +
  165 + // Do an autocorrelation to get the frequency of the signal
  166 + std::unique_ptr<double[]> conjugateComplex = std::move(windowedValues);
  167 +
  168 + // Real values
  169 + unsigned int position;
  170 + double correctionFactor = 1.0 / dftLength / dftLength;
  171 + conjugateComplex[0] = (channelData->spectrum.sample[0] * channelData->spectrum.sample[0]) * correctionFactor;
  172 + for (position = 1; position < dftLength; ++position)
  173 + conjugateComplex[position] =
  174 + (channelData->spectrum.sample[position] * channelData->spectrum.sample[position] +
  175 + channelData->spectrum.sample[sampleCount - position] *
  176 + channelData->spectrum.sample[sampleCount - position]) *
  177 + correctionFactor;
  178 + // Complex values, all zero for autocorrelation
  179 + conjugateComplex[dftLength] =
  180 + (channelData->spectrum.sample[dftLength] * channelData->spectrum.sample[dftLength]) * correctionFactor;
  181 + for (++position; position < sampleCount; ++position) conjugateComplex[position] = 0;
  182 +
  183 + // Do half-complex to real inverse transformation
  184 + std::unique_ptr<double[]> correlation = std::unique_ptr<double[]>(new double[sampleCount]);
  185 + fftw_plan fftPlan =
  186 + fftw_plan_r2r_1d(sampleCount, conjugateComplex.get(), correlation.get(), FFTW_HC2R, FFTW_ESTIMATE);
  187 + fftw_execute(fftPlan);
  188 + fftw_destroy_plan(fftPlan);
  189 +
  190 + // Get the frequency from the correlation results
  191 + double minimumCorrelation = correlation[0];
  192 + double peakCorrelation = 0;
  193 + unsigned int peakPosition = 0;
  194 +
  195 + for (unsigned int position = 1; position < sampleCount / 2; ++position) {
  196 + if (correlation[position] > peakCorrelation && correlation[position] > minimumCorrelation * 2) {
  197 + peakCorrelation = correlation[position];
  198 + peakPosition = position;
  199 + } else if (correlation[position] < minimumCorrelation)
  200 + minimumCorrelation = correlation[position];
  201 + }
  202 + correlation.reset(nullptr);
  203 +
  204 + // Calculate the frequency in Hz
  205 + if (peakPosition)
  206 + channelData->frequency = 1.0 / (channelData->voltage.interval * peakPosition);
  207 + else
  208 + channelData->frequency = 0;
  209 +
  210 + // Finally calculate the real spectrum if we want it
  211 + if (scope->spectrum[channel].used) {
  212 + // Convert values into dB (Relative to the reference level)
  213 + double offset = 60 - scope->spectrumReference - 20 * log10(dftLength);
  214 + double offsetLimit = scope->spectrumLimit - scope->spectrumReference;
  215 + for (std::vector<double>::iterator spectrumIterator = channelData->spectrum.sample.begin();
  216 + spectrumIterator != channelData->spectrum.sample.end(); ++spectrumIterator) {
  217 + double value = 20 * log10(fabs(*spectrumIterator)) + offset;
  218 +
  219 + // Check if this value has to be limited
  220 + if (offsetLimit > value) value = offsetLimit;
  221 +
  222 + *spectrumIterator = value;
  223 + }
  224 + }
  225 + }
  226 +}
... ...
openhantek/src/analyse/dataanalyzer.h renamed to openhantek/src/post/spectrumgenerator.h
... ... @@ -8,43 +8,28 @@
8 8 #include <QThread>
9 9 #include <memory>
10 10  
11   -#include "dataanalyzerresult.h"
  11 +#include "ppresult.h"
12 12 #include "dsosamples.h"
13 13 #include "utils/printutils.h"
14 14 #include "enums.h"
15 15  
  16 +#include "processor.h"
  17 +
16 18 class DsoSettings;
17 19 struct DsoSettingsScope;
18 20  
19 21 /// \brief Analyzes the data from the dso.
20 22 /// Calculates the spectrum and various data about the signal and saves the
21 23 /// time-/frequencysteps between two values.
22   -class DataAnalyzer : public QObject {
23   - Q_OBJECT
24   -
  24 +class SpectrumGenerator : public Processor {
25 25 public:
26   - ~DataAnalyzer();
27   - void applySettings(DsoSettings *settings);
28   - void setSourceData(const DSOsamples *data);
29   - std::unique_ptr<DataAnalyzerResult> getNextResult();
30   - /**
31   - * Call this if the source data changed.
32   - */
33   - void samplesAvailable();
34   -
35   - private:
36   - static std::unique_ptr<DataAnalyzerResult> convertData(const DSOsamples *data, const DsoSettingsScope *scope, unsigned physicalChannels);
37   - static void spectrumAnalysis(DataAnalyzerResult *result, Dso::WindowFunction &lastWindow,
38   - unsigned int lastRecordLength, double *&lastWindowBuffer,
39   - const DsoSettingsScope *scope);
  26 + SpectrumGenerator(const DsoSettingsScope* scope);
  27 + virtual ~SpectrumGenerator();
  28 + virtual void process(PPresult *data) override;
40 29  
41 30 private:
42   - DsoSettings *settings;
  31 + const DsoSettingsScope* scope;
43 32 unsigned int lastRecordLength = 0; ///< The record length of the previously analyzed data
44 33 Dso::WindowFunction lastWindow = (Dso::WindowFunction)-1; ///< The previously used dft window function
45   - double *window = nullptr;
46   - const DSOsamples *sourceData = nullptr;
47   - std::unique_ptr<DataAnalyzerResult> lastResult;
48   - signals:
49   - void analyzed();
  34 + double *lastWindowBuffer = nullptr;
50 35 };
... ...